我所知道的EC====>SPI

来源:互联网 发布:设计师网站源码 编辑:程序博客网 时间:2024/04/29 13:21

我所知道的EC====>SPI

 

1.Introduction

      SPI 全称为Serial Peripheral Interface Bus串行外围总线。它是由Motorola制定的四线式全双工的同步串行数据通信标准。spi允许mcu和各种外围设备进行全双工的串行通信。常见的spi deviceflash rom,触摸屏,LCD等。它有比较高的传输速率,传输速度通常可以达到几Mbpsspi采用主从模式。通常master只有一个,但是可以有多个slave,多个slave通过片选定址。

 

2.Hardware Interface

    Spi接口如下图 1 所示,通常就四根pinEC Chip有按照Motorola的经典命名方式将这四根pin分别称为MISOMOSISPICLKSPICS#。不同的IC厂商pin命名的方式可能会有不同,比如有些也会命名为SCKSDOSDICS#等。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

其中SPICLK提供通信所需要的clockclock太重要了,没有了它,那就全乱套了,什么时候开始、结束,何时是有效数据,何时为跳变都无法分辨,由此可见SPICLK是非常重要的同步时钟信号。SPICS#是片选信号,如果要使用某一个slave device首要做的就是先片选该设备,也就是将该SPICS# pin pull lowMISO master input slave output反过来读就是output slave input master,正反读都讲的通,MOSI也同解Motorola真是太牛了!名字取得这么好。当masterslave发送信息时,mater将数据送到MOSI上,slaveMOSI上读走该信息,如果slave要回信息给masterslave就会将数据送到MISOmasterMISO上获得信息。spi支持单masterslave,单materslave模式,在NB上我们常用的就是将BIOS rom通过spi接在南桥,或者接在ECspi接口,都是单masterslave的模式。因此我们只讨论这种模式。下图2是我手上的NB专案的线路,EC spi接口上接了一颗w25x80 flash rom,我们后续的讨论将基于这颗IC

 

 

 

3.SPI Instructions

 

        为了方便的操纵slave deviceslave device定义一些instructions。这些instructions包括了操作device的基本操作如:读数据,写数据,擦除数据等等。W25x80这颗ICspec中定义了1.write enable 2.write disable 3.read status register 4.write status register 5.read data 6.fast read 7.fast read dual 8.page program 9.sector erase 10. block erase 11.chip erase 12.power down 13.release power down 14.read manu/devid 15.read jedec id等具体可以参考w25x80 spec。对一颗flash rom我们常用的操作就是通过写里面的内容完成刷bios的功能,而刷bios之前spec 规定还需要做一个erase的动作,将rom中所有的内容清为“1。另外有时无法开机时,我们还需要读取flash rom的内容判断出错的原因。针对上述讨论我们只需要如下几个instructions即可完成任务。

 

v     Read data

Read data指令可以从flash rom中一次读取多个字节,执行该指令需要先片选SPICS#(将该pin pull low),然后送read data指令给spi flash rom,随后将24位的地址按照MSB格式分成三个字节A2,A1,A0,然后分别送给spi flash rom,然后就可以从SPIDAT中读取其中该地址上的数据了,地址会自动累加,该指令会一直读下去直到将SPICS# pull high。时序如下图3所示:


 

 

v     Sector erase

Sector erase 指令将flash rom的指定的4kBytes内容全部清为‘1。执行该指令之前要先发write enable 指令而且status registerblock protect bits必须要清‘0,否则sector erase指令将不会执行,做完上述准备工作以后,仍然要做的是片选SPICS#,然后送sector erase指令给spi flash rom,随后按照MSB格式送24位地址A2,A1,A0。要注意的是判断sector erase指令是否完成要使用read status register指令检查BUSY位,一次指令完成要将SPICS# pull high。时序如下图4所示:

 

 

v     Page program

Page program指令允许一次写特定地址开始的256个字节,前提是该区域必须被清为‘1’,所以要先发sector erase指令将flash rom清为‘1’然后才能执行page program。执行page program之前要先送write enable指令而且status registerblock protect bits必须要清‘0’,接下来送page program指令并给出MSB格式的24位地址A2,A1,A0,后续将数据送到spi bus上,如果数据小于256bytes,则只修改特定的字节数,如果大于256bytes,写的内容就会回绕到开始地址的头部。可以通过read status register指令检查BUSY位确定指令是否执行完毕,一次指令完成要将SPICS# pull high。时序如下图5所示:

    

