Linux网卡驱动移植--Dm9000网卡驱动分析

1. Linux网络体系结构由以下5部分组成

系统调用接口: 位于Linux网络子系统的顶部,为应用程序提供访问内核网络子系统的方法,主要指socket系统调用。

协议无关接口: 实现一组基于socket的通用函数来访问不同的协议。(Linux中的socket使用sock结构来描述(定义于include/net/sock.h),该结构包含特定socket所需要的所有状态信息,还包含socket所使用的特定协议和在socket上可以执行的一些操作。)

网络协议: 用于实现具体的网络协议,如TCP、UDP等

设备无关接口: 将协议与各种网络设备驱动连接在一起

设备驱动:负责管理物理网络设备

 注:与字符设备与块设备不同,网络接口设备在Linux的/dev目录下不存在与之对应的设备文件,因此无法向字符设备或块设备那样通过访问设备文件来操作。Linux通过一些系统提供的工具来访问和设置网络设备,如ifconfig、mii-tool、ethtool等。

2. 每个网络设备都由一个net_device结构来描述,该结构位于include/linux/netdevice.h中定义

struct net_device
{
    char            name[IFNAMSIZ];/*
    unsigned long        base_addr;    /* device I/O address    */
    unsigned int        irq;        /* device IRQ number    */
 
  unsigned
long state;

  unsigned mtu; /* interface MTU value */

  unsigned char        dev_addr[MAX_ADDR_LEN];    /* hw address, (before bcast

/* Management operations */ const struct net_device_ops *netdev_ops; const struct ethtool_ops *ethtool_ops; /* Hardware header description */ const struct header_ops *header_ops;
  ...
}

① name: 设备名称

② state: 设备状态

③ base_addr:网络接口的I/O基地址,由驱动在设备探测时赋值

④ irq:中断号

⑤ mtu:网卡设备可以处理的最大传输单元

⑥ dev_addr:设备的6字节MAC地址

netdev_ops:网络接口设备的操作接口函数集

3. 分配及注册网络设备

① 分配一个net_device结构可以使用宏:alloc_netdev

#define alloc_netdev(sizeof_priv, name, setup) \
    alloc_netdev_mq(sizeof_priv, name, setup, 1)

② 对于不同类型的网络设备,内核提供了更直接的分配函数。如以太网网卡分配函数:alloc_etherdev

#define alloc_etherdev(sizeof_priv) alloc_etherdev_mq(sizeof_priv, 1)

(1)alloc_etherdev_mq()函数只是对alloc_netdev_mq()函数的简单封装

struct net_device *alloc_etherdev_mq(int sizeof_priv, unsigned int queue_count)
{
    return alloc_netdev_mq(sizeof_priv, "eth%d", ether_setup, queue_count);
}

(2)ether_setup()是以太网设备专属初始化函数

void ether_setup(struct net_device *dev)
{
    dev->header_ops        = &eth_header_ops;
#ifdef CONFIG_COMPAT_NET_DEV_OPS
    dev->change_mtu        = eth_change_mtu;
    dev->set_mac_address     = eth_mac_addr;
    dev->validate_addr    = eth_validate_addr;
#endif
    dev->type        = ARPHRD_ETHER;
    dev->hard_header_len     = ETH_HLEN;
    dev->mtu        = ETH_DATA_LEN;
    dev->addr_len        = ETH_ALEN;
    dev->tx_queue_len    = 1000;    /* Ethernet wants good queues */
    dev->flags        = IFF_BROADCAST|IFF_MULTICAST;

    memset(dev->broadcast, 0xFF, ETH_ALEN);

}

③ 注册网络设备:register_netdev

4. sk_buff称为“套接字缓冲区”,用于在网络子系统各层之间传递数据。

struct sk_buff {
    /* These two members must be first. */
    struct sk_buff        *next;
    struct sk_buff        *prev;

    struct sock        *sk;
    struct net_device    *dev;

    __be16            protocol;

    void            (*destructor)(struct sk_buff *skb);

