程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

linux驱动移植-DM9000网卡驱动

----------------------------------------------------------------------------------------------------------------------------

内核版本:linux 5.2.8

根文件系统:busybox 1.25.0

u-boot:2016.05

----------------------------------------------------------------------------------------------------------------------------

在学习Mini2440裸机程序时,我们介绍过关于DM9000网卡的相关知识,包括电路图、以及DM9000寄存器等信息。具体可以参考Mini2440裸机开发之DM9000

本节对之前已经介绍过的知识不会再进行重复介绍。这一节我们将直入主题,介绍如何移植DM9000网卡驱动。

一、platform设备注册(dm9000)

在刚学习驱动移植的时候,我们为了使用nfs作为根文件系统,我们在linux驱动移植-DM9000网卡驱动小节介绍了DM9000网卡驱动的移植,但是那时候我们仅仅是移植,并未对源码进行深入研究。 DM9000网卡设备驱动,其采用的也是platform设备驱动模型。

1.1 smdk2440_device_eth

我们定位到arch/arm/mach-s3c24xx/mach-smdk2440.c文件,在该文件中我们引入了dm9000.h头文件:

#include <linux/dm9000.h>

定义了DM9000网卡设备的物理基地址:

#define MACH_SMDK2440_DM9K_BASE (S3C2410_CS4 + 0x300)     # S3C2410_CS4 = 0X20000000

定义了网卡platform设备smdk2440_device_eth:

/* DM9000AEP 10/100 ethernet controller */

static struct resource smdk2440_dm9k_resource[] = {
        [0] = DEFINE_RES_MEM(MACH_SMDK2440_DM9K_BASE, 4),
        [1] = DEFINE_RES_MEM(MACH_SMDK2440_DM9K_BASE + 4, 4),
        [2] = DEFINE_RES_NAMED(IRQ_EINT7, 1, NULL, IORESOURCE_IRQ
                                                | IORESOURCE_IRQ_HIGHEDGE),   // 高电平触发
};

/*
 * The DM9000 has no eeprom, and it's MAC address is set by
 * the bootloader before starting the kernel.
 */
static struct dm9000_plat_data smdk2440_dm9k_pdata = {
        .flags          = (DM9000_PLATF_16BITONLY | DM9000_PLATF_NO_EEPROM),    // 标志位,16位模式、DM9000没有外挂EPPROM
};

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

我们重点关注一下platform设备资源定义,这里定义了三个资源,前两个是内存资源、第三个是中断资源:

注意:

  • 在定义DM9000网卡platform设备的时,第一个资源必须是IO内存资源,且定义的是DM9000地址寄存器地址;
  • 在定义DM9000网卡platform设备的时,第二个资源必须是IO内存资源,且定义的是DM9000数据寄存器地址;
  • 在定义DM9000网卡platform设备的时,第三个资源必须是中断资源,CPU中断引脚连接DM9000 INT引脚;

dm9000_plat_data用来保存DM9000设备的初始化配置,除了flags,还有一些其它参数,比如:

  • dev_addr:设置DM9000设备的初始化MAC地址;
  • inblk:设置接收数据操作函数;
  • outblk:设置发送数据操作函数;

我们已经定义了网卡相关的platform_device设备smdk2440_device_eth,并进行了初始化,那platform设备啥时候注册的呢?

linux内核启动的时候会根据uboot中设置的机器id执行相应的初始化工作,比如.init_machine、.init_irq,我们首先定位到arch/arm/mach-s3c24xx/mach-smdk2440.c:

MACHINE_START(S3C2440, "SMDK2440")
        /* Maintainer: Ben Dooks <ben-linux@fluff.org> */
        .atag_offset    = 0x100,

        .init_irq       = s3c2440_init_irq,
        .map_io         = smdk2440_map_io,
        .init_machine   = smdk2440_machine_init,
        .init_time      = smdk2440_init_time,
MACHINE_END

重点关注init_machine,init_machine中保存的是开发板资源注册的初始化代码。

1.2  smdk2440_machine_init

static void __init smdk2440_machine_init(void)
{
        s3c24xx_fb_set_platdata(&smdk2440_fb_info);   
        s3c_i2c0_set_platdata(NULL);

        platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));  // s3c2440若干个platform设备注册 usb host controller、lcd、wdt等
        smdk_machine_init();  // s3c24x0系列若干个platform设备注册(通用)
}

这里利用platform_add_devices进行若干个platform设备的注册,该函数通过调用platform_device_register实现platform设备注册;

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

二、platform驱动注册(dm9000)

我们需要配置内核支持DM9000网卡驱动:

Device Drivers --->
   [*] Network device support -->
       [*] Ethernet driver support -->
[*] DM9000 support

这样我们内核才会支持DM9000网卡驱动。配置了DM9000 support之后,配置文件.config中会包含如下项:

# CONFIG_GEMINI_ETHERNET is not set
CONFIG_DM9000=y

当我们使用make uImage编译内核时会将dm9000.o编译进内核:

drivers/net/ethernet/davicom/Makefile:6:obj-$(CONFIG_DM9000) += dm9000.o

dm9000.c文件位于drivers/net/ethernet/davicom目录下。

