关于dm9000的驱动移植分为两篇,第一篇在mini2440上实现,基于linux2.6.29,也成功在在6410上移植了一遍,和2440非常类似,第二篇在fs4412(CortexA9)上实现,基于linux3.14.78,用设备树匹配,移植过程中调试和整体理解很重要,一路上幸有良师益友指点,下面详细介绍:
DM9000芯片是DAVICOM公司生产的一款以太网处理芯片,提供一个通用的处理器接口、一个10/100M自适应的PHY芯片和4K双字的SRAM.内部框架如下,涉及到4个基本概念:
1、TCP/IP参考模型包含应用层、传输层、网络层和网络接口层,其中的网络接口层包含有数据链路层和网络层;
2、MAC:网卡数据链路层的芯片成为MAC控制器,数据链路层则提供寻址机构、数据帧的构建、数据差错检查、传送控制、向网络层提供标准的数据接口等功能;
3、PHY:网卡物理芯片,物理定义了数据传送和接收所需的电和光信号、线路状态、时钟基准、数据编码和电路等,并向数据链路层设备提供标准接口;
4、MII:介质无关接口,它是IEEE802.3定义的以太网行业标准,包含一个数据接口,以及一个MAC和PHY之间的管理接口,数据接口包括分别用于发送器和接收器的两条独立通道,管理接口用来监视和控制PHY,介质无关表明不对MAC硬件重新设计或替换的情况下,任何类型的PHY设备都可以正常工作;
图1DM9000内部结构框架
图2DM9000读时序
图3DM9000写时序
图5s3c2440的存储BANK
1、物理连接和理解:S3c2440有27根地址线:2^27=128MB,所以一个bank最大可寻址128M,8个bank说明s3c2440最大可寻址1G,Mini2440采用的是dm9000直接连接CPU(s3c2440)上。就像是nandflash一样直接被挂在CUP上,被挂在s3c2440的bank4上,s3c2440芯片把存储系统分为了8个Bank,由nGCS0[0]~nGCS[7]这8根引脚决定当前访问的是哪一个Bank对应的存储器。其中,前6个Bank用于连接ROM或者SRAM,第7个Bank地址作为SDRAM的起始地址(即0x30000000)。DM9000通过CMD端口控制写命令和读写数据,mini2440开发板上的DM9000和S3c2440的连接方式如图6所示,数据信号SD0-SD15对应DATA0-DATA15,CMDADDR2识别为地址还是数据,INTEINT7中断,IOR#nOE读命令使能,IOW#nWE写命令使能,AENnGCS4片选使能,连接了16条数据线,1条地址线,而这唯一的一条地址线用于判断数据线传输的是地址还是数据,所以这16条数据线为数据和地址复用。
2、详尽时序分析:要使挂接在BANK4上的DM9000正常工作,需要配置存储器控制器的BWSCON和BANKCON4两个寄存器,前者可以设置DM9000的总线宽度,后者可以设置DM9000的访问时序,DM9000的寄存器读写时序分别如图2和图3所示,T1s3C2440的BANK4读写时序如图4,内存控制器使用HCLK作为时钟,在HCLK为100MHz时,1个clock大约为10ms,通过对比:Tcos对应T1,呢么最少应该为5ns,也就是1个clockTacc对应T2,呢么最少应该为22ns,呢么我们这里最少也要选3个clock,也就是30ns,Toch对应T5,Toch最少应该为5ns,也就是1个clock。从DM9000的读写时序图中可以看出,T2+T6实际上构成了DM9000的一个访问周期,因此还需要满足:Tacs+Tcos+Tacc+Tcoh+Tcah>=T2+T6,最终使用下面的表达式来表达:
(Tacs>=0&&Tacs<=4)&&(Tcos>=1&&Tcos<=4)&&(Tacc>=3&&Tacc<=14)&&(Tcoh>=1&&Tcoh<=4),故使用下值进行设置:
在dm9000_init()函数中添加如下代码:
2.核心数据结构和网络子系统分析
网络体系结构由5个部分组成,分别如下:
系统调用接口:为应用程序提供访问内核网络子系统的方法,主要指socket系统调用;协议无关接口:实现一组基于socket的通用函数来访问各种不同的协议;网络协议:网络协议层用于实现各种具体的网络协议,如TCP、UDP;设备无关接口:设备物管接口层将协议和各种网络设备驱动连接在一起,这一层提供一组通用函数供底层网络设备驱动程序使用,使他们可以操作高层协议栈;设备驱动:网络体系结构的最底部是负责管理物理网络设备的设备驱动程序层。
将上面概念搞清楚后,紧接着分析核心数据结构net_device和sk_buff,分别用来描述一个网络设备和用来接收和发送的数据包,以不变应万变,实现多种硬件在软件层次上的统一:
int(*init)(structnet_device*dev);//初始化函数,该函数在register_netdev时被调用来完成对net_device结构的初始化
int(*hard_start_xmit)(structsk_buf*skb,structnet_device*dev);//数据发送函数
int(*hard_header)(structsk_buff*skb,structnet_device*dev,unsignedshorttype,void*daddr,void*saddr,unsignedlen);//该方法根据先前检索到的源和目的硬件地址建立硬件头
int(*rebuild_header)(structsk_buff*skb);//以太网的mac地址是固定的,为了高效,第一个包去询问mac地址,得到对应的mac地址后就会作为cache把mac地址保存起来。以后每次发包不用询问了,直接把包的地址拷贝出来
传输包的所有信息都保存在sk_buff结构中,这一结构被所有网络层使用,也被称为“套接字缓冲区”,用于在网络子系统各层之间传递数据。内核中,所有的sk_buff结构都被组织在一个双向链表中,见下图:
3.驱动代码详尽分析
step1、紧接着在注册平台驱动时,内核遍历平台总线上的所有平台设备,linux3.0之后有四种方式进行匹配,典型的就是通过基于设备树风格的匹配,现在移植的为2.6.29时通过匹配platform_device设备名和驱动名的名称匹配,并在找到匹配的设备后,调用平台驱动中的probe函数。平台驱动通常利用probe()函数从匹配上的平台设备中获取平台资源,并根据这些资源申请和映射IO内存、获取并注册IRQ中断,dm9000_probe()最终调用register_netdev()注册网卡设备,下面详尽分析dm9000_probe()函数:
1staticintdm9000_start_xmit(structsk_buff*skb,structnet_device*dev)2{3unsignedlongflags;4board_info_t*db=netdev_priv(dev);56dm9000_dbg(db,3,"%s:\n",__func__);78if(db->tx_pkt_cnt>1)9returnNETDEV_TX_BUSY;1011spin_lock_irqsave(&db->lock,flags);//获得自旋锁1213/*MovedatatoDM9000TXRAM*/14writeb(DM9000_MWCMD,db->io_addr);//根据IO操作模式(8-bitor16-bit)来增加写指针1或21516(db->outblk)(db->io_data,skb->data,skb->len);//将数据从sk_buff中copy到网卡的TXSRAM中17dev->stats.tx_bytes+=skb->len;//统计发送的字节数1819db->tx_pkt_cnt++;//待发送计数20/*TXcontrol:Firstpacketimmediatelysend,secondpacketqueue*/21if(db->tx_pkt_cnt==1){//如果计数为1,直接发送22dm9000_send_packet(dev,skb->ip_summed,skb->len);23}else{24/*Secondpacket*/25db->queue_pkt_len=skb->len;26db->queue_ip_summed=skb->ip_summed;27netif_stop_queue(dev);//告诉上层停止发送28}2930spin_unlock_irqrestore(&db->lock,flags);//解锁3132/*freethisSKB*/33dev_kfree_skb(skb);//释放SKB3435returnNETDEV_TX_OK;36}
step4、数据包发送和数据包接收,数据包接收的软件流程时序具体如下:
1、判断包是否已经接收到了,通过MRCMDX寄存器(从RX缓冲区中读取数据命令,地址不增加),读出RX缓冲区中数据包包头的第一个字节来判断是否接受到数据;
2、检查数据包的状态和长度,通过MRCMD寄存器(从RX缓冲区中读取数据命令,读指针自动增加),读出数据包的包头,分析status字段并记录实际接收到的数据长度;
3、读取数据包,通过MRCMD寄存器,按照上一步中获取的数据包长度读出数据包内容;
4、利用读取到的数据包的实际内容构造skb,并调用netif_rx()操作将skb送到上层协议层处理;
数据包接收函数通过dm90_rx()来实现:
数据包发送的软件流程时序具体如下:
1、在发送一个数据包之前,将包中的有效数据通过MWCMD寄存器,写入TX缓冲区;
2、如果待发送数据包是第一个包,则直接启动包发送,方法是将发送报的长度写入寄存器TXPLL和TXPLH中,并设置TXCR寄存器的TXREQ位启动包发送;
3、如果待发送数据包是第二个包,则咱不发送该包,而是记录包长度和校验控制位,并停止发送队列,以上3个步骤用于发送启动包,在net_device的hard_start_xmit()方法中实现;
4、如果设置了IMR寄存器的PTM位,则当数据发送完成后产生中断,并使ISR寄存器的PTS位被设置,因此可以在中断处理函数中判断该中断,并执行对应操作,当在中断处理程序中检测到发送中断后,执行的操作函数是dm9000_tx_done(),下面的步骤5-7在该函数中实现;
5、读取寄存器NSR获取发送的状态,判断该寄存器的NSR_TX2END、NSR_TX1END位,只要有一个被置位,则进行接下来的处理,否则结束流程;
6、将待发送的数据包数量减一,检查变量db->tx_pkt_cnt是否大于0,大于0,则表示还有数据包要发送,调用函数dm9000_send_packet()发送队列中的数据包;
7、调用函数netif_wake_queue(dev)通知内核可以将待发送的数据包加入发送队列,因为dm9000的TX缓冲区容量有限,但可以同时放入两个数据包,因此只要有一个包发送完成,就可以唤醒发送队列发送上层协议请求的包;
具体软件实现见下图:
dm9000网络设备接收数据的主要方法是有中断引发设备的中断处理函数,中断处理函数判断中断的类型,如果为接收中断,则读取接收到的数据,分配sk_buff数据结构和数据缓冲区,将接收到的数据复制到数据缓冲区中,并调用netif_rx()函数将sk_buff传递给上层协议,实现如下:
4.添加DM9000平台设备资源
5.内核配置添加DM9000支持
重新配置内核,加入DM9000的驱动支持,在内核目录下执行“makemenuconfig”命令进行如下的配置:DeviceDrivers--->[*]Networkdevicesupport--->[*]Ethernet(10or100Mbit)---><*>DM9000support[*]Networkingsupport--->Networkingoptions---><*>TCP/IPnetworking<*>IP:kernelleelautoconfiguration//增加对nfs的支持Filesystems--->[*]NetworkingFileSystems---><*>NFSclientsupport[*]NFSclientsupportforNFSversion3[*]NFSclientsupportfortheNFSv3ACLprotocolextension[*]BootfilesystemonNFS[*]NFSserversupport