    sk_buff_data_t        transport_header;
    sk_buff_data_t        network_header;
    sk_buff_data_t        mac_header;
    /* These elements must be at the end, see alloc_skb() for details.  */
    sk_buff_data_t        tail;
    sk_buff_data_t        end;
    unsigned char        *head,
                *data;
    unsigned int        truesize;
    atomic_t        users;
};

① sk_buff定义了4个指向数据包缓冲区不同位置的指针head、data、tail、end。

(1) head与end分别指向数据包缓冲区的起始地址和结束地址,它们是在sk_buff和相关数据块分配后就固定下来

(2) data与tail分别指向当前协议层有效数据的起始地址和结束地址,因此随着sk_buff表示的数据包在不同协议层次间的传递,data与tail指针也会做相应的移动。

② 网卡接收到数据包,或者用户空间请求发送网络数据,skb都会被创建。内核中分配一个sk_buff使用函数:alloc_skb()

static inline struct sk_buff *alloc_skb(unsigned int size, gfp_t priority)

③ 在网卡驱动中,通常使用dev_alloc_skb()函数来分配一个skb。dev_alloc_skb()是对__dev_alloc_skb() 的简单封装,而__dev_alloc_skb()调用alloc_skb()以GFP_ATOMIC方式从内存分配skb,因此它可以用在中断上下文中。

struct sk_buff *dev_alloc_skb(unsigned int length)
{
    return __dev_alloc_skb(length, GFP_ATOMIC);
}

static inline struct sk_buff *__dev_alloc_skb(unsigned int length, gfp_t gfp_mask)
{
    struct sk_buff *skb = alloc_skb(length + NET_SKB_PAD, gfp_mask);
    if (likely(skb))
        skb_reserve(skb, NET_SKB_PAD);
    return skb;
}

注:GFP_ATOMIC,用来从中断处理和进程上下文之外的其他代码中分配内存,从不睡眠。

④ sk_buff的4个指针的基本操作函数

(1)skb_headroom: 获取sk_buff结构成员指针head到data之间空间的长度

static inline unsigned int skb_headroom(const struct sk_buff *skb)
{
    return skb->data - skb->head;
}

(2)skb_tailroom: 获取sk_buff结构成员指针tail到end之间空间的长度

static inline int skb_tailroom(const struct sk_buff *skb)
{
    return skb_is_nonlinear(skb) ? 0 : skb->end - skb->tail;
}

(3)skb_reserve: 将data和tail指针同时下移

static inline void skb_reserve(struct sk_buff *skb, int len)
{
    skb->data += len;
    skb->tail += len;
}

(4)skb_push: 将data指针上移,同时增加sk_buff中的len

unsigned char *skb_push(struct sk_buff *skb, unsigned int len)
{
    skb->data -= len;
    skb->len  += len;
    if (unlikely(skb->data<skb->head))
        skb_under_panic(skb, len, __builtin_return_address(0));
    return skb->data;
}

(5)skb_pull: 将data指针下移,同时减少sk_buff中的len

unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)
{
    return unlikely(len > skb->len) ? NULL : __skb_pull(skb, len);
}
static inline unsigned char *__skb_pull(struct sk_buff *skb, unsigned int len)
{
    skb->len -= len;
    BUG_ON(skb->len < skb->data_len);
    return skb->data += len;
}

(6)skb_put:将tail指针下移len长度,并增加sk_buff中len的值,返回改变前的tail值

unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
{
    unsigned char *tmp = skb_tail_pointer(skb);
    SKB_LINEAR_ASSERT(skb);
    skb->tail += len;
    skb->len  += len;
    if (unlikely(skb->tail > skb->end))
        skb_over_panic(skb, len, __builtin_return_address(0));
    return tmp;
}

(7)skb_trim:重设data到tail之间有效数据长度为len

void skb_trim(struct sk_buff *skb, unsigned int len)
{
    if (skb->len > len)
        __skb_trim(skb, len);
}
static inline void __skb_trim(struct sk_buff *skb, unsigned int len)
{
    if (unlikely(skb->data_len)) {
        WARN_ON(1);
        return;
    }
    skb->len = len;
    skb_set_tail_pointer(skb, len);
}

5. DM9000网卡驱动分析