4.BIOS Flash Tool

   看完上述instructions我们就有能力写一只像AMI提供的Afudos.exe这样的flash bios的工具了(当然只是功能接近,Afudos.exe好像并不是直接操纵spiflash biosJ)。心动不如行动Just do it!一个简化的flash tool只需要能flash bios,可以dump bios rom,可以读device id即可。一共三个function,让我来一个一个搞定它。

 

v     Implement Myflash.exe

      我的NB专案使用是一颗w25x80,为了简化复杂度,同时也降低因spi实现标准不同而造成的兼容性问题。Myflash.exe就只为w25x80服务。在动手写code之前,有一些基础知识需要搞明白。EC chip有一个index io一说,这个东西也被BIOS戏称为back door。一旦Chipset&EC打开这个功能以后,BIOS就能通过back door随便访问EC register &memory而更绝的是EC丝毫不知情。有了后门以后我也可以在myflash.exe中使用这个功能直接操纵EC  spi register进而操纵总线完成读写flash rom的功能。另外还需要知道的就是有关Idle mode & Reset mode。在flash bios之前一定要发命令让ECReset mode(rom内容还好,写 rom肯定不能让EC再跑code)

1.       Read device id

按照spec的描述我们需要将SPICS# pull low选中spi device 然后送9Fcommanddevicedevice就会回三个字节分别代表manufacture idmemory typecapacityIC厂商为了兼容性考虑JEDEC ID必须要提供。下述code演示该过程。注意红色部分,每次读取一个字节都要先送一个dummy command

 

          void wx_read_jedec(void)

            {

            unsigned char count = 0;

            unsigned char ids[3] = {0};

            cs_sel();

 

            reg_write(SPIBASE,SPICMD,WX_READ_JEDEC_CMD);

            while(reg_read(SPIBASE,SPICFG)&0x02);

 

            while(count < 3)

            {

                        reg_write(SPIBASE,SPICMD,WX_DUMMY_CMD);

                        while(reg_read(SPIBASE,SPICFG)&0x02);

                        ids[count] = reg_read(SPIBASE,SPIDAT);

                        ++count;

            }

            cs_res();

           

            printf("jedec :%x %x %/x /n",ids[0],ids[1],ids[2]);

 

}

下图6演示使用myflash.exe 获得jedec id

 

  

 

2.     Dump rom

通过前面提到的read data instruction 就可以读出spi rom中的内容。下述code演示了该过程。代码先创建一个文件,然后片选设备,发read data命令给spi device 接着送三个字节的地址A2A1A0,然后spi device就会回对应地址的数据我是从0地址处开始读一共读了1M byte。要注意的是直到读完所需数据才能将SPICS# Pull high。同样在读数据的时候要发dummy command

 

          void wx_read_data()

            {

            unsigned int pos = 0;

            unsigned int count = 0;

            unsigned char buffer[BUFFER_SIZE] = {0};

 

            FILE*   fp = NULL;

 

            fp = fopen(gpargv,"wb");

            printf("read data start/n");

 

            cs_sel();

 

            reg_write(SPIBASE,SPICMD,WX_READ_DATA_CMD);

            while(reg_read(SPIBASE,SPICFG)&0x02);

 

            reg_write(SPIBASE,SPICMD,0);

            while(reg_read(SPIBASE,SPICFG)&0x02);

            reg_write(SPIBASE,SPICMD,0);

            while(reg_read(SPIBASE,SPICFG)&0x02);

            reg_write(SPIBASE,SPICMD,0);

            while(reg_read(SPIBASE,SPICFG)&0x02);

 

            for( pos = 0; pos < 256 ; ++pos)

            {

                        for(count = 0; count < BUFFER_SIZE; ++count)

                        {

                                    reg_write(SPIBASE,SPICMD,WX_DUMMY_CMD);

                                    while(reg_read(SPIBASE,SPICFG)&0x02);

                                    buffer[count] = reg_read(SPIBASE,SPIDAT);

                        }

                        fwrite(buffer,sizeof(unsigned char), BUFFER_SIZE, fp);

                        if(pos == 0)

                                    printf("=>");

                        else

                        {

                                    putchar('/b');

                                    printf("=>");

                        }

            }

            putchar('/n');

 

            cs_res();

            printf("read data end /n");

            fclose(fp);

            }

