Linux那些事儿之我是EHCI(1) 接口体系 – fudan

EHCI首先是一个PCI设备,我们可以lspci一下看看。

00:1a:7 USB Controller: Intel Corporation USB2 EHCI Controller #1 (rev 03)

我们与外围硬件打交道,可以把数据用in(out)指令传递给外围硬件,还可以把数据传输到cpu和外围硬件共享的内存里面去。这些都是计算机与硬件的接口。(参见ldd3 第9章)

那么我们的程序如何与EHCI联系,交流呢?EHCI定义了三个接口空间。如图

作为一个程序员,我们关心的是如何在代码中读/写这些地方的内容。概念性的东西肯定是LDD3写的最好,我就不赘述了。

1)pci configuration space. (ldd3 第12章)

由于EHCI是一个PCI设备,这里用于系统组件枚举和PCI的电源管理。

以x86为例,读取PCI总线套路是这样的。我们要读取PCI总线上地址为add,长度为4个字节的内容。

outl(add, 0xcf8); // 先把add的out到地址为0xcf8的地方

value = inl(0xcfc); // 然后再读取0xcfc的内容

网上找到了一段程序,大家可以试验一下。

/**//*name:pci.c*/#include<stdio.h>#include<assert.h>#include<sys/io.h>#defineIO_PORTS11/*ioport<=0x3ff*/#defineIO_PORTS22/*ioport>0x3ff&&ioport<0xffff*/#defineIO_PERMOFF0#defineIO_PERMON1#defineIO_PERMON23#defineRW_DELAY10000/*delay100000microsecondsforreadingandwritingI/Oports.*/#ifndefBOOLtypedefunsignedcharBOOL;#endif#ifndefBYTEtypedefunsignedcharBYTE;#endif#ifndefDWORDtypedefunsignedlongDWORD;#endif#ifndefINTtypedefunsignedintINT;#endif#ifndefULONGtypedefunsignedlongULONG;#endif#ifndefWORDtypedefunsignedshortWORD;#endif/**//***Function:WritethevalueofthespecifiedI/Oportbygivingthelengthandthe**startingaddress.**Parameter:PortAddr:theportaddress**PortVal:thevaluetoset**size:size=1forreading1byte,2forword,4fordoublewords**Return:1returnedifsuccess,or0returned*/BOOLSetPortVal(WORDPortAddr,DWORDPortVal,BYTEsize)…{BOOLRet=0;INTtmpRet=0;ULONGnumperm=1;INTprivilege=0;assert(PortAddr>0);if(PortAddr<=0x3ff)…{tmpRet=ioperm((ULONG)PortAddr,numperm,IO_PERMON);privilege=IO_PORTS1;}elseif(PortAddr>0x3ff)…{tmpRet=iopl(IO_PERMON2);privilege=IO_PORTS2;}elsereturnRet;if(tmpRet<0)…{fprintf(stderr,"can’tsettheioportpermissionforsetting! ");returnRet;}else…{switch(size)…{case1:/**//*writeonebytetotheport*/outb(PortVal,PortAddr);break;case2:/**//*writeonewordtotheport*/outw(PortVal,PortAddr);break;case4:/**//*writedoublewordstotheport*/outl(PortVal,PortAddr);break;default:Ret=0;break;}usleep(RW_DELAY);Ret=1;}if(privilege==IO_PORTS1)ioperm((ULONG)PortAddr,numperm,IO_PERMOFF);elseif(privilege==IO_PORTS2)iopl(IO_PERMOFF);returnRet;}/**//***Function:ReadthevalueofthespecifiedI/Oportbygivingthelenghtandthe**startingaddress.**Parameter:PortAddr:theportaddress**PortVal:valuefromport**size:size=1forreading1byte,2forword,4fordoublewords**Return:1returnedifsuccess,or0returned.*/BOOLGetPortVal(WORDPortAddr,DWORD*PortVal,BYTEsize)…{BOOLRet=0;inttmpRet=0;unsignedlongnumperm=1;intprivilege=0;assert(PortAddr>0);assert(PortVal!=NULL);if(PortAddr<=0x3ff)…{tmpRet=ioperm((unsignedlong)PortAddr,numperm,IO_PERMON);privilege=IO_PORTS1;}elseif(PortAddr>0x3ff)…{tmpRet=iopl(IO_PERMON2);privilege=IO_PORTS2;}elsereturnRet;if(tmpRet<0)…{fprintf(stderr,"can’tsettheioportpermissionforreading! ");returnRet;}else…{switch(size)…{case1:/**//*readonebytefromtheport*/*PortVal=inb(PortAddr);break;case2:/**//*readonewordfromtheport*/*PortVal=inw(PortAddr);break;case4:/**//*readdoublewordsfromtheport*/*PortVal=inl(PortAddr);break;default:Ret=0;break;}usleep(RW_DELAY);Ret=1;}if(privilege==IO_PORTS1)ioperm((unsignedlong)PortAddr,numperm,IO_PERMOFF);elseif(privilege==IO_PORTS2)iopl(IO_PERMOFF);returnRet;}intmain(intargc,char*argv[])…{WORDadd_port=0xcf8;WORDdata_port=0xcfc;DWORDaddr=0x80000000;DWORDport_value;BYTEsize=4;intinput;printf("Pleaseselecttheoptionnumberasfollow: ");printf("1–bus0:dev:0fun:0asaddress0x80000000 ");printf("2–bus0:dev:1fun:0asaddress0x80000800 ");printf("3–inputyourowndefinedaddressvalue: ");scanf("%d",&input);switch(input)…{case1:addr=0x80000000;break;case2:addr=0x80000800;break;case3:printf("pleaseinputthe32bitsaddressinHexformat(suchas80007800):");scanf("%x",&addr);break;default:printf("inputinvalidoptionnum,exitprogram. ");return-1;}printf("Theaddris:%X ",addr);printf("Theadd_portis:%X ",add_port);printf("Thedata_portis:%X ",data_port);if(SetPortVal(add_port,addr,size))…{if(GetPortVal(data_port,&port_value,size))…{printf("portvalueis:%08X ",port_value);return0;}}return-1;}