① Linux2.6.29内核中已经有完整的DM9000网卡驱动,位于源文件drivers/net/dm9000.c。驱动将DM9000的IO内存资源和IRQ资源以平台设备资源的方式进行管理,并在dm9000.c中实现并注册了平台驱动,只要再向内核中注册板级相关的DM9000平台设备,网卡就能正常工作了。DM9000平台驱动定义如下

static struct platform_driver dm9000_driver = {
    .driver    = {
        .name    = "dm9000",
        .owner     = THIS_MODULE,
    },
    .probe   = dm9000_probe,
    .remove  = __devexit_p(dm9000_drv_remove),
    .suspend = dm9000_drv_suspend,
    .resume  = dm9000_drv_resume,
};

注:dm9000_probe是分析的重点。

board_info结构:board_info是对net_device结构的扩展,以描述一个具体的网卡设备,也就是驱动中的DM9000。

/* Structure/enum declaration ------------------------------- */
typedef struct board_info {

    void __iomem    *io_addr;    /* Register I/O base address */
    void __iomem    *io_data;    /* Data I/O address */
    u16         irq;        /* IRQ */

    u16        tx_pkt_cnt;
    u16        queue_pkt_len;
    u16        queue_start_addr;
    u16        dbug_cnt;
    u8        io_mode;        /* 0:word, 2:byte */
    u8        phy_addr;
    u8        imr_all;

    unsigned int    flags;
    unsigned int    in_suspend :1;
    int        debug_level;

    enum dm9000_type type;

    void (*inblk)(void __iomem *port, void *data, int length);
    void (*outblk)(void __iomem *port, void *data, int length);
    void (*dumpblk)(void __iomem *port, int length);

    struct device    *dev;         /* parent device */

    struct resource    *addr_res;   /* resources found */
    struct resource *data_res;
    struct resource    *addr_req;   /* resources requested */
    struct resource *data_req;
    struct resource *irq_res;

    struct mutex     addr_lock;    /* phy and eeprom access lock */

    struct delayed_work phy_poll;
    struct net_device  *ndev;

    spinlock_t    lock;

    struct mii_if_info mii;
    u32        msg_enable;
} board_info_t;

注:net_device描述所有网卡的共性,按照面向对象的思想分析,board_info由net_device派生而来。驱动使用board_info的变量作为net_device对象的私有数据(紧跟着你net_device对象后存放,按32字节对齐)

dm9000_probe()函数

(1)在注册平台驱动时,内核遍历平台总线上的所有平台设备,通过名称匹配,并在找到匹配的设备后,调用平台驱动中的probe()方法。平台驱动通常利用probe()方法从匹配上的平台设备中获取平台资源,并根据这些资源申请和映射IO内存、获取并注册IRQ中断。DM9000_probe()除了做以上内容外,还最终调用register_netdev()注册网卡设备。

(2)dm9000_probe()函数分析1:获取设备私有数据(board_info结构),并为私有数据中的重要成员赋值。

static int __devinit
dm9000_probe(struct platform_device *pdev)
{
    struct dm9000_plat_data *pdata = pdev->dev.platform_data;
    struct board_info *db;    /* Point a board information structure */
    struct net_device *ndev;
  ...

  /* Init network device */ ndev = alloc_etherdev(sizeof(struct board_info)); SET_NETDEV_DEV(ndev, &pdev->dev);/* setup board info structure */ db = netdev_priv(ndev); memset(db, 0, sizeof(*db)); db->dev = &pdev->dev; db->ndev = ndev; spin_lock_init(&db->lock); mutex_init(&db->addr_lock); INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work);

  对于代码INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work),安装延迟调度工作dm9000_poll_work,而dm9000_poll_work的作用是动态轮询检查DM9000的连接状态(由于DM9000E系列芯片不支持连接状态改变中断,故用此方法来检查连接状态)

(3)dm9000_probe()函数分析2:获取并保存平台资源和根据这些资源信息申请IO内存和中断

  /* 获取平台资源 */
  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);
  /* 申请IO内存并映射--DM9000的INDEX端口 */
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);
  /* 申请IO内存并映射--DM9000的DATA端口 */
