关于dm9000的驱动移植分为两篇,第一篇在mini2440上实现,基于linux2.6.29,也成功在在6410上移植了一遍,和2440非常类似,第二篇在fs4412(Cortex A9)上实现,基于linux3.14.78,用设备树匹配,移植过程中调试和整体理解很重要,一路上幸有良师益友指点,下面详细介绍:

1.物理时序分析相关  

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设备都可以正常工作;

图1 DM9000内部结构框架

图2 DM9000读时序

图3 DM9000写时序

图4 s3c2440的BANK4的读写时序

图5 s3c2440的存储BANK

图6 mini2440的DM9000相关原理图

  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 ,CMD ADDR2 识别为地址还是数据,INT EINT7 中断,IOR# nOE 读命令使能,IOW# nWE 写命令使能,AEN nGCS4 片选使能,连接了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个clock Tacc对应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()函数中添加如下代码:

 #if defined(CONFIG_ARCH_S3C2410)
unsigned int oldval_bwscon; //用来保存BWSCON寄存器的值
unsigned int oldval_bankcon4; //用来保存S3C2410_BANKCON4寄存器的值
#endif

#if defined (CONFIG_ARCH_S3C2410)
//设置BANK4:总线宽度为16,使能nWAIT
oldval_bwscon = *((volatile unsigned int *)S3C2410_BWSCON);
*((volatile unsigned int *)S3C2410_BWSCON) = (oldval_bwscon & ~(<<)) \
| S3C2410_BWSCON_DW4_16 | S3C2410_BWSCON_WS4 | S3C2410_BWSCON_ST4; //使能总线宽度为16位,使能nWAIT
//设置BANK4的时间参数
oldval_bankcon4 = *((volatile unsigned int *)S3C2410_BANKCON4);
*((volatile unsigned int *)S3C2410_BANKCON4) = 0x1f7c; // BANK4控制寄存器,用于控制BANK4外接设备的访问时序
#endif

if (!is_valid_ether_addr(ndev->dev_addr))
dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please "
"set using ifconfig\n", ndev->name); #if defined(CONFIG_ARCH_S3C2410)
printk("Now use the default MAC address:08:90:90:90:90:90\n");
ndev->dev_addr[] = 0x08;
ndev->dev_addr[] = 0x90;
ndev->dev_addr[] = 0x90;
ndev->dev_addr[] = 0x90;
ndev->dev_addr[] = 0x90;
ndev->dev_addr[] = 0x90;
#endif

2.核心数据结构和网络子系统分析 

  网络体系结构由5个部分组成,分别如下:

  系统调用接口:为应用程序提供访问内核网络子系统的方法,主要指socket系统调用;协议无关接口:实现一组基于socket的通用函数来访问各种不同的协议;网络协议:网络协议层用于实现各种具体的网络协议,如TCP、UDP;设备无关接口:设备物管接口层将协议和各种网络设备驱动连接在一起,这一层提供一组通用函数供底层网络设备驱动程序使用,使他们可以操作高层协议栈;设备驱动:网络体系结构的最底部是负责管理物理网络设备的设备驱动程序层。

  将上面概念搞清楚后,紧接着分析核心数据结构net_device和sk_buff,分别用来描述一个网络设备和用来接收和发送的数据包,以不变应万变,实现多种硬件在软件层次上的统一:

  int  (*init)(struct  net_device*dev); //初始化函数,该函数在register_netdev时被调用来完成对net_device结构的初始化

  int (*hard_start_xmit)(structsk_buf*skb,struct net_device *dev); //数据发送函数

  int (*hard_header)(struct sk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len); //该方法根据先前检索到的源和目的硬件地址建立硬件头

  int (*rebuild_header)(struct sk_buff *skb); //以太网的mac地址是固定的,为了高效,第一个包去询问mac地址,得到对应的mac地址后就会作为cache把mac地址保存起来。以后每次发包不用询问了,直接把包的地址拷贝出来

  void (*tx_timeout)(struct net_device *dev);  //如果数据包发送在超时时间内失败,这时该方法被调用,这个方法应该解决失败的问题,并重新开始发送数据
  struct net_device_stats *(*get_stats)(struct net_device *dev); //当应用程序需要获得接口的统计信息时,这个方法被调用
  int (*set_config)(struct net_device *dev, struct ifmap *map);  //改变接口的配置,比如改变I/O端口和中断号等,现在的驱动程序通常无需该方法
  int (*do_ioctl)(struct net_device *dev, struct ifmap *map);  //该函数用来处理特定网络命令的ioctl命令
  void (*set_multicast_list)(struct net_device *dev); //当设备的组播列表改变或设备标志改变时,该方法被调用
  int (*set_mac_address)(struct net_device *dev, void *addr);  //该函数用来设置网络设备的MAC地址

  传输包的所有信息都保存在sk_buff结构中,这一结构被所有网络层使用,也被称为“套接字缓冲区”,用于在网络子系统各层之间传递数据。内核中,所有的sk_buff结构都被组织在一个双向链表中,见下图:

3.驱动代码详尽分析

  从网上下载下来的Linux2.6.29中有完整的DM9000网卡驱动,该驱动对应源文件drivers/net/dm9000.c,在移植bootloader的过程中可知,DM9000可通过S3C2440地址总线访问,因此有专属它的IO内存。驱动将DM9000的IO内存和IRQ资源以平台设备资源的方法进行管理,并在dm9000.c中实现并注册了相应的平台驱动,这样,只要向内核中注册板级相关的DM9000平台设备,网卡就能正常工作,按照面向对象设计的思想,任何一个网卡设备都可以由net_device结构描述,这是因为net_device包含了所有网卡设备的共性,而任何一个具体网卡设备的表述结构应该由net_device派生而来。其中的board_info可视为对net_device结构的扩展,以描述一个具体的网卡设备,dm9000的平台驱动定义和board_info如下:

  step1、紧接着在注册平台驱动时,内核遍历平台总线上的所有平台设备,linux3.0之后有四种方式进行匹配,典型的就是通过基于设备树风格的匹配,现在移植的为2.6.29时通过匹配platform_device设备名和驱动名的名称匹配,并在找到匹配的设备后,调用平台驱动中的probe函数。平台驱动通常利用probe()函数从匹配上的平台设备中获取平台资源,并根据这些资源申请和映射IO内存、获取并注册IRQ中断,dm9000_probe()最终调用register_netdev()注册网卡设备,下面详尽分析dm9000_probe()函数:

 static int __devinit  dm9000_probe(struct platform_device *pdev)
{
struct dm9000_plat_data *pdata = pdev->dev.platform_data;//驱动程序中获得系统定义的网卡板级信息
//定义在/arch/arm/mach-s3c64xx/mach-smdk6410.c中。
struct board_info *db; /* Point a board information structure */
struct net_device *ndev; //定义设备结构体
const unsigned char *mac_src;
int ret = ;
int iosize;
int i;
u32 id_val; /* Init network device */
//分配生成net_device结构体 alloc_etherdev是alloc_netdev()针对以太网的快捷操作函数
ndev = alloc_etherdev(sizeof(struct board_info)); //判断是否分配正确
if (!ndev) {
dev_err(&pdev->dev, "could not allocate device.\n");
return -ENOMEM;
}
//建立net_device到device的连接
SET_NETDEV_DEV(ndev, &pdev->dev);
//内核输出信息
dev_dbg(&pdev->dev, "dm9000_probe()\n");
//函数netdev_priv直接返回了net_device结构末端地址,也就是网卡私有数据结构的起始地址。
/* setup board info structure */
db = netdev_priv(ndev); db->dev = &pdev->dev;
db->ndev = ndev; spin_lock_init(&db->lock);//初始化自旋锁
mutex_init(&db->addr_lock); INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work); //??
//获取平台设备资源 resource 地址空间、数据空间、中断信号 7号中断
db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, );
db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, );
db->irq_res = platform_get_resource(pdev, IORESOURCE_IRQ, );
//判断资源是否获取成功
if (db->addr_res == NULL || db->data_res == NULL ||
db->irq_res == NULL) {
dev_err(db->dev, "insufficient resources\n");
ret = -ENOENT;
goto out;
}
//platform_get_resource的变体 同 platform_get_resource(pdev, IORESOURCE_IRQ, 1)
db->irq_wake = platform_get_irq(pdev, );
if (db->irq_wake >= ) {
dev_dbg(db->dev, "wakeup irq %d\n", db->irq_wake);
//前面获得中断号 申请中断
ret = request_irq(db->irq_wake, dm9000_wol_interrupt,
IRQF_SHARED, dev_name(db->dev), ndev);
if (ret) {
dev_err(db->dev, "cannot get wakeup irq (%d)\n", ret);
} else {
/* test to see if irq is really wakeup capable */
ret = set_irq_wake(db->irq_wake, );
if (ret) {
dev_err(db->dev, "irq %d cannot set wakeup (%d)\n",
db->irq_wake, ret);
ret = ;
} else {
set_irq_wake(db->irq_wake, );
db->wake_supported = ;
}
}
}
//IO资源分配大小 地址 为resource分配内存
iosize = resource_size(db->addr_res);
db->addr_req = request_mem_region(db->addr_res->start, iosize,
pdev->name); //内存申请 if (db->addr_req == NULL) {
dev_err(db->dev, "cannot claim address reg area\n");
ret = -EIO;
goto out;
} db->io_addr = ioremap(db->addr_res->start, iosize); if (db->io_addr == NULL) {
dev_err(db->dev, "failed to ioremap address reg\n");
ret = -EINVAL;
goto out;
} iosize = resource_size(db->data_res);
db->data_req = request_mem_region(db->data_res->start, iosize,
pdev->name); if (db->data_req == NULL) {
dev_err(db->dev, "cannot claim data reg area\n");
ret = -EIO;
goto out;
} db->io_data = ioremap(db->data_res->start, iosize); if (db->io_data == NULL) {
dev_err(db->dev, "failed to ioremap data reg\n");
ret = -EINVAL;
goto out;
}
//初始化net_device中的成员
/* fill in parameters for net-dev structure */
ndev->base_addr = (unsigned long)db->io_addr; //网络接口的IO基地址
ndev->irq = db->irq_res->start;//中断号 ifconfig时会打印出这个值 也可通过这个修改 /* ensure at least we have a default set of IO routines */
dm9000_set_io(db, iosize); /* check to see if anything is being over-ridden ,pdata 是在dm9000_probe函数开头获取的平台数据,其flags成员记录了DM9000的访问位宽,下面的代码根据flags的值,调用dm9000_set_io()函数来确定board_info结构中关于DM9000块读写操作的具体函数*/
if (pdata != NULL) {
/* check to see if the driver wants to over-ride the
* default IO width */
//检测与板级信息是否相同
if (pdata->flags & DM9000_PLATF_8BITONLY)
dm9000_set_io(db, ); if (pdata->flags & DM9000_PLATF_16BITONLY)
dm9000_set_io(db, ); if (pdata->flags & DM9000_PLATF_32BITONLY)
dm9000_set_io(db, ); /* check to see if there are any IO routine
* over-rides */ if (pdata->inblk != NULL)
db->inblk = pdata->inblk; if (pdata->outblk != NULL)
db->outblk = pdata->outblk; if (pdata->dumpblk != NULL)
db->dumpblk = pdata->dumpblk; db->flags = pdata->flags;
} #ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL
db->flags |= DM9000_PLATF_SIMPLE_PHY;
#endif dm9000_reset(db); /* try multiple times, DM9000 sometimes gets the read wrong */
for (i = ; i < ; i++) { //宏定义在dm9000.h中
id_val = ior(db, DM9000_VIDL);
id_val |= (u32)ior(db, DM9000_VIDH) << ;
id_val |= (u32)ior(db, DM9000_PIDL) << ;
id_val |= (u32)ior(db, DM9000_PIDH) << ; if (id_val == DM9000_ID)
break;
dev_err(db->dev, "read wrong id 0x%08x\n", id_val);
} if (id_val != DM9000_ID) {
dev_err(db->dev, "wrong id: 0x%08x\n", id_val);
ret = -ENODEV;
goto out;
} /* Identify what type of DM9000 we are working on */ id_val = ior(db, DM9000_CHIPR);
dev_dbg(db->dev, "dm9000 revision 0x%02x\n", id_val); switch (id_val) {
case CHIPR_DM9000A:
db->type = TYPE_DM9000A;
break;
case CHIPR_DM9000B:
db->type = TYPE_DM9000B;
break;
default:
dev_dbg(db->dev, "ID %02x => defaulting to DM9000E\n", id_val);
db->type = TYPE_DM9000E;
} /* dm9000a/b are capable of hardware checksum offload */
if (db->type == TYPE_DM9000A || db->type == TYPE_DM9000B) {
db->can_csum = ;
db->rx_csum = ;
ndev->features |= NETIF_F_IP_CSUM;
} /* from this point we assume that we have found a DM9000 */ /* driver system function */ //初始化以太网设备的公有成员
ether_setup(ndev);//在调用register_netdev之前必须初始化完全。该函数中为net_device设置了很多默认值 ndev->netdev_ops = &dm9000_netdev_ops;
ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
ndev->ethtool_ops = &dm9000_ethtool_ops; db->msg_enable = NETIF_MSG_LINK;
db->mii.phy_id_mask = 0x1f;
db->mii.reg_num_mask = 0x1f;
db->mii.force_media = ;
db->mii.full_duplex = ;
db->mii.dev = ndev;
db->mii.mdio_read = dm9000_phy_read;
db->mii.mdio_write = dm9000_phy_write; mac_src = "eeprom"; /* try reading the node address from the attached EEPROM */
for (i = ; i < ; i += )
dm9000_read_eeprom(db, i / , ndev->dev_addr+i); if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) {
mac_src = "platform data";
memcpy(ndev->dev_addr, pdata->dev_addr, );
} if (!is_valid_ether_addr(ndev->dev_addr)) {
/* try reading from mac */ mac_src = "chip";
for (i = ; i < ; i++)
ndev->dev_addr[i] = ior(db, i+DM9000_PAR);
} if (!is_valid_ether_addr(ndev->dev_addr))
dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please "
"set using ifconfig\n", ndev->name);
platform_set_drvdata(pdev, ndev); //将ndev保存成平台总线设备的私有数据,以后再要使用时可以通过调用platform_get_drvdata(),从响应平台设备中获取
ret = register_netdev(ndev); //注册net_device结构体 if (ret == )
printk(KERN_INFO "%s: dm9000%c at %p,%p IRQ %d MAC: %pM (%s)\n",
ndev->name, dm9000_type_to_char(db->type),
db->io_addr, db->io_data, ndev->irq,
ndev->dev_addr, mac_src);
return ; out:
dev_err(db->dev, "not found (%d).\n", ret); dm9000_release_board(pdev, db);
free_netdev(ndev); //分配错误则释放net_device结构 return ret;
}

  step2、接下来向内核注册中断、复位并初始化dm9000、检查MII接口,由dm9000_open()函数来实现:

 m9000_open(struct net_device *dev)
{
board_info_t *db = netdev_priv(dev);//获取设备私有数据 返回board_info_t的地址
unsigned long irqflags = db->irq_res->flags & IRQF_TRIGGER_MASK; if (netif_msg_ifup(db))
dev_dbg(db->dev, "enabling %s\n", dev->name); /* If there is no IRQ type specified, default to something that
* may work, and tell the user that this is a problem */ if (irqflags == IRQF_TRIGGER_NONE)
dev_warn(db->dev, "WARNING: no IRQ resource flags set.\n"); irqflags |= IRQF_SHARED;
//注册中断
if (request_irq(dev->irq, dm9000_interrupt, irqflags, dev->name, dev))
return -EAGAIN; /* Initialize DM9000 board */
dm9000_reset(db); //复位DM9000
dm9000_init_dm9000(dev); //初始化dm9000中net_device结构中的成员 /* Init driver variable */
db->dbug_cnt = ; mii_check_media(&db->mii, netif_msg_link(db), );//检测mii接口状态
netif_start_queue(dev); //启动发送队列 协议栈向网卡发送 dm9000_schedule_poll(db); return ;
}

  step3、设备驱动程序从上层协议传递过来的sk_buff参数获得数据包的有效数据和长度,并将有效数据放入临时缓冲区中,设置硬件寄存器,linux中DM9000发送函数如下:

 static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