2.1 入口和出口函数

我们在dm9000.c文件定位到驱动模块的入口和出口:

module_platform_driver(dm9000_driver);

module_platform_driver宏展开后本质上就是:

module_init(dm9000_driver_init); 
module_exit(dm9000_driver_exit); 
static int __init dm9000_driver_init(void)
{
     platform_driver_register(dm9000_driver);
}

static void __exit dm9000_driver_exit(void)
{
     platform_driver_unregister(dm9000_driver);
}

看到这里是不是有点意外这里是通过platform_driver_register函数注册了一个platform驱动。

在plaftrom总线设备驱动模型中,我们知道当内核中有platform设备platform驱动匹配,会调用到platform_driver里的成员.probe,在这里就是dm9000_probe函数。

#ifdef CONFIG_OF
static const struct of_device_id dm9000_of_matches[] = {   // 设备树匹配使用
        { .compatible = "davicom,dm9000", },
        { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, dm9000_of_matches);
#endif

static struct platform_driver dm9000_driver = {
        .driver = {
                .name    = "dm9000",
                .pm      = &dm9000_drv_pm_ops,
                .of_match_table = of_match_ptr(dm9000_of_matches),
        },
        .probe   = dm9000_probe,
        .remove  = dm9000_drv_remove,
};

2.2 dm9000_probe

/*
 * Search DM9000 board, allocate space and register it
 */
static int
dm9000_probe(struct platform_device *pdev)
{
        struct dm9000_plat_data *pdata = dev_get_platdata(&pdev->dev);      // pdev->dev.platform_data
        struct board_info *db;  /* Point a board information structure */
        struct net_device *ndev;
        struct device *dev = &pdev->dev;
        const unsigned char *mac_src;
        int ret = 0;
        int iosize;
        int i;
        u32 id_val;
        int reset_gpios;
        enum of_gpio_flags flags;
        struct regulator *power;
        bool inv_mac_addr = false;

        power = devm_regulator_get(dev, "vcc");
        if (IS_ERR(power)) {
                if (PTR_ERR(power) == -EPROBE_DEFER)
                        return -EPROBE_DEFER;
                dev_dbg(dev, "no regulator provided\n");
        } else {
                ret = regulator_enable(power);
                if (ret != 0) {
                        dev_err(dev,
                                "Failed to enable power regulator: %d\n", ret);
                        return ret;
                }
                dev_dbg(dev, "regulator enabled\n");
        }

        reset_gpios = of_get_named_gpio_flags(dev->of_node, "reset-gpios", 0,
                                              &flags);
        if (gpio_is_valid(reset_gpios)) {
                ret = devm_gpio_request_one(dev, reset_gpios, flags,
                                            "dm9000_reset");
                if (ret) {
                        dev_err(dev, "failed to request reset gpio %d: %d\n",
                                reset_gpios, ret);
                        return -ENODEV;
                }

                /* According to manual PWRST# Low Period Min 1ms */
                msleep(2);
                gpio_set_value(reset_gpios, 1);
                /* Needs 3ms to read eeprom when PWRST is deasserted */
                msleep(4);
        }

        if (!pdata) {             // 不会走这里
                pdata = dm9000_parse_dt(&pdev->dev);
                if (IS_ERR(pdata))
                        return PTR_ERR(pdata);
        }

        /* Init network device */
        ndev = alloc_etherdev(sizeof(struct board_info));             // 为网卡设备动态申请net_device结构体,同时额外分配board_info大小的私有内存
        if (!ndev)
                return -ENOMEM;

        SET_NETDEV_DEV(ndev, &pdev->dev);

        dev_dbg(&pdev->dev, "dm9000_probe()\n");

        /* setup board info structure */
        db = netdev_priv(ndev);                 // 获取ndev私有内存

        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);

        db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);    // 获取第一个内存资源(地址寄存器地址) start地址0x20000000,长度4
        db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);    // 获取第二个内存资源(数据寄存器地址) start地址0x20000004, 长度4

        if (!db->addr_res || !db->data_res) {   // 参数校验
                dev_err(db->dev, "insufficient resources addr=%p data=%p\n",
                        db->addr_res, db->data_res);
                ret = -ENOENT;
                goto out;
        }

        ndev->irq = platform_get_irq(pdev, 0);       // 获取中断编号
        if (ndev->irq < 0) {                         // 参数校验
                dev_err(db->dev, "interrupt resource unavailable: %d\n",
                        ndev->irq);
                ret = ndev->irq;
                goto out;
        }

        db->irq_wake = platform_get_irq(pdev, 1);     // 获取第二个个中断资源 
        if (db->irq_wake >= 0) {                      // 未指定 不会进入   
                dev_dbg(db->dev, "wakeup irq %d\n", db->irq_wake);

                ret = request_irq(db->irq_wake, dm9000_wol_interrupt,
                                  IRQF_SHARED, dev_name(db->dev), ndev);
                if (ret) {
                        dev_err(db->dev, "cannot get wakeup irq (%d)\n", ret);
                } else {

                        /* test to see if irq is really wakeup capable */
                        ret = irq_set_irq_wake(db->irq_wake, 1);
                        if (ret) {
                                dev_err(db->dev, "irq %d cannot set wakeup (%d)\n",
                                        db->irq_wake, ret);
                                ret = 0;
                        } else {
                                irq_set_irq_wake(db->irq_wake, 0);
                                db->wake_supported = 1;
                        }
                }
        }

        iosize = resource_size(db->addr_res);           
        db->addr_req = request_mem_region(db->addr_res->start, iosize,    // 动态申请内存
                                          pdev->name);

        if (db->addr_req == NULL) {                                      // 内存申请失败
                dev_err(db->dev, "cannot claim address reg area\n");
                ret = -EIO;
                goto out;
        }

        db->io_addr = ioremap(db->addr_res->start, iosize);           // 内存地址映射,物理地址映射虚拟地址

        if (db->io_addr == NULL) {                                   // 映射失败 
                dev_err(db->dev, "failed to ioremap address reg\n");
                ret = -EINVAL;
                goto out;
        }

        iosize = resource_size(db->data_res);
        db->data_req = request_mem_region(db->data_res->start, iosize,  // 动态申请内存
                                          pdev->name);

        if (db->data_req == NULL) {                                     // 内存申请失败
                dev_err(db->dev, "cannot claim data reg area\n");
                ret = -EIO;
                goto out;
        }

        db->io_data = ioremap(db->data_res->start, iosize);            // 内存地址映射,   物理地址映射虚拟地址

        if (db->io_data == NULL) {                                     // 映射失败 
                dev_err(db->dev, "failed to ioremap data reg\n");
                ret = -EINVAL;
                goto out;
        }

        /* fill in parameters for net-dev structure */
        ndev->base_addr = (unsigned long)db->io_addr;

        /* 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)    // 总线位宽16位,设置db读写函数成员变量
                        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;
        }

#ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL
        db->flags |= DM9000_PLATF_SIMPLE_PHY;
#endif

        dm9000_reset(db);    // 软件复位

        /* try multiple times, DM9000 sometimes gets the read wrong */
        for (i = 0; i < 8; i++) {                              // DM9000网卡捕获
                id_val  = ior(db, DM9000_VIDL);                // 读取生产厂家ID低8字节
                id_val |= (u32)ior(db, DM9000_VIDH) << 8;      // 读取生产厂家ID高8位字节 
                id_val |= (u32)ior(db, DM9000_PIDL) << 16;     // 读取产品ID低8位字节
                id_val |= (u32)ior(db, DM9000_PIDH) << 24;     // 读取产品ID高8位字节 

                if (id_val == DM9000_ID)                       // 判断读取到的ID是否正确 
                        break;
                dev_err(db->dev, "read wrong id 0x%08x\n", id_val);
        }

        if (id_val != DM9000_ID) {                             // DM9000网卡捕获失败  0x90000A46
                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);                      // 获取芯片修订版本,修订type参数
        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;
        }

        /* dm9000a/b are capable of hardware checksum offload */
        if (db->type == TYPE_DM9000A || db->type == TYPE_DM9000B) {     // 我们使用的DM9000E,所以不会走这里
                ndev->hw_features = NETIF_F_RXCSUM | NETIF_F_IP_CSUM;
                ndev->features |= ndev->hw_features;
        }

        /* from this point we assume that we have found a DM9000 */

        ndev->netdev_ops        = &dm9000_netdev_ops;              // 设置网络设备的操作函数集
        ndev->watchdog_timeo    = msecs_to_jiffies(watchdog);
        ndev->ethtool_ops       = &dm9000_ethtool_ops;             //  ethtool的操作集,用于支持应用层的ethtool命令

        db->msg_enable       = NETIF_MSG_LINK;
        db->mii.phy_id_mask  = 0x1f;          // MII接口相关配置,这里我们不用关注                   
        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;

        mac_src = "eeprom";

        /* try reading the node address from the attached EEPROM */
        for (i = 0; i < 6; i += 2)                                     //由于DM9000没有外挂EPPROM,所以这个读取无效,这里读取到的都是0x99
                dm9000_read_eeprom(db, i / 2, ndev->dev_addr+i);

        if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) {   // MAC地址无效,设置MAC地址为pdata->dev_addr
                mac_src = "platform data";
                memcpy(ndev->dev_addr, pdata->dev_addr, ETH_ALEN);
        }

        if (!is_valid_ether_addr(ndev->dev_addr)) {   // MAC地址无效
                /* try reading from mac */

                mac_src = "chip";
                for (i = 0; i < 6; i++)
                        ndev->dev_addr[i] = ior(db, i+DM9000_PAR);   // 读取DM9000 PAR寄存器,获取MAC
        }

        if (!is_valid_ether_addr(ndev->dev_addr)) {   // MAC地址无效
                inv_mac_addr = true;
                eth_hw_addr_random(ndev);    // 这里应该是随机初始化个MAC地址
                mac_src = "random";
        }


        platform_set_drvdata(pdev, ndev);  // 设置pdev->dev.driver_data = ndev,即将ndev设置为pdev的私有数据
        ret = register_netdev(ndev);       // 注册网络设备

        if (ret == 0) {    // 失败
                if (inv_mac_addr)
                        dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please set using ip\n",
                                 ndev->name);
                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:
        dev_err(db->dev, "not found (%d).\n", ret);

        dm9000_release_board(pdev, db);    // 释放内存
        free_netdev(ndev);

        return ret;
}

