DM9000 驱动移植实战:从读 ID 到 ping 通的嵌入式网卡调试指南
简介这份资源面向嵌入式开发与网络驱动学习者聚焦DM9000系列以太网控制器的驱动实现覆盖DM9000A、DM9000B、DM9000E等型号的适配差异。内容围绕初始化寄存器配置、DMA收发缓冲区、中断响应与CRC校验等错误管理展开帮助读者理解驱动如何与芯片交互并完成数据收发。压缩包内共2个文件包含1个c源文件与1个h头文件分别承载驱动核心逻辑与寄存器、接口声明整体约11KB体量轻便适合直接嵌入工程阅读与移植参考。目前已有137人学习下载。对于需要调试网卡、优化传输性能或对接Linux网络子系统的开发者可借此梳理驱动模块划分、中断处理流程与常见排错思路为实际项目提供可复用的代码骨架与配置依据。1. DM9000 驱动移植从一颗老网卡芯片看嵌入式网络底层的真实门槛如果你手里有一块十年前甚至更早的嵌入式开发板上面焊着一颗 DM9000 系列网卡芯片而厂商给的 BSP 里网络驱动跑不起来那你大概率会搜到 DM9000_driver 这类驱动包。DM9000 是很多国产 ARM 开发板、工控板、路由器方案上常见的以太网控制器衍生型号包括 DM9000A、DM9000B、DM9000E它们在寄存器层面高度兼容但时序、PHY 接口和复位行为有细微差别。这个标题对应的不是某个新潮框架而是一份典型的嵌入式 Linux 或裸机环境下的网卡驱动移植工作。适合谁看适合正在做板级支持包适配、需要让板子 ping 通外网、或者想搞明白 MAC 控制器和 PHY 之间到底怎么握手的一线工程师。接下来我会按“先搞懂芯片在干什么、再动手改驱动、最后把坑填平”的顺序把这件事拆到能直接复现的程度。2. DM9000 驱动到底在驱动什么从总线时序到数据包收发2.1 先分清 DM9000A/B/E 的共性与差异DM9000 系列本质上是一个集成了 MAC 和 PHY 的以太网控制器对外提供 SRAM 风格的并行总线接口。主机侧通过地址线和数据线访问它的 INDEX 端口和 DATA 端口先写索引寄存器选中内部寄存器再读写数据端口。DM9000A、DM9000B、DM9000E 在核心寄存器布局上基本一致差异集中在几个地方DM9000E 对复位时序要求更宽松DM9000B 在某些批次上对 16 位模式下的字节序处理有区别DM9000A 的 PHY 自协商行为在部分老交换芯片下需要强制设置。移植时不要假设一份驱动能原封不动跑遍所有后缀先确认板子上丝印到底是哪一颗再决定要不要改初始化序列。2.2 总线接口模式16 位还是 8 位这不是随便选的大多数嵌入式板子把 DM9000 挂在 16 位宽的总线上因为 16 位模式下收发效率明显更高。但有些低成本方案为了省引脚会接成 8 位模式。驱动里必须根据硬件实际连接方式设置DM9000_DATA_WIDTH之类的宏或者通过ior/iow的访问宽度来体现。如果你在 16 位硬件上按 8 位访问读出来的 ID 会错位典型现象是读到的厂商 ID 和产品 ID 高低字节颠倒。反过来8 位硬件按 16 位访问轻则读写异常重则总线挂死。确认方法很简单读VID和PID寄存器DM9000 的厂商 ID 是0x0A46产品 ID 根据型号不同有0x9000、0x9001等对不上就先查总线宽度。2.3 中断模式与轮询模式的选择DM9000 支持中断上报收包事件也支持驱动主动轮询。在 Linux 内核里标准做法是申请中断在中断处理里读ISR寄存器判断是收包还是发包完成。但在调试阶段中断没调通之前可以先改成轮询模式让网络先通起来确认 MAC 和 PHY 基本工作正常再回头解决中断问题。轮询模式下的代价是 CPU 占用高不适合最终产品但作为 bring-up 手段非常有效。我一般会在驱动里留一个编译开关方便在两种模式间切换。2.4 收包和发包的环形缓冲区逻辑DM9000 内部有 SRAM 作为收发 FIFO驱动侧通常维护一个接收缓冲区队列。收包流程是中断触发后读ISR如果ISR的PRS位被置起说明有包到达然后读MRCMDX和MRCMD寄存器先读到一个 4 字节的包头里面包含包长度和状态再根据长度把后续数据读出来。发包则是把数据写入MWCMD寄存器然后设置TCR的发送位。这里的关键参数是接收缓冲区的阈值和发送超时时间设得太小会导致丢包设得太大在低内存设备上会挤占其他模块。常见做法是接收缓冲区按 1536 字节对齐发送超时给 5000 个总线周期左右。3. 把驱动跑起来从读 ID 到 ping 通的最小步骤3.1 确认硬件连接和基地址在写任何驱动代码之前先拿到板子的原理图或至少确认三件事DM9000 的片选信号接在哪个片选上、CMD 引脚接在地址线的哪一位、中断线接到哪个 GPIO 或中断控制器。CMD 引脚决定 INDEX 和 DATA 端口的地址偏移常见做法是接在地址线的最低位这样基地址加 0 是 INDEX加 2 是 DATA16 位模式。下面是一段裸机环境下的探测代码用来确认芯片是否在线。/* 假设基地址为 0x88000000CMD 接在地址线 A1 */ #define DM9000_BASE 0x88000000 #define DM9000_INDEX (DM9000_BASE) #define DM9000_DATA (DM9000_BASE 4) static unsigned int dm9000_read_reg(unsigned char reg) { /* 先写索引寄存器选中内部寄存器 */ *(volatile unsigned short *)DM9000_INDEX reg; /* 再从数据端口读回值 */ return *(volatile unsigned short *)DM9000_DATA; } static void dm9000_write_reg(unsigned char reg, unsigned int val) { *(volatile unsigned short *)DM9000_INDEX reg; *(volatile unsigned short *)DM9000_DATA val; } int dm9000_probe(void) { unsigned int vid, pid; /* 读厂商 ID 和产品 ID这是判断芯片是否在线的第一步 */ vid dm9000_read_reg(0x28); /* VID 寄存器 */ pid dm9000_read_reg(0x2A); /* PID 寄存器 */ if (vid ! 0x0A46) { return -1; /* 厂商 ID 不对检查总线宽度和基地址 */ } return 0; }这段代码的逻辑很直接通过 INDEX 和 DATA 两个地址完成一次寄存器访问。参数说明DM9000_BASE必须和片选实际映射的地址一致DM9000_DATA的偏移取决于 CMD 引脚接在地址线的哪一位如果 CMD 接在 A2那偏移就是 8。读出来的vid如果不是0x0A46先别急着改驱动用示波器看片选和读写信号有没有出来很多时候是基地址写错了。3.2 复位和 PHY 初始化确认芯片在线后下一步是软复位。DM9000 的复位通过NCR寄存器的RST位完成置位后需要等待一段时间然后清除。复位之后要配置NCR的总线宽度和TCR的发送参数再通过EPCR和EPAR访问 PHY 寄存器设置自协商或强制模式。void dm9000_reset(void) { /* 置位复位位等待至少 10us */ dm9000_write_reg(0x00, 0x01); /* NCR: RST1 */ udelay(20); /* 清除复位位同时设置 16 位总线模式 */ dm9000_write_reg(0x00, 0x00); /* NCR: 16-bit, normal mode */ udelay(100); } void dm9000_phy_init(void) { /* 通过 EPCR/EPAR/EPDR 访问 PHY 的 BMCR 寄存器 */ dm9000_write_reg(0x0B, 0x40); /* EPAR: 选择 BMCR自动扫描 PHY 地址 */ dm9000_write_reg(0x0C, 0x00); /* EPCR: 写操作 */ dm9000_write_reg(0x0D, 0x8000); /* EPDR: 自协商使能 */ dm9000_write_reg(0x0C, 0x08); /* EPCR: 写完成触发 PHY 写 */ mdelay(10); }复位后的等待时间不能省DM9000 的 PLL 需要稳定时间太短会导致后续寄存器读写不稳定。PHY 初始化里EPAR的0x40表示自动扫描 PHY 地址如果你的板子 PHY 地址固定也可以直接写具体地址。自协商使能后等 2 到 3 秒再读BMSR确认链路状态不要立刻发包。3.3 中断注册和收发使能在 Linux 内核里这一步对应request_irq和netif_start_queue。裸机环境下则是配置中断控制器、使能IMR寄存器里的收包中断位然后设置RCR的接收使能位。下面以 Linux 平台驱动为例展示关键调用顺序。/* 简化后的 Linux 驱动初始化片段 */ static int dm9000_open(struct net_device *dev) { int ret; /* 申请中断注意触发方式要和硬件一致一般是下降沿或低电平 */ ret request_irq(dev-irq, dm9000_interrupt, IRQF_TRIGGER_FALLING, dev-name, dev); if (ret) { return ret; } /* 使能接收和发送 */ dm9000_write_reg(0xFF, 0x01); /* IMR: 使能收包中断 */ dm9000_write_reg(0x05, 0x01); /* RCR: 接收使能 */ dm9000_write_reg(0x02, 0x00); /* TCR: 清除发送状态 */ netif_start_queue(dev); return 0; }request_irq的触发方式必须和硬件实际电平匹配如果中断一直触发或者完全不触发先查这里。IMR的0x01是接收中断使能位RCR的0x01是接收控制寄存器的使能位两个都要置起才能收包。发包不需要单独使能但TCR里的发送状态位在每次发送后要检查如果一直不置起说明发送 DMA 或总线时序有问题。3.4 用 ping 验证链路是否真正通了驱动加载后先ifconfig看网口是否出现再ping网关。如果ping不通按这个顺序排查先看ifconfig的RX packets和TX packets有没有增长如果 TX 有增长但 RX 为零说明发包出去了但收包路径有问题重点查中断和接收缓冲区如果 TX 也不增长说明发送路径没通查TCR和总线写时序。另一个常见现象是ping能通但丢包严重这通常是接收缓冲区溢出或中断处理太慢可以尝试增大接收缓冲区或降低中断延迟。4. 避坑与排查DM9000 驱动移植中最容易翻车的五个点4.1 读到的 ID 是 0xFFFF 或 0x0000现象探测时读VID和PID结果全是0xFFFF或0x0000。原因片选没有真正选中芯片或者总线宽度配置错误导致读写时序不满足。解决先用示波器确认片选信号在读写期间有拉低再检查NCR寄存器的总线宽度位是否和硬件一致。如果是 8 位硬件按 16 位访问读出来的数据会高低字节错位看起来就像全1或全0。4.2 中断注册成功但收不到包现象request_irq返回成功ifconfig显示网口正常但RX packets始终为零。原因中断触发方式配错或者IMR寄存器没有使能接收中断位。解决先确认中断触发方式DM9000 的中断引脚通常是低电平有效或下降沿触发配成高电平会一直不触发。然后读ISR寄存器如果ISR有值但中断没进说明中断控制器侧没配好如果ISR一直是零说明IMR没使能。4.3 发包正常但收包丢包严重现象ping能通但丢包率很高或者大包传输时直接卡死。原因接收缓冲区太小或者中断处理里读包速度跟不上。解决检查接收缓冲区的分配大小DM9000 的接收 SRAM 有限驱动侧要尽快把数据读走。可以在中断处理里用循环把ISR的PRS位清干净之前把所有包都读出来而不是一次中断只读一个包。4.4 PHY 自协商失败导致链路不通现象驱动加载后网口灯不亮或者灯亮但ping不通。原因PHY 自协商没有完成或者EPAR的 PHY 地址扫描失败。解决读BMSR寄存器确认链路状态位如果自协商一直不完成可以尝试强制设置BMCR为 100M 全双工模式绕过自协商。另外检查EPAR的自动扫描位是否置起有些板子的 PHY 地址不是默认的需要手动指定。4.5 复位后寄存器读写不稳定现象复位后第一次读写正常连续读写几次后数据出错。原因复位等待时间不够或者总线时序参数没有配置。解决复位后至少等待 20 微秒再访问寄存器NCR里的WCOL位可以观察总线冲突状态。如果总线频率较高可能需要在读写之间插入等待周期具体通过TCR或NCR的时序控制位调整。5. 进阶技巧用寄存器级调试把 DM9000 的收发路径看透当你已经能让 DM9000 跑起来之后下一步值得做的是把它的内部状态机摸清楚。我习惯在驱动里加一个 debugfs 或 proc 节点把ISR、NSR、RCR、TCR这几个关键寄存器的值实时打印出来。这样在遇到偶发丢包或链路抖动时不用猜直接看寄存器状态。比如NSR寄存器的LNKST位能告诉你链路是否建立ISR的PRS和PTS位能区分收包和发包事件。下面是一个简单的寄存器 dump 函数可以挂在定时器里周期性输出。static void dm9000_dump_regs(void) { /* 只打印关键寄存器避免刷屏 */ printk(ISR%04x NSR%04x RCR%04x TCR%04x\n, dm9000_read_reg(0xFE), /* ISR */ dm9000_read_reg(0x01), /* NSR */ dm9000_read_reg(0x05), /* RCR */ dm9000_read_reg(0x02)); /* TCR */ }参数说明ISR的 bit0 是PRS收包事件bit1 是PTS发包事件bit2 是ROS接收溢出。如果ROS频繁置起说明接收缓冲区溢出需要加快中断处理或增大缓冲区。NSR的 bit2 是LNKST链路建立后应该为 1如果一直是 0说明 PHY 侧有问题。RCR的 bit0 是接收使能TCR的 bit0 是发送使能这两个位在驱动运行期间应该始终为 1。另一个实用技巧是抓取 DM9000 的接收包头。每次收包时驱动先读到 4 字节包头里面包含包长度和状态位。如果状态位显示FF或00说明读到的不是有效包头通常是接收 SRAM 的读指针没有正确同步。这时候可以尝试在收包前先读一次MRCMDX丢弃一个无效包头再读MRCMD取真实数据。这个操作在官方驱动里也有体现但很多人移植时会忽略。最后说一个我自己的习惯每次改完 DM9000 的初始化序列不要只测ping还要用iperf跑一遍吞吐再用大包和小包混合测试。因为ping只验证了基本收发吞吐测试才能暴露缓冲区管理和中断处理的瓶颈。我见过太多板子ping通但一传文件就断问题都出在接收缓冲区溢出或中断里读包太慢。把iperf跑稳了这个驱动才算真正能用。希望帮到你。本文还有配套的精品资源点击获取