基于S3C2440的Linux-3.6.6移植——DM9000网卡驱动移植

时间:2021-11-11 10:02:52

Linux-3.6.6很好的支持了DM9000,因此对于S3C2440芯片来说无需进行任何修改,甚至连menuconfig都已经默认配置了网卡驱动。但我们还需要设置网卡的MAC和IP等信息。有许多方法可以实现网卡的设置,在这里我们选择一种比较简单的方法——修改根文件系统的启动脚本文件rcS。在根文件etc/init.d/rcS文件中添加下列语句:

/sbin/ifconfig  lo  127.0.0.1

/sbin/ifconfig  eth0  hw ether  5e:f7:90:82:66:28

/sbin/ifconfig  eth0  192.168.1.234  up

route  add  default  gw  192.168.1.1

这样就完成了网卡的移植。我们测试一下网卡。在启动开发板时,系统会打印出类似下列信息:

dm9000 dm9000: eth0: link down

dm9000 dm9000: eth0: link up, 100Mbps,full-duplex, lpa 0xCDE1

如果在提示符下输入ifconfig命令,则:

[root@zhaocj /]#ifconfig

eth0     Link encap:Ethernet  HWaddr5E:F7:90:82:66:28

         inet addr:192.168.1.234 Bcast:192.168.1.255 Mask:255.255.255.0

         UP BROADCAST RUNNING MULTICAST MTU:1500  Metric:1

         RX packets:51 errors:0 dropped:0 overruns:0 frame:0

         TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

         collisions:0 txqueuelen:1000

         RX bytes:5324 (5.1 KiB)  TXbytes:0 (0.0 B)

         Interrupt:51 Base address:0x2300

 

lo       Link encap:Local Loopback

         inet addr:127.0.0.1 Mask:255.0.0.0

         UP LOOPBACK RUNNING MTU:16436  Metric:1

         RX packets:0 errors:0 dropped:0 overruns:0 frame:0

         TX packets:0 errors:0 dropped:0 overruns:0 carrier:0

         collisions:0 txqueuelen:0

         RX bytes:0 (0.0 B)  TX bytes:0(0.0 B)

另外主机和开发板都可以互相ping通对方。

 

下面我们就简单介绍一下DM9000网卡驱动。

在arch/arm/mach-s3c24xx/mach-zhaocj2440.c文件内定义了DM9000网卡平台设备:

static struct resource zhaocj2440_dm9k_resource[]= {

       [0]= DEFINE_RES_MEM(MACH_ZHAOCJ2440_DM9K_BASE, 4),

       [1]= DEFINE_RES_MEM(MACH_ZHAOCJ2440_DM9K_BASE + 4, 4),

       [2]= DEFINE_RES_NAMED(IRQ_EINT7, 1, NULL, IORESOURCE_IRQ \

                                          |IORESOURCE_IRQ_HIGHEDGE),

};

 

/*

 *The DM9000 has no eeprom, and it's MAC address is set by

 *the bootloader before starting the kernel.

 */

static struct dm9000_plat_data zhaocj2440_dm9k_pdata= {

       .flags             = (DM9000_PLATF_16BITONLY |DM9000_PLATF_NO_EEPROM),

};

 

static struct platform_device zhaocj2440_device_eth= {

       .name             = "dm9000",

       .id          = -1,

       .num_resources      = ARRAY_SIZE(zhaocj2440_dm9k_resource),

       .resource =zhaocj2440_dm9k_resource,

       .dev        ={

              .platform_data       = &zhaocj2440_dm9k_pdata,

       },

};

 

并把网卡平台设备添加进平台设备数组内:

static structplatform_device *zhaocj2440_devices[] __initdata = {

       ……

       &zhaocj2440_device_eth,

       ……

};

 

在drivers/net/ethernet/davicom/dm9000.c文件内定义了DM9000网卡平台驱动:

static struct platform_driver dm9000_driver= {

       .driver     = {

              .name    = "dm9000",

              .owner    = THIS_MODULE,

              .pm  = &dm9000_drv_pm_ops,

       },

       .probe   = dm9000_probe,

       .remove  = __devexit_p(dm9000_drv_remove),

};

 

下面分析一下探测函数dm9000_probe:

static int__devinit

dm9000_probe(structplatform_device *pdev)

{

       struct dm9000_plat_data *pdata =pdev->dev.platform_data;

       struct board_info*db;    /* Point a board informationstructure */

       structnet_device *ndev;

       constunsigned char *mac_src;

       intret = 0;

       intiosize;

       inti;

       u32id_val;

 

       /*Init network device */

       //初始化网络设备结构体net_device

       ndev= alloc_etherdev(sizeof(struct board_info));

       if(!ndev)

              return-ENOMEM;

 

       SET_NETDEV_DEV(ndev,&pdev->dev);

 

       dev_dbg(&pdev->dev,"dm9000_probe()\n");

 

       /*setup board info structure */

       //设置板块信息结构体——db

       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);

       //提取出DM9000网卡资源,即zhaocj2440_dm9k_resource

       db->addr_res= platform_get_resource(pdev, IORESOURCE_MEM, 0);   //地址

       db->data_res= platform_get_resource(pdev, IORESOURCE_MEM, 1);   //数据

       db->irq_res  = platform_get_resource(pdev, IORESOURCE_IRQ,0);     //中断

 

       if(db->addr_res == NULL || db->data_res == NULL ||

           db->irq_res == NULL) {

              dev_err(db->dev,"insufficient resources\n");

              ret =-ENOENT;

              gotoout;

       }

       //获取网卡中断资源

       db->irq_wake= platform_get_irq(pdev, 1);

       if(db->irq_wake >= 0) {

              dev_dbg(db->dev,"wakeup irq %d\n", db->irq_wake);

              //申请网卡中断,中断函数为dm9000_wol_interrupt

              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= irq_set_irq_wake(db->irq_wake, 1);

                     if(ret) {

                            dev_err(db->dev,"irq %d cannot set wakeup (%d)\n",

                                   db->irq_wake, ret);

                            ret = 0;

                     } else {

                            irq_set_irq_wake(db->irq_wake,0);

                            db->wake_supported = 1;

                     }

              }

       }

       //申请网卡地址所需的内存空间

       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;

              gotoout;

       }

       //地址内存空间映射

       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;

              gotoout;

       }

       //申请网卡数据所需的内存空间

       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;

              gotoout;

       }

       //数据内存空间映射

       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;

              gotoout;

       }

 

       /*fill in parameters for net-dev structure */

       //设置网络设备结构体——ndev

       ndev->base_addr= (unsigned long)db->io_addr;

       ndev->irq       = db->irq_res->start;

 

       /*ensure at least we have a default set of IO routines */

       //确保至少有一种IO路由方式,即先设置一种缺省的IO路由

       dm9000_set_io(db,iosize);

 

       /*check to see if anything is being over-ridden */

       /*检查是否可以替代刚才设置的IO路由,由于我们在zhaocj2440_dm9k_pdata结构体内的flags元素定义了DM9000_PLATF_16BITONLY,所以缺省的IO路由方式会被替代,即执行dm9000_set_io(db, 2);*/

       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,1);

 

              if(pdata->flags & DM9000_PLATF_16BITONLY)

                     dm9000_set_io(db,2);

 

              if(pdata->flags & DM9000_PLATF_32BITONLY)

                     dm9000_set_io(db,4);

 

              /*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 */

       //多次读取DM9000的ID,以确保能够正确识别DM9000

       for (i = 0; i < 8; i++) {

              id_val  = ior(db, DM9000_VIDL);

              id_val |= (u32)ior(db,DM9000_VIDH) << 8;

              id_val |= (u32)ior(db, DM9000_PIDL)<< 16;

              id_val |=(u32)ior(db, DM9000_PIDH) << 24;

              //判断读取的值是否为0x90000A46

              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;

              gotoout;

       }

 

       /*Identify what type of DM9000 we are working on */

       //读取CHIPR寄存器,获得type信息

       id_val= ior(db, DM9000_CHIPR);

       dev_dbg(db->dev,"dm9000 revision 0x%02x\n", id_val);

 

       switch(id_val) {

       caseCHIPR_DM9000A:

              db->type= TYPE_DM9000A;

              break;

       caseCHIPR_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 arecapable of hardware checksum offload */

       if(db->type == TYPE_DM9000A|| db->type == TYPE_DM9000B) {

              ndev->hw_features= NETIF_F_RXCSUM | NETIF_F_IP_CSUM;

              ndev->features|= ndev->hw_features;

       }

 

       /*from this point we assume that we have found a DM9000 */

 

       /*driver system function */

       //设置以太网卡设备,即把有关以太网设备的数据赋值给ndev

       ether_setup(ndev);

 

       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  = 0;

       db->mii.full_duplex  = 0;

       db->mii.dev         =ndev;

       db->mii.mdio_read    = dm9000_phy_read;

       db->mii.mdio_write   = dm9000_phy_write;

 

       //通过不同方式读取网卡的MAC

       mac_src= "eeprom";

 

       /*try reading the node address from the attached EEPROM */

       //从eeprom中读取MAC

       for(i = 0; i < 6; i += 2)

              dm9000_read_eeprom(db,i / 2, ndev->dev_addr+i);

       /*如果从eeprom中读取MAC失败,则从平台设备中读取MAC,即提取出zhaocj2440_dm9k_pdata结构中的dev_addr数组元素*/

       if(!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) {

              mac_src= "platform data";

              memcpy(ndev->dev_addr,pdata->dev_addr, 6);

       }

       /*如果到目前为止,还没有读取到正确的MAC,则从DM9000芯片的PAR寄存器内提取出MAC*/

       if(!is_valid_ether_addr(ndev->dev_addr)) {

              /*try reading from mac */

             

              mac_src= "chip";

              for (i = 0; i < 6; i++)

                     ndev->dev_addr[i] =ior(db, i+DM9000_PAR);

       }

       /*如果上述方法都不行,则MAC值最终是要通过ifconfig来确定。但由于现在还没有执行ifconfig命令,因此系统在启动初始化的过程中,要随机分配MAC。*/

       if(!is_valid_ether_addr(ndev->dev_addr)) {

              dev_warn(db->dev,"%s: Invalid ethernet MAC address. Please "

                      "set using ifconfig\n",ndev->name);

 

              eth_hw_addr_random(ndev);

              mac_src= "random";

       }

 

       //把ndev保存为平台设备pdev的私有数据,以后可以用platform_get_drvdata获取该数据

       platform_set_drvdata(pdev,ndev);

       //注册网络设备

       ret= register_netdev(ndev);

       //在系统启动过程中,打印网卡相关数据

       if(ret == 0)

              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);

       return0;

 

out:

       dev_err(db->dev,"not found (%d).\n", ret);

 

       dm9000_release_board(pdev,db);

       free_netdev(ndev);

 

       returnret;

}

 

在dm9000_probe函数内定义了dm9000_netdev_ops:

static const struct net_device_opsdm9000_netdev_ops = {

       .ndo_open             = dm9000_open,           //打开设备

       .ndo_stop              = dm9000_stop,            //关闭设备

       .ndo_start_xmit             = dm9000_start_xmit,           //开始发送数据

       .ndo_tx_timeout            = dm9000_timeout,              //发送超时

       .ndo_set_rx_mode  = dm9000_hash_table,          //设定多播列表

       .ndo_do_ioctl         = dm9000_ioctl,            //io操作函数

       .ndo_change_mtu          = eth_change_mtu,        //改变mtu

       .ndo_set_features    = dm9000_set_features,

       .ndo_validate_addr = eth_validate_addr,

       .ndo_set_mac_address    = eth_mac_addr,

#ifdef CONFIG_NET_POLL_CONTROLLER

       .ndo_poll_controller      = dm9000_poll_controller,

#endif

};

 

我们分析几个重要的回调函数。

当执行ifconfig命令时,会调用dm9000_open函数,以打开网络接口

static int

dm9000_open(struct net_device *dev)