这段代码属实有点长了,让人一眼看过去,就有点想放弃去读的想法,既然都学习到了这一步,我们还是耐着性去分析吧。这里代码理解起来还是比较简单的,主要流程如下:

  • 调用alloc_etherdev()为DM9000网卡设备动态分配net_device结构体;
  • 设置与网卡设备硬件相关的寄存器:
    • 获取DM9000资源信息并保存在board_info变量db中,包括:DM9000地址寄存器地址、数据寄存器地址、中断源;
    • 根据DM9000地址寄存器地址、数据寄存器地址申请内存,申请中断;并将申请后的资源信息也保存在db中;
    • 根据选择的总线位宽,调用dm9000_set_io()初始化bd的读写函数;
    • 向DM9000_NCR寄存器写入数据NCR_RST,实现DM9000的软件复位;
    • dm9000 芯片的捕获,实际上就是对dm9000上的生产厂家ID、产品ID信息进行对比;
    • 获取芯片修订版本,修订type参数;
  • 设置net_device结构体的成员;
    • 设置网络设备的操作函数集dm9000_netdev_ops;
    • 设置网卡设备的MAC:这里首先从pdata->dev_addr中获取MAC值(如果设置了的话),然后尝试读取网卡硬件的MAC地址、...;
  • 使用register_netdev()向内核注册网络设备;