unsigned long flags;
board_info_t *db = netdev_priv(dev); dm9000_dbg(db, , "%s:\n", __func__); if (db->tx_pkt_cnt > )
return NETDEV_TX_BUSY; spin_lock_irqsave(&db->lock, flags); //获得自旋锁 /* Move data to DM9000 TX RAM */
writeb(DM9000_MWCMD, db->io_addr); //根据 IO 操作模式(8-bit or 16-bit)来增加写指针 1 或 2 (db->outblk)(db->io_data, skb->data, skb->len); //将数据从sk_buff中copy到网卡的TX SRAM中
dev->stats.tx_bytes += skb->len; //统计发送的字节数 db->tx_pkt_cnt++; //待发送计数
/* TX control: First packet immediately send, second packet queue */
if (db->tx_pkt_cnt == ) { //如果计数为1,直接发送
dm9000_send_packet(dev, skb->ip_summed, skb->len);
} else {
/* Second packet */
db->queue_pkt_len = skb->len;
db->queue_ip_summed = skb->ip_summed;
netif_stop_queue(dev); //告诉上层停止发送
} spin_unlock_irqrestore(&db->lock, flags);//解锁 /* free this SKB */
dev_kfree_skb(skb); //释放SKB return NETDEV_TX_OK;
}

  step4、数据包发送和数据包接收,数据包接收的软件流程时序具体如下:

    1、判断包是否已经接收到了,通过MRCMDX寄存器(从RX缓冲区中读取数据命令,地址不增加),读出RX缓冲区中数据包包头的第一个字节来判断是否接受到数据;

    2、检查数据包的状态和长度,通过MRCMD寄存器(从RX缓冲区中读取数据命令,读指针自动增加),读出数据包的包头,分析status字段并记录实际接收到的数据长度;

    3、读取数据包,通过MRCMD寄存器,按照上一步中获取的数据包长度读出数据包内容;

    4、利用读取到的数据包的实际内容构造skb,并调用netif_rx()操作将skb送到上层协议层处理;

    数据包接收函数通过dm90_rx()来实现:

 dm9000_rx(struct net_device *dev)
{
board_info_t *db = netdev_priv(dev); //获得网卡私有数据首地址
struct dm9000_rxhdr rxhdr;
struct sk_buff *skb;
u8 rxbyte, *rdptr;
bool GoodPacket;
int RxLen; /* Check packet ready or not */
do {//存储器地址不变的读数据
ior(db, DM9000_MRCMDX); /* Dummy read */ //MRCMDX是内存数据预取读命令 /* Get most updated data */
rxbyte = readb(db->io_data); /* Status check: this byte must be 0 or 1 */ //0、1为正确,2表示接收出错
if (rxbyte & DM9000_PKT_ERR) {
dev_warn(db->dev, "status check fail: %d\n", rxbyte);
iow(db, DM9000_RCR, 0x00); /* Stop Device */ //关闭设备 并停止中断请求
iow(db, DM9000_ISR, IMR_PAR); /* Stop INT request */
return;
} if (!(rxbyte & DM9000_PKT_RDY)) // 0x01没准备好,直接返回
return; /* A packet ready now & Get status/length */
GoodPacket = true;
writeb(DM9000_MRCMD, db->io_addr);
//读取数据,从RX_SRAM读取到rxhdr中
(db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr)); RxLen = le16_to_cpu(rxhdr.RxLen); if (netif_msg_rx_status(db))
dev_dbg(db->dev, "RX: status %02x, length %04x\n",
rxhdr.RxStatus, RxLen); /* Packet Status check */ //检查包得完整性
if (RxLen < 0x40) {
GoodPacket = false;
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "RX: Bad Packet (runt)\n");
} if (RxLen > DM9000_PKT_MAX) {
dev_dbg(db->dev, "RST: RX Len:%x\n", RxLen);
} /* rxhdr.RxStatus is identical to RSR register. */
if (rxhdr.RxStatus & (RSR_FOE | RSR_CE | RSR_AE |
RSR_PLE | RSR_RWTO |
RSR_LCS | RSR_RF)) {
GoodPacket = false;
if (rxhdr.RxStatus & RSR_FOE) {
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "fifo error\n");
dev->stats.rx_fifo_errors++;
}
if (rxhdr.RxStatus & RSR_CE) {
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "crc error\n");
dev->stats.rx_crc_errors++;
}
if (rxhdr.RxStatus & RSR_RF) {
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "length error\n");
dev->stats.rx_length_errors++;
}
} /* Move data from DM9000 */ //从DM9000获取数据
if (GoodPacket && //分配SKB
((skb = dev_alloc_skb(RxLen + )) != NULL)) { //+4是因为除了数据包前面还有空读一字节、读状态一字节、读长度低位和高位各一个字节
skb_reserve(skb, ); //定位data指针 DA(6)+SA(6)+type(2)+IP包+CFS(校验码4字节) IP包要求四字节对齐
rdptr = (u8 *) skb_put(skb, RxLen - ); //定位tail指针 -4是减去校验码 /* Read received packet from RX SRAM */
//读取数据 从RX SRAM 到sk_buff中
(db->inblk)(db->io_data, rdptr, RxLen);
dev->stats.rx_bytes += RxLen; /* Pass to upper layer */ //获取上层协议类型
skb->protocol = eth_type_trans(skb, dev);
if (db->rx_csum) {
if ((((rxbyte & 0x1c) << ) & rxbyte) == )
skb->ip_summed = CHECKSUM_UNNECESSARY;
else
skb->ip_summed = CHECKSUM_NONE;
}
netif_rx(skb); //把数据包交给上层
dev->stats.rx_packets++; } else {
/* need to dump the packet's data */ (db->dumpblk)(db->io_data, RxLen);
}
} while (rxbyte & DM9000_PKT_RDY);
}

  数据包发送的软件流程时序具体如下:

    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传递给上层协议,实现如下:

 static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
