☰
VxWorks下DM9000A网卡驱动移植实战:END框架与避坑指南
2026/10/31 5:32:05 网站建设 项目流程

简介:这份资源面向嵌入式网络驱动开发初学者与进阶工程师,聚焦DM9000A以太网控制器在VxWorks实时操作系统下的驱动实现,帮助读者理解硬件寄存器操作、内核驱动框架与中断处理机制。压缩包内共2个文件,包含1个c源码文件与1个txt说明文档,整体约18KB,源码对应驱动核心逻辑,说明文档则提供必要的背景与使用提示。目前已有140人学习下载,适合作为驱动移植与调试的参考素材。通过阅读源码,读者可掌握DM9000A初始化流程、数据收发路径、中断服务例程设计以及内核态与用户态数据交互方式,并借鉴其寄存器配置与数据结构定义,快速搭建可运行的网络通信驱动,为嵌入式项目开发与技能提升提供实用支撑。

1. DM9000A 网卡在 VxWorks 下的驱动移植:从标题拆解到最小可跑通路径

手上有一块带 DM9000A 网卡芯片的工控板,BSP 里网络死活起不来,ping 不通、ifconfig 看不到设备,这是很多做 VxWorks 底层移植的人都会撞上的场景。DM9000A 是一颗 10/100M 自适应以太网控制器,接口简单、寄存器少,在嵌入式领域用量很大;VxWorks 则是硬实时操作系统,网络栈走的是 END(Enhanced Network Driver)框架。把这两者接起来,就是标题里说的 dm9000 vxworks driver 这件事。它解决的是「板子有网口但系统认不出、收发包异常」的问题,适合正在做 BSP 移植、网络驱动调试的嵌入式工程师。下面按「先搞清框架、再动手接寄存器、最后排坑」的顺序讲透。

2. VxWorks END 驱动框架与 DM9000A 的对接点

2.1 为什么必须走 END 而不是自己写收发

VxWorks 的网络协议栈(TCP/IP)不直接调用网卡寄存器,中间隔着一层 END 驱动接口。协议栈只认END_OBJ这个结构体,里面挂着一组函数指针:start、stop、send、ioctl、mCastAddrAdd等。你写的 DM9000A 驱动,本质就是把这组函数指针填好,再通过muxDevLoad/muxDevStart注册进 MUX 层。常见做法是拿 VxWorks 自带的templateEnd.c当骨架,把里面空实现换成 DM9000A 的寄存器操作。这样做的价值在于:协议栈、缓冲管理、中断收包都不用你重造,你只关心「怎么把数据塞进芯片 FIFO、怎么从 FIFO 取出来」。

DM9000A 的寄存器分两类:一类是 NCR、NSR、TCR、RCR 这种控制寄存器,一类是 FCR、MWCMD、MRCMD 这种 FIFO 和内存访问寄存器。地址线接法决定了访问方式——常见做法是命令线接一根地址线(比如 A2),数据线接 16 位总线,通过CMD引脚区分「写索引」还是「读写数据」。这个接法必须在 BSP 的sysLib.c或驱动头文件里用宏定义清楚,否则后面所有寄存器读写全是错的。

2.2 寄存器基址与访问宏的落地写法

先把访问层写对,这是后面一切的前提。下面这段是典型的寄存器读写宏,基址和命令线偏移按你的实际硬件改:

/* dm9000a.h - 寄存器访问层,基址按实际 BSP 修改 */ #define DM9000A_BASE_ADDR 0x80000000 /* 片选基址,按硬件改 */ #define DM9000A_CMD_OFFSET 0x04 /* 命令线接 A2 时偏移为 4 */ /* 写索引寄存器:先写 INDEX,再读写 DATA */ #define DM9000A_INDEX(cmd) (*(volatile UINT16 *)(DM9000A_BASE_ADDR + DM9000A_CMD_OFFSET)) #define DM9000A_DATA(cmd) (*(volatile UINT16 *)(DM9000A_BASE_ADDR)) /* 读寄存器:先写索引,再读数据 */ LOCAL UINT8 dm9000a_reg_read(UINT8 reg) { DM9000A_INDEX(0) = reg; /* 把寄存器号写进 INDEX */ return (UINT8)(DM9000A_DATA(0) & 0xFF); } /* 写寄存器:先写索引,再写数据 */ LOCAL void dm9000a_reg_write(UINT8 reg, UINT8 val) { DM9000A_INDEX(0) = reg; DM9000A_DATA(0) = val; }

