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; }
这块代码有点绕人,需要结合中断处理函数一起来看才能理解。
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2020-02-02 Spring Boot -- 认识Spring Boot