iosize = res_size(db->data_res); db->data_req = request_mem_region(db->data_res->start, iosize, pdev->name);

  /* fill in parameters for net-dev structure */
    db->io_data = ioremap(db->data_res->start, iosize);
    ndev->base_addr = (unsigned long)db->io_addr;
    ndev->irq    = db->irq_res->start;

(4)dm9000_probe()函数分析3: 根据在平台设备中指定的私有数据,确定board_info结构中关于DM9000块读写的具体函数

    /* ensure at least we have a default set of IO routines */
    dm9000_set_io(db, iosize);

    /* check to see if anything is being over-ridden */
    if (pdata != NULL) {
        /* check to see if the driver wants to over-ride the
         * default IO width */

        if (pdata->flags & DM9000_PLATF_8BITONLY)
            dm9000_set_io(db, 1);

        if (pdata->flags & DM9000_PLATF_16BITONLY)
            dm9000_set_io(db, 2);

        if (pdata->flags & DM9000_PLATF_32BITONLY)
            dm9000_set_io(db, 4);

        /* check to see if there are any IO routine
         * over-rides */

        if (pdata->inblk != NULL)
            db->inblk = pdata->inblk;

        if (pdata->outblk != NULL)
            db->outblk = pdata->outblk;

        if (pdata->dumpblk != NULL)
            db->dumpblk = pdata->dumpblk;

        db->flags = pdata->flags;
    }

(5)dm9000_probe()函数分析4:确定驱动找到的芯片是DM9000,并且确定芯片的具体型号。

    dm9000_reset(db);

    /* try multiple times, DM9000 sometimes gets the read wrong */
    for (i = 0; i < 8; i++) {
        id_val  = ior(db, DM9000_VIDL);
        id_val |= (u32)ior(db, DM9000_VIDH) << 8;
        id_val |= (u32)ior(db, DM9000_PIDL) << 16;
        id_val |= (u32)ior(db, DM9000_PIDH) << 24;

        if (id_val == DM9000_ID)
            break;
        dev_err(db->dev, "read wrong id 0x%08x\n", id_val);
    }

    if (id_val != DM9000_ID) {
        dev_err(db->dev, "wrong id: 0x%08x\n", id_val);
        ret = -ENODEV;
        goto out;
    }

    /* Identify what type of DM9000 we are working on */

    id_val = ior(db, DM9000_CHIPR);
    dev_dbg(db->dev, "dm9000 revision 0x%02x\n", id_val);

    switch (id_val) {
    case CHIPR_DM9000A:
        db->type = TYPE_DM9000A;
        break;
    case CHIPR_DM9000B:
        db->type = TYPE_DM9000B;
        break;
    default:
        dev_dbg(db->dev, "ID %02x => defaulting to DM9000E\n", id_val);
        db->type = TYPE_DM9000E;
    }

(6)dm9000_probe()函数分析5:进一步填充ndev和db这两个结构体变量的成员,注册网络设备。

  /* from this point we assume that we have found a DM9000 */
    /* driver system function */
    ether_setup(ndev);
  /* 网卡操作方法填充 */ ndev
->open = &dm9000_open; ndev->hard_start_xmit = &dm9000_start_xmit; ndev->tx_timeout = &dm9000_timeout; ndev->watchdog_timeo = msecs_to_jiffies(watchdog); ndev->stop = &dm9000_stop; ndev->set_multicast_list = &dm9000_hash_table; ndev->ethtool_ops = &dm9000_ethtool_ops; ndev->do_ioctl = &dm9000_ioctl; #ifdef CONFIG_NET_POLL_CONTROLLER ndev->poll_controller = &dm9000_poll_controller; #endif   /* 调试信息只对连接事件开放 */ db->msg_enable = NETIF_MSG_LINK;
  /* 指定网卡MII的属性和方法 */ db
