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的内容
网上找到了一段程序,大家可以试验一下。
#include < stdio.h >
#include < assert.h >
#include < sys / io.h >
#define IO_PORTS1 1 /* ioport <= 0x3ff */
#define IO_PORTS2 2 /* ioport >0x3ff && ioport < 0xffff */
#define IO_PERMOFF 0
#define IO_PERMON 1
#define IO_PERMON2 3
#define RW_DELAY 10000 /*delay 100000 microseconds for reading and writing I/O ports. */
#ifndef BOOL
typedef unsigned char BOOL;
#endif
#ifndef BYTE
typedef unsigned char BYTE;
#endif
#ifndef DWORD
typedef unsigned long DWORD;
#endif
#ifndef INT
typedef unsigned int INT;
#endif
#ifndef ULONG
typedef unsigned long ULONG;
#endif
#ifndef WORD
typedef unsigned short WORD;
#endif
/*
** Function : Write the value of the specified I/O port by giving the length and the
** starting address.
** Parameter: PortAddr: the port address
** PortVal : the value to set
** size : size = 1 for reading 1 byte, 2 for word, 4 for double words
** Return : 1 returned if success, or 0 returned
*/
BOOL SetPortVal(WORD PortAddr, DWORD PortVal, BYTE size)
{
BOOL Ret = 0 ;
INT tmpRet = 0 ;
ULONG numperm = 1 ;
INT privilege = 0 ;
assert(PortAddr > 0 );
if (PortAddr <= 0x3ff )
{
tmpRet = ioperm((ULONG)PortAddr, numperm, IO_PERMON);
privilege = IO_PORTS1;
}
else if ( PortAddr > 0x3ff )
{
tmpRet = iopl(IO_PERMON2);
privilege = IO_PORTS2;
}
else
return Ret;
if (tmpRet < 0 )
{
fprintf(stderr, " can't set the io port permission for setting ! " );
return Ret;
}
else
{
switch (size)
{
case 1 : /* write one byte to the port */
outb(PortVal, PortAddr);
break ;
case 2 : /* write one word to the port */
outw(PortVal, PortAddr);
break ;
case 4 : /* write double words to the port */
outl(PortVal, PortAddr);
break ;
default :
Ret = 0 ;
break ;
}
usleep(RW_DELAY);
Ret = 1 ;
}
if ( privilege == IO_PORTS1 )
ioperm((ULONG)PortAddr, numperm, IO_PERMOFF);
else if (privilege == IO_PORTS2 )
iopl(IO_PERMOFF);
return Ret;
}
/*
** Function : Read the value of the specified I/O port by giving the lenght and the
** starting address.
** Parameter: PortAddr : the port address
** PortVal : value from port
** size : size = 1 for reading 1 byte, 2 for word, 4 for double words
** Return : 1 returned if success, or 0 returned.
*/
BOOL GetPortVal(WORD PortAddr, DWORD * PortVal, BYTE size)
{
BOOL Ret = 0 ;
int tmpRet = 0 ;
unsigned long numperm = 1 ;
int privilege = 0 ;
assert(PortAddr > 0 );
assert(PortVal != NULL);
if (PortAddr <= 0x3ff )
{
tmpRet = ioperm((unsigned long )PortAddr, numperm, IO_PERMON);
privilege = IO_PORTS1;
}
else if ( PortAddr > 0x3ff )
{
tmpRet = iopl(IO_PERMON2);
privilege = IO_PORTS2;
}
else
return Ret;
if (tmpRet < 0 )
{
fprintf(stderr, " can't set the io port permission for reading ! " );
return Ret;
}
else
{
switch (size)
{
case 1 : /* read one byte from the port */
* PortVal = inb(PortAddr);
break ;
case 2 : /* read one word from the port */
* PortVal = inw(PortAddr);
break ;
case 4 : /* read double words from the port */
* PortVal = inl(PortAddr);
break ;
default :
Ret = 0 ;
break ;
}
usleep(RW_DELAY);
Ret = 1 ;
}
if ( privilege == IO_PORTS1 )
ioperm( (unsigned long )PortAddr, numperm, IO_PERMOFF );
else if ( privilege == IO_PORTS2 )
iopl(IO_PERMOFF);
return Ret;
}
int main ( int argc, char * argv[])
{
WORD add_port = 0xcf8 ;
WORD data_port = 0xcfc ;
DWORD addr = 0x80000000 ;
DWORD port_value;
BYTE size = 4 ;
int input;
printf( " Please select the option number as follow: " );
printf( " 1--bus 0:dev:0 fun:0 as address 0x80000000 " );
printf( " 2--bus 0:dev:1 fun:0 as address 0x80000800 " );
printf( " 3--input your own defined address value: " );
scanf( " %d " , & input);
switch (input)
{
case 1 :
addr = 0x80000000 ;
break ;
case 2 :
addr = 0x80000800 ;
break ;
case 3 :
printf( " please input the 32 bits address in Hex format(such as 80007800): " );
scanf ( " %x " , & addr);
break ;
default :
printf( " input invalid option num, exit program. " );
return - 1 ;
}
printf ( " The addr is :%X " , addr);
printf ( " The add_port is : %X " , add_port);
printf ( " The data_port is : %X " , data_port);
if (SetPortVal(add_port, addr, size))
{
if (GetPortVal(data_port, & port_value, size))
{
printf( " port value is :%08X " , port_value);
return 0 ;
}
}
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)影射成虚拟地址后再使用。
我写了一段程序。大家可以试验一下。
{
unsigned long port_value, mem_value;
void __iomem * add;
int i;
printk(KERN_ALERT " Hello, world " );
add = ioremap( 0xfe226400 , 0x400 );
for (i = 0 ; i < 100 ; i ++ ) {
mem_value = ioread32(add + i * 4 );
printk( " %08X mem value is :%08X " , add + i * 4 , mem_value);
}
iounmap(add);
return 0 ;
}
以上是基于ldd3中那个最简单的模块hello.ko改的。主要是为了可以在内核空间运行。大家可以把打印出来的内容与ehci spec 2.2对照一下。
3)Schedule Interface Space.
这里就是普通的内存。我们直接就可以访问它。