以dm9000驱动为例理解I/O端口

 原创文章,转载请注明出处!

        以dm9000网卡驱动的一部分为例,分析一下I/O端口的作用及使用方法。适当的,会引用LDD3中的short的一些代码。下面从概念入手,了解一下I/O端口是个什么东东,理论指导实践嘛。

1. I/O体系结构

       首先说明一下I/O体系结构,这里不多说,先放个图。

以dm9000驱动为例理解I/O端口_第1张图片

(1) I/O总线

        在计算机的CPU,RAM和I/O设备之间传递信息的数据通路成为总线。一台计算机可以包括多种类型的总线,而不同类型总线之间就靠硬件“”来连接。任何设备有且仅能连接一条总线,总线就类似于当地公安局,你总不能在多个公安局都注册户口吧,注册一个让别人知道你是谁就好了。理所当然的,CPU与I/O设备之间的总线就是I/O总线。

(2) I/O端口

        每个连接到I/O总线上的设备都有自己的I/O地址集,称为I/O端口,一个I/O端口只占一个字节。ULK3中说为了降低成本,通常把同一I/O端口用于不同的目的,也可把同一I/O端口作为输入输出寄存器,这点在dm9000驱动程序中将有所体现。而内核采用一种resource结构对硬件设备的I/O端口,中断等进行分配。另外,在LDD3中还提到了I/O内存的概念,它是映射到内存的设备寄存器或设备内存,也就是说他要在分配之后利用ioremap映射之后才可以被内核访问,也就是得到了虚拟地址。看一段short代码,就很清楚了。

if (!use_mem) {

              if (! request_region(short_base, SHORT_NR_PORTS, "short")) {

                     printk(KERN_INFO "short: can't get I/O port address 0x%lx\n",

                                   short_base);

                     return -ENODEV;

              }

}

else {

              if (! request_mem_region(short_base, SHORT_NR_PORTS, "short")) {

                     printk(KERN_INFO "short: can't get I/O mem address 0x%lx\n",

                                   short_base);

                     return -ENODEV;

              }

              short_base = (unsigned long) ioremap(short_base, SHORT_NR_PORTS);

}

        下面说一个让我头疼很久的问题。两者的区别在哪里?通过查找资料,这主要涉及到了I/O的编址方式,I/O编址方式有独立编址和统一编址两种,独立编址就是I/O端口于内存单元分开编址,这样需要专用的I/O指令对设备进行操作;统一编址则是让I/O端口占用一个存储单元,与内存统一编址,好处是可以用访问内存的指令访问设备,但不足就是会占用一定的内存空间。所以,如果我们的I/O端口采用独立编址,这时I/O端口与I/O内存有严格的差异,I/O端口在I/O地址空间运行,而I/O内存则被映射到了内存。但如果I/O端口采用统一编制,那么I/O内存就可以理解为在地址上连续的I/O端口的集合,两者的区别就不那么明显了。

      对于I/O端口,用到三个函数,request_region()用于分配n个I/O端口,release_region()用于释放n个I/O端口,check_region()用于查询I/O端口集是否可用。操作方面用到了in[c.w.l]和out[b,w,l]。

      对于I/O内存,也用到三个函数,request_mem_region()用于分配n个I/O端口,release_mem_region()用于释放n个I/O端口,check_mem_region()用于查询I/O端口集是否可用。操作方面旧的方法是read[c.w.l]和write[b,w,l],新的方法为read[8.16.32]和write[8,16,32]。

(3) I/O接口

        引用ULK3中讲的,I/O接口是处于一组I/O端口和对应的设备控制器之间的一种硬件电路。它起翻译器的作用,即把I/O端口中的值转化为设备所需要的命令和数据,或在相反的方向上,它检测设备状态变化并对起到状态寄存器作用的I/O端口进行相应的更新。还可以通过一条IRQ线把这种电路连接到可编程中断控制器上。

(4) 设备控制器

设备控制的核心部分,有两个作用:

a. 对从I/O接口接收到的高级命令进行解释,并通过向设备发送适当的电信号序列强制执行特定的操作。

b. 对从设备接收到的电信号进行转换和适当的解释,并修改(通过I/O接口)状态寄存器的值

 

理论说完了,下面开始结合dm9000驱动代码分析对I/O端口的使用。分析不到位之处还请各位指正。首先给出在ARM移植驱动时arch/arm/mach-smdk2440.c添加的一段代码:

static struct resource s3c_dm9k_resource[] = {

      [0] = {

           .start= S3C2410_CS4,

           .end = S3C2410_CS4 + 3,

           .flags     = IORESOURCE_MEM,

      },

      [1] = {

           .start= S3C2410_CS4 + 4,

           .end = S3C2410_CS4 + 4 + 3,

           .flags     = IORESOURCE_MEM,

      },

