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

来源:互联网 发布:武汉软件开发公司 编辑:程序博客网 时间:2024/05/06 17:44

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)影射成虚拟地址后再使用。

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

    #include <asm/io.h>
static int hello_init(void)
{
        unsigned 
long  port_value, mem_value;
        
void __iomem *add;
        
int i;
        printk(KERN_ALERT 
"Hello, world ");
        add = ioremap(0xfe2264000x400);
        
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.

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