2.2.1 board_info

我们分析DM9000驱动模块入口函数,我们注意到有一个比较重要的结构体struct board_info,贯穿整个函数。这个结构体用来保存DM9000芯片的一些私有信息,这些信息与网卡设备的接线有关,定义在drivers/net/ethernet/davicom/dm9000.c:文件:

/* DM9000 register address locking.
 *
 * The DM9000 uses an address register to control where data written
 * to the data register goes. This means that the address register
 * must be preserved over interrupts or similar calls.
 *
 * During interrupt and other critical calls, a spinlock is used to
 * protect the system, but the calls themselves save the address
 * in the address register in case they are interrupting another
 * access to the device.
 *
 * For general accesses a lock is provided so that calls which are
 * allowed to sleep are serialised so that the address register does
 * not need to be saved. This lock also serves to serialise access
 * to the EEPROM and PHY access registers which are shared between
 * these two devices.
 */

/* The driver supports the original DM9000E, and now the two newer
 * devices, DM9000A and DM9000B.
 */

enum dm9000_type {
        TYPE_DM9000E,   /* original DM9000,也是我们开发板所使用的型号 */
        TYPE_DM9000A,
        TYPE_DM9000B
};
/* Structure/enum declaration ------------------------------- */
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             queue_ip_summed;
        u16             dbug_cnt;
        u8              io_mode;                /* 0:word, 2:byte */
        u8              phy_addr;
        u8              imr_all;

        unsigned int    flags;
        unsigned int    in_timeout:1;
        unsigned int    in_suspend:1;
        unsigned int    wake_supported:1;

        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;

        int              irq_wake;

        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;
        u32             wake_state;

        int             ip_summed;
};

其中部分参数含义如下:

  • irq:IRQ编号;
  • type:DM9000网卡类型,即具体型号;
  • inblk:16位总线时,初始化为dm9000_inblk_16bit;接收数据操作函数;
  • outblk:6位总线时,初始化为dm9000_outblk_16bit;发送数据操作函数;
  • flags:DM9000 IO控制标志位,定义在include/linux/dm9000.h文件;
  • dumpblk:16位总线时,初始化为dm9000_dumpblk_16bit;
  • io_addr:存放DM9000地址寄存器地址的虚拟地址;
  • io_data:存放DM9000数据寄存器地址的虚拟地址;
  • addr_res:内存资源,描述DM9000地址寄存器的地址;
  • data_res:内存资源,描述DM9000数据寄存器的地址;
  • addr_req:同addr_res,只不过这个是通过request_mem_region()函数动态申请得到的;request_mem_region 告诉内核你的驱动程序将使用这个范围的 I/O 地址,这将阻止其他驱动程序通过request_mem_region 对同一区域的任何重叠调用,这种机制不做任何类型的映射,它是一种纯粹的保留机制,它依赖于所有内核设备驱动程序都必须良好的事实,并且它们必须调用request_mem_region,检查返回值,并表现以防万一。当然如果没有使用该函数也是可以,只是这样不符合内核编码规则;
  • data_req:同data_res,只不过这个是通过request_mem_region()函数动态申请得到的;
  • irq_wake:
  • ndev:指向DM9000网卡设备;
  • io_mode:处理器模式,0为16位模式、1为32位模式、2为8位模式 ;
  • tx_pkt_cnt:当前正在发送的数据包个数,不包含发送完成的;
