WINCE6.0+S3C6410下的DM9000A驱动
********************************LoongEmbedded************************
作者:LoongEmbedded(kandi)
时间:2011.05.26
类别:WINCE驱动开发
********************************LoongEmbedded************************
1. 原理图设计
图1
DM9000的总线是16位的,接在6410的Xm0的总线上。
DM9000 默认I/0 基地址为300H。CMD 引脚用于设置COMMAND 模式,CMD为高时,选择数据端口。CMD为低时,选地址端口。数据端口和地址端口的地址码由下式决定:
DM9000地址端口=高位片选地址+300H+0H
DM9000数据端口=高位片选地址+300H+4H
其中,高位片选地址由S3C6410的Bank1提供,即为:0x18000000 ,结合图2和图3可更深刻理解:
图2
图3
⑴DM9000 CMD引脚接至6410的Xm0的地址线ADDR2上,可以用来选择发送的是地址还是数据。
⑵DM9000的IOR 和IOW接至6410的Xm0总线的读写引脚。
⑶DM9000的复位引脚接至6410的复位输出,以保证CPU运行后再复位芯片。
⑷DM9000的EECS和EECK引脚可以选择8位还是16位总线和高低电平触发中断。
⑸DM9000的中断引脚接至CPU的EINT7引脚,高电平触发中断。
2. DM9000A介绍
2.1 DM9000A
DM9000A具有一个通用的微处理器接口,内部集成了16kB SRAM(其中13kB用作接收缓冲区,3kB作为发送缓冲区),对内部存储器访问支持8位和16位数据接口以适用于不同的微处理器;内部集成了一个 10/100M自适应PHY,可以连接到3类、4类、5类的10M无屏蔽双绞线和5类的100M无屏蔽双绞线。
DM9000A体积小,只有48个引脚,有利于缩小PCB面积;它完全支持IEEE802.3u规格,还支持IEEE802.3x全双工流控制。该电路还集成了EEPROM接口,自举时通过EEPROM接口接入到芯片中,从而实现自动初始化DM9000A功耗非常低。DM9000A单电源3.3V工作,内置 3.3V变2.5V电源电路,I/O端口支持3.3V到5V的容差
2.2 DM9000A的工作原理
2.2.1 DM9000A的总线
总线是ISA总线兼容模式,8个IO基址,分别是300H, 310H,320H, 330H, 340H, 350H, 360H, 370H。IO基址与设定引脚或内部EEPROM的共同选定
在DM9000A中只有INDEX端口与DATA端口两个寄存器可以直接被CPU直接访问,其它所有内部控制和状态寄存器都是通过这两个端口寄存器间接访问的。网络控制器CMD引脚决定了处理器访问的是哪个端口寄存器:当CMD=0时,主机访问的是INDEX端口寄存器;当CMD=1时,访问的是DATA端口寄存器。设计中将CMD引脚与处理器的地址线Xm0ADDR2相连,则DM9000A的2个外部接口端口地址分别为:
INDEX端口地址=IOaddress+0x00,注意这里的0x00不是指网络控制寄存器(NCR)。
DATA端口地址=IOaddress+0x04,这里的0x04不是指帧II的TX状态寄存器。
实际中INDEX端口寄存器保存的是访问DATA端口寄存器的内部寄存器(比如网络状态寄存器)的地址(01H),因此对DM9000A控制或者状态寄存器访问的命令顺序是:
1) 写要访问寄存器的地址到INDEX端口。
2) 通过DATA端口来读/写数据(这里是要访问寄存器的值)。
至于中间的动作时由DM9000A的DMA来完成的。
2.2.2 DM9000A的SRAM
DM9000A内置了16KB的SRAM用来作为首发数据的缓冲区,其中从0x000到0x0BFF工3KB用于发送数据缓冲区,此缓冲区可以同时保存2个完整的以太帧,在DM9000A的设计中将发送缓冲区看作2个独立的发送缓冲区,分别用来保存帧I和帧II;从地址0x0C00到0x3FFF的13KB的空间作为数据接收缓冲区,它是一个环形结构。
2.2.3 DM9000A的DMA控制(Direct Memory Access Control)
DM9000提供DMA(直接存取技术)来简化对内部存储器的访问。在对内部存储器起始地址完成编程后,然后发出伪读写命令就可以加载当期数据到内部数据缓冲区,可以通过读写命令寄存器来定位内部存储区地址。根据当前总线模式的字长使存储地址自动加1,下一个地址数据将会自动加载到内部数据缓冲区。要注意的是在连续突发式的第一次访问(伪读写命令命令)的数据应该被忽略,因为此数据是上一个读写命令获取的值。
内部存储器空间大小是16K字节。低3K字节单元用作发送包的缓冲区,其他13K字节用作接收包的缓冲区。所以在写内存缓冲区操作的时候,如果中断屏蔽寄存器(IMR,FFH)的第七位被置1(使能SRAM读/写指针在超过SRAM大小的时候可以自动返回到开始地址),当SRAM写地址超过SRAM的大小的时候,自动跳回0地址(为什么是0呢?因为地址0x0000到0x0BFF是作为数据发送缓冲区的)。同样在读接收数据缓冲区的时候,当SRAM写地址超过SRAM的大小的时候,自动跳回0x0C00地址(为什么是0x0C00呢?因为地址0x0C00到0x3FFF是作为数据发送缓冲区的)。
2.2.4 9000A的初始化
为了启动网络控制器DM9000A,并使之处于接收和发送就绪状态,必须对其进行初始化。DM9000A的初始化主要是设置一些关键的寄存器,见图20的代码处理,具体流程如下:
1) 激活PHY
设置GPR(1FH)的 bit[0]=0,复位后,DM9000A恢复默认的休眠状态,以降低功耗,因此需要首先唤醒PHY。
2) 进行两次软复位,具体步骤如下
设置NCR(REG_00)bit[2:0]=011,至少保持20μs;
清除NCR(REG_00)bit[2:0]=000;
设置NCR(REG_00)bit[2:0]=011,至少保持20μs;
清除NCR(REG_00)bit[2:0]=000;
3) 配置NCR寄存器
设置NCR(00h)的bit[2:1]=00;配置为正常模式,通过改变该寄存器可以选择设置内部或者外部PHY、全双工或者半双工模式、使能唤醒事件等网络操作。
4) 清除发送状态
设置NSR(01h) bit[3]=1 bit[2]=1。
5) 设置中断屏蔽寄存器IMR的Bit[7](PAR位)为1,使能RX/TX缓冲区的
存读/写地址指针的自动返回功能。
6) 设置中断屏蔽寄存器IMR的Bit[0]/Bit[1],使能发送和接收中断。如果需
要在一个数据帧发送完成后产生一个中断,就应该将bit[1]=1;如果需要在接收到一帧新数据时产生一个中断,就应该将bit[0]=1。
7) 设置接收控制寄存器RCR的Bit[0](RXEN)为1,使能接收功能.
完成上述初始化后,网络控制器就可以正常启动和收发数据包了。
2.2.5 DM9000A的发送
DM9000A中的发送缓冲区可以同时存储两帧数据,依次命名为帧index I和帧index II。TX控制寄存器(02H)控制CRC校验码和PAD的插入,这两个帧发送的状态分别保存在帧index I发送状态寄存器(03H)和帧index II发送状态寄存器(04H)
在软件或者硬件复位后,发送的起始地址是0x00h和当前是帧index II。首先CPU通过写MWCMD命令来告诉DM9000A有数据发送(CPU有数据发送给DM9000A),这时DM9000A的DMA就把CPU通过SD数据线发送过来的数据发送到TX SRAM中(先写入6个字节的目的MAC地址,再写入6字节的源MAC地址,最后再写入发送的数据),然后写要发送的数据的长度到TX帧长度寄存器FCH和FDH中,如果数据长度是16位,将高8位写入寄存器FCH,低8位写入寄存器FDH中。随后,CPU通过写TX状态寄存器(TXCR,02h)的第0位为1来请求DM9000A发送index I帧,DM9000A会自动做一些处理才将数据发往以太网,这包括:插入报头和帧起始分隔符;插入来自上层协议的数据,如果数据量小于64字节,则自动补齐64字节;根据目标地址、源地址、长度/类型和数据产牛CRC校验序列,并插入校验序列位。这些处理都无需CPU干预。处理完毕后,这样DM9000A就会发index I数据帧。在index I帧发送完成之前,index II帧会写入到发送缓冲区。在index I帧发送完成后,将index II帧的数据长度写到TX帧长度寄存器FCH和FDH中,并且写TX状态寄存器(TXCR,02h)的第0位为1来请求DM900A发送index II帧,这样就可以发送index II帧。依此类推,下面发送的帧将会继续是index I帧、index II帧、index I帧、index II帧……按照同样的方式发送。
如果CPU将中断屏蔽寄存器 IMR(FFH)的bit[1]置为高电平(这个动作在DeviceEnableInterrupt函数设置),那么发送完毕后,DM9000A将会产生一个指示发送完成的中断信号。在发送过程中,CPU可以查询寄存器标志位寄存器NSR(01h)中的TX1END bit[2]或者TX2END bit[3],若为1表示对应的帧发送完成(图28有代码说明),得到数据帧的发送状态,通过对这两个发送缓存区进行如此轮流操作,不仅可以避免在单一发送缓冲区模式下向发送缓存区写入数据包时容易产生覆盖了上一次没有发送完的数据的错误操作,也不必等待上次数据发完后再向发送缓存区写入当前的数据,从而有效的避免了处理器的等待时间,提高数据发送的效率和速度。
发送流程,寄存器ISR中的PTS标志位是发送中断标志位,当一帧数据发送完毕,PTS=0,CPU检测到该标志后,应清除标志位以便发送新的数据帧。这里需要注意的是,向FC、FD所写的帧长度应该包含目的MAC地址段、源MAC地址段和有效数据的总长度。
发送流程的总结:
当处理器要向以太网发送数据帧时,先将数据打包成UDP或IP数据包,并通过8位或16位总线逐字节发送到DM9000A的数据发送缓存中,然后将数据长度等信息填充到DM9000A的相应寄存器内,随后发送使能命令,DM9000A将缓存的数据和数据帧信息进行MAC组帧,并发送出去。
2.2.6 DM9000A的接收
DM9000A的接收缓存区是一个环形结构,初始化后的起始地址为0C00H,每帧数据都有4字节长的首部,然后是有效数据和CRC校验序列。首部4字节依次是01H、状态、长度低字节和长度高字节。 首部4字节含义如下:
第一个字节用来检测接收缓存区中是否有数据,如果这个字节是01H,表明接收到了数据;如果为00H,则说明没有数据。但是,如果第一个字节既不是01H,也不是00H,DM9000A就必须作一次软复位来从这种异常状态中恢复。
第二个字节存储着以太网帧状态,由此可判断所接收帧是否正确。
第三和第四字节存储着以太网帧长度。后续的字节就是有效数据。
接收过程如下:
查看中断状态寄存器,如果接收到新数据,寄存器ISR的PR位将被置为0;如果检测到PR=0,清除PR,CPU开始读接收缓存区数据。如过第一字节是01H,说明有数据,00H说明无数据,否则要进行复位; 根据获取的长度信息,判断是否读完一帧,如果读完,接着读下一帧,直到遇到首字节是00H的帧,说明接收数据已读完。CPU可重新查看中断状态寄存器,等待新的有效数据帧。在接收过程中,DM9000A将收到的数据帧经过解码、去帧头和地址检验等步骤后缓存在接收数据缓冲区中(这些是DMA9000A完成的,不需要CPU参与),接着在C_DM9000::DeviceInterruptEventHandler()函数->C_DM9000::Dm9LookupRxBuffers函数来读取接收缓冲区的数据包。
3. WINCE下DM9000AEP驱动的实现
3.1 WINCE下网络驱动的架构
图4
图5
NDIS:
WinSock应用层:
最上层的WinSock是提供给应用层的API接口,一般开发网络应用都会用WinSock接口来开发。
协议驱动层:
协议驱动层执行具体的网络协议,如TCP/IP、IrDA等、协议驱动层为应用层客户程序提供服务,接收来自网卡或中间驱动程序的信息。
图6
NDIS接口
在WINCE系统中式以ndis.dll的形式存在的,NDIS也是一组API,也是一个库ndis.lib,网络底层驱动需要静态连接ndis.lib来使用它的函数。协议驱动层通过调用NDIS封装层的接口函数,实现与底层硬件驱动的交互。对于协议层来说,NDIS相当于一个Miniport Driver,而对于底层的硬件驱动来说,NDIS相当于上层的协议层,所以NDIS起到承上启下的作用,也起到对底层硬件接口的规范作用。
Miniportq驱动层
也就是Miniport Driver,实际的网卡驱动就是指Miniport Driver,它向上为NDIS提供了
Miniport相关的接口函数,向下则通过NDIS的接口来访问硬件网卡。Miniport driver导出的这些接口函数会在系统注册一个Miniport Driver的时候与NDIS封装层的接口函数对接,这样内核协议层通过调用NDIS的接口就可以访问顶层硬件了。
图7
3.2 Miniport Driver导出的函数接口
如图4和图5所示,在协议层看来,它调用NDIS接口函数访问网络设备,具体的实现过程是通过调用底层的Miniport Driver接口函数来实现的。在WINCE系统中NDIS接口函数库是微软开发好的,所以开发WINCE下的网卡驱动就是编写Miniport driver,它向上导出函数接口与NDIS接口实现对接,向下直接管理网卡硬件,为了后面更好去理解一些函数,下面贴出DM9000A的驱动的注册表信息
图8
3.2.1 入口函数DriverEntry
图9
DriverEntry的参数介绍如下:
⑴pDriverObject:指向一个由系统创建的驱动对象,这里pDriverObject是PDRIVER_OBJECT类型指针,此结构体在PUBLIC\COMMON\DDK\INC\ntcompat.h下定义
图10
结合图11,通过添加打印信息可知,pDriverObject->DriverName.Buffer=“DM9CE”和pDriverObject->RegistryPath.Buffer=“Comm\DM9CE”。
⑵pRegistryPath:指向注册表中该驱动参数的路径,此参数的结构类型在PUBLIC\COMMON\DDK\INC\Wdm.h下定义,如下:
图11
过添加打印信息可知,pRegistryPath->Buffer=“Comm\DM9CE”。
DriverEntry函数主要有两个任务:
⑴调用NdisMInitializeWrapper函数来通知ndis.lib要注册一个miniport,这样NDIS就创建一个来跟踪miniport driver的状态并且返回一个句柄hwrapper。因为在随后调用NdisXXX函数和初始化函数时会用到这个句柄,所以Miniport driver会保存这个句柄。
⑵填充NDIS40_MINIPORT_CHARACTERISTICS结构体类型变量ndischar的成员变量,然后调用NdisMRegisterMiniport函数来注册miniport,而调用NdisMRegisterMiniport函数并且在DriverEntry函数结束后会紧接调用MiniportInitialize函数。
3.2.2 网络设备的初始化接口函数MiniportInitialize
参数描述:
⑴OpenErrorStatus:如果MiniportInitialize函数返回NDIS_STATUS_OPEN_ERROR,MiniportInitialize函数会设置一个指示错误的额外信息的变量的值为NDIS_STATUS_XXX,而此参数就是指向于这个变量的指针。
⑵ SelectedMediumIndex:被选中的媒介类型的索引号,以太网一般是NdisMedium802_3。
⑶ MediumArray:媒介类型数组,包含了不同类型的网络媒介
⑷ MediumArraySize:媒介类型数组大小
⑸ MiniportAdapterHandle:Miniport适配器句柄,该参数要被保存,以后调用Ndisxxx函数时会被用到。
⑹ WrapperConfigurationContext:一个封装配置句柄,会被NdisOpenConfiguration函数用到。
函数体如下:
图12
⑴ NIC_DRIVER_OBJECT::EDriverInitialize函数
看此函数体的第一部分:
图13
下面先来看DeviceRetriveConfigurations的函数体
图14
继续看NIC_DRIVER_OBJECT::EDriverInitialize函数,第二部分函数体如下:
图15
1) C_DM9000::EDeviceRegisterIoSpace函数
图16
在此也学习一个很重要的函数C_DM9000::DeviceReadPort
图17
假如我们要读取0x2A寄存器的值,需要经过下面的动作才能完成
①调用NdisRawWritePortUchar函数来把0x2A的值写入到总线0x18800300+00地址处。
②Xm0总线拉低Xm0WEn引脚来向DM9000A发出写命令的动作,这样0x2A会写到DM9000A的内部存储器起始地址处(00h,也就是相当于填写了DMA的源地址),通过写命令寄存器,DMA把0x2A寄存器的值复制到内部数据缓冲区中。
③调用NdisRawReadPortUchar函数来从总线地址0x18800300+04地址处读取数据。
④调用NdisRawReadPortUchar函数后,Xm0总线拉低Xm0OEn引脚来向DM9000A发出读命令的动作,通过读命令寄存器,DMA把内部数据缓冲区的数据拷贝到DM9000数据端口地址0x18800300+04处,这样NdisRawReadPortUchar函数就可以获取到0x2A寄存器的值了。
补充一点,根据下图我们可以更深刻理解ndis.lib提供的函数和miniport driver导出的函数接口的调用关系
图18
2)NIC_DEVICE_OBJECT::EDeviceLoadEeprom函数
图19
上图计算出来DIM(m_szEeprom)/sizeof(EEPROM_DATA_TYPE)=128/2=64,在此不明白为什么需要读取64次。
3) C_DM9000::EDeviceInitialize函数
先看此函数的第一部分函数体:
图20
接着看第二部分
图21
4) NIC_DEVICE_OBJECT::EDeviceRegisterInterrupt函数
图22
5) C_DM9000::DeviceOnSetupFilter函数
先看此函数体的第一部分
图23
接着看此函数体的第二部分
图24
⑵NIC_DRIVER_OBJECT::DriverStart函数
此函数主要是通过调用C_DM9000::DeviceStart函数来实现的,下面就来看这个函数
图25
3.2.3 MiniportISRHandler函数
该函数为网卡的ISR函数,该函数中应做尽可能少的工作,大部分工作应该交给MiniportHandleInterrupt函数来完成。
InterruptRecognized:中断确认。如果确实是一个网卡中断,返回TURE。
QueueMiniportHandleInterrupt:如果需要MiniportHandleInterrupt函数处理,返回TRUE。
MiniportAdapterContext:一个指向网卡结构的句柄,该网卡结构在MiniportInitialize函数中被创建。
此函数会调用C_DM9000::DeviceIsr函数来实现
图26
3.2.4 MiniportInterruptHandler函数
该函数调用NIC_DRIVER_OBJECT::DriverInterruptHandler()->C_DM9000::DeviceInterruptEventHandler()函数,下面就来看最底层的函数
图27
下面来学习C_DM9000::Dm9LookupRxBuffers函数
先看该函数体第一部分:
图28
看第二部分:
图29
该函数如果接收过程发现出错,例如CRC出错,将会丢掉当前的包并且对出错包的数量进行计数并且在最后接收完成后往上层报告。
3.2.5 MiniportQueryInformation函数
该函数用于查询网卡信息,返回网卡状态等信息。
3.2.6 MiniportSetInformation函数
该函数用于设置网卡的信息。
3.2.7 MiniportReset函数
对DM9000A复位,该函数主要通过调用C_DM9000::DeviceReset函数和C_DM9000::DeviceStart函数来实现,其中C_DM9000::DeviceStart函数见图26,下面就来学习C_DM9000::DeviceReset函数。
图30
3.2.8 MiniportCheckForHang函数
该函数检查硬件网卡的状态,该函数为可选的,可以不去实现。该函数的调用流程:MiniportCheckForHang()->NIC_DRIVER_OBJECT::DriverCheckForHang()->
NIC_DEVICE_OBJECT::DeviceCheckForHang()下面来看DeviceCheckForHang函数体:
图31
Miniport确定网络适配器没有工作的状态下,或者中间层驱动检测到miniport
Driver没有响应的情况下,该函数会返回TRUE。
DeviceCheckForHang函数中需要进一步描述的内容:
⑴ C_DM9000::DeviceReadPhy函数
图32
⑵ 络状态寄存器link status位描述
图33
⑶ NIC_DEVICE_OBJECT::SetConnectionStatus函数
图34
3.2.9 MiniportHalt函数
该函数用于停止网络适配器(network adapter),如果驱动控制一个物理网络适配器,必须释放驱动为网络适配器分配的所有资源,该函数调用关系:MiniportHalt()->NIC_DRIVER_OBJECT::DriverHalt(),本驱动中此函数什么都没有做。
3.2.10 MiniportSend函数
该函数的调用关系:MiniportSend()->NIC_DRIVER_OBJECT::DriverSend()
图35
下面介绍最后的发送函数C_DM9000::DeviceSend
图36
4. DM9000A调试
4.1 没有接入网线
上层驱动会每隔一段时间调用MiniportCheckForHang函数来判断网络线是否连接,见3.2.8的描述。
4.2 网线到DM9000A的对外接口RJ45,没有用户参与的任何动作
我根据串口输出信息来说明处理流程,其中上层驱动会每隔一段时间调用ISR函数MiniportISRHandler来处理,存在的流程如下:
1) 接收到数据包
图37
2) 没有接收到数据包
见图37中间的注释部分
3) 发送数据包
图38
是检测到发送数据包中断,在调用MiniportISRHandler函数之前会先调用MiniportSend函数,因为就是上层的NDIS调用MiniportSend函数之后才会产生发送数据包的中断,这个动作会被MiniportISRHandler捕捉到,从而被MiniportInterruptHandler函数做具体的发送处理。当然也有TX包II发送的处理。
4.3 打开网页应用程序,访问网页,比如http://192.168.1.10。
输出串口信息如下:
图39