下图7演示使用myflash.exe 获得dump flash romkbc.bin:

 

 

3.     Flash rom

按照spec的描述,在flash rom之前需要先erase rom所以我们需要先发sector erase 指令擦掉rom中的内容,然后再通过page program 指令flash romsector4Kbyte为单位,page256Byte为单位。下面的code演示了上述过程。sector erasepage program是整个程序中最为复杂的部分,要注意的是一定要严格的按照w25x80 spec规定的步骤,而且尤其要注意判断erase或者page命令是否完成要看status register而不是EC 中的spicfg registerw25x80 spec有提到这个部分(红色代码部分)。我因为没有看status register的状态确定命令是否完成而调试了整整一个礼拜L

 

          void wx_sector_erase(void)

            {

            unsigned char a2=0;

            unsigned char a1=0;

            unsigned char a0=0;

            unsigned char status;

            int count = 0;

 

            printf("sector erase start/n");

 

            status = wx_read_status();

            wx_write_enable();

            status |= 0x02;

 

            wx_write_status(status & 0x03);

 

            status = wx_read_status();

 

            while(wx_read_status()&0x01);

 

            for(count = 0; count < 0x100; ++count)

            {

                        a2 = (unsigned char)(count&0xff0)>>4;

                        a1 = (unsigned char)(count&0x0f)<<4;

                        a0 = 0;

                       

                        wx_write_enable();

                       

                        cs_sel();

                        reg_write(SPIBASE,SPICMD,WX_SEC_ERASE_CMD);

                        while(reg_read(SPIBASE,SPICFG)&0x02);

                        reg_write(SPIBASE,SPICMD,a2);

                        while(reg_read(SPIBASE,SPICFG)&0x02);

                        reg_write(SPIBASE,SPICMD,a1);

                        while(reg_read(SPIBASE,SPICFG)&0x02);

                        reg_write(SPIBASE,SPICMD,a0);

                        while(reg_read(SPIBASE,SPICFG)&0x02);

                       

                        cs_res();

                       

                        while(wx_read_status()&0x01);

                       

                        if(count == 0)

                                    printf("=>");

                        else

                        {

                                    putchar('/b');

                                    printf("=>");

                        }

            }

            putchar('/n');

           

            printf("sector erase start/n");

            }

 

            void wx_page_write(void)

            {

            unsigned int count = 0;

            unsigned int bc=0;

            unsigned char status=0;

            unsigned char a2=0;

            unsigned char a1=0;

            unsigned char a0=0;

            unsigned long add = 0;

            unsigned char buffer[256] = {0};

            unsigned long shadowadd = 0x400000;

 

            printf("page write start/n");

           

            status = wx_read_status();

           

            wx_write_enable();

           

            status |= 0x02;

 

            wx_write_status(status & 0x03);

 

            while(wx_read_status()&0x01);

            for(count = 0; count < 0x1000; ++count)

            {

                        a0 = 0;

                        a1 =(unsigned char)((add&0xff00)>>8);

                        a2 =(unsigned char)((add&0xff0000) >> 16) ;

 

                        /*get flash rom content from shadow memory*/

                        for(bc = 0; bc < 256; ++bc)

                        {

                                    buffer[bc] = dshadow(shadowadd+bc);

                        }

                        shadowadd += 256;

                       

                        wx_write_enable();

                        cs_sel();

                        reg_write(SPIBASE,SPICMD,WX_PAGE_PRG_CMD);

                        while(reg_read(SPIBASE,SPICFG)&0x02);

                        reg_write(SPIBASE,SPICMD,a2);

                        while(reg_read(SPIBASE,SPICFG)&0x02);

                        reg_write(SPIBASE,SPICMD,a1);

                        while(reg_read(SPIBASE,SPICFG)&0x02);

                        reg_write(SPIBASE,SPICMD,a0);

                        while(reg_read(SPIBASE,SPICFG)&0x02);

                       

                        for(bc = 0; bc < 256;++bc)

                        {

                                    reg_write(SPIBASE,SPICMD,buffer[bc]);

                                    while(reg_read(SPIBASE,SPICFG)&0x02);

                        }

                        cs_res();

 

                        while(wx_read_status()&0x01);

                       

                        add += 0x100;

                        if(count == 0)

                                    printf("=>");

                        else if(count % 16 == 0)

                        {

                                    putchar('/b');

                                    printf("=>");

                        }

            }

           

            wx_write_disable();

            while(wx_read_status()&0x01);

            putchar('/n');

            printf("page write stop/n");

 

            }

