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

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

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

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

那么我们的程序如何与EHCI联系,交流呢?EHCI定义了三个接口空间。如图Linux那些事儿之我是EHCI(1) 接口体系_第1张图片

作为一个程序员,我们关心的是如何在代码中读/写这些地方的内容。概念性的东西肯定是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;
unsigned
longnumperm=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((unsigned
long)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和地址的转换关系是这样的:

Linux那些事儿之我是EHCI(1) 接口体系_第2张图片

我们只要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>
static int hello_init( void )
... {
unsigned
longport_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)