board_info_t *db = netdev_priv(dev);
int int_status;
unsigned long flags;
u8 reg_save; dm9000_dbg(db, , "entering %s\n", __func__); /* A real interrupt coming */ /* holders of db->lock must always block IRQs */
spin_lock_irqsave(&db->lock, flags); /* Save previous register address */
reg_save = readb(db->io_addr); /* Disable all interrupts */
iow(db, DM9000_IMR, IMR_PAR); /* Got DM9000 interrupt status */ //获取中断类型
int_status = ior(db, DM9000_ISR); /* Got ISR */
iow(db, DM9000_ISR, int_status); /* Clear ISR status */ if (netif_msg_intr(db))
dev_dbg(db->dev, "interrupt status %02x\n", int_status); /* Received the coming packet */ //接收到一个数据包
if (int_status & ISR_PRS)
dm9000_rx(dev); //调用数据接收函数 /* Trnasmit Interrupt check */ //发送一个数据包
if (int_status & ISR_PTS)
dm9000_tx_done(dev, db); //调用数据发送函数 if (db->type != TYPE_DM9000E) {
if (int_status & ISR_LNKCHNG) {
/* fire a link-change request */
schedule_delayed_work(&db->phy_poll, );
}
} /* Re-enable interrupt mask */
iow(db, DM9000_IMR, db->imr_all); /* Restore previous register address */
writeb(reg_save, db->io_addr); spin_unlock_irqrestore(&db->lock, flags); return IRQ_HANDLED;
}