下图8演示使用myflash.exe flash Testbios.rom的过程:

 

 

v     Shadow

如此这番,Myflash.exe好像可以工作了J。可是我发现AMIAfu系列的工具一旦开刷可以将U盘直接拔掉,而且还可以正常的刷完,没有任何问题?很酷哦!DOS下面受限于1MByte的限制,而我们的bin file都要1M Afu是怎么做到的呢?答案就在big real modeBIOS肯定熟悉这个概念,进入BRM以后1M的限制就不复存在了,但是有一点要注意load段选择子的时候千万不能让CS,DS这些常用的段寄存器也装入选择子,若CS,DS装了段选择子,DOS就没法活了JMyflash.exe选择load FS。下述代码演示了这个过程(参考计将网binicode)

 

        unsigned long gdt_tab[ ]=

            {

            0,0,

            0x0000FFFF,

            0x008F9200

            };

 

            unsigned char gdt_addr[6]={0};

 

            void enable_a20(void)

            {

            _asm     pusha

            _asm     in al, 92H

            _asm     or al, 0x02

            _asm     out 0x92, al

            _asm     out 0xed, al

            _asm     popa

            }

 

            void enable_brm(void)

            {

            enable_a20();

 

            _asm     cli

 

            _asm     mov        word ptr gdt_addr[0], (2*8-1)

            _asm     mov        eax, ds

            _asm     shl        eax, 4

            _asm     xor        ebx, ebx

            _asm     mov        bx, offset gdt_tab

            _asm     add        eax, ebx

            _asm     mov        dword ptr gdt_addr[2], eax

            _asm     lgdt       fword ptr gdt_addr

 

 

            _asm     mov        bx, 8

            _asm     mov        eax, cr0

            _asm     or         al, 1

 

 

            _asm     mov        cr0, eax

           

 

            _asm     mov        fs, bx

            _asm     and        al, 0x0FE

 

            _asm     mov        cr0, eax

           

            _asm     mov        ax, 0

            _asm     mov        fs, ax

           

            _asm     sti

 

            }

 

 

            unsigned char read_byte (unsigned long Address)

            {

            unsigned char tmp;

           

            _asm db 0x66

            _asm mov di,word ptr Address // MOV EDI, Address

            _asm db 0x67 //32 bit Address Prefix

            _asm db 0x64 //FS:

            _asm mov al,byte ptr [BX] // =MOV AL, FS: [EDI]

            _asm mov tmp,al

 

            return tmp;

            }

 

            void write_byte(unsigned long Address,unsigned char value)

            {

            _asm mov al,value

            _asm db 0x66

            _asm mov di,word ptr Address //MOV EDI, Address

            _asm db 0x67 //32 bit Address Prefix

            _asm db 0x64 //FS:

            _asm mov byte ptr [BX],al //=MOV FS: [EDI],AL

            }

上述代码先打开A20,解除1M的限制,然后修改CR0切到保护模式(PM)然后将数据段选择子loadFS中,后来切回实模式(RM),并给FS0,上述步骤完成以后就可以予取予求了。既然又切回了RM那么为什么还可以寻址4G空间了呢???原因是从286开始每个段寄存器都有一个程序员不可见的部分称为高速缓冲寄存器或者描述符投影寄存器,一旦段寄存器的内容发生变化时,CPU会把段的基址、界限、权限等加载到不可见的高速缓冲寄存器部分;加载了段选择子以后,CPU就会把GDT中的描述符加载进高速缓存中,因此虽然切回了RM但是FS仍然可以寻址4G空间!了解这些以后我们就可以猜测一下为何上电后cpu会跳转到4G-10h地址处执行code了,原因可能就是上电后CS的高速缓冲寄存器中的base address被置入了缺省值0xFFFF0000EIP default0xFFF0

 

v     OCP