逻辑说明:DM9000A 的寄存器访问是「索引 + 数据」两拍,INDEX写寄存器编号,DATA读写内容。参数上,DM9000A_CMD_OFFSET取决于命令线接哪根地址线,接 A2 就是 4,接 A1 就是 2,这个值错了会出现「读出来全是 0xFF 或 0x00」的典型翻车现象。volatile不能省,否则编译器优化会把连续寄存器访问合并掉。写完后建议先读一次VID(厂商 ID,应为 0x9000)和PID(产品 ID,应为 0x9000),能读到正确值才说明访问层通了。

2.3 初始化序列与收发描述符的建立

访问层通了之后,按 DM9000A 手册的初始化顺序走:软复位、清中断、设 MAC 地址、设接收控制寄存器 RCR、设发送控制寄存器 TCR、使能接收。下面是最小初始化骨架:

LOCAL STATUS dm9000a_init(DM9000A_DEV *pDev) { UINT8 i; /* 1. 软复位,等复位完成 */ dm9000a_reg_write(DM9000A_NCR, NCR_RST); for (i = 0; i < 100; i++) { if (!(dm9000a_reg_read(DM9000A_NCR) & NCR_RST)) break; taskDelay(1); } /* 2. 清中断状态,关中断 */ dm9000a_reg_write(DM9000A_ISR, 0xFF); dm9000a_reg_write(DM9000A_IMR, 0x00); /* 3. 写 MAC 地址,PAR 从 0x10 开始共 6 字节 */ for (i = 0; i < 6; i++) { dm9000a_reg_write(DM9000A_PAR + i, pDev->macAddr[i]); } /* 4. 接收控制:使能接收,丢弃 CRC 错、长度错 */ dm9000a_reg_write(DM9000A_RCR, RCR_RXEN | RCR_DIS_CRC | RCR_DIS_LONG); /* 5. 发送控制:使能发送 */ dm9000a_reg_write(DM9000A_TCR, TCR_TXREQ); /* 6. 使能接收中断 */ dm9000a_reg_write(DM9000A_IMR, IMR_PRM | IMR_PTM | IMR_RXOV); return OK; }

参数说明:NCR_RST是复位位,写完要轮询等它自动清零,等不到说明硬件没响应。RCR里RCR_RXEN必须置位,否则永远收不到包;RCR_DIS_CRC和RCR_DIS_LONG按需,调试阶段建议先关掉过滤,能收到广播包再说。IMR是中断屏蔽寄存器,IMR_PRM是收包中断,IMR_PTM是发包完成中断,IMR_RXOV是接收溢出。中断号要在 BSP 的sysIntrConnect里挂上,ISR 里读ISR寄存器判断中断源并清标志,这是后面避坑章要重点说的点。

3. 把驱动挂进 VxWorks:END 函数指针与 MUX 注册

3.1 END_OBJ 里必须填对的几个函数

END 框架要求你提供一个END_OBJ,核心是net_funcs结构体。最少要实现这几个:dm9000a_start(启动设备,开中断)、dm9000a_stop(停设备)、dm9000a_send(协议栈调用来发包)、dm9000a_ioctl(处理EIOCGADDR、EIOCSFLAGS等)、dm9000a_mCastAddrAdd(组播,调试阶段可先返回 OK)。dm9000a_send是最容易写错的地方:协议栈传进来的是一个M_BLK链,你要把它拷成连续内存再写进芯片 TX FIFO,不能直接拿mblk指针往寄存器里怼。

LOCAL int dm9000a_send(END_OBJ *pEnd, M_BLK_ID pMblk) { DM9000A_DEV *pDev = (DM9000A_DEV *)pEnd; UINT8 *pBuf; int len = 0; /* 把 mblk 链拷成连续缓冲,len 为总长度 */ pBuf = dm9000a_mblk_to_buf(pMblk, &len); if (pBuf == NULL || len == 0) return ERROR; /* 等 TX FIFO 就绪,写长度和长度高字节 */ dm9000a_reg_write(DM9000A_TXPLL, len & 0xFF); dm9000a_reg_write(DM9000A_TXPLH, (len >> 8) & 0xFF); /* 把数据写进 MWCMD,逐字节或逐字 */ dm9000a_fifo_write(pBuf, len); /* 触发发送 */ dm9000a_reg_write(DM9000A_TCR, TCR_TXREQ); return OK; }

