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
>
#define
IO_PORTS11/*ioport<=0x3ff*/
#define
IO_PORTS22/*ioport>0x3ff&&ioport<0xffff*/
#define
IO_PERMOFF0
#define
IO_PERMON1
#define
IO_PERMON23
#define
RW_DELAY10000/*delay100000microsecondsforreadingandwritingI/Oports.*/
#ifndefBOOL
typedefunsigned
char
BOOL;
#endif
#ifndefBYTE
typedefunsigned
char
BYTE;
#endif
#ifndefDWORD
typedefunsigned
long
DWORD;
#endif
#ifndefINT
typedefunsigned
int
INT;
#endif
#ifndefULONG
typedefunsigned
long
ULONG;
#endif
#ifndefWORD
typedefunsigned
short
WORD;
#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;
}
else
returnRet;
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;
}
else
returnRet;
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;
}
int
main(
int
argc,
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)影射成虚拟地址后再使用。
我写了一段程序。大家可以试验一下。
以上是基于ldd3中那个最简单的模块hello.ko改的。主要是为了可以在内核空间运行。大家可以把打印出来的内容与ehci spec 2.2对照一下。
3)Schedule Interface Space.
这里就是普通的内存。我们直接就可以访问它。