• 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
    ", id_val);
        }
    
        if (id_val != DM9000_ID) {
            dev_err(db->dev, "wrong id: 0x%08x
    ", 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
    ", 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
    ", 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 "); 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 ", 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) ", 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). ", 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
    ", 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.
    ");
    
        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:
    ", __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
    ", 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
    ", __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
    ", 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,
    };
  • 相关阅读:
    Android 开发遇到的问题及解决办法
    Android Studio HelloWorld
    Android Studio与Genymontion的安装
    自定义简单版本python线程池
    唯一图库爬取图片
    网页登陆验证之图片验证码
    向后台提交数据:利用cookie加session提交更多数据,
    向后台提交数据:cookie,secure_cookie,
    利用js里的Dom和Date,自定义cookie的前端设置方法
    向后台提交数据:通过form表单提交数据需刷新网页 但通过Ajax提交数据不用刷新网页可通过原生态Ajax或jqueryAjax。Ajax代码部分
  • 原文地址:https://www.cnblogs.com/wulei0630/p/10825284.html
Copyright © 2020-2023  润新知