      [2] = {

           .start= IRQ_EINT7,

           .end = IRQ_EINT7,

           .flags     = IORESOURCE_IRQ | IRQF_TRIGGER_RISING,

      }

};

这段代码填充的就是前面提到的resource结构数组,该结构分配了两个I/O端口作为寄存器(IORESOURCE_MEM),同时分配了一个中断号。但是地址为什么选择在S3C2410_CS4到S3C2410_CS4 + 3和S3C2410_CS4+4到S3C2410_CS4 + 4+3,主要看硬件电路图的引脚连接,我的开发板上dm9000的AEN引脚连接的是nGCS4,即将网卡挂在了bank4上面,也就是说,网卡的所有寄存器都将映射到bank4上(bank4有128M,网卡中的寄存器总共使用的地址是0xff),所以起始地址为S3C2410_CS4,那为什么选择S3C2410_CS4+3,而不是要加其他的值呢,这就关系到另一个连线,截个图

 

以dm9000驱动为例理解I/O端口_第2张图片

 

看到没?就是CMD这个引脚,dm9000数据手册上有这样一句:

Command Type

When high, the access of this command cycle is DATA port

When low, tha access of this command cycle is ADDRESS port

这个CMD引脚为高电平时,是数据端口,CMD为低电平时,位地址端口。而我的开发板上CMD口接的是LADDR2,也就是第三根地址线,在bank4中,当地址小于四个字节时,该引脚为低电平,使用地址端口,而在大于四个字节而小于八个字节时是高电平,使用数据端口,这样,我们就可以很清楚的知道了,resource[0]就是定义了地址端口,而resource[0]则定义了数据端口,更进一步,只要以8为倍数的bank4的地址为一组,都可以用来使用,只要保证期间LADDR2只变化一次。看后面的例子时,这点将予以体现。

 

下面看驱动代码,在dm9000.c当中的dm9000_probe函数中,使用了刚才定义的resource结构:

db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);

db->irq_res  = platform_get_resource(pdev, IORESOURCE_IRQ, 0);

其中,db是board_info结构,该结构定义了相关的硬件信息,其中的成员addr_res(地址resource),data_res(数据resource)和irq_res(中断resource)都是resource结构。而platform_get_resource()函数,则是按条件提取resource数组中的对应的项,并赋给相应的resource结构。我们已经知道了,分配的两个I/O端口,实际一个是地址端口,一个是数据端口。下面我们不关心中断号资源,把注意力只转向其他两个。

然后假定我们的这两个结构都分配成功,继续往下看probe函数,看到了让人兴奋的语句:

iosize = res_size(db->addr_res);

db->addr_req = request_mem_region(db->addr_res->start, iosize,pdev->name);

跨过几行代码:

db->io_addr = ioremap(db->addr_res->start, iosize);

这几句,从db->addr_res中获得了分配的I/O端口数目iosize,由于ARM支持I/O内存,同时它也就是I/O内存空间的大小,然后用request_mem_region()分配I/O内存,用ioremap()重映射I/O内存到内存当中。同样可以看到:

iosize = res_size(db->data_res);

db->data_req = request_mem_region(db->data_res->start, iosize,pdev->name);

db->io_data = ioremap(db->data_res->start, iosize);

这样,按我们理论中说的,db->io_addr,db->io_data就保存了相关的I/O端口虚拟地址,我们就可以使用得到的I/O内存了,其中db->io_addr是一个地址端口,用于传输地址,db->io_data是数据端口,用以传输数据。

下面看看怎么用,继续往下看代码,注意到

ndev->base_addr = (unsigned long)db->io_addr;

ndev是一个net_device结构,定义了一个网络设备的相关信息,ndev->base_addr是设备的I/O基址。这句把内核可访问的网卡的设备基址赋给了该成员。继续向下看代码,看到

dm9000_reset(db);

完成dm9000的复位,跳转到dm9000_reset函数看看:

dm9000_reset(board_info_t * db)

{

       dev_dbg(db->dev, "resetting device\n");

 

       /* RESET device */

       writeb(DM9000_NCR, db->io_addr);

       udelay(200);

       writeb(NCR_RST, db->io_data);

       udelay(200);

}

记得上面说过的吗?db->io_addr是一个地址端口,用于传输地址,db->io_data是数据端口,用以传输数据。这里充分体现了,首先,想地址端口写DM9000_NCR,即dm9000的NCR寄存器的地址,找到该寄存器,然后向数据端口写NCR_RST,为NCR寄存器的RST位赋1,实现复位。后面还有很多对端口操作的代码,也是类似的,这里不再赘述。

可见最关键的问题就在于应该理解我们的端口是作为什么端口使用的,只要明白了这点,一切问题迎刃而解。

你可能感兴趣的:(c,command,cmd,Access,电信,代码分析)