2.2.2 dm9000_set_io
static void dm9000_set_io(struct board_info *db, int byte_width)
{
    /* use the size of the data resource to work out what IO
     * routines we want to use
     */

    switch (byte_width) {
    case 1:
        db->dumpblk = dm9000_dumpblk_8bit;
        db->outblk  = dm9000_outblk_8bit;
        db->inblk   = dm9000_inblk_8bit;
        break;


    case 3:
        dev_dbg(db->dev, ": 3 byte IO, falling back to 16bit\n");
    case 2:
        db->dumpblk = dm9000_dumpblk_16bit;
        db->outblk  = dm9000_outblk_16bit;
        db->inblk   = dm9000_inblk_16bit;
        break;

    case 4:
    default:
        db->dumpblk = dm9000_dumpblk_32bit;
        db->outblk  = dm9000_outblk_32bit;
        db->inblk   = dm9000_inblk_32bit;
        break;
    }
}
2.2.3 dm9000_reset
static void dm9000_reset(board_info_t * db)
{
    dev_dbg(db->dev, "resetting device\n");

    /* RESET device */
    writeb(DM9000_NCR, db->io_addr);  //  DM9000地址寄存器写入DM9000_NCR
    udelay(200);
    writeb(NCR_RST, db->io_data);     //  DM9000数据寄存器写入NCR_RST: 1 << 0
    udelay(200);
}

重启DM9000网卡设备,只需要向DM9000_NCR寄存器写入NCR_RST,分为两步实现:

  • 写地址:向DM9000地址寄存器写入DM9000_NCR;
  • 写数据:向DM9000数据寄存器写入NCR_RST;

三、dm9000_netdev_ops

我们重点看一下DM9000设备的操作函数集,定义如下:

static const struct net_device_ops dm9000_netdev_ops = {
        .ndo_open               = dm9000_open,
        .ndo_stop               = dm9000_stop,
        .ndo_start_xmit         = dm9000_start_xmit,
        .ndo_tx_timeout         = dm9000_timeout,
        .ndo_set_rx_mode        = dm9000_hash_table,
        .ndo_do_ioctl           = dm9000_ioctl,
        .ndo_set_features       = dm9000_set_features,
        .ndo_validate_addr      = eth_validate_addr,
        .ndo_set_mac_address    = eth_mac_addr,
#ifdef CONFIG_NET_POLL_CONTROLLER
        .ndo_poll_controller    = dm9000_poll_controller,
#endif
};

3.1 dm9000_open

当用户执行命令ifconfig eth0 up(启用eth0网卡)后会调用网卡驱动的open函数,函数定义如下:

/*
 *  Open the interface.
 *  The interface is opened whenever "ifconfig" actives it.
 */
static int
dm9000_open(struct net_device *dev)
{
        struct board_info *db = netdev_priv(dev);                 // 获取板载网卡私有信息
        unsigned int irq_flags = irq_get_trigger_type(dev->irq);  // 获取中断触发类型

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

        /* If there is no IRQ type specified, tell the user that this is a
         * problem
         */
        if (irq_flags == IRQF_TRIGGER_NONE)                      // 未设置中断触发类型  
                dev_warn(db->dev, "WARNING: no IRQ resource flags set.\n");

        irq_flags |= IRQF_SHARED;          // 设置共享中断标志位,因为CPU这个中断引也有可能被其它设备所使用的

        /* GPIO0 on pre-activate PHY, Reg 1F is not set by reset */
        iow(db, DM9000_GPR, 0); /* REG_1F bit0 activate phyxcer 通过GPIO0写如0为内部PHY提供电源 */
        mdelay(1); /* delay needs by DM9000B */

        /* Initialize DM9000 board */
        dm9000_init_dm9000(dev);     // 初始化dm9000

        if (request_irq(dev->irq, dm9000_interrupt, irq_flags, dev->name, dev))  // 注册中断,中断处理函数为dm9000_interrupt  由于使用了共享中断标志位,最后一个参数不能为空
                return -EAGAIN;
        /* Now that we have an interrupt handler hooked up we can unmask
         * our interrupts
         */
        dm9000_unmask_interrupts(db);   // 使能DM9000 TX/RX中断

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

        mii_check_media(&db->mii, netif_msg_link(db), 1);  // mii接口相关,忽略即可
        netif_start_queue(dev);     // 激活设备发送队列,允许上层调用dev_queue_xmit()函数

        /* Poll initial link status */
        schedule_delayed_work(&db->phy_poll, 1);

        return 0;
}

open 函数主要做了申请收发中断,初始化 DM9000、使能TX、RX中断,激活设备发送队列。

其中 DM9000 的初始化全是对硬件寄存器的操作:

/*
 * Initialize dm9000 board
 */
