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_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)影射成虚拟地址后再使用。
我写了一段程序。大家可以试验一下。
以上是基于ldd3中那个最简单的模块hello.ko改的。主要是为了可以在内核空间运行。大家可以把打印出来的内容与ehci spec 2.2对照一下。
3)Schedule Interface Space.
这里就是普通的内存。我们直接就可以访问它。