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网卡驱动(下)
[6]IO端口&IO内存