费劲心力,Myflash.exe终于可以工作了,可是这样就可以高枕无忧了吗?肯定不行!Myflash.exe仅仅实现了w25x80 flash功能,如果我换了另一个厂家IC呢?Spi是一个事实上标准,它并没有被国际会议标准化,Motorola提出以后大家觉得方便,就模仿着做了。于是就导致了各家的IC虽然都能实现相关功能但是有可能存在instructions/sequence的兼容性的问题。那如何让Myflash.exe能够做到有新的spi rom IC加入时,可以在不影响现有功能的基础上轻松的扩展Myflash.exe的功能呢?这也就是所谓的OCP(开闭原则),系统的设计针对修改关闭对扩展开放,这样的系统才是好的设计。那么如何做到OCP呢?答案就是通过抽象。抽象出这类spi devie的共同的属性和接口,然后由设备按照接口去实现。说不如做,let’s go! 若干年前我曾经在linux下面写过简单的driver,觉得linux driver的架构非常好,具备了OCP的原则,它将对device driveroperations抽象为file_operations,不同的device driver分别实现该file_operations并且向OS注册该driver。一旦user访问该driverOS查找到该driver以后call对应device driver file_operations interface很好实现了多态的概念。无独有偶EFI BIOS中的protocol 的概念应该是异曲同工 (install protocollocate protocol然后使用该protocol interfaces) Myflash.exe实现了一个简单的抽象,spi device由属性jedecinterface spi_operations组成。Jedec固态工业的技术标准,它是国际化的标准,每个spi device都会提供jedec commandjedec id可以用于标识该spi deviceMyflash.exe将每个spi device放入一个数组中,一旦Myflash.exe run就去获取spi device jedec id,然后根据该id查找spi_device,一旦找到该device 后续所有的操作都会调用该device spi_operations interface去完成。代码如下:

 

          typedef            struct   __JEDEC

            {

            unsigned char id0;

            unsigned char id1;

            unsigned char id2;

            }jedec;

 

            typedef struct __SPI_OPR

            {

            void (*write_enable)(void);

            void (*write_disable)(void);

            unsigned char (*read_status)(void);

            void (*write_status)(unsigned char);

            void (*read_data)(void);

            void (*fast_read)(void);

            void (*fast_read_dual)(void);

            void (*page_program)(void);

            void (*sector_erase)(void);

            void (*block_erase)(void);

            void (*chip_erase)(void);

            void (*read_jedec)(void);

            }spi_operations;

 

            typedef            struct __SPI_DEV

            {

            jedec*  m_jedec_id;

            spi_operations* m_opr;

            }spi_device;

 

            jedec w25x80_jedecid =

            {

            0xef,

            0x30,

            0x14

            };

 

            spi_operations w25x80_spi_opr =

            {

            wx_write_enable,

            wx_write_disable,

            wx_read_status,

            wx_write_status,

            wx_read_data,

            wx_fast_read,

            NULL,

            wx_page_write,

            wx_sector_erase,

            NULL,

            NULL,

            wx_read_jedec

            };

           

            spi_device w25x80_dev =

            {

            &w25x80_jedecid,

            &w25x80_spi_opr

            };

           

            spi_device* device_install[DEV_NUM]=

            {

            &w25x80_dev,

            //...

            //...

            //...

            NULL

            };

 

            spi_device* find_dev_byjedec(jedec* pjedecid)

            {

            unsigned char id = 0;

            for( id = 0; device_install[id] != NULL; ++id)

            {

                        if((device_install[id]->m_jedec_id->id0 == pjedecid->id0)

                                    &&(device_install[id]->m_jedec_id->id1 == pjedecid->id1)

                                    &&(device_install[id]->m_jedec_id->id2 == pjedecid->id2))

                                    return device_install[id];

            }

            return NULL;

            }

 

v     Keep Moving

DOS下的flash tool已经完成了,那么windows下应该怎么做呢?大家都知道windows已经进了保护模式了,直接操纵底层将会被限制L。其实也不困难,熟悉DDK的朋友肯定知道答案了。没错一个简单的IO portdriver就可以搞定了,完成driver的接口以后Myflash.exe代码几乎不用改变就可以在windows运行了。有兴趣的话自己写一个,我就不写了J

 

Myflash.exe完整的source code和可执行文件可以在这里下载Source code使用BC31编译,别忘了勾上compiler->advanced code generation->instruction set-> 80386的选项。

    

That’s all!

 

Peter