4.添加DM9000平台设备资源

5.内核配置添加DM9000支持

   重新配置内核,加入DM9000的驱动支持,在内核目录下执行“make menuconfig”命令进行如下的配置: 
Device Drivers---> 
      [*]Network device support---> 
             [*]Ethernet(10 or 100Mbit)---> 
                     <*>DM9000 support 
[*]Networking support---> 
      Networking options---> 
             <*>TCP/IP networking 
             <*>IP:kernel leel autoconfiguration 
//增加对nfs的支持 
File systems---> 
      [*]Networking File Systems---> 
             <*>NFS client support 
             [*]NFS client support for NFS version 3 
             [*]NFS client support for the NFSv3 ACL protocol extension 
             [*]Boot file system on NFS 
             [*]NFS server support

6.实验验证测试

  通过ping命令测试,如下图:

DM9000驱动移植在mini2440(linux2.6.29)和FS4412(linux3.14.78)上的实现(deep dive)篇一的更多相关文章

  1. LCD驱动移植在在mini2440(linux2.6.29)和FS4412(linux3.14.78)上实现对比(deep dive)

    1.Linux帧缓冲子系统 帧缓冲(FrameBuffer)是Linux为显示设备提供的一个接口,用户可以将帧缓冲看成是显示内存的一种映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作 ...

  2. IIC驱动移植在linux3.14.78上的实现和在linux2.6.29上实现对比(deep dive)

    首先说明下为什么写这篇文章,网上有许多博客也是介绍I2C驱动在linux上移植的实现,但是笔者认为他们相当一部分没有分清所写的驱动时的驱动模型,是基于device tree, 还是基于传统的Platf ...

  3. SPI在linux3.14.78 FS_S5PC100(Cortex A8)和S3C2440上驱动移植(deep dive)

    由于工作的原因,对SPI的理解最为深刻,也和SPI最有感情了,之前工作都是基于OSEK操作系统上进行实现,也在US/OS3上实现过SPI驱动的实现和测试,但是都是基于基本的寄存器操作,没有一个系统软件 ...

  4. Linux网卡驱动移植--Dm9000网卡驱动分析

    1. Linux网络体系结构由以下5部分组成 ① 系统调用接口: 位于Linux网络子系统的顶部,为应用程序提供访问内核网络子系统的方法,主要指socket系统调用. ② 协议无关接口: 实现一组基于 ...

  5. 【Linux驱动】TQ2440 DM9000E网卡驱动移植(Linux-2.6.30.4)

    花了一天的时间研究了一下Linux-2.6.30.4版本号内核下关于TQ2440 DM9000E的网卡驱动移植.总结一下自己的收获. 事实上.在Linux-2.6.30.4版本号内核下有关于网卡驱动, ...

  6. Mini2440 DM9000 驱动分析(一)

    Mini2440 DM9000 驱动分析(一) 硬件特性 Mini2440开发板上DM9000的电气连接和Mach-mini2440.c文件的关系: PW_RST 连接到复位按键,复位按键按下,低电平 ...

  7. 【转】基于linux下的dm9000网卡移植全分析

    转自:http://blog.sina.com.cn/s/blog_6abf2c04010189ui.html DM9000可以直接与ISA总线相连,也可以与大多数CPU直接相连.Mini2440采用 ...

  8. wifi 驱动移植范例

    .改Makefile:  里面没有dm6441平台的,我看到有dm6446的,所以就在这里改了 ifeq ($(PLATFORM),DM6446) LINUX_SRC = /root/work/lin ...

  9. linux网卡驱动移植

    这里重要的是物理层PHY receiver,MAC(media access control)层,这里与软件中的协议栈不同,在硬件上MAC是PHY的下一层.DM9000A将MAC和PHY做到一起,也可 ...