static void
dm9000_init_dm9000(struct net_device *dev)
{
        struct board_info *db = netdev_priv(dev);
        unsigned int imr;
        unsigned int ncr;

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

        dm9000_reset(db);             // 复位 DM9000_NCR寄存器写入NCR_RST     
        dm9000_mask_interrupts(db);   // 屏蔽中断  DM9000_IMR寄存器写入IMR_PAR

        /* I/O mode */
        db->io_mode = ior(db, DM9000_ISR) >> 6; /* ISR bit7:6 keeps I/O mode  设置处理器模式 */

        /* Checksum mode */
        if (dev->hw_features & NETIF_F_RXCSUM)
                iow(db, DM9000_RCSR,
                        (dev->features & NETIF_F_RXCSUM) ? RCSR_CSUM : 0);

        iow(db, DM9000_GPCR, GPCR_GEP_CNTL);    /* Let GPIO0 output  GPIO0设置为输出 */
        iow(db, DM9000_GPR, 0);  // 通过对GPIO0写入0为内部的PHY提供电源

        /* If we are dealing with DM9000B, some extra steps are required: a
         * manual phy reset, and setting init params.
         */
        if (db->type == TYPE_DM9000B) {  // 跳过
                dm9000_phy_write(dev, 0, MII_BMCR, BMCR_RESET);
                dm9000_phy_write(dev, 0, MII_DM_DSPCR, DSPCR_INIT_PARAM);
        }

        ncr = (db->flags & DM9000_PLATF_EXT_PHY) ? NCR_EXT_PHY : 0;

        /* if wol is needed, then always set NCR_WAKEEN otherwise we end
         * up dumping the wake events if we disable this. There is already
         * a wake-mask in DM9000_WCR */
        if (db->wake_supported)   //  跳过
                ncr |= NCR_WAKEEN;

        iow(db, DM9000_NCR, ncr);

        /* Program operating register */
        iow(db, DM9000_TCR, 0);         /* TX Polling clear */
        iow(db, DM9000_BPTR, 0x3f);     /* Less 3Kb, 200us */
        iow(db, DM9000_FCR, 0xff);      /* Flow Control */
        iow(db, DM9000_SMCR, 0);        /* Special Mode */
        /* clear TX status */
        iow(db, DM9000_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END);
        iow(db, DM9000_ISR, ISR_CLR_STATUS); /* Clear interrupt status */

        /* Set address filter table */
        dm9000_hash_table_unlocked(dev);

        imr = IMR_PAR | IMR_PTM | IMR_PRM;
        if (db->type != TYPE_DM9000E)
                imr |= IMR_LNKCHNG;

        db->imr_all = imr;

        /* Init Driver variable */
        db->tx_pkt_cnt = 0;
        db->queue_pkt_len = 0;
        netif_trans_update(dev);
}

3.2 dm9000_stop

/*
 * Stop the interface.
 * The interface is stopped when it is brought.
 */
static int
dm9000_stop(struct net_device *ndev)
{
        struct board_info *db = netdev_priv(ndev);

        if (netif_msg_ifdown(db))
                dev_dbg(db->dev, "shutting down %s\n", ndev->name);

        cancel_delayed_work_sync(&db->phy_poll);

        netif_stop_queue(ndev);     // 停止设备发送队列
        netif_carrier_off(ndev);

        /* free interrupt */
        free_irq(ndev->irq, ndev);  // 释放中断

        dm9000_shutdown(ndev);

        return 0;
}

3.3 dm9000_start_xmit

应用程序调用send函数去发送数据,内核协议栈会将数据构造成struct sk_buff后放入等待队列,最后调用start_xmit通知网卡发送数据。

/*
 *  Hardware start transmission.
 *  Send a packet to media from the upper layer.
 */
static int
dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
        unsigned long flags;
        struct board_info *db = netdev_priv(dev);

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

        if (db->tx_pkt_cnt > 1)               // 正在发送的数据包数量>1,直接返回
                return NETDEV_TX_BUSY;

        spin_lock_irqsave(&db->lock, flags);  // 上锁 由于进程、中断可能访问同一临界资源,所以使用了spin_lock_irqsave

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

        (db->outblk)(db->io_data, skb->data, skb->len); // 将skb->data缓冲区的skn_len长度数据写入到DM9000 TX SRAM中
        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) {          // 只有一个数据包需要发送  立即发送
                dm9000_send_packet(dev, skb->ip_summed, skb->len);
        } else {                            // 第二个进来  进到这里,表明有两个数据包正在发送,第一个包已经启动发送但是未发送完成,第二个包仅仅写入到DM9000 TX SRAM,还未启动发送
                /* Second packet */
                db->queue_pkt_len = skb->len;
                db->queue_ip_summed = skb->ip_summed;
                netif_stop_queue(dev);     // 阻止上层将新的数据传送过来
        }

        spin_unlock_irqrestore(&db->lock, flags);  // 释放锁

        /* free this SKB */
        dev_consume_skb_any(skb);   // 释放skb缓冲区

        return NETDEV_TX_OK;
}

这块代码有点绕人,需要结合中断处理函数一起来看才能理解。

我们知道网卡设备有一个TX SRAM缓冲区,用来存放需要发送的数据,当我们需要进行一次发包时,需要经过以下jige 步骤:

  • 将需要发送的数据包写入到DM9000 TX SRAM中;
  • 向TXPLL、TXPLH寄存器写入待发送的数据长度;
  • 启动发送;
  • 等待发送完成,发送完成进入中断处理程序;