->mii.phy_id_mask = 0x1f; db->mii.reg_num_mask = 0x1f; db->mii.force_media = 0; db->mii.full_duplex = 0; db->mii.dev = ndev; db->mii.mdio_read = dm9000_phy_read; db->mii.mdio_write = dm9000_phy_write; #if defined(CONFIG_ARCH_S3C2410) printk("Now use the default MAC address: 08:90:90:90:90:90\n"); mac_src = "friendly-arm"; ndev->dev_addr[0] = 0x08; ndev->dev_addr[1] = 0x90; ndev->dev_addr[2] = 0x90; ndev->dev_addr[3] = 0x90; ndev->dev_addr[4] = 0x90; ndev->dev_addr[5] = 0x90; #else mac_src = "eeprom"; /* try reading the node address from the attached EEPROM */ for (i = 0; i < 6; i += 2) dm9000_read_eeprom(db, i / 2, ndev->dev_addr+i); if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) { mac_src = "platform data"; memcpy(ndev->dev_addr, pdata->dev_addr, 6); } if (!is_valid_ether_addr(ndev->dev_addr)) { /* try reading from mac */ mac_src = "chip"; for (i = 0; i < 6; i++) ndev->dev_addr[i] = ior(db, i+DM9000_PAR); } if (!is_valid_ether_addr(ndev->dev_addr)) dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please " "set using ifconfig\n", ndev->name); #endif platform_set_drvdata(pdev, ndev); ret = register_netdev(ndev); if (ret == 0) printk(KERN_INFO "%s: dm9000%c at %p,%p IRQ %d MAC: %pM (%s)\n", ndev->name, dm9000_type_to_char(db->type), db->io_addr, db->io_data, ndev->irq, ndev->dev_addr, mac_src); return 0; out: #if defined(CONFIG_ARCH_S3C2410) *(volatile unsigned int *)S3C2410_BWSCON = oldval_bwscon; *(volatile unsigned int *)S3C2410_BANKCON4 = oldval_bankcon4; #endif dev_err(db->dev, "not found (%d).\n", ret); dm9000_release_board(pdev, db); free_netdev(ndev); return ret; }

(7)dm9000_probe()函数总结:dm9000_probe()函数的作用是分配ndev(net_device结构)存储空间、填充其成员,并最终将其注册进内核。而db(board_info结构)作为ndev的私有数据也将一并注册进内核。

注1:dm9000_probe()函数在申请内存空间的时候是带有board_info结构体的空间的,

ndev = alloc_etherdev(sizeof(struct board_info))