打印出来的内容与(1)用lspci -xxx 命令输出;(2)EHCI spec 2.1章的内容对照一下。

好了,现在问题是我们怎么知道PCI总线上EHCI的地址add。lspci可以看到所有PCI设备的地址。首先,EHCI不管有没有驱动,它这个PCI设备在PCI总线枚举时就被探测到了,这时候它就被分配了地址。每个PCI 外设有一个总线号, 一个设备号, 一个功能号标识号。比如00:1a:7,00总线号,1a设备号,7功能号。这些个号组成了独一无二的ID。ID和地址的转换关系是这样的:

我们只要ID,就知道了外设的地址,然后就可以读写PCI寄存器的内容。另外可以看看

pci_read(),pci_write() //arch/i386/pci/common.c

的内容,这样会有更深的理解。

2)regster space.

这是基于内的i/o寄存器,就是i/o内存。这里包含了Capability Registers和Operational Registers。我们可以读取/proc/iomem 看看io内存的分配情况。我们可以看到ehci的地址是fe226400-fe2267ff。这段内存不可以直接读写,先要调用ioremap(或是ioremap_nocache)影射成虚拟地址后再使用。

我写了一段程序。大家可以试验一下。

#include <asm/io.h>staticinthello_init(void)…{unsignedlongport_value,mem_value;void__iomem*add;inti;printk(KERN_ALERT"Hello,world ");add=ioremap(0xfe226400,0x400);for(i=0;i<100;i++)…{mem_value=ioread32(add+i*4);printk("%08Xmemvalueis:%08X ",add+i*4,mem_value);}iounmap(add);return0;}

以上是基于ldd3中那个最简单的模块hello.ko改的。主要是为了可以在内核空间运行。大家可以把打印出来的内容与ehci spec 2.2对照一下。

3)Schedule Interface Space.

这里就是普通的内存。我们直接就可以访问它。

带着感恩的心启程,学会爱,爱父母,爱自己,爱朋友,爱他人。

Linux那些事儿之我是EHCI(1) 接口体系 – fudan

相关文章:

你感兴趣的文章:

标签云: