关于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. Elasticsearch 5.0 中term 查询和match 查询的认识

    Elasticsearch 5.0 关于term query和match query的认识 一.基本情况 前言:term query和match query牵扯的东西比较多,例如分词器.mapping ...

  2. CI Weekly #10 | 2017 DevOps 趋势预测

    2016 年的最后几个工作日,我们对 flow.ci Android & iOS 项目做了一些优化与修复: iOS 镜像 cocoapods 版本更新: fir iOS上传插件时间问题修复: ...

  3. Android注解使用之注解编译android-apt如何切换到annotationProcessor

    前言: 自从EventBus 3.x发布之后其通过注解预编译的方式解决了之前通过反射机制所引起的性能效率问题,其中注解预编译所采用的的就是android-apt的方式,不过最近Apt工具的作者宣布了不 ...

  4. SDWebImage源码解读 之 NSData+ImageContentType

    第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...

  5. [原]Cachedb 网络模块文档

    Cachedb 网络模块文档 整体结构 多路复用 (epoll 模块) 事件驱动 (事件封装) 缓冲管理 (上层buffer管理) 设计思想 层次化的设计,每一个模块只调用上一个模块的接口,并将耦合聚 ...

  6. IT运维监控解决方案介绍

    现状 •小公司/ 创业团队< 500台服务器规模 开源方案:Zabbix.Nagios.Cacti- 云服务提供商:监控宝.oneAlert等 •BAT级别> 10万台服务器 投 ...

  7. 15个C++项目列表

    实验楼上有很多C++的实战项目,从简单到进阶,学习每个项目都可以掌握相应的知识点. 如果你还是C++新手的话,那么这个C++的项目列表你可以拿去练手实战开发,毕竟学编程动手实践是少不了的! 如果你不知 ...

  8. mac好用的markdown编辑器

    在刚开始接触markdown的时候,就被吸引了.此后一直在找贴心的好用的markdown编辑器.印象笔记和马克飞象配合着用也是挺好的,唯一的缺点就是比较封闭,发个笔记的链接给同学,还得注册才能看,导致 ...

  9. 使用Hystrix提高系统可用性

    今天稍微复杂点的互联网应用,服务端基本都是分布式的,大量的服务支撑起整个系统,服务之间也难免有大量的依赖关系,依赖都是通过网络连接起来. (图片来源:https://github.com/Netfli ...

  10. C#与yaml解析

    YAML 官方网站称 YAML 是"一种所有编程语言可用的友好的数据序列化标准".YAML Ain't Markup Language,和GNU一样,YAML是一个递归着说&quo ...