逻辑说明:dm9000a_mblk_to_buf要自己实现,遍历M_BLK链把m_data拷到一块连续内存。参数上,TXPLL/TXPLH是发送长度低/高字节,必须先写长度再写数据,顺序反了芯片会按错误长度发。TCR_TXREQ置位触发发送,发完后芯片会清掉这位并置ISR的PTM位。调试时如果send返回 OK 但抓包看不到,先查TXPLL/TXPLH写的长度对不对,再查 FIFO 写时序。

3.2 muxDevLoad 与 muxDevStart 的调用时机

驱动编译进内核后,要在 BSP 的网络初始化里注册。典型调用是:

/* 在 usrNetInit 或 BSP 网络初始化函数里 */ int dm9000aLoad(void) { DM9000A_DEV *pDev; /* 分配设备结构,填 END_OBJ 和 net_funcs */ pDev = dm9000a_dev_create(); if (pDev == NULL) return ERROR; /* 注册到 MUX 层,返回 unit 号 */ pDev->endObj.unit = muxDevLoad(0, dm9000a_load_cb, NULL, TRUE, NULL); if (pDev->endObj.unit == END_OF_UNITS) return ERROR; /* 启动设备 */ muxDevStart(&pDev->endObj); return OK; }

参数说明:muxDevLoad第一个参数是 unit 号,从 0 开始;第二个是加载回调,里面做硬件探测和初始化;第四个TRUE表示立即启动。muxDevStart会调用你填的start函数,里面开中断、使能收发。常见做法是在usrConfig.c或 BSP 的sysNetInit里调dm9000aLoad,确保在协议栈初始化之后、应用起来之前执行。如果muxDevLoad返回END_OF_UNITS,说明 MUX 层没找到空闲 unit 或回调返回失败,先查回调里的硬件探测。

3.3 中断服务程序与收包路径

收包靠中断驱动。ISR 里读ISR寄存器,判断是PRM(收到包)还是PTM(发完包),清对应标志,然后调netJobAdd把实际收包工作丢到网络任务里做,ISR 本身要短。收包时先读MRCMDX拿状态字节,再读MRCMD拿长度和数据。

LOCAL void dm9000a_isr(DM9000A_DEV *pDev) { UINT8 isr = dm9000a_reg_read(DM9000A_ISR); /* 清中断标志,写 1 清除 */ dm9000a_reg_write(DM9000A_ISR, isr); if (isr & ISR_PRM) { /* 收到包,丢给网络任务处理,不在 ISR 里做拷贝 */ netJobAdd((FUNCPTR)dm9000a_rx_job, (int)pDev, 0, 0, 0, 0); } if (isr & ISR_PTM) { /* 发包完成,可唤醒等待发送的调用者 */ pDev->txBusy = FALSE; } }

逻辑说明:ISR寄存器写 1 清标志,写 0xFF 清全部。netJobAdd把收包任务挂到网络任务队列,避免在中断上下文做长拷贝。参数上,ISR_PRM是收包中断位,ISR_PTM是发包完成位。收包任务里要循环读,直到MRCMDX的状态字节表示没有更多包,否则会丢包。这里最容易翻车的是「ISR 里直接读 FIFO」,中断上下文里做大量 IO 会导致系统响应变差甚至丢中断。

4. DM9000A 驱动调试避坑:5 个血泪踩坑记录

4.1 读厂商 ID 全是 0xFF 或 0x00

现象:初始化第一步读VID,返回 0xFF 或 0x00,后面全乱。原因:寄存器访问层没通,通常是命令线偏移写错,或者片选基址不对,或者总线位宽配错(DM9000A 支持 8/16 位,BSP 里配成 8 位但硬件接 16 位)。解决:先用示波器或逻辑分析仪看命令线和数据线有没有波形;确认DM9000A_CMD_OFFSET和实际地址线一致;确认 BSP 里片选控制寄存器配的是 16 位总线。读VID应为 0x9000,读不到就别往下走。

