peterhu 发表于 2009-5-6 14:14:34

[原创]我所知道的EC====>SPI

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

1. Introduction

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

2. Hardware InterfaceSpi接口如下图 1 所示,通常就四根pin,EC Chip有按照Motorola的经典命名方式将这四根pin分别称为MISO、MOSI、SPICLK、SPICS#。不同的IC厂商pin命名的方式可能会有不同,比如有些也会命名为SCK、SDO、SDI、CS#等。




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



3. SPI Instructions

为了方便的操纵slave device,slave device定义一些instructions。这些instructions包括了操作device的基本操作如:读数据,写数据,擦除数据等等。W25x80这颗IC的spec中定义了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即可完成任务。

vRead 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所示:



vSector erase
Sector erase 指令将flash rom的指定的4kBytes内容全部清为‘1’。执行该指令之前要先发write enable 指令而且status register的block 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所示:


vPage program
Page program指令允许一次写特定地址开始的256个字节,前提是该区域必须被清为‘1’,所以要先发sector erase指令将flash rom清为‘1’然后才能执行page program。执行page program之前要先送write enable指令而且status register的block 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好像并不是直接操纵spi去flash biosJ)。心动不如行动Just do it!一个简化的flash tool只需要能flash bios,可以dump bios rom,可以读device id即可。一共三个function,让我来一个一个搞定它。

vImplement 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之前一定要发命令让EC进Reset mode(读rom内容还好,写 rom肯定不能让EC再跑code)。
1.Read device id
按照spec的描述我们需要将SPICS# pull low选中spi device,
然后送9F的command给device,device就会回三个字节分别代表manufacture id、memory type、capacity。IC厂商为了兼容性考虑JEDEC ID必须要提供。下述code演示该过程。注意红色部分,每次读取一个字节都要先送一个dummy command。

          void wx_read_jedec(void)

{
            unsigned char count = 0;
            unsigned char ids = {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 = reg_read(SPIBASE,SPIDAT);

++count;

}

cs_res();



printf("jedec :%x %x %\x \n",ids,ids,ids);

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



2.Dump rom
通过前面提到的read data instruction 就可以读出spi rom中的内容。下述code演示了该过程。代码先创建一个文件,然后片选设备,发read data命令给spi device 接着送三个字节的地址A2、A1、A0,然后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 = {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 = 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 rom到kbc.bin:


3.Flash rom
按照spec的描述,在flash rom之前需要先erase rom所以我们需要先发
sector erase command擦掉rom中的内容,然后再通过page program
Command flash rom。sector以4Kbyte为单位,page以256Byte为单位。下面的code演示了上述过程。sector erase和page program是整个程序中最为复杂的部分,要注意的是一定要严格的按照w25x80 spec规定的步骤,而且尤其要注意判断erase或者page命令是否完成要看status register而不是EC 中的spicfg register,w25x80 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 = {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 = 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);
                                    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的过程:


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

      unsigned long gdt_tab[ ]=

{

0,0,

0x0000FFFF,

0x008F9200

};


unsigned char gdt_addr={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, (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, 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 // =MOV AL, FS:
            _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 ,al //=MOV FS: ,AL

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

vOCP
费劲心力,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 driver的operations抽象为file_operations,不同的device driver分别实现该file_operations并且向OS注册该driver。一旦user访问该driver,OS查找到该driver以后call对应device driver file_operations interface很好实现了多态的概念。无独有偶EFI BIOS中的protocol 的概念应该是异曲同工
(install protocol、locate protocol然后使用该protocol interfaces) 。Myflash.exe实现了一个简单的抽象,spi device由属性jedec和interface spi_operations组成。Jedec是固态工业的技术标准,它是国际化的标准,每个spi device都会提供jedec command,jedec id可以用于标识该spi device。Myflash.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=

{

&w25x80_dev,
            //...
            //...
            //...

NULL

};


spi_device* find_dev_byjedec(jedec* pjedecid)

{
            unsigned char id = 0;
            for( id = 0; device_install != NULL; ++id)

{
                        if((device_install->m_jedec_id->id0 == pjedecid->id0)

&&(device_install->m_jedec_id->id1 == pjedecid->id1)

&&(device_install->m_jedec_id->id2 == pjedecid->id2))
                                    return device_install;

}
            return NULL;

}

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

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


That’s all!

Peter

[ 本帖最后由 peterhu 于 2009-5-8 09:18 编辑 ]

LordZhou 发表于 2009-5-6 18:53:24

下载不了,能否传到这里,以便学习,多谢!

peterhu 发表于 2009-5-8 09:21:21

myflash source code

我已经将myflash source code上传在文章的附件部分了,有需要的朋友可以下载。

wwwllllove 发表于 2009-5-10 22:07:14

学习,太好了.开源才是硬道理!佩服

shermanliu 发表于 2009-5-12 10:26:23

SPI Development Tool, 便宜,好使 http://www.totalphase.com/protocols/spi/

A_jack 发表于 2009-5-14 17:12:35

about index

peter请教一个问题,如果SPI flash是挂在EC后面。可以通过访问SPI controller的方法来实现flash吗。

peterhu 发表于 2009-5-14 19:36:57

我写的code就是读写挂在EC下面的SPI flash.
你说的SPI controller指的是EC chip内部的吗?
我是通过back door操作EC chip内部的spi controller读写spi flash的.

A_jack 发表于 2009-5-15 10:41:19

原帖由 peterhu 于 2009-5-14 19:36 发表 http://www.ufoit.com/bbs/images/common/back.gif
我写的code就是读写挂在EC下面的SPI flash.
你说的SPI controller指的是EC chip内部的吗?
我是通过back door操作EC chip内部的spi controller读写spi flash的.


    是的,peter我所指的SPI controller指的就是南桥内部的SPI controller.所以我有一个疑问:如果SPI flash是挂在EC后面,这样的话操作南桥内部SPI controller能够对SPI flash实现读写吗。还有就是back door是所有的EC都支持的么。

peterhu 发表于 2009-5-15 11:25:40

你误会我的意思了,我操作的spi controller是EC内部的,而不是南桥的。
back door也有听别家EC提起过,可能是名称不同,应该有功能相近的东东。

A_jack 发表于 2009-5-15 14:30:11

peter我知道你是控制EC内部的SPI controller。这种方法我知道是OK的。我的问题是当SPI flash连在EC后端的情况下,我们可以通过控制EC来flash。那这种情况是不是也能通过南桥内部的SPI controller进行flash? 还是说南桥的SPI controller只能操作直接挂在南桥SPI接口上的SPI flash?对挂在EC后端的SPI flash无效

peterhu 发表于 2009-5-15 19:53:50

南桥的spi controller应该只能控制接在南桥的spi flash.

chi0901 发表于 2009-6-16 12:18:11

AFU工具是通过SMI来做的,读写擦除函数由BIOS实现。这样AFU就只能刷AMI的BIOS,楼主的方法好不管哪家的BIOS都可以刷,呵呵,学习了

zangmf 发表于 2009-6-26 15:43:59

:handshake

strength 发表于 2009-7-7 16:27:51

楼主提纲挈领,对SPI FLASH function 领悟颇深,佩服

ddning 发表于 2012-7-26 22:05:43

学习,太好了!佩服!!!

enjoylife 发表于 2016-3-1 17:59:21

受教,谢谢。
页: [1]
查看完整版本: [原创]我所知道的EC====>SPI