
简介面向嵌入式Linux及网络设备开发者的DM9000驱动样例包围绕DM9000、DM9000A、DM9000B、DM9000E四种型号的适配展开重点解决网卡初始化、DMA数据传输、中断处理与错误管理等核心问题。压缩包体积仅11KB共2个文件由1个C语言源文件和1个头文件组成结构精简适合作为学习样例直接阅读或移植到实际工程。驱动代码对DM9000A、DM9000B、DM9000E之间的功耗、封装及特性差异做了针对性配置能够帮助开发者理解同一系列芯片的驱动写法并结合Linux网络子系统完成设备注册、寄存器操作和数据处理流程。代码中包含寄存器配置、收发缓冲区管理及中断服务程序等实现细节为驱动移植和排错提供了直观参考。目前已有136人学习下载适合正在做以太网控制器驱动移植或想掌握DM9000芯片底层交互逻辑的嵌入式工程师参考。1. 从一块旧网卡芯片讲起DM9000 驱动为何至今仍值得研究在中低端嵌入式设备里DM9000A、DM9000B 和 DM9000E 这三兄弟依然是百兆以太网的主力。无论是工控机、开发板还是老旧的路由器驱动代码里一水的dm9000前缀。很多人以为这种芯片早该被淘汰但实际情况是它的驱动移植和维护工作在嵌入式 Linux 项目中从未断过。这份资源名字里同时带了 DM9000、DM9000A、dm9000b、dm9000e说明作者要么在整理多型号适配要么在做平台间的驱动迁移。DM9000 系列的驱动难点不在芯片本身而在它那套 IO 端口访问机制和 EEPROM 配置逻辑。这篇文章直接讲清楚 DM9000 驱动从原理到移植、再到线上排查的完整路径适合正在做 BSP 移植或调试网络丢包的工程师。2. DM9000 的 IO 机制和通用驱动骨架先搞懂这些再看代码2.1 DM9000A 的地址线复用和数据端口访问方式DM9000A 是一颗 100M 以太网控制器它的对外接口不是普通的内存映射而是通过 INDEX 端口和 DATA 端口这对 IO 组合来访问内部寄存器。大多数 ARM 平台把 DM9000A 挂在片选上地址线 A2 决定访问的是 INDEX 还是 DATA。驱动初始化时的第一个动作就是向 INDEX 端口写寄存器地址再从 DATA 端口读写数据。这个双端口机制是理解整个驱动的钥匙——它不像主流 MAC 那样把寄存器直接映射到内存地址空间而是用类似 74LS373 锁存的方式间接访问。DM9000B 和 DM9000E 在寄存器布局上保持了兼容但 DM9000E 改用了通用总线接口地址线的连接方式可能不同。这就是为什么同一份驱动代码在不同板子上表现迥异。驱动初始化时通常有个dm9000_probe函数它会向 INDEX 端口写入DM9000_VIDL和DM9000_VIDH来读取厂商 ID如果读回来的值不是0x0A46就说明地址线接法有问题。提示代码里大量出现DM9000_INDEX和DM9000_DATA这两个宏它们对应的物理地址由板级代码的platform_data或设备树决定不要自作主张在驱动里改。2.2 Linux 内核里的 DM9000 驱动架构和数据包收发路径之所以说它是通用驱动骨架是因为 Linux 内核的dm9000.c已经抽象好了 platform driver 接口。它会根据platform_device里的资源来自动获取 IO 地址和中断号然后注册成net_device。驱动中dm9000_open负责申请中断和初始化 PHYdm9000_start_xmit负责发送dm9000_rx负责收包。这套结构自 2.6 内核沿用至今几乎没有大改这说明它的设计足够稳定。收发路径里最值得关注的是接收方向DM9000 内部有一个 FIFO硬件收到数据包后会写入 FIFO驱动要轮询或者等中断来触发读取。而发送方向则相对简单驱动把要发送的数据写入发送缓冲区再置位发送请求寄存器就行。static netdev_tx_t dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct board_info *db netdev_priv(dev); unsigned long flags; spin_lock_irqsave(db-lock, flags); // 把数据写入 DM9000 的发送缓冲区 dm9000_write_srom(db, DM9000_MWCMD, skb-data, skb-len); // 设置发送长度寄存器 dm9000_write_reg(db, DM9000_TXPLL, (skb-len 8) 0xff); dm9000_write_reg(db, DM9000_TXPLH, skb-len 0xff); // 触发发送 dm9000_write_reg(db, DM9000_TCR, TCR_TXREQ); spin_unlock_irqrestore(db-lock, flags); dev_kfree_skb(skb); return NETDEV_TX_OK; }DM9000_MWCMD是内存写命令寄存器驱动会先用dm9000_write_srom把 skb 里的数据写入芯片内部的 SRAM。DM9000_TXPLL和DM9000_TXPLH是一对高低字节寄存器共同表示数据包的长度注意这里的高低字节顺序和直觉相反高位在前。最后通过DM9000_TCR的TCR_TXREQ位来通知芯片开始发送。这个函数的参数含义skb是内核网络子系统传递过来的数据包dev是当前网卡的net_device结构体。驱动里要用netdev_priv(dev)拿到自定义的board_info结构体里面存放着 IO 地址映射、自旋锁和统计信息等上下文。而db-lock这个自旋锁的作用是保护发送路径不被中断处理函数打断。2.3 常见移植修改点IO 地址重映射、EEPROM 和 PHY 配置2.3.1 寄存器偏移与平台 IO 重映射在 x86 或 ARM 平台上驱动会用devm_ioremap把物理地址映射成虚拟地址。但 DM9000A 的数据端口是 16 位的有些平台只映射了 8 位导致读出数据只对了一半。常见的做法是res platform_get_resource(pdev, IORESOURCE_MEM, 0); db-io_addr devm_ioremap(pdev-dev, res-start, resource_size(res)); db-io_data db-io_addr 2; // 数据端口偏移量db-io_addr是 INDEX 端口的虚拟地址db-io_data是 DATA 端口的虚拟地址。这里偏移量2是因为 DM9000A 的地址线 A[9:1] 连接到系统总线而 A0 不接所以数据端口总是在 INDEX 地址的基础上偏移 2 字节。如果你的板子用的是 8 位总线这个偏移量就要改成1否则所有的寄存器读写都会错位。2.3.2 EEPROM 配置与 MAC 地址读取DM9000 支持外接 93C46 EEPROM 来保存 MAC 地址和 PHY 配置。驱动初始化时读 EEPROM 的前三个 word 作为 MAC 地址。很多开发板出厂时没有焊 EEPROM这时驱动会退回到随机 MAC导致每次启动地址都变。要避免这个麻烦可以在板级代码里用platform_data传入固定 MAC或者在设备树里设置local-mac-address属性。驱动代码里读取 EEPROM 的行为通常是条件编译的有些厂商的 BSP 直接把 EEPROM 读取写成死循环这就解释了为什么某些板子拔掉 EEPROM 后网卡无法识别。2.3.3 该踩的坑EEPROM 位序、读写互斥和中断共享DM9000A 的 EEPROM 是 93C46一次只能操作一个 word。有些板上 EEPROM 的位序和驱动默认不一致读出的 MAC 会整体反转症状就是 DHCP 拿不到地址但链路是通的。驱动里一般有DM9000_PHY和DM9000_EEPROM的配置开关调 PHY 地址时记得看原理图不是所有板子都是 0x01。读写互斥是另一个高频问题。DM9000 的 EEPROM 操作和寄存器访问共用同一条总线驱动里如果没做信号量保护dm9000_read_eeprom和dm9000_write_eeprom并发就会把总线搞挂。还有个老问题DM9000 支持中断共享但板级配置里如果没把 IRQ 类型设成 level或者没在中断处理里清标志就会出现“一开网络就死机”的经典症状。提示调 EEPROM 前先检查ethtool -e eth0能否读出数据。读不出来就不要写先解决总线时序问题。3. 在 Linux 上把 DM9000A 驱动跑通从设备树到模块参数3.1 确认你的 DM9000 是哪个型号A、B 还是 EDM9000A 是 EPI 并行接口DM9000B 在 A 的基础上强化了 EEPROM 自举和电源管理DM9000E 则走的是 generic 总线寄存器偏移规则和 A/B 有区别。拿到板子先做两件事查芯片丝印再看驱动里的型号判断逻辑。static int dm9000_probe(struct platform_device *pdev) { struct board_info *db; u32 id_val; // 读芯片 ID 寄存器判断具体型号 id_val dm9000_read_reg(db, DM9000_VIDL) | (dm9000_read_reg(db, DM9000_VIDH) 8); if (id_val 0x0A46) { dev-type TYPE_DM9000A; } else if (id_val 0x0B46) { dev-type TYPE_DM9000B; } else { dev-type TYPE_DM9000E; } }这段代码的逻辑不复杂VID 的高字节是 0x46低字节区分型号。A 是 0x0AB 是 0x0BE 的识别走的是另一套寄存器序列。实际项目里丝印被磨掉的情况不少驱动里加个 debugfs 节点把 ID 打出来最省事。DM9000B 和 DM9000A 在 Linux 驱动的适配差异主要体现在 EEPROM 的读取时序和 PHY 地址的默认值上。B 的 EEPROM 支持 16 位地址访问A 只支持 8 位这也是驱动里dm9000_read_eeprom的地址参数为什么要分两次写的原因。3.2 设备树节点的标准写法与参数说明Linux 下 DM9000A 驱动靠 platform driver 匹配设备树节点节点写错直接-ENODEV。下面是实际项目里验证过的最小节点gmac { status okay; phy-mode rmii; ethernet18000000 { compatible davicom,dm9000; reg 0x18000000 0x2 0x18000004 0x2; interrupt-parent gpio2; interrupts 14 IRQ_TYPE_LEVEL_LOW; local-mac-address [00 12 34 56 78 9a]; davicom,no-eeprom; }; };reg的两组地址分别对应 DM9000 的 INDEX 端口和 DATA 端口第二个值是地址线宽度DM9000A 用 2 字节对齐。davicom,no-eeprom告诉驱动别去读 EEPROM直接使用local-mac-address这个属性在无 EEPROM 的板子上能省掉一堆初始化失败的日志。中断的IRQ_TYPE_LEVEL_LOW要和板级原理图一致。DM9000 的中断脚是开漏输出低电平有效如果你的板子上加了上拉电阻这里就应该是IRQ_TYPE_LEVEL_LOW写错成边沿触发会导致中断风暴。提示不同内核版本的 DM9000 设备树解析逻辑略有差异老版本要求reg的地址值带0x前缀新版本则要求地址总线宽度必须匹配实际硬件。3.3 编译选项、模块参数和运行时验证内核配置里打开CONFIG_DM9000可以编成模块也可以编进内核。编成模块时加载参数里有两个值得关注debug和mode。debug控制消息级别mode可以强制 PHY 的工作模式比如强制 100M 半双工。make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- modules insmod dm9000.ko debug1 mode0x21mode0x21表示 100Mbps 半双工默认是自动协商。排障时先把mode固定成0x21能快速排除 PHY 自动协商的问题而debug1能把驱动里的dm9000_probe、dm9000_open、dm9000_start_xmit等关键函数的执行路径打出来。加载成功后验证分两步先看dmesg | grep dm9000确认探测信息再用ethtool eth0确认链路速率。这两个命令的输出里如果出现link down多半是 PHY 地址或 MDIO 引脚配置不对和 MAC 层无关。4. 中断、NAPI 和性能DM9000 驱动真正容易翻车的地方4.1 经典中断处理逻辑与共享中断的兼容写法DM9000 的中断状态寄存器 ISR 是写 1 清除如果驱动在中断处理里用read-modify-write的方式清标志极容易把新产生的中断位再次触发。正确写法是直接回写读取到的 ISR 值static irqreturn_t dm9000_interrupt(int irq, void *dev_id) { struct board_info *db (struct board_info *) dev_id; int int_status; // 读 ISR 后立即写回写 1 清对应中断位 int_status dm9000_read_reg(db, DM9000_ISR); dm9000_write_reg(db, DM9000_ISR, int_status); if (int_status ISR_ROS) { netif_wake_queue(db-ndev); } if (int_status ISR_PRS) { dm9000_poll_link(db); } return IRQ_HANDLED; }int_status读出来之后直接回写避免了“读到的是旧值写回去又把新中断清掉”的竞态。ISR_ROS表示发送完成ISR_PRS表示接收状态变化这两个位在丢包排障里最关键。共享中断是 DM9000 驱动在嵌入式 Linux 里最常踩的坑。如果板子上多个设备共享一根 IRQ 线中断处理必须无条件调用disable_irq_nosync之后再查状态寄存器否则会因为中断风暴导致 CPU 占用率飙到 100%。驱动里要检测irq_flags确认 IRQ 是否共享共享时不要返回IRQ_NONE。4.2 NAPI 收包在 DM9000 上的实际收益与限制DM9000 的接收 FIFO 只有 16KB在千兆环境下是瓶颈但在百兆环境里足够跑满。把收包逻辑改造成 NAPI 模式能显著降低中断频率。static int dm9000_rx(struct napi_struct *napi, int budget) { struct board_info *db container_of(napi, struct board_info, napi); int rx_count 0; while (rx_count budget) { dm9000_read_reg(db, DM9000_MRCMDX); // 从 RX FIFO 读取数据包 rx_count; } return rx_count; }NAPI 的收益在于收包时关中断、轮询 RX FIFO收完再开中断。DM9000 的MRCMDX寄存器是内存数据读取指针读它就能拿到当前包的字节数。budget一般设 64表示一次轮询最多处理 64 个包处理不完就下次中断再继续。注意DM9000A 的 FIFO 读时序要求先读MRCMDX再读数据如果直接读数据端口会把 FIFO 地址指针搞乱导致丢包。4.3 性能瓶颈定位看丢包计数器还是看中断频率DM9000 驱动的性能问题分两类一类是 FIFO 溢出导致的丢包一类是中断频繁导致的 CPU 占用过高。定位时用ethtool -S eth0看驱动统计重点看两项rx_dropped和tx_timeout。ethtool -S eth0 | grep -E rx_dropped|tx_timeout cat /proc/interrupts | grep dm9000rx_dropped增长说明 FIFO 溢出说明budget太小或中断处理太慢tx_timeout增长说明发送队列卡死多半是 TX 完成中断丢失。/proc/interrupts的计数能看出中断频率百兆线速下每秒中断数超过 10000 就该考虑 NAPI 了。5. 验证与进阶把 DM9000 驱动改到能跑线速的实用技巧5.1 用 ping 和 iperf 验证驱动改动的有效性改完驱动别急着提交先用最小命令验证链路和吞吐量。ping只能证明链路通iperf才能证明驱动没把性能做坏。ping -c 100 -s 1400 192.168.1.1 iperf -c 192.168.1.1 -t 30 -i 5ping 的-s 1400把包加大到接近 MTU 上限能暴露分片和校验和问题iperf 的-i 5每 5 秒打印一次带宽观察吞吐是否稳定。如果 ping 通但 iperf 跑不到 80Mbps问题通常不在驱动本身而在总线时钟或 DMA 配置。吞吐量测试的参考值百兆网络下DM9000A 驱动的 TCP 吞吐应该在 88-94 Mbps 之间。低于 70 Mbps 就说明有问题优先检查中断频率和 RX FIFO 溢出计数。5.2 常见误用把 DM9000 当千兆网卡调、忽略 EEPROM 影响一个常见的误区是看到网卡速率只有 100Mbps就想调驱动参数跑千兆。DM9000 物理层只有百兆能力调驱动不可能突破硬件上限。另一个误区是忽略 EEPROM 中的配置项。某些开发板出厂时 EEPROM 里已经写好了 MAC 地址和 PHY 配置驱动里又写死了davicom,no-eeprom结果 MAC 地址随机变化导致 DHCP 每次拿到的地址都不一样。正确的做法是有 EEPROM 就让它生效没有 EEPROM 才用local-mac-address。5.3 使用 ethtool 读取 EEPROM 和寄存器状态ethtool是排查 DM9000 驱动问题的利器不需要重新编译内核就能看到寄存器状态。ethtool -e eth0 offset 0 length 16 ethtool -d eth0-e参数直接读 EEPROM 内容-d参数 dump 寄存器和 PHY 状态。如果-d输出里transceiver显示为unknown说明 PHY 驱动没配对如果port显示为MII说明 PHY 初始化成功。这两个命令在板卡调试现场比任何日志都好用。提示ethtool -e读 EEPROM 时如果驱动里没实现get_eeprom回调命令会直接报错。此时先确认驱动源码里有ethtool_ops的赋值别急着怀疑硬件。本文还有配套的精品资源点击获取