当前位置:Linux教程 - Linux - 精析RTL8139 驱动程序!1

精析RTL8139 驱动程序!1



        
    前言
    RTL8139 可能是目前最受欢迎的网络卡,它的价格便宜,功能上也还能接受。虽然在效能上有时会略不及Intel 的 eepro100,但因为价格实在太便宜了,所以芯片上的一点小问题通常也接忽略不计。

    废话少话,马上来说明 8139too 这个驱动程序。8139 虽然价格不高,但该有的功能一点也不缺。它内建了符合 MII 规格的 tranceiver,可以自动判断连接的网络是那一种型态。它也可以使用 DMA 直接使用位于主记忆体的缓区来存网络上接收的封包,同样的,待传送的封包也可利用 DMA 传送到网络卡上。所以虽然在 8139 芯片上只有 2K 的接收缓冲区和 2K 的传送缓冲区,其效能仍十分不错。

    除了 realtek 本身外,有不少的厂商也使用相同的内核生产了和 8139 相容的网络芯片,包括了

    SMC 1211
    MPX 5030
    DELTA 8139
    ADDTRON 8139
    DFE 538
    可能还有更多。
    驱动程序初始化
    就像其它的驱动程序一样,驱动程序在使用 insmod 载入时,第一个初呼叫的函数是 init_module,在使用 rmmod 移除时,cleanuo_module 会被呼叫。在 init_module 中,我们注册了一个 PCI 驱动程序
    static struct pci_driver rtl8139_pci_driver = {
    name: MODNAME,
    id_table: rtl8139_pci_tbl,
    probe: rtl8139_init_one,
    remove: rtl8139_remove_one,
    suspend: rtl8139_suspend,
    resume: rtl8139_resume,
    };


    static int __init rtl8139_init_module (void)
    {
    return pci_module_init (&rtl8139_pci_driver);
    }

    这个结构和上次介绍的 sis900 其实差别不大。rtl8139_init_one 用来初始化一个 8139 芯片。PCI 驱动程序最大的好处是 PCI BUS 提供了组态空间 (configuration space) 来存放驱动程序所需的 IO 位址及中断号码等资料,我们不必再像 ISA 驱动程序一样需要指定这些资源。

    rtl8139_init_one 会呼叫 rtl8139_init_board 来初始化芯片,基本上 8139 这个芯片算是一个很容易使用的芯片,基本的 PCI 初始化后就可以直接使用了。所以 rtl8139_init_one 和 rtl8139_init_board 其实多半是在做一些错误检查的动作,并由 PCI 表格中所得稍后会用的到的资源。

    我从 rtl8139_init_board 中取出一些比较重要的片断加以说明,其它的部份请自行参考源代码。
    ......
    // 由 PCI 子系统中读出所需的资源
    mmio_start = pci_resource_start (pdev, 1);
    mmio_end = pci_resource_end (pdev, 1);
    mmio_flags = pci_resource_flags (pdev, 1);
    mmio_len = pci_resource_len (pdev, 1);
    ......
    ......
    // 将这些资源保留下来
    rc = pci_request_regions (pdev, "8139too");
    pci_set_master (pdev);
    ......
    // 将 IO 位址对映到记忆体
    ioaddr = ioremap (mmio_start, mmio_len);
    dev->base_addr = (long) ioaddr;
    tp->mmio_addr = ioaddr;
    ......
    // 重设芯片
    RTL_W8 (ChipCmd, (RTL_R8 (ChipCmd) & ChipCmdClear) | CmdReset);

    /* Check that the chip has finished the reset. */
    for (i = 1000; i > 0; i--) {
    barrier();
    udelay (10);
    if ((RTL_R8 (ChipCmd) & CmdReset) == 0)
    break;
    }
    // 判断芯片确的版本
    ......

    在 rtl8139_init_one 中最重要的是下面这一段
    dev->open = rtl8139_open;
    dev->hard_start_xmit = rtl8139_start_xmit;
    dev->stop = rtl8139_close;
    dev->get_stats = rtl8139_get_stats;
    dev->set_multicast_list = rtl8139_set_rx_mode;
    dev->do_ioctl = mii_ioctl;
    dev->tx_timeout = rtl8139_tx_timeout;
    dev->watchdog_timeo = TX_TIMEOUT;

    dev->irq = pdev->irq;

    基本上和上次介绍的函数基本上相同,在此不再重复。上面比较特别的可能只有 ioremap 这个函数,它的用途是将 mmio_start 开始 mmio_len 长度的 IO 映射到记忆体中,之后我们就可以直接使用函数的传回值来做 IO 的动作了。

    一般而言,mmio_start 的值是一个位于 CPU 定址空间中的实体位址,在一般的架构下,硬件的设计者会保留一块记忆体位置给记忆体映射装置 (memory-mapped device) 使用。这些装置允许 CPU 用记忆体调用的方式取用其上的暂存器,在有些不支援 IO 调用的架构中,这些唯一取得装置暂存器的方法。

    举个例说,如果你要调用第 100 号暂存器,你可以使用
    unsigned int *ap = (unsigned int *) mmio_start + 0x100;
    printf("register 0x100 = %x\n", *ap);

    接下来我们一一解释这些函数。


    开始装置-- rtl8139_open
    这个函数会在你使用 ifconfig 时初呼叫,在这个函数中,你必须做下列的事

    注册中断函数 rtl8139_interrupt
    分配并初始化 8139 所需的接收与传送缓冲区。
    产生一个 kernel thread 负责查看网络连线的状态
    比较特别的是第三个动作,


    rtl8139_start_xmit
    这个函数会在传送一个封包时初呼叫,
    static int rtl8139_start_xmit (struct sk_buff *skb, struct net_device *dev)
    {
    entry = tp->cur_tx % NUM_TX_DESC;


    8139 支援四个传送缓冲区,你必须挑出下一个要用的缓冲区。接下来,你必须把缓冲区记忆体的实体表址 (physical address) 设定到 8139 的暂存器中。
    tp->tx_info[entry].skb = skb;
    if ((long) skb->data & 3) { /* Must use alignment buffer. */
    /* tp->tx_info[entry].mapping = 0; */
    memcpy (tp->tx_buf[entry], skb->data, skb->len);
    RTL_W32 (TxAddr0 + (entry * 4),
    tp->tx_bufs_dma + (tp->tx_buf[entry] - tp->tx_bufs));
    } else {
    tp->tx_info[entry].mapping =
    pci_map_single (tp->pci_dev, skb->data, skb->len,
    PCI_DMA_TODEVICE);
    RTL_W32 (TxAddr0 + (entry * 4), tp->tx_info[entry].mapping);
    }

    上面的程序码小心的处理的 alignment 的问题, 8139 要求缓冲区的表址必须对齐 32 位元。也就是说位址必须能被 4 除尽。如果不行的话,我们必须另外安排一个表址对齐 32 位元的缓冲区,把资料拷贝到那里去,然后将这个新缓冲区的实体表址存放到暂存器中去。


    RTL_W32 (TxStatus0 + (entry * sizeof (u32)),
    tp->tx_flag | (skb->len >= ETH_ZLEN ? skb->len : ETH_ZLEN));

    这一段程序用来设定封包的长度,一个正确的 ethernet 封包必须至少有 64 位元组长。不幸的,8139 不管这件事,你设定多长它就送多少。上面这一行程序就在确定封包的长度至少有 ETH_ZLEN。


    dev->trans_start = jiffies;
    spin_lock_irq (&tp->lock);

    tp->cur_tx++;
    if ((tp->cur_tx - NUM_TX_DESC) == tp->dirty_tx)
    netif_stop_queue (dev);

    spin_unlock_irq (&tp->lock);
    return 0;
    }

    这以前解释过到,当缓冲区用完时必须通知上层不要再送封包下来了。


    rtl8139_set_rx_mode
    这个函数用来设定接收的模式,8139 提供了 64 组 MAC 位址 filter。只有符合这些 filter 的位址芯片才会用中断通知 CPU 前来处理,一般状态下,我们只接收和 8139 本身 MAC 相符的封包。只有在像 tcpdump 之类的程序中才会想要接收其它的封包。

    rtl8139_interrupt
    在中断函数中,我们必须将状态码读入,然后根据状态码的指示做不同的事。我们要处理的状况有

    发生错误,可能是接收缓冲区满了,传送发生错误,bus 发生错误,接收发生错误。根据不同的状况,必须做不同的处理。如果传送错误,则再送一次。如果接收错误,那可能只好等待上层协定发现并重送封包。如果是 PVCI BUS 错误,则可能要重置 BUS。
    接收到封包,呼叫 rtl8139_rx_interrupt
    传送完一个封包,呼叫 rtl8139_tx_interrupt
    当接收到一个封包时,我们必须通知上层协定前检处理
    skb = dev_alloc_skb (pkt_size + 2);
    if (skb) {
    skb->dev = dev;
    skb_reserve (skb, 2); /* 16 byte align the IP fields. */

    eth_copy_and_sum (skb, &rx_ring[ring_offset + 4], pkt_size, 0);
    skb_put (skb, pkt_size);

    skb->protocol = eth_type_trans (skb, dev);
    netif_rx (skb);
    dev->last_rx = jiffies;
    tp->stats.rx_bytes += pkt_size;
    tp->stats.rx_packets++;
    } else {

    这段程序是非常典型的接收封包程序,先使用 dev_alloc_skb 分配一段足够大小的缓冲区。skb_put会调整缓冲区的大小,关鉴在使用 netif_rx 通知上层协定有新的封包传入。在稍后会由 BH_NET 这个 bottom half 处理这个封包。
    当一个封包传送完成后,我们必须将缓冲区释放。这件工作在 rtl8139_tx_interrupt 中被完成,此时我们必须呼叫上层协定表示可以传送新的封包了。这件事由下列在 rtl8139_tx_interrupt 最后面的程序完成
    if (tp->dirty_tx != dirty_tx) {
    tp->dirty_tx = dirty_tx;
    if (netif_queue_stopped (dev))
    netif_wake_queue (dev);
    }

    我们小心的避免呼叫太多次 netif_wake_queue,只有在装置己经因为缓冲区满了且有新的封包要传送时才去呼叫 netif_wake_queue。

    结语
    其实我还有很多细节还没有解释,如 MII 的处理,错误的处理和 eeprom 的处理,这些就留给大家自行研究了。如果有人有兴趣将这些细节补上,我很乐意将它们加入文章之中。

    本文作者: wycc
    发布人:Crystal 来自:LinuxFab