随机推荐

  1. 【AR实验室】ARToolKit之Example篇

    0x00 - 前言 PS : 我突然意识到ARToolKit本质可能就是一个可以实时求解相机内外参的解决方案. 拿到一个新的SDK,90%的人应该都会先跑一下Example.拿到ARToolKit的S ...

  2. 测试一下StringBuffer和StringBuilder及字面常量拼接三种字符串的效率

    之前一篇里写过字符串常用类的三种方式<java中的字符串相关知识整理>,只不过这个只是分析并不知道他们之间会有多大的区别,或者所谓的StringBuffer能提升多少拼接效率呢?为此写个简 ...

  3. 一次修改闭源 Entity Provider 程序集以兼容新 EntityFramework 的过程

    读完本文你会知道,如何在没有源码的情况下,直接修改一个 DLL 以去除 DLL 上的强命名限制,并在该程序集上直接添加你的“友元程序集(一种特殊的 Attribute,将它应用在程序集上,使得程序集内 ...

  4. premere cs4绿色版 安装 并且 视频导出 讲解

    最近室友,开始在玩视频剪辑,用的是 premere cs4 绿色版.让他遇到的最大问题也是我之前遇到的最大问题,就是视频导出. 所以我在这里上传一套自己的一点点经验吧. 接下来,我就总结一下 我是怎么 ...

  5. PHP-自定义模板-学习笔记

    1.  开始 这几天,看了李炎恢老师的<PHP第二季度视频>中的“章节7:创建TPL自定义模板”,做一个学习笔记,通过绘制架构图.UML类图和思维导图,来对加深理解. 2.  整体架构图 ...

  6. C++的内存泄漏检测

    C++大量的手动分配.回收内存是存在风险的,也许一个函数中一小块内存泄漏被重复放大之后,最后掏空内存. 这里介绍一种在debug模式下测试内存泄漏的方法. 首先在文件的开头以确定的顺序写下这段代码: ...

  7. 【NLP】蓦然回首:谈谈学习模型的评估系列文章(一)

    统计角度窥视模型概念 作者:白宁超 2016年7月18日17:18:43 摘要:写本文的初衷源于基于HMM模型序列标注的一个实验,实验完成之后,迫切想知道采用的序列标注模型的好坏,有哪些指标可以度量. ...

  8. HTML 5 应用程序缓存manifest

    什么是应用程序缓存(Application Cache)? HTML5 引入了应用程序缓存,这意味着 web 应用可进行缓存,并可在没有因特网连接时进行访问. 应用程序缓存为应用带来三个优势: 离线浏 ...

  9. Win7安装MySQL-5.7.16过程

    1.在C盘新建MYSQL文件夹:2.将mysql-5.7.16-winx64拷贝到C:\MYSQL文件夹下,更名为mysql-5.7.16:3.在mysql-5.7.16目录下,建my.ini文件,内 ...

  10. Spring Batch在大型企业中的最佳实践

    在大型企业中,由于业务复杂.数据量大.数据格式不同.数据交互格式繁杂,并非所有的操作都能通过交互界面进行处理.而有一些操作需要定期读取大批量的数据,然后进行一系列的后续处理.这样的过程就是" ...