每次启动发送后,发送完成之前,如果有新的数据包需要发送,这里又会将新的数据包写入到DM9000 TX SRAM中,同时这时会关闭发送队列,阻止上层将新的数据包传送过来,因此这时即便有新的数据包过来,网卡设备也来不及发送。只有等第一个数据包发送完成,才会在中断处理程序中开始第二个数据包的发送, 并唤醒被阻塞的上层,让上层协议继续调用设备数据操作函数传递数据。

其中dm9000_send_package()定义如下,这个函数才是真正进行数据包的发送工作:

static void dm9000_send_packet(struct net_device *dev,
                               int ip_summed,
                               u16 pkt_len)
{
        struct board_info *dm = to_dm9000_board(dev);

        /* The DM9000 is not smart enough to leave fragmented packets alone. */
        if (dm->ip_summed != ip_summed) {
                if (ip_summed == CHECKSUM_NONE)
                        iow(dm, DM9000_TCCR, 0);
                else
                        iow(dm, DM9000_TCCR, TCCR_IP | TCCR_UDP | TCCR_TCP);
                dm->ip_summed = ip_summed;
        }

        /* Set TX length to DM9000,写入发送数据的长度  */
        iow(dm, DM9000_TXPLL, pkt_len);  
        iow(dm, DM9000_TXPLH, pkt_len >> 8);

        /* Issue TX polling command,启动发送,发送完会触发中断 */
        iow(dm, DM9000_TCR, TCR_TXREQ); /* Cleared after TX complete */
}

3.4 dm9000_interrupt

dm9000_interrupt是中断处理程序,当DM9000数据包发送完成或者接收到数据包时执行:

static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
{
        struct net_device *dev = dev_id;
        struct board_info *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);

        dm9000_mask_interrupts(db);   // 屏蔽中断
        /* Got DM9000 interrupt status */
        int_status = ior(db, DM9000_ISR);       /* Got ISR  读取中断状态寄存器的值 */
        iow(db, DM9000_ISR, int_status);        /* Clear ISR status 写1清除中断标志 */

        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);

        /* Transmit 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);
                }
        }

        dm9000_unmask_interrupts(db);  // 取消中断屏蔽
        /* Restore previous register address */
        writeb(reg_save, db->io_addr);

        spin_unlock_irqrestore(&db->lock, flags);

        return IRQ_HANDLED;
}

在中断处理函数中:

  • 首先是屏蔽到DM9000 TX/RX中断,防止中断嵌套进入;
  • 然后清除中断标志位;
  • 如果是接收中断,接收到数据包后将执行dm9000_rx函数;
  • 如果是发送中断,数据包发送完成将会执行dm9000_tx_done函数;
  • 取消DM9000 TX/RX中断屏蔽;
3.4.1 dm9000_tx_done

数据包发送完成处理函数:

/*
 * DM9000 interrupt handler
 * receive the packet to upper layer, free the transmitted packet
 */

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

        if (tx_status & (NSR_TX2END | NSR_TX1END)) {  // TX(发送)数据包1完成标志  TX(发送)数据包2完成标志
                /* One packet sent complete */
                db->tx_pkt_cnt--;         // 当前数据包发送完成,正在发送的数据包个数-1
                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)   // 还有数据包需要发送
                        dm9000_send_packet(dev, db->queue_ip_summed,
                                           db->queue_pkt_len);
                netif_wake_queue(dev);   // 唤醒被阻塞的上层,让上层协议继续调用设备数据操作函数传递数据
        }
}
3.4.2 dm9000_rx

DM9000 RX SRAM 中一个完整数据包包含4字节的头部:

  • 其中第一个字节固定为 0x01;
  • 第二个字节为数据包状态;
  • 最后两个字节表示有效数据的长度;

驱动代码中用这样一个结构体来表示头部,头部之后的数据才为真正有效数据;

struct dm9000_rxhdr {
        u8      RxPktReady;
        u8      RxStatus;
        __le16  RxLen;
} __packed;

dm9000_rx函数比较长度,代码如下:

/*
 *  Received a packet and pass to upper layer
 */
