ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

DM9000网卡驱动深度解析:从硬件原理到Linux驱动移植实战

DM9000网卡驱动深度解析:从硬件原理到Linux驱动移植实战 搞嵌入式的人十有八九都跟DM9000打过交道。这颗芯片虽然老但在工业控制板、路由器、开发板上依然随处可见尤其是新塘、三星S3C、各种ARM9/Cortex-A系列平台上它几乎成了“标配网卡”。我早几年做一款基于ARM平台的工控主板时正好被安排去移植和调试DM9000的驱动踩过不少坑也把整条驱动链路从头到尾捋了个遍。这篇就把我对DM9000有线网卡驱动的理解以及实际编写和调试的经验原原本本分享出来。这篇文章适合正准备在Linux下移植DM9000驱动的嵌入式工程师也适合那些对网络驱动整体框架感兴趣、觉得“网卡驱动到底是怎么跑起来”很神秘的同学。我会从硬件原理讲到驱动代码结构再讲到收发路径和实际排障过程尽量做到看完了能直接上手。1. 先把芯片吃透DM9000为什么这么设计写驱动之前得先搞明白DM9000这颗芯片的工作方式。它是一颗集成了MAC和PHY的单芯片以太网控制器支持10Mbps和100Mbps自适应接口上既可以接MII也可以直接通过内部PHY连网口变压器外部总线是ISA-like的并行接口支持8位和16位模式。在很多嵌入式平台上它就挂在CPU的片选总线上像访问普通SRAM一样去读写。1.1 地址线与数据线的访问机制DM9000有一个非常典型的访问方式它把内部寄存器分成两类——索引寄存器和数据寄存器。芯片的CMD引脚通常是地址线比如A2决定当前访问的是索引端口还是数据端口。当CMD引脚为低电平时读写的地址是索引寄存器INDEX用来指定接下来要访问哪个内部寄存器当CMD引脚为高电平时访问的是数据寄存器DATA这时读出或写入的是刚才指定的那个寄存器的值。很多第一次写DM9000驱动的人会被这种“两次访问”搞迷糊。比如你想读寄存器0x01的PHY状态不是直接去读地址0x01而是往CMD0的地址写0x01表示“我要操作0x01寄存器”从CMD1的地址读一个字节取回来的才是0x01寄存器的值如果芯片工作在16位模式第二步读回来的是一个16位的数据寄存器值在低字节高字节通常无效。理解了这一点驱动里访问寄存器的函数就可以封装成非常简洁的read/write原语。以16位模式为例static u8 dm9000_read_reg(struct dm9000_priv *priv, int reg) { writeb(reg, priv-io_addr); // 写索引寄存器 return readb(priv-io_data); // 读数据寄存器 } static void dm9000_write_reg(struct dm9000_priv *priv, int reg, u8 value) { writeb(reg, priv-io_addr); writeb(value, priv-io_data); }看起来简单但这个封装是整个驱动的地基。后面所有寄存器操作、PHY操作、收发数据操作都建立在这两个函数之上。1.2 8位模式与16位模式到底怎么选DM9000支持8位和16位两种总线模式由硬件引脚设定驱动在probe时读取哪个引脚或者通过读取寄存器的方式来确定。8位模式下每次总线访问只能读写一个字节16位模式下数据总线的低16位有效一次可以读写两个字节。我的建议是只要CPU外部总线支持优先选16位模式。原因很直接以太网帧最少也有60字节如果走8位模式每次收发包都要多一倍的IO访问次数CPU开销明显增加在高吞吐场景下差距更明显。当然8位模式也有它的价值比如GPIO模拟总线的场景引脚不够时只能委屈一下。驱动里怎么判断模式DM9000有个寄存器可以读出芯片ID和版本信息比如0x29、0x28这两个寄存器能读出芯片型号DM9000A是0x90000A但不直接体现总线宽度。实际工程中最可靠的方法还是看硬件原理图和板级配置。比如在设备树里就通过reg属性中的地址偏移和数据偏移来描述当前板子的连接方式。2. 驱动整体骨架从设备树到net_device的注册链路现在Linux内核里写驱动已经很少直接去init一个平台驱动了。DM9000的驱动基本上都是platform device platform driver的模型设备信息由设备树或板级文件描述驱动负责在probe阶段做初始化。2.1 设备树节点怎么写如果你用的是设备树DM9000的节点一般长这样ethernet18000000 { compatible davicom,dm9000; reg 0x18000000 0x2 0x18000004 0x2; interrupt-parent gpx0; interrupts 6 IRQ_TYPE_LEVEL_HIGH; davicom,no-eeprom; mac-address [00 11 22 33 44 55]; local-mac-address [00 11 22 33 44 55]; };这里有个关键点reg字段通常给两段地址。第一段是索引端口地址第二段是数据端口地址。上面这个例子中0x18000000是索引地址0x18000004是数据地址两段长度都是2字节正好对应16位模式下的最小访问粒度。为什么要两个地址因为DM9000的CMD引脚就接到了CPU地址总线上的某一根线上。当CPU访问0x18000000这个地址时地址线对应位是0CMD为低访问的是索引端口当CPU访问0x18000004时地址线对应位是1CMD为高访问的是数据端口。这个设计非常精巧驱动不需要额外操作GPIO去切换CMD状态直接ioremap两段地址就行。2.2 probe函数里要做的事probe是整个驱动的“上电开机”流程职责很明确获取资源、初始化硬件、分配net_device结构、注册中断、最后把网卡挂到内核网络子系统上。我习惯把probe流程拆成下面几步解析设备树资源拿到IO地址、中断号等ioremap索引地址和数据地址并把它们保存到私有结构体读取芯片ID确认芯片存在且型号正确复位芯片执行寄存器初始化序列读取或分配MAC地址初始化net_device结构设置netdev_ops和ethtool_opsrequest_irq注册中断中断标志用IRQF_TRIGGER_LOW或实际电平触发方式注册mii_bus或phy驱动完成PHY配置register_netdev将设备注册到内核一个容易被忽略的细节是MAC地址的处理。DM9000内部有一小块EEPROM接口板子上如果焊了EEPROM芯片上电后会自动把MAC地址读到内部寄存器。驱动在probe时可以先读这几个寄存器如果读出来的MAC是全0xFF或者全0x00说明EEPROM没写或者没有需要从设备树里的local-mac-address属性去取或者用随机MAC地址兜底。我在实际项目中就遇到过板子设计时没焊EEPROM设备树里也没写MAC地址结果每块板子的MAC地址都一样导致局域网内多台设备同时在线时地址冲突。后来在设备树里给每块板子烧录了单独的MAC地址才彻底解决。2.3 net_device初始化时要注意的选项net_device的初始化说多不多说少不少。对DM9000来说有几个点要特别留意ndev-netdev_ops dm9000_netdev_ops; ndev-ethtool_ops dm9000_ethtool_ops; ndev-flags | IFF_MULTICAST; ndev-mtu 1500;先看netdev_ops里面包含open、stop、start_xmit、set_rx_mode、ioctl这些方法。DM9000的驱动里open函数负责申请RX/TX描述符和缓冲区、开启NAPI、使能中断stop函数做相反的操作。再看接口标志IFF_MULTICAST这个一定要设。DM9000硬件支持多播过滤内核里如果跑IGMP、组播路由之类的协议网卡不支持多播会出很多奇怪的问题。还有一点DM9000的驱动一般都会用NAPI机制。原因很简单在高中断频率下如果是传统的中断收包方式每个包进一次中断CPU频繁被打断吞吐量上不去。NAPI的核心思路是收包中断来了之后如果包量很大就关掉接收中断转成轮询收包把一批包都收完了再重新开启中断。对DM9000这种百兆网卡来说NAPI能显著降低CPU占用率。3. 收发路径的代码级实现收发路径是网卡驱动的核心地带也是排查问题时的重点区域。DM9000的收发路径相比PCIe网卡要简单很多但简单不等于可以随便写尤其收发缓冲区的管理和中断标志位的处理稍有不慎就会导致数据错乱。3.1 发送路径从协议栈到FIFO发送的入口是netdev_ops里的ndo_start_xmit函数内核协议栈组好一个skb后会调用这个函数把数据交给驱动。DM9000的发送流程是这样的把skb里的数据拷贝到发送缓冲区写发送控制寄存器TX_CTRL触发芯片发送等待发送完成中断或轮询发送完成标志释放skb更新统计信息实际代码里通常会在probe时分配一块发送缓冲区用完复用。为什么不像某些驱动那样直接让芯片从skb内存DMA读取因为DM9000的数据总线是并行总线没有DMA控制器芯片自己不能主动去内存搬运数据必须CPU把数据写到芯片的FIFO里。所以用一块固定的发送缓冲区比每次动态分配再拷贝要可靠得多。举一段简化版的发送代码static netdev_tx_t dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct dm9000_priv *priv netdev_priv(dev); unsigned long flags; spin_lock_irqsave(priv-lock, flags); /* 将数据写入芯片发送缓冲区 */ dm9000_write_reg(priv, DM9000_TXPLL, 0); dm9000_write_reg(priv, DM9000_TXPLH, 0); /* 计算发送长度写入长度寄存器 */ dm9000_write_reg(priv, DM9000_TXPLL, skb-len 0xff); dm9000_write_reg(priv, DM9000_TXPLH, (skb-len 8) 0xff); /* 把数据通过数据端口写入芯片 */ writesw(priv-io_data, skb-data, (skb-len 1) 1); /* 启动发送 */ dm9000_write_reg(priv, DM9000_TXCR, TXCR_TXREQ); dev-stats.tx_bytes skb-len; dev-stats.tx_packets; dev_kfree_skb(skb); spin_unlock_irqrestore(priv-lock, flags); return NETDEV_TX_OK; }这里有个细节writesw是按16位去写的所以长度要按(skb-len 1) 1计算保证偶数长度的对齐。如果数据长度是奇数最后一个字节会自动补零不影响以太网帧的有效载荷。还有一个东西叫TXREQ位写这个位表示“我开始发送了”芯片收到后会把FIFO里的数据发出去。但要注意如果上一次发送还没完成你就又写TXREQ芯片可能会拒绝或者产生错误。所以生产级驱动里一般会用一个变量记录当前是否有包在发送中如果有就先把skb缓存起来等发送完成中断后再补发。有些实现简单粗暴直接把新包丢掉但这在网络拥堵时会严重影响TCP吞吐量。3.2 接收路径中断唤醒和NAPI轮询接收方向DM9000的工作方式是芯片收到一个完整的以太网帧后把数据写到芯片的接收SRAM里然后拉起中断。驱动在中断里读中断状态寄存器发现接收中断置位后进入接收流程。接收流程的核心是从芯片接收FIFO里读出数据。DM9000的接收FIFO是按队列组织的读数据之前必须先读两个字节一个是指示帧头一个是指示帧状态。帧头是01表示正常帧状态字节的最低位置1表示帧有效再往后读两个字节是帧长度。读完后把整个帧数据搬出来然后跳过4字节的CRC校验尾。我在实际写代码时把接收部分拆成下面三步第一步读取接收FIFO的帧头u8 rx_byte dm9000_read_reg(priv, DM9000_MRCMDX); if (rx_byte ! 0x01) { /* 异常情况回滚读取指针 */ dm9000_write_reg(priv, DM9000_RCR, 0x00); dm9000_write_reg(priv, DM9000_RCR, 0x01); return; }第二步读取帧状态和长度u8 status dm9000_read_reg(priv, DM9000_MRCMD); u8 len_l dm9000_read_reg(priv, DM9000_MRCMD); u8 len_h dm9000_read_reg(priv, DM9000_MRCMD); int rx_len (len_h 8) | len_l;第三步把数据读出来构造skb交给上层struct sk_buff *skb netdev_alloc_skb(dev, rx_len 2); if (skb) { skb_reserve(skb, 2); if (rx_len 0) { /* 由于数据端口是16位访问需要按字读取 */ unsigned char *rdptr skb_put(skb, rx_len); if (rx_len 0x01) insw(priv-io_data, rdptr, (rx_len 1) 1); else insw(priv-io_data, rdptr, rx_len 1); } skb-protocol eth_type_trans(skb, dev); netif_receive_skb(skb); dev-stats.rx_packets; dev-stats.rx_bytes rx_len; }这里最容易犯错的地方是数据长度的对齐。DM9000接收到的帧长度是字节数但数据端口在16位模式下一次读两个字节如果帧长度是奇数最后会多读一个字节到接收缓冲区里。我在第一次写这个部分时就没处理奇数长度导致小包有时尾部多出垃圾字节上层协议栈解析失败ping小包都通不了。正确的做法是按上面代码那样先判断rx_len是否为奇数然后用不同参数调用insw。insw每次读16位奇数长度时多读一个16位但只把前rx_len个字节作为有效数据。3.3 中断处理与NAPI的配合DM9000的中断处理可以做得非常简单也可以做成标准NAPI模型。我建议直接上NAPI别图省事。为什么你可以想象一下百兆网卡满载时每秒要收一万多个包每个包都产生一次中断CPU每秒钟要被打断上万次每次打断都要保存现场、执行中断处理、恢复现场大量CPU时间都耗在这个上面。NAPI的设计思路是中断来了以后如果发现数据量很大就先关掉接收中断把中断处理函数里的事情转成软中断轮询把一批包全部收完再重新开中断。这样能大幅减少中断次数。实现NAPI需要几步在probe里初始化napi结构netif_napi_add(dev, priv-napi, dm9000_poll, 64);在open里调用napi_enable在贴片中断处理函数里读中断状态寄存器如果接收中断置位就返回IRQ_HANDLED并向NAPI调度__napi_schedule(priv-napi);在poll函数里把FIFO里的包全部收完然后判断是否收完static int dm9000_poll(struct napi_struct *napi, int budget) { struct dm9000_priv *priv container_of(napi, struct dm9000_priv, napi); int rx_count 0; while (rx_count budget) { /* 检查FIFO是否有更多包 */ if (!dm9000_rx_packet(priv)) break; rx_count; } if (rx_count budget) { napi_complete_done(napi, rx_count); /* 重新使能接收中断 */ dm9000_enable_rx_interrupt(priv); } return rx_count; }poll函数的返回值是实际处理了多少个包这个值不能超过budget。如果返回budget值内核认为还有更多包会继续轮询如果返回小于budget的值说明收完了那就结束本轮轮询并重新打开中断。这里有个细节值得注意napi_complete_done调用后一定记得把接收中断重新使能。不然下一次有包进来时中断被关着NAPI不会再被调度网卡就彻底“死”了。这个bug非常隐蔽我调试时花了整整一个下午才发现是漏了这个使能。4. 初始化序列和PHY配置里那些坑DM9000的上电初始化序列看起来也就是十几个寄存器写操作但如果照抄参考手册而不理解每个寄存器的含义遇到问题时会完全没有头绪。我在项目里把初始化序列反复对过几遍整理了几个最容易踩坑的地方。4.1 复位时序与寄存器默认值芯片上电后第一个动作是复位。DM9000的复位有两种方式硬件复位引脚拉低或者软件复位即写寄存器RCR并设置RST位。软件复位之后芯片并非立即就绪需要等待一段时间让内部PHY完成复位和自检。通常驱动里会延时几百毫秒如果用GPIO硬件复位至少也要等100毫秒以上。复位后的寄存器默认值也是写代码时必须清楚的。比如网络控制寄存器NCR默认值是0x00表示内部PHY还没被使能需要向NCR写入0x03其中bit0用来使能内部PHYbit1用于设置全双工自动协商模式。如果漏了这一步后面读PHY状态和收发数据都会不正常。初始化序列里我一般按这样的顺序来软件复位并延时配置网络控制寄存器NCR使能内部PHY配置发送控制寄存器TXCR和接收控制寄存器RCR清除中断状态寄存器ISR把遗留的中断标志清掉设置流控相关寄存器开启背压和暂停帧支持配置PHY寄存器启动自动协商第4步经常被忽略。芯片上电过程中可能因为电平抖动产生虚假中断标志。如果驱动注册中断前不把ISR清干净注册中断后第一个中断错误处理会把状态搞乱严重时直接进错误分支把网卡状态弄挂。4.2 PHY自动协商与双工模式DM9000内置了PHY所以驱动里还要负责PHY的初始化。PHY的寄存器通过MDIO接口访问DM9000把PHY寄存器映射到了芯片自己的寄存器空间里访问方式是通过索引寄存器0x0C写入PHY寄存器地址再从0x0D读取数据或者通过0x0E写数据。下面是典型的PHY寄存器读取函数static u16 dm9000_phy_read(struct dm9000_priv *priv, u8 reg) { u16 val; dm9000_write_reg(priv, DM9000_EPCR, 0x00); /* 关闭EEPROM访问 */ dm9000_write_reg(priv, DM9000_EPAR, reg); /* 选择PHY寄存器 */ dm9000_write_reg(priv, DM9000_EPCR, 0x0c); /* 开启PHY读操作 */ udelay(500); dm9000_write_reg(priv, DM9000_EPCR, 0x00); /* 关闭PHY访问 */ val (dm9000_read_reg(priv, DM9000_EPDRL) | (dm9000_read_reg(priv, DM9000_EPDRH) 8)); return val; }PHY自动协商完成后可以通过读取PHY的基本状态寄存器地址0x01和基本模式状态寄存器地址0x05来判断链路是否建立以及协商出来的速率和双工模式。这部分信息会用于配置DM9000自身的MAC工作模式比如设置全双工时让MAC也进入全双工模式并开启流控功能。一个很实际的经验有些廉价交换机或者老设备自动协商可能不太标准导致DM9000与对端协商出半双工模式。半双工模式下如果MAC没有正确开启碰撞重传处理网络延迟和丢包率都会明显上升。所以产品化的设备建议在驱动里把自动协商的结果打印出来方便在产线上验证网络质量。u16 reg5 dm9000_phy_read(priv, 0x05); if (reg5 0x0004) /* 协商成功 */ netdev_info(ndev, Link is up at %dMbps, %s duplex\n, (reg5 0x2000) ? 100 : 10, (reg5 0x0040) ? full : half);4.3 接收控制寄存器的过滤位DM9000的接收控制寄存器RCR支持多种过滤模式比如只接收发给本机的包、接收所有包混杂模式、接收多播包、接收广播包等。驱动在ndo_set_rx_mode回调里要根据内核的要求切换这些位。这个回调容易被忽略但如果你要做应用比如跑tcpdump抓包、做桥接就必须支持混杂模式。如果驱动不实现set_rx_mode网卡永远只收发给自己的单播包和广播包其他包全被硬件过滤掉tcpdump就什么都抓不到。static void dm9000_set_rx_mode(struct net_device *dev) { struct dm9000_priv *priv netdev_priv(dev); u8 rcr RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN; if (dev-flags IFF_PROMISC) { rcr | RCR_PRMSC; } else if (!netdev_mc_empty(dev)) { rcr | RCR_MULTICAST; /* 根据多播地址表设置hash表 */ } dm9000_write_reg(priv, DM9000_RCR, rcr); }5. 调试手段与常见问题排查写驱动最难的不是把代码敲完而是出了问题后怎么定位。DM9000这种老芯片网上资料多但很杂大部分问题的答案都在实验里。我把自己踩过的坑和排查思路整理一下希望对你有用。5.1 收包错误CRC错误和FIFO溢出如果网卡能link up但ping不通或者大包不通、小包通优先怀疑接收路径。用ethtool统计一下ethtool -S eth0或者直接看ifconfig的RX errors、RX overruns、CRC errors。收到很多CRC错误一是线路质量问题换根网线试试二是PHY配置错误比如协商出来的双工模式不匹配对端是全双工本机却是半双工冲突会导致大量CRC错误。此时可以先手动固定成100M全双工来排除问题ethtool -s eth0 speed 100 duplex full如果错误消失了基本可以确定是自动协商的问题去检查PHY的协商寄存器配置吧。FIFO溢出的典型现象是ifconfig里RX overruns持续增加。DM9000的接收FIFO设计是环形队列芯片收到包后写入SRAM驱动再搬出来。如果驱动处理太慢新的包会把旧的包覆盖掉。解决方向有两个一是检查中断是否被频繁关闭二是确认NAPI的budget是否太小导致一次轮询收不完所有包。还有一个原因和中断触发方式有关之前说过DM9000需要电平触发如果用了边沿触发漏中断的概率很高包就收不完。5.2 发送超时和TX watchdog内核网络子系统有个看门狗机制如果网卡长时间没有完成发送netdev_watchdog会触发并调用ndo_tx_timeout回调。出现这个问题的原因通常有两个第一发送中断丢失。DM9000的发送完成标志在中断状态寄存器里如果驱动在处理接收中断时把发送中断标志也顺带清掉了发送完成事件就没被正确感知后续发包时芯片还在忙发送队列就卡住了。第二TXREQ写得太早芯片还没准备好。我调试时遇到过一种情况高负载下上一个图还没发完下一个图已经过来了此时不断写TXREQ芯片内部状态机可能紊乱表现为发送寄存器写不进去。解决方法是加一个发送忙碌标志如果上次发送没完成就把skb缓存到pending队列等发送完成中断时再补发。先查一下cat /proc/interrupts | grep eth0看看中断次数是否持续增长。如果中断次数不涨说明中断丢失或者中断没使能。然后在of_probe函数里检查request_irq时的触发标志电平触发写成边沿触发的排查方法上面说过了。5.3 吞吐量只有几Mbps怎么办百兆网卡理论上能跑满接近100Mbps如果实际测下来只有几Mbps通常不是芯片的问题而是驱动路径上的拷贝和中断开销太大。排查步骤我建议这样先确认对端和网线都正常用iperf3测吞吐检查网卡是否跑在100M全双工如果是10M或者半双工吞吐自然上不去确认NAPI已经生效而不是每次收包都走中断检查是否每个包都在驱动里做了一次不必要的memcpy关于第4点DM9000没有DMA数据从芯片FIFO搬到skb是必须的拷贝这个没法省。但要注意发送路径如果每发一个包都重新分配发送缓冲区并拷贝性能会很难看。我习惯用固定的发送缓冲区把skb内容拷过来后尽快释放skb这样CPU开销小很多。还有一个很大但常被忽视的瓶颈是总线宽度和对齐。前面说的insw和writesw如果访问不对齐磁盘性能会急剧下降所以数据端口ioremap时的地址对齐也要留意。5.4 常见问题速查表现象可能原因排查方法网卡link up但ping不通PHY协商模式不匹配固定100M全双工验证小包通大包不通发送或接收缓冲区长度处理错误对比长度计算代码RX overruns增长中断丢失或处理慢检查触发方式、NAPI配置一段时间后网卡“死掉”接收中断被关闭后未重新使能检查NAPI的poll流程吞吐量极低半双工或拷贝过多ethtool查速度、检查发送路径5.5 抓包怎么看DM9000收到的数据对不对调试网卡驱动抓包工具是必不可少的。在驱动已经注册到内核的前提下用tcpdump能确认协议栈收到的包是否正常。如果tcpdump能看到包但应用层ping不通大概率是协议栈或上层问题如果tcpdump什么都看不到驱动收到包根本没往上层送去检查skb构造部分。还有一种更底层的调试方式直接在驱动里临时加打印把每个收到的帧头几个字节打印出来。以太网帧目的MAC地址是6个字节如果收错字节顺序就会对错线序报文根本不会被识别。我记得有一次就是因为io访问的字节序错乱ping包在对面收到的是乱序广播报文打印帧头后一眼就看出来了。DM9000 RX: 00 11 22 33 44 55 08 00 ...如果看到目的MAC和自己网卡不同而且不是广播包说明数据读取错位了多半是MRCMD的读取时序和长度判断出了问题。6. 我能分享的几个工程经验DM9000驱动看起来不复杂但真正要做到稳定可靠还是有不少讲究的。最后说几个我个人在项目里消化过的经验。第一一定要把寄存器操作封装好。DM9000的寄存器访问方式在所有地方都是一样的把它封装成read_reg/write_reg后驱动其他部分读起来会非常清爽排查问题时也能一眼看出哪个环节访问错了。第二调试时不要直接用ifconfig eth0 up建议先手动设置一个固定的IP或者用dhclient再配合ethtool查看链路状态。这样能快速区分是PHY没协商成功还是驱动根本没把net_device注册好。第三中断引脚一定要加pull-up或按硬件手册要求处理。我曾经在一块板子上遇到网卡时好时坏的问题后来查出来是中断引脚悬空电平不稳定导致误触发中断。这种硬件问题在驱动层面很难抓只能靠示波器去查。第四如果板子量产建议在驱动里加入MAC地址校验和备份逻辑。DM9000的EEPROM是可选器件很多低成本板子没焊这时候从设备树读取MAC地址或者在内核cmdline指定都比用随机MAC稳定得多。随机MAC会导致重启后IP地址变化对需要固定IP的场景简直是灾难。第五多做稳定性测试。驱动写完后别只ping几个包就算通过。我一般会跑长时间双向打流至少12小时观察有没有丢包、吞吐有没有掉、CPU占用率有没有异常。有些问题只在长时间高负载下才会暴露比如内存泄漏或者中断状态机紊乱。对我个人来说DM9000是学习嵌入式网络驱动的一个非常好的样本。它不像PCIe网卡那样有复杂的DMA描述符和MSI中断机制核心逻辑就是“中断 手动搬数据”把驱动最本质的东西讲得明明白白。如果你能把DM9000的驱动写透再去看那些更复杂的网卡驱动思路会顺畅很多。希望这篇东西能帮你少走点弯路。
返回列表