其申请的内存空间长度实际为:sizeof(struct net_device) + align + (sizeof(struct board_info)。

注2:db(board_info)指针获取的方法是通过ndev(net_device),即db = ndev + sizeof(net_device) + align

db = netdev_priv(ndev);

static inline void *netdev_priv(const struct net_device *dev)
{
    return (char *)dev + ((sizeof(struct net_device)
                   + NETDEV_ALIGN_CONST)
                  & ~NETDEV_ALIGN_CONST);
}

dm9000_open()函数:在网卡被激活时调用,主要完成的工作有向内核注册中断、复位并初始化dm9000、检查MII接口等。

static int dm9000_open(struct net_device *dev)
{
    board_info_t *db = netdev_priv(dev);
    unsigned long irqflags = db->irq_res->flags & IRQF_TRIGGER_MASK;

    if (netif_msg_ifup(db))
        dev_dbg(db->dev, "enabling %s\n", dev->name);

    /* If there is no IRQ type specified, default to something that
     * may work, and tell the user that this is a problem */

    if (irqflags == IRQF_TRIGGER_NONE)
        dev_warn(db->dev, "WARNING: no IRQ resource flags set.\n");

    irqflags |= IRQF_SHARED;

    if (request_irq(dev->irq, &dm9000_interrupt, irqflags, dev->name, dev))
        return -EAGAIN;

    /* Initialize DM9000 board */
    dm9000_reset(db);
    dm9000_init_dm9000(dev);

    /* Init driver variable */
    db->dbug_cnt = 0;

    mii_check_media(&db->mii, netif_msg_link(db), 1);
    netif_start_queue(dev);
    
    dm9000_schedule_poll(db);

    return 0;

dm9000_start_xmit():用于启动数据包的发送,负责将上层送过来的skb数据包发送出去

static int
dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
    unsigned long flags;
    board_info_t *db = netdev_priv(dev);

    dm9000_dbg(db, 3, "%s:\n", __func__);

    if (db->tx_pkt_cnt > 1)
        return 1;

    spin_lock_irqsave(&db->lock, flags);

    /* Move data to DM9000 TX RAM */
    writeb(DM9000_MWCMD, db->io_addr);

    (db->outblk)(db->io_data, skb->data, skb->len);
    dev->stats.tx_bytes += skb->len;

    db->tx_pkt_cnt++;
    /* TX control: First packet immediately send, second packet queue */
    if (db->tx_pkt_cnt == 1) {
        /* Set TX length to DM9000 */
        iow(db, DM9000_TXPLL, skb->len);
        iow(db, DM9000_TXPLH, skb->len >> 8);

        /* Issue TX polling command */
        iow(db, DM9000_TCR, TCR_TXREQ);    /* Cleared after TX complete */

        dev->trans_start = jiffies;    /* save the time stamp */
    } else {
        /* Second packet */
        db->queue_pkt_len = skb->len;
        netif_stop_queue(dev);
    }

    spin_unlock_irqrestore(&db->lock, flags);

    /* free this SKB */
    dev_kfree_skb(skb);

    return 0;
}

⑥ 数据包接收函数:dm9000_rx()

(1)网卡收到数据包后,调用dm9000_rx()完成以下工作

  * 判断包是否接收到

  * 检查包的状态和长度

  * 读取包数据

  * 利用读取到的包的实际数据构造skb,并调用netif_rx()将skb送到上层协议处理。

(2)dm9000_rx()中构造skb,并且递交到上层处理的代码

static void
dm9000_rx(struct net_device *dev)
{
    ...
  do {   ...
     
/* Move data from DM9000 */ if (GoodPacket && ((skb = dev_alloc_skb(RxLen + 4)) != NULL)) { skb_reserve(skb, 2); rdptr = (u8 *) skb_put(skb, RxLen - 4); /* Read received packet from RX SRAM */ (db->inblk)(db->io_data, rdptr, RxLen); dev->stats.rx_bytes += RxLen; /* Pass to upper layer */ skb->protocol = eth_type_trans(skb, dev); netif_rx(skb); dev->stats.rx_packets++; } else { /* need to dump the packet's data */ (db->dumpblk)(db->io_data, RxLen); } } while(rxbyte == DM9000_PKT_RDY) }

⑦ 数据包发送函数:dm9000_tx_done()

 (1)DM9000数据包发送完整过程:

  * 在发送一个包之前,将包中的有效数据通过MWCMD寄存器,写入TX缓冲区

  * 如果待发送包是第一个包,则直接启动包发送:

  * 如果待发送包不是第一个包,则暂不发送,而是记录包长度和校验控制位,并停止发送队列

  (注:以上三步在dm9000_start_xmit()方法中实现的)

  * 如果设置了IMR(中断屏蔽)寄存器的PTM位,则当数据发送完成后产生中断,并使ISR(中断状态)寄存器的PTS位被设置。因此可以在中断处理函数中判断中断,执行剩余的发送操作

  (注:以下三步在dm9000_tx_done()中完成)

  * 读取NSR(网络状态)寄存器获取发送的状态,根据NSR_TX2END、NSR_TX1END为来判断是否进行接下来的处理

  * 将待发送数据包计数(db->tx_pkt_cnt)减1,且检查db->tx_pkt_cnt是否大于0。如果大于0,则表明还有数据包要发送,将待发送包的长度写入TXPLL和TXPLH寄存器,并通过TCR寄存器请求发送

  * 调用函数netif_wake_queue()通知内核可以将待发送的数据包加入发送队列(将skb包通过MWCMD寄存器,写入TX缓冲区)

(2)dm9000_tx_done()函数实现如下:

static void dm9000_tx_done(struct net_device *dev, board_info_t *db)
{
    int tx_status = ior(db, DM9000_NSR);    /* Got TX status */

    if (tx_status & (NSR_TX2END | NSR_TX1END)) {
        /* One packet sent complete */
        db->tx_pkt_cnt--;
        dev->stats.tx_packets++;

        if (netif_msg_tx_done(db))
            dev_dbg(db->dev, "tx done, NSR %02x\n", tx_status);

        /* Queue packet check & send */
        if (db->tx_pkt_cnt > 0) {
            iow(db, DM9000_TXPLL, db->queue_pkt_len);
            iow(db, DM9000_TXPLH, db->queue_pkt_len >> 8);
            iow(db, DM9000_TCR, TCR_TXREQ);
            dev->trans_start = jiffies;
        }
        netif_wake_queue(dev);
    }
}

 ⑧ 中断处理函数:dm9000_interrupt()

(1)Linux源码中的DM9000驱动采用中断方式实现,触发中断的时机发生在:接收到一个数据包后、发送完一个数据包后及连接状态改变之后(DM9000E不支持连接状态改变中断)

(2)中断处理函数在设备执行open方法时被注册进内核,dm9000_interrupt()源码如下

static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
{
    struct net_device *dev = dev_id;
    board_info_t *db = netdev_priv(dev);
    int int_status;
    unsigned long flags;
    u8 reg_save;

    dm9000_dbg(db, 3, "entering %s\n", __func__);

    /* A real interrupt coming */

    /* holders of db->lock must always block IRQs */
    spin_lock_irqsave(&db->lock, flags);

    /* Save previous register address */
    reg_save = readb(db->io_addr);

    /* Disable all interrupts */
    iow(db, DM9000_IMR, IMR_PAR);

    /* Got DM9000 interrupt status */
    int_status = ior(db, DM9000_ISR);    /* Got ISR */
    iow(db, DM9000_ISR, int_status);    /* Clear ISR status */

    if (netif_msg_intr(db))
        dev_dbg(db->dev, "interrupt status %02x\n", int_status);

    /* Received the coming packet */
    if (int_status & ISR_PRS)
        dm9000_rx(dev);

    /* Trnasmit Interrupt check */
    if (int_status & ISR_PTS)
        dm9000_tx_done(dev, db);

    if (db->type != TYPE_DM9000E) {
        if (int_status & ISR_LNKCHNG) {
            /* fire a link-change request */
            schedule_delayed_work(&db->phy_poll, 1);
        }
    }

    /* Re-enable interrupt mask */
    iow(db, DM9000_IMR, db->imr_all);

    /* Restore previous register address */
    writeb(reg_save, db->io_addr);

    spin_unlock_irqrestore(&db->lock, flags);

    return IRQ_HANDLED;
}

6. Mini2440的DM9000网卡驱动移植

① 由于Linux2.6.29内核已经有DM9000完整的驱动,我们只需在板级初始化文件中添加DM9000网卡的平台设备。

② 在板级初始化文件arch/arm/mach-s3c2440/mack-smdk2440.c中添加如下内容:

#include <linux/dm9000.h>
#define MACH_SMDK2440_DM9000_BASE (S3C2410_CS4)

static struct resource smdk2440_dm9000_resource[] = {
    [0] = {
        .start = MACH_SMDK2440_DM9000_BASE,
        .end = MACH_SMDK2440_DM9000_BASE + 3,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = MACH_SMDK2440_DM9000_BASE + 4,
        .end = MACH_SMDK2440_DM9000_BASE + 7,
        .flags = IORESOURCE_MEM,
    },
    [2] = {
        .start = IRQ_EINT7,
        .end = IRQ_EINT7,
        .flags = (IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHEDGE),
    }
};

static struct dm9000_plat_data smdk2440_dm9000_pdata = {
    .flags = (DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM),
};

static struct platform_device smdk2440_device_eth = {
    .name = "dm9000",
    .id = -1,
    .num_resources = ARRAY_SIZE(smdk2440_dm9000_resource),
    .resource = smdk2440_dm9000_resource,
    .dev = {
        .platform_data = &smdk2440_dm9000_pdata,
    }
};

static struct platform_device *smdk2440_devices[] __initdata = {
    &s3c_device_usb,
    &s3c_device_lcd,
    &s3c_device_wdt,
    &s3c_device_i2c0,
    &s3c_device_iis,
    &smdk2440_device_eth,
};

猜你喜欢

转载自www.cnblogs.com/wulei0630/p/10825284.html