static void
dm9000_rx(struct net_device *dev)
{
        struct board_info *db = netdev_priv(dev);
        struct dm9000_rxhdr rxhdr;
        struct sk_buff *skb;
        u8 rxbyte, *rdptr;
        bool GoodPacket;
        int RxLen;

        /* Check packet ready or not */
        do {
                ior(db, DM9000_MRCMDX); /* Dummy read 空读,读取RX SRAM的数据,地址不会改变 */

                /* Get most updated data */
                rxbyte = readb(db->io_data);  // 读取RX SRAM第一个字节  固定值0x01

                /* Status check: this byte must be 0 or 1 */
                if (rxbyte & DM9000_PKT_ERR) {  
                        dev_warn(db->dev, "status check fail: %d\n", rxbyte);
                        iow(db, DM9000_RCR, 0x00);      /* Stop Device */
                        return;
                }

                if (!(rxbyte & DM9000_PKT_RDY))  // 0x01 RX SRAM存储的数据的四字节头部第一个字节固定位0x01
                        return;

                /* A packet ready now  & Get status/length */
                GoodPacket = true;
                writeb(DM9000_MRCMD, db->io_addr);                // 读取RX SRAM的数据 每次读取完地址自增2字节 ; 这一步只是向DM9000进行写地址操作,与ior命令不同,ior包含写地址,和读取数据两步

                (db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr));  // 读数据操作 一共读取4个字节长度,也就是RX SRAM数据包头部

                RxLen = le16_to_cpu(rxhdr.RxLen);    // 数据包的总长度 字节数

                if (netif_msg_rx_status(db))
                        dev_dbg(db->dev, "RX: status %02x, length %04x\n",
                                rxhdr.RxStatus, RxLen);

                /* Packet Status check  以太帧长度(64,1536] */
                if (RxLen < 0x40) {
                        GoodPacket = false;
                        if (netif_msg_rx_err(db))
                                dev_dbg(db->dev, "RX: Bad Packet (runt)\n");
                }

                if (RxLen > DM9000_PKT_MAX) {
                        dev_dbg(db->dev, "RST: RX Len:%x\n", RxLen);
                }

                /* rxhdr.RxStatus is identical to RSR register. 校验头部的状态值,判断是不是一个正常的数据包 */
                if (rxhdr.RxStatus & (RSR_FOE | RSR_CE | RSR_AE |
                                      RSR_PLE | RSR_RWTO |
                                      RSR_LCS | RSR_RF)) {
                        GoodPacket = false;
                        if (rxhdr.RxStatus & RSR_FOE) {
                                if (netif_msg_rx_err(db))
                                        dev_dbg(db->dev, "fifo error\n");
                                dev->stats.rx_fifo_errors++;
                        }
                        if (rxhdr.RxStatus & RSR_CE) {
                                if (netif_msg_rx_err(db))
                                        dev_dbg(db->dev, "crc error\n");
                                dev->stats.rx_crc_errors++;
                        }
                        if (rxhdr.RxStatus & RSR_RF) {
                                if (netif_msg_rx_err(db))
                                        dev_dbg(db->dev, "length error\n");
                                dev->stats.rx_length_errors++;
                        }
                }

                /* Move data from DM9000 */
                if (GoodPacket &&
                    ((skb = netdev_alloc_skb(dev, RxLen + 4)) != NULL)) {  // 如果是正常数据包,就申请sk_buff
                        skb_reserve(skb, 2);
                        rdptr = skb_put(skb, RxLen - 4);   // 返回sk_buff数据缓冲区指针

                        /* Read received packet from RX SRAM */

                        (db->inblk)(db->io_data, rdptr, RxLen);   // 读数据操作,将RX SRAM中真正的数据拷贝到rdptr
                        dev->stats.rx_bytes += RxLen;    // 更新接收统计信息

                        /* Pass to upper layer */
                        skb->protocol = eth_type_trans(skb, dev);  // 获取上层协议
                        if (dev->features & NETIF_F_RXCSUM) {
                                if ((((rxbyte & 0x1c) << 3) & rxbyte) == 0)
                                        skb->ip_summed = CHECKSUM_UNNECESSARY;
                                else
                                        skb_checksum_none_assert(skb);
                        }
                        netif_rx(skb);       // 将sk_buff传递给上层
                        dev->stats.rx_packets++;  // 更新接收统计信息

                } else {
                        /* need to dump the packet's data */

                        (db->dumpblk)(db->io_data, RxLen);  // 无效数据,读取出来,废弃
                }
        } while (rxbyte & DM9000_PKT_RDY);
}

主要流程如下:

  • 先读取 RX SRAM 中 4 字节头部到struct dm9000_rxhdr rxhdr中;
  • 判断第一字节是否为 0x01, 判断数据包总长度是否符合以太网规范,最后根据头部中的状态值是否是一个正常的封包;
  • 经过2判断是正常封包后,读取有效数据;
  • 创建分配 sk buffer,并将有效数据拷贝到 sk buffer 中;
  • 使用netif_rx()函数将sk_buff传递给上层;

3.5 dm9000_timeout

中断发送超时函数:

/* Our watchdog timed out. Called by the networking layer */
static void dm9000_timeout(struct net_device *dev)
{
        struct board_info *db = netdev_priv(dev);
        u8 reg_save;
        unsigned long flags;

        /* Save previous register address */
        spin_lock_irqsave(&db->lock, flags);
        db->in_timeout = 1;
        reg_save = readb(db->io_addr);

        netif_stop_queue(dev);
        dm9000_init_dm9000(dev);
        dm9000_unmask_interrupts(db);
        /* We can accept TX packets again */
        netif_trans_update(dev); /* prevent tx timeout */
        netif_wake_queue(dev);

        /* Restore previous register address */
        writeb(reg_save, db->io_addr);
        db->in_timeout = 0;
        spin_unlock_irqrestore(&db->lock, flags);
}

参考文章:

[1] 移植DM900C网卡驱动

[2]二十、Linux驱动之移植DM9000C网卡驱动(上)

[3]二十一、Linux驱动之移植DM9000C网卡驱动(下)

[4]27.Linux-DM9000C网卡移植(详解)

[5]Linux驱动之网卡驱动剖析

[6]IO端口&IO内存

posted @ 2023-02-02 22:40  大奥特曼打小怪兽  阅读(433)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步