{

       board_info_t*db = netdev_priv(dev);

       unsignedlong 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 aproblem */

 

       if(irqflags == IRQF_TRIGGER_NONE)

              dev_warn(db->dev,"WARNING: no IRQ resource flags set.\n");

 

       irqflags|= IRQF_SHARED;

 

       /*GPIO0 on pre-activate PHY, Reg 1Fis not set by reset */

       //将GPR寄存器的第0位清零,激活内部PHY

       iow(db,DM9000_GPR, 0);   /* REG_1F bit0 activate phyxcer */

       mdelay(1);/* delay needs by DM9000B */

 

       /*Initialize DM9000 board */

       dm9000_reset(db);        //复位DM9000

       //初始化DM9000,主要就是配置DM9000的内部寄存器

       dm9000_init_dm9000(dev);        

       //注册中断,中断函数为dm9000_interrupt,该中断负责收发数据

       if(request_irq(dev->irq, dm9000_interrupt, irqflags, dev->name, dev))

              return-EAGAIN;

 

       /*Init driver variable */

       db->dbug_cnt= 0;

       //检测mii接口的状态

       mii_check_media(&db->mii,netif_msg_link(db), 1);

       //允许上层协议使用该设备,开启发送队列

       netif_start_queue(dev);

       /*在dm9000_probe函数中,使用INIT_DELAYED_WORK初始化了一个工作队列,现在就调用它,即执行dm9000_poll_work函数,用以检测并显示网络信息*/

       dm9000_schedule_poll(db);

 

       return 0;

}

 

关闭网卡设备函数dm9000_stop:

static int

dm9000_stop(struct net_device *ndev)

{

       board_info_t*db = netdev_priv(ndev);

 

       if(netif_msg_ifdown(db))

              dev_dbg(db->dev,"shutting down %s\n", ndev->name);

       //终止phy_poll队列中被延迟的任务

       cancel_delayed_work_sync(&db->phy_poll);

       //关闭发送队列

       netif_stop_queue(ndev);

       //载波丢失

       netif_carrier_off(ndev);

 

       /*free interrupt */

       free_irq(ndev->irq,ndev);            //释放中断

 

       dm9000_shutdown(ndev);            //关闭网卡

 

       return0;

}

 

发送数据函数dm9000_start_xmit:

static int

dm9000_start_xmit(struct sk_buff *skb,struct net_device *dev)

{

       unsignedlong flags;

       board_info_t*db = netdev_priv(dev);

 

       dm9000_dbg(db,3, "%s:\n", __func__);

 

       if(db->tx_pkt_cnt > 1)

              returnNETDEV_TX_BUSY;

       //获得自旋锁

       spin_lock_irqsave(&db->lock,flags);

 

       /*Move data to DM9000 TX RAM */

       //根据io操作数据宽度,来设置MWCMD,即填充数据完毕后指针加1还是加2

       writeb(DM9000_MWCMD,db->io_addr);

       //将数据从sk_buff中复制到DM9000内部发送RAM(TX_Buffer)中

       (db->outblk)(db->io_data,skb->data, skb->len);

       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) {

              //计数值为1,立即发送

              dm9000_send_packet(dev,skb->ip_summed, skb->len);

       } else {

              /* Secondpacket */

              //如果是第2个数据包,加入队列以后,停止发送

              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);

 

       returnNETDEV_TX_OK;

}

 

dm9000_start_xmit函数只负责发送一个数据包,那么如何发送第二个数据包呢?网卡又如何接收数据呢?还记得在dm9000_open函数内,申请了一个中断,中断函数为dm9000_interrupt,该中断就是负责接收数据和发送多个数据包的任务:

static irqreturn_t dm9000_interrupt(intirq, 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, 3, "entering%s\n", __func__);

 

       /* A realinterrupt 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);       /* ClearISR 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,1);

              }

       }

 

       /*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);

 

       returnIRQ_HANDLED;

}

 

从上面函数的分析可以看出,当是接收数据触发的中断时,会调用dm9000_rx函数来接收数据,然后调用netif_rx函数把数据传输到上层网络协议中;而当一个数据包发送完毕后也会触发该中断,并调用dm9000_tx_done函数。在该函数内,如果判断还有待发送的数据则调用执行dm9000_send_packet函数再次发送数据。例如我们要发送两个数据包,则首先调用dm9000_start_xmit函数发送第一个数据包,当发送完毕后触发中断dm9000_interrupt,并调用dm9000_tx_done函数发送第二个数据包,至此两个数据包都发送了出去。