4.2 能收广播包但 ping 不通单播

现象:抓包能看到广播包进来,但 ping 本机 IP 没反应。原因:MAC 地址没写对,或者RCR里过滤条件把单播过滤掉了。DM9000A 的PAR寄存器从 0x10 开始 6 字节,写反顺序或写错地址都会导致单播收不到。解决:确认PAR写入顺序和网卡实际 MAC 一致;调试阶段先把RCR的过滤位全关,只留RCR_RXEN,能收到所有包再逐步开过滤。

4.3 发包返回 OK 但对端收不到

现象:send函数返回成功,TCR_TXREQ也置位了,但对端抓不到包。原因:TXPLL/TXPLH长度写错,或者 FIFO 写时序不对,或者TCR里没使能发送。解决:先确认长度低字节先写、高字节后写;确认dm9000a_fifo_write里每次写MWCMD后指针推进正确;确认TCR的TCR_TXREQ确实置位且发完后被硬件清零。如果长度写大了,芯片会等更多数据,表现为「发了但没发出去」。

4.4 中断进不去或进去就死循环

现象:收包中断一直不触发,或者触发后 ISR 反复进。原因:中断号没在 BSP 里sysIntrConnect挂上,或者 ISR 里没清ISR寄存器导致中断一直挂起。解决:确认 BSP 中断控制器配置和硬件中断线一致;ISR 里第一件事就是读ISR并写回清标志;如果用了netJobAdd,确认网络任务已经启动。反复进 ISR 基本都是没清标志。

4.5 大包丢、小包正常

现象:ping 小包正常,传大文件或 ping -l 大包就丢。原因:接收缓冲不够,或者收包任务里没循环读到 FIFO 空,或者M_BLK簇大小配小了。解决:确认MRCMD读长度后分配的M_BLK够大(至少 1520 字节);收包任务里循环读MRCMDX直到状态字节为 0;检查 BSP 里NUM_MBLK和簇大小配置。大包丢多半是缓冲管理问题,不是芯片问题。

5. 进阶:用寄存器回读和抓包交叉验证驱动状态

驱动调通之后,别急着收工。我一般会留一个「寄存器快照」函数,在ioctl里加一个自定义命令,把NCR、NSR、ISR、RCR、TCR、FCR一次性读出来打印。这样现场出问题时,不用改代码重新烧,直接敲命令看状态。下面这个快照函数可以直接抄:

/* 在 ioctl 里加 EIOCGDM9K 命令,回读关键寄存器 */ LOCAL void dm9000a_dump_regs(DM9000A_DEV *pDev) { printf("NCR=0x%02X NSR=0x%02X ISR=0x%02X\n", dm9000a_reg_read(DM9000A_NCR), dm9000a_reg_read(DM9000A_NSR), dm9000a_reg_read(DM9000A_ISR)); printf("RCR=0x%02X TCR=0x%02X FCR=0x%02X\n", dm9000a_reg_read(DM9000A_RCR), dm9000a_reg_read(DM9000A_TCR), dm9000a_reg_read(DM9000A_FCR)); printf("ISR 的 PRM=%d PTM=%d RXOV=%d\n", (dm9000a_reg_read(DM9000A_ISR) & ISR_PRM) ? 1 : 0, (dm9000a_reg_read(DM9000A_ISR) & ISR_PTM) ? 1 : 0, (dm9000a_reg_read(DM9000A_ISR) & ISR_RXOV) ? 1 : 0); }

参数说明:NSR里能看到链路状态和速度,ISR里能看到收包、发包、溢出标志。如果ISR的RXOV经常置位,说明收包任务处理太慢,FIFO 溢出,要加大收包任务优先级或减小中断延迟。如果NSR显示链路没 up,先查 PHY 和网线,别在驱动里死磕。

交叉验证的另一个手段是抓包。VxWorks 侧可以用netstat看接口统计,对端用抓包工具看实际收到的帧。两边对不上时,优先信抓包结果——芯片说发了但线上没有,就是 FIFO 或长度问题;线上有但协议栈没收到,就是 ISR 或收包任务问题。这个「寄存器快照 + 抓包」的组合,是我调 DM9000A 驱动时最省时间的习惯,比反复改代码烧板子快得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询