ITKeyword,专注技术干货聚合推荐

注册 | 登录

Linux驱动学习——网络接口DM9000驱动学习 mini2440

yanhc519 分享于

2020腾讯云10周年活动,优惠非常大!(领取2860元代金券),
地址https://cloud.tencent.com/act/cps/redirect?redirect=1040

2020阿里云最低价产品入口,含代金券(新老用户有优惠),
地址https://www.aliyun.com/minisite/goods

推荐:嵌入式Linux之我行——深入理解DM9000在mini2440上的驱动

嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便。如有错误之处,谢请指正

网络接口DM9000驱动学习:

/drivers/input/touchscreen/s3c2410_ts.c
/drivers/input/s3c2410_ts.c

参考:

http://blogold.chinaunix.net/u3/118227/showart_2353723.html
http://blog.csdn.net/ypoflyer/archive/2011/02/26/6209922.aspx

等其他网络资料
首先看一下DM9000的引脚和MINI2440的引脚连接

DM9000  MINI2440 功能描述
SD0   DATA0  数据信号
 |      |
SD15  DATA15  数据信号
CMD  ADDR2  识别为地址还是数据
INT   EINT7  中断
IOR#   nOE   读命令使能
IOW#  nWE   写命令使能
AEN   nGCS4  片选使能

可以看出连接了16条数据线,1条地址线,而这唯一的一条地址线用于判断数据线传输的是地址还是数据,所以这16条数据线为数据和地址复用

而片选信号使用的BANK4,则访问0x2000 0000 – 0x27FF FFFF这个范围的地址时会激活片选使能信号nGCS4

而在MINI2440提供的内核中,DM9000的地址IO地址为0x20000000,数据IO为0x2000 0004

一、Mini2440开发板上DM9000的电气连接和Mach-mini2440.c文件的关系
Mini2440开发板上DM9000与S3C2440的连接关系如下:  
 

其中片选信号AEN使用了nGCS4,所以网卡的内存区域在BANK4,也就是从地址0x20000000开始。DM9000的TXD[2:0]作为strap pin(表带引脚)在电路图中是空接的,所以IO base是300H。中断使用了EINT7。

/arch/arm/plat-s3c24xx/devs.c

/* DM9000 */
static struct resource s3c_dm9k_resource[]= {
    [0] = {
        .start =S3C2410_CS4,
       .end   = S3C2410_CS4 + 3,       //大小size是3个字节,为什么啊?
        .flags =IORESOURCE_MEM,
    },
    [1] = {
        .start =S3C2410_CS4 + 4,
       .end   = S3C2410_CS4 + 4 + 3,
        .flags =IORESOURCE_MEM,
    },
    [2] = {
        .start =IRQ_EINT7,
       .end   = IRQ_EINT7,
        .flags =IORESOURCE_IRQ | IRQF_TRIGGER_RISING,
    }
};

             /arch/arm/mach-s3c2410/include/mach/map.h

/* physicaladdresses of all the chip-select areas */

#defineS3C2410_CS0 (0x00000000)
#defineS3C2410_CS1 (0x08000000)
#defineS3C2410_CS2 (0x10000000)
#defineS3C2410_CS3 (0x18000000)
#define S3C2410_CS4 (0x20000000)
#defineS3C2410_CS5 (0x28000000)
#defineS3C2410_CS6 (0x30000000)
#defineS3C2410_CS7 (0x38000000)
/* for the moment we limit ourselves to 16bit IO untilsome
 * better IOroutines can be written and tested
*/
static struct dm9000_plat_datas3c_dm9k_platdata = {
    .flags      = DM9000_PLATF_16BITONLY,
};

struct platform_devices3c_device_dm9k = {
    .name       = "dm9000",
    .id     = 0,
   .num_resources  =ARRAY_SIZE(s3c_dm9k_resource),  //算出单元的个数,3
   .resource   = s3c_dm9k_resource,
    .dev        = {
       .platform_data = &s3c_dm9k_platdata,
    }
};

EXPORT_SYMBOL(s3c_device_dm9k);
       platform_device 结构体定义如下:
struct platform_device {
       const char      * name;
       int           id;
       struct device  dev;
       u32        num_resources;
       struct resource      * resource;
};
Probe函数中获取地址和数据资源 ,探测,检测网口是否在线。
       db->addr_res= platform_get_resource(pdev, IORESOURCE_MEM, 0); //MEM中的第一个资源
       db->data_res= platform_get_resource(pdev, IORESOURCE_MEM, 1); //MEM中的第二个资源
       db->irq_res  = platform_get_resource(pdev, IORESOURCE_IRQ,0); //IRQ中的第一个资源
 platform_get_resource
/**
 * platform_get_resource - get a resource for adevice
 * @dev: platform device
 * @type: resource type
 * @num: resource index
 */
struct resource *platform_get_resource(structplatform_device *dev,
                                   unsigned int type, unsigned int num)
{
       int i;

       for (i = 0; i < dev->num_resources;i++) {
              struct resource *r =&dev->resource[i];
              if (type == resource_type(r)&& num-- == 0) //某一类type资源中的第num个资源
                     return r;
       }
       return NULL;
}

二、两个重要的结构体简单介绍:sk_buff和net_device

   *sk_buff

   如果把网络传输看成是运送货物的话,那么sk_buff就是这个“货物”了,所有经手这个货物的人都要干点什么事儿,要么加个包装,要么印个戳儿等等。收 货的时候就要拆掉这些包装,得到我们需要的货物(payload data)。没有货物你还运输什么呢?由此可见sk_buff的重要性了。关于sk_buff的详细介绍和几个操作它的函数,参考本博客转载的一篇文 章:“linux内核sk_buff的结构分析”,写得非常明白了。赞一个~

  *net_device

   又是一个庞大的结构体。好吧,我承认我从来就没有看全过这个结构体。它在内核中就是指代了一个网络设备。驱动程序需要在探测的时候分配并初始化这个结构体,然后使用register_netdev来注册它,这样就可以把操作硬件的函数与内核挂接在一起。

定义一个platform_driver结构,该结构类似普通char类型驱动中的fops

static struct platform_driver dm9000_driver= {
       .driver    = {
              .name    = "dm9000",
              .owner    = THIS_MODULE,
       },
       .probe   = dm9000_probe,
       .remove  = __devexit_p(dm9000_drv_remove),
       .suspend =dm9000_drv_suspend,
       .resume  = dm9000_drv_resume,
};

该结构的定义如下/include/linux/platform_device.h:

struct platform_driver {
       int(*probe)(struct platform_device *);
       int(*remove)(struct platform_device *);
       void(*shutdown)(struct platform_device *);
       int(*suspend)(struct platform_device *, pm_message_t state);
       int(*suspend_late)(struct platform_device *, pm_message_t state);
       int(*resume_early)(struct platform_device *);
       int(*resume)(struct platform_device *);
       structdevice_driver driver;
};

/**
 * platform_get_resource - get a resource for adevice
 * @dev: platform device
 * @type: resource type
 * @num: resource index
 */
struct resource *platform_get_resource(structplatform_device *dev,
                                   unsigned int type, unsigned int num)
{
       int i;
       for (i = 0; i < dev->num_resources;i++) {
              struct resource *r =&dev->resource[i];
              if (type == resource_type(r)&& num-- == 0) //某一类type资源中的第num个资源
                     return r;
       }
       return NULL;
}

dm9000_ethtool_ops变量。是ethtool_ops结构体变量,为了支持ethtool,其中的函数主要是用于查询和设置网卡参数(当然也有的驱动程序可能不支持ethtool)。代码清单如下:

static const struct ethtool_opsdm9000_ethtool_ops = {
       .get_drvinfo          = dm9000_get_drvinfo,
       .get_settings          = dm9000_get_settings,
       .set_settings          = dm9000_set_settings,
       .get_msglevel        = dm9000_get_msglevel,
       .set_msglevel        = dm9000_set_msglevel,
       .nway_reset          = dm9000_nway_reset,
       .get_link        = dm9000_get_link,
      .get_eeprom_len          = dm9000_get_eeprom_len,
      .get_eeprom         =dm9000_get_eeprom,
      .set_eeprom         =dm9000_set_eeprom,
};

Probe函数,探测,检测网口是否在线。

   主要完成的任务是:探测设备获得并保存资源信息,根据这些信息申请内存和中断,最后调用register_netdev注册这个网络设备。以下是代码清单,可以分成几个部分来看:

//Search DM9000 board, allocate space and register it
static int __devinit
dm9000_probe(struct platform_device *pdev)
{
       structdm9000_plat_data *pdata = pdev->dev.platform_data;
       structboard_info *db; /* Point a boardinformation structure */
       struct net_device *ndev;
       constunsigned char *mac_src;

   1) 首先定义了几个局部变量:

        struct dm9000_plat_data *pdata = pdev->dev.platform_data;
         struct board_info *db; /*Point a board information structure */
         struct net_device *ndev;
   2) 初始化一个网络设备。关键系统函数:alloc_etherdev()

       /* Initnetwork device */
       ndev = alloc_etherdev(sizeof(structboard_info));

   2.1)将platform_devicenet_device关联起来

      SET_NETDEV_DEV(ndev,&pdev->dev);
3) 获得资源信息并将其保存在board_info变量db中。关键系统函数:netdev_priv(), platform_get_resource()

使用INIT_DELAYED_WORK来初始化一个延迟工作队列并关联了一个操作函数m9000_poll_work()

      /* setupboard info structure */
       db = netdev_priv(ndev);
       memset(db,0, sizeof(*db));
 
       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);

       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);
   4) 根据资源信息分配内存,申请中断等等,并将申请后的资源信息也保存到db中,并且填充ndev中的参数。 关键系统函数:request_mem_region(), ioremap()。 自定义函数:dm9000_set_io()

       iosize =res_size(db->addr_res);
       db->addr_req = request_mem_region(db->addr_res->start,iosize, pdev->name);
db->io_addr = ioremap(db->addr_res->start, iosize);
iosize = res_size(db->data_res);
db->data_req = request_mem_region(db->data_res->start,iosize,pdev->name);
db->io_data = ioremap(db->data_res->start, iosize);
    
   /* fill in parameters for net-devstructure */
       ndev->base_addr= (unsigned long)db->io_addr;
       ndev->irq      = db->irq_res->start; 

       /* ensureat least we have a default set of IO routines */
       dm9000_set_io(db, iosize);     iosize表示字节数
       /* checkto see if anything is being over-ridden搁置?背离? */
       if (pdata!= NULL) {
              /*check to see if the driver wants to over-ride the
               * default IO width */

   5)完成了第4步以后,回顾一下db和ndev中都有了什么:

      struct board_info *db:
                 addr_res-- 地址资源
                data_res -- 数据资源
                irq_res    -- 中断资源
                addr_req -- 分配的地址内存资源
                 io_addr  -- 寄存器I/O基地址
                data_req -- 分配的数据内存资源
                io_data   -- 数据I/O基地址
                dumpblk  -- IO模式
                outblk     -- IO模式
                inblk        -- IO模式
                lock         -- 自旋锁(已经被初始化)
                addr_lock -- 互斥锁(已经被初始化)
       struct net_device *ndev:
                base_addr  -- 设备IO地址
                irq             -- 设备IRQ号

    6) 设备复位。硬件操作函数dm9000_reset()

static void
dm9000_reset(board_info_t * db)
{
       dev_dbg(db->dev,"resetting device/n");
       /* RESETdevice */
       writeb(DM9000_NCR,db->io_addr);
       udelay(200);
       writeb(NCR_RST,db->io_data);    // io_addr=io_data?
       udelay(200);
}

7) 读一下生产商和制造商的ID,VID和PID,应该是0x90000A46。 关键函数:ior()

              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;

    8) 读一下芯片类型。读取CHIPR,确定是DM900A-B-E,

/* Identify what type of DM9000 we areworking on */
       id_val =ior(db, DM9000_CHIPR);

    ========以上步骤结束后我们可以认为已经找到了DM9000========

     9)借助ether_setup()函数来部分初始化ndev。因为对以太网设备来讲,很多操作与属性是固定的,内核可以帮助完成。

   10) 手动初始化ndev的ops和db的mii部分。

11) (如果有的话)从EEPROM中读取节点地址。这里可以看到mini2440这个板子上没有为DM9000外挂EEPROM,所以读取出来的全部是 0xff。见函数dm9000_read_eeprom。 关于外挂EEPROM,可以参考datasheet上的7.EEPROM Format一节。

       /*try reading the node address from the attached EEPROM */
       for(i = 0; i < 6; i += 2)
              dm9000_read_eeprom(db,i / 2, ndev->dev_addr+i);

   12)  很显然ndev是我们在probe函数中定义的局部变量,如果我想在其他地方使用它怎么办呢?这就需要把它保存起来。内核提供了这个方法,使用函数platform_set_drvdata()可以将ndev保存成平台总线设备的私有数据。以后再要使用它时只需调用platform_get_drvdata()就可以了。

       platform_set_drvdata(pdev, ndev);

   13) 使用register_netdev()注册ndev。

       ret= register_netdev(ndev);

——————————————————————————————

dm9000_open

进行的工作有向内核注册中断,复位并初始化dm9000,检查MII接口,使能传输等。代码清单如下:

* Open the interface.
 *  Theinterface is opened whenever "ifconfig" actives it.
 */
static int
dm9000_open(struct net_device *dev)

向内核注册中断,中断服务函数为dm9000_interrupt

       if(request_irq(dev->irq, &dm9000_interrupt,irqflags, dev->name, dev))
              return-EAGAIN;

复位,初始化

       /*Initialize DM9000 board */
       dm9000_reset(db);
       dm9000_init_dm9000(dev);

dm9000_init_dm9000

*Initilize dm9000 board
 */
staticvoid
dm9000_init_dm9000(structnet_device *dev)

读取IO模式寄存器

       /* I/O mode */
       db->io_mode = ior(db, DM9000_ISR)>> 6; /* ISR bit7:6 keeps I/O mode*/

power up PHY,

       /* GPIO0 on pre-activate PHY */
       iow(db, DM9000_GPR, 0);      /* REG_1F bit0 activate phyxcer */
       iow(db, DM9000_GPCR, GPCR_GEP_CNTL);  /* Let GPIO0 output */
       iow(db, DM9000_GPR, 0);      /* Enable PHY */

Tx Control Reg, Back Presure,…

       /* Program operating register */
       iow(db, DM9000_TCR, 0);             /* TX Polling clear */
       iow(db, DM9000_BPTR, 0x3f);       /* Less 3Kb, 200us */
       iow(db, DM9000_FCR, 0xff);   /* Flow Control */
       iow(db, DM9000_SMCR, 0);        /* Special Mode */

清楚TX的状态位。

       /* clear TX status */
       iow(db, DM9000_NSR, NSR_WAKEST |NSR_TX2END | NSR_TX1END);
       iow(db, DM9000_ISR, ISR_CLR_STATUS); /*Clear interrupt status */

设置广播地址,hash table

       /* Set address filter table */
       dm9000_hash_table(dev);

设置中断掩码寄存器,使能SRAM地址自动归零,Packet Tx,Packet Rx中断,把imr给db(board info),同时,配置DM9000。

       imr = IMR_PAR | IMR_PTM | IMR_PRM;
       db->imr_all = imr;
       /* Enable TX/RX interrupt mask */
       iow(db, DM9000_IMR, imr);

初始化驱动变量

       /* Init Driver variable */
       db->tx_pkt_cnt = 0;
       db->queue_pkt_len = 0;
       dev->trans_start = 0;

MII检测媒介状态,主要检测Link状态,这里使用msg可以选择获取许多信息

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

mii_check_media

如果是强制媒介,自动谈判关闭,那么退出。

       /* if forced media, go no further */
       if (mii->force_media)
              return 0; /* duplex did not change*/

检测新的媒介

       /* check current and old link status */
       old_carrier =netif_carrier_ok(mii->dev) ? 1 : 0;
       new_carrier = (unsigned int) mii_link_ok(mii);

mii_link_ok

检测phy寄存器的状态位,可以检测到link状态

intmii_link_ok (struct mii_if_info *mii)
{
       /* first, a dummy read, needed to latchsome MII phys */
       mii->mdio_read(mii->dev,mii->phy_id, MII_BMSR);
       if (mii->mdio_read(mii->dev,mii->phy_id, MII_BMSR) & BMSR_LSTATUS)
              return 1;
       return 0;
}

这里使用了mii->mdio_read函数,该函数在dm9000_probe中定义:

       db->mii.mdio_read    = dm9000_phy_read;
       db->mii.mdio_write   = dm9000_phy_write;

dm9000_phy_read在dm9000.c中就有定义了,可以看出phy寄存器的读取方式采用了间接的方式

推荐:Linux-2.6.32.2内核在mini2440上的移植(三)---DM9000网卡驱动移植

移植环境(红色粗字体字为修改后内容,蓝色粗体字为特别注意内容) 1,主机环境:VMare下CentOS 5.5 ,1G内存。 2,集成开发环境:Elipse IDE 3,编译编译环境:ar

由于dm9000没有区分普通寄存器和phy寄存器,所以,通过控制寄存器触发dm9000读取phy,然后放入一个数据寄存器中,就可以读取phy了,phy是不能直接读取的

如果新媒介和旧媒介一样,那么不用改变

如果没有媒介,那么输出信息,返回。

       if ((!init_media) && (old_carrier== new_carrier))
              return 0; /* duplex did not change*/
       /* no carrier, nothing much to do */
       if (!new_carrier) {
              netif_carrier_off(mii->dev);
              if (ok_to_print)
                     printk(KERN_INFO "%s:link down/n", mii->dev->name);
              return 0; /* duplex did not change*/
       }

读取MII advertise and LPA(linkpartner ability)

       if ((!init_media) &&(mii->advertising))
              advertise = mii->advertising;
       else {
              advertise =mii->mdio_read(mii->dev, mii->phy_id, MII_ADVERTISE);
             mii->advertising = advertise;
       }
       lpa = mii->mdio_read(mii->dev,mii->phy_id, MII_LPA);
       if (mii->supports_gmii)
              lpa2 =mii->mdio_read(mii->dev, mii->phy_id, MII_STAT1000);

打印状态信息

       if (ok_to_print)
              printk(KERN_INFO "%s: linkup, %sMbps, %s-duplex, lpa 0x%04X/n",
                     mii->dev->name,
                     lpa2 & (LPA_1000FULL | LPA_1000HALF)? "1000" :
                     media & (ADVERTISE_100FULL |ADVERTISE_100HALF) ? "100" : "10",
                     duplex ? "full" :"half",
                     lpa);

告诉上层网络驱动层驱动空间有缓冲区可用,开始发送数据包到驱动层。

       netif_start_queue(dev);

启动设备工作队列

/*之前在probe函数中已经使用INIT_DELAYED_WORK来初始化一个延迟工作队列并关联了一个操作函数dm9000_poll_work(), 此时运行schedule来调用这个函数*/  

       dm9000_schedule_poll(db);

dm9000_close

进行的工作有向内核注册中断,复位并初始化dm9000,检查MII接口,使能传输等。代码清单如下:

* Stop the interface.
 *The interface is stopped when it is brought.
 */
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);

       cancel_delayed_work_sync(&db->phy_poll); /*杀死延迟工作队列phy_poll*/ 
       netif_stop_queue(ndev);
       netif_carrier_off(ndev); /*停止传输并清空carrier*/

       /*free interrupt */
       free_irq(ndev->irq,ndev);
       dm9000_shutdown(ndev);
 return0;
}

dm9000_start_xmit()

    重要的发送数据包函数。从上层发送sk_buff包。在看代码之前先来看一下DM9000是如何发送数据包的。

   

如上图所示,在DM9000内部SRAM中,地址0x0000~0x0BFF是TX Buffer,地址0x0C00~0x3FFF是RX Buffer。在发送一个包之前,包中的有效数据必须先被存储到TXBuffer中并且使用输出端口命令来选择MWCMD寄存器。包的长度定义在TXPLL和TXPLH中。最后设置TXCR寄存器的bit[0] TXREQ来自动发送包。如果设置了IMR寄存器的PTM位,则DM9000会产生一个中断触发在ISR寄存器的bit[1]=PTS=1,同时设置一个完成标志在NSR寄存器的bit[2]=TX1END或者 bit[3]=TX2END,表示包已经发送完了。发送一个包的具体步骤如下:

Step 1: 检查存储数据宽度。通过读取中断状态寄存器(ISR)的bit[7:6]来确定是8bit,16bit还是32bit。

Step 2: 写数据到TX SRAM中

Step 3: 写传输长度到TXPLL和TXPLH寄存器中

Step 4: 设置TXCR寄存器的bit[0]TXREQ来开始发送一个包。

代码清单如下,让我们看看在获得自旋锁这段期间都干了些什么:

/*
 * Hardware start transmission.
 *  Senda packet to media from the upper layer.
 */
static int
dm9000_start_xmit(struct sk_buff *skb,struct net_device *dev)
{

保存中断的当然状态,并禁止本地中断,然后再去获取指定的锁

       spin_lock_irqsave(&db->lock,flags)

下面四行代码将skb中的data部分写入DM9000的TX RAM,并更新已发送字节数和发送计数

       /*Move data to DM9000 TX RAM */
       writeb(DM9000_MWCMD,db->io_addr);
       (db->outblk)(db->io_data,skb->data, skb->len);
       dev->stats.tx_bytes+= skb->len;
       db->tx_pkt_cnt++;

如果发送的是第一个包,则设置一下包的长度后直接发送

如果发送的是第二个数据包(表明队列中此时有包发送),则将其加入队列中:将 skb->len和skb->ip_summed(控制校验操作)赋值给board_info_t中有关队列的相关成员。调用函数 netif_stop_queue(dev),通知内核现在queue已满,不能再将发送数据传到队列中,注:第二个包的发送将在tx_done中实现。

       /*TX control: First packet immediately send, second packet queue */
       if(db->tx_pkt_cnt == 1) {
              /*Set TXlength to DM9000 */
             iow(db,DM9000_TXPLL, skb->len);
              iow(db,DM9000_TXPLH, skb->len >>8);

              /*Issue TXpolling command */
              iow(db,DM9000_TCR, TCR_TXREQ);     /* Clearedafter TX complete */
             dev->trans_start= jiffies;    /* save the time stamp */
       }else {
              /*Second packet */
              db->queue_pkt_len= skb->len;
              netif_stop_queue(dev);
       }
dm9000_timeout()
   当watchdog超时时调用该函数。主要的功能是保存寄存器地址,停止队列,重启并初始化DM9000,唤醒队列,恢复寄存器地址。
dm9000_hash_table()
该函数用来设置DM9000的组播地址。代码清单如下:
dm9000_ioctl()
   从源码可以看出,dm9000的ioctl实际上是使用了mii的ioctl。代码清单如下:
dm9000_poll_controller()
    当内核配置Netconsole时该函数生效。代码清单如下:
dm9000_get_drvinfo()
   该函数去的设备的基本信息(设备名,版本,总线名)传给ethtool_drvinfo结构体变量。代码清单如下:
*dm9000_get_settings()
   该函数得到由参数cmd指定的设置信息。
   *dm9000_set_settings()
   该函数设置由参数cmd指定的信息。
   *dm9000_get_msglevel()
   *dm9000_set_msglevel()
   这两个函数设置和取得message level,实际是设置和取得board_info中的msg_enable信息。
   *dm9000_nway_reset()
   重启mii的自动协商
   *dm9000_get_link()
    该函数的到link状态。如果带外部PHY,则返回mii链接状态。否则返回DM9000 NSR寄存器数值。
   *dm9000_get_eeprom_len()
      dm9000_get_eeprom()
      dm9000_set_eeprom()
   这三个函数用来读写eeprom。

dm9000_rx

/*
 * Received a packet and pass to upper layer
 */
static void
dm9000_rx(struct net_device *dev)
{
       /*Check packet ready or not */
       do{
              ior(db,DM9000_MRCMDX);  /* Dummy read */

读取最新数据的第一个字节byte

              /*Get most updated data */
              rxbyte= readb(db->io_data);

检测第一个byte的值

DM9000_PKT_RDY定义是0x01,如果第一个字节大于0x01,则不是正确的状态。因为第一个字节只能是01h或00h

              /*Status check: this byte must be 0 or 1 */
              if(rxbyte > DM9000_PKT_RDY) {
                    dev_warn(db->dev,"status check fail: %d/n", rxbyte);
                     iow(db,DM9000_RCR, 0x00);       /* Stop Device */
                     iow(db,DM9000_ISR, IMR_PAR);       /* Stop INTrequest */
                     return;
              }
              if(rxbyte != DM9000_PKT_RDY)
                     return;

一次性读入四个字节的内容到rxhdr变量,获得接收数据长度

              /*A packet ready now  & Getstatus/length */
              GoodPacket= true;
              writeb(DM9000_MRCMD,db->io_addr);

              (db->inblk)(db->io_data,&rxhdr, sizeof(rxhdr));
              RxLen= le16_to_cpu(rxhdr.RxLen);

             dm9000_rxhdr是接收数据的头,一共4byte

structdm9000_rxhdr {
       u8   RxPktReady;
       u8   RxStatus;
       __le16    RxLen;
}

检测接收数据长度有效性,太小了不行(不是完整的Eth包?),超过一个包的最大长度也不行

              /*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++;
                     }
              }


关键的代码就是这里啦。使用到了上面提到的sk_buff。将RX SRAM中的data段数据放入sk_buff,然后发送给上层,至于怎么发送,不用去驱动操心了。sk_buff的protocol全部搞定。

开始dev_alloc_skb,由于len不包括前4个字节,所以,多分配了4个字节。

skb_reserve用于将数据起始指针向后移2个字节,因为

skb socket buffer通过移动起始指针将帧头去掉

              /*Move data from DM9000 */
              if(GoodPacket
                  && ((skb = dev_alloc_skb(RxLen + 4)) != NULL)) {
                     skb_reserve(skb, 2);
                     rdptr= (u8 *) skb_put(skb, RxLen - 4);
                     /*Read received packet from RX SRAM */
                     (db->inblk)(db->io_data,rdptr, RxLen);
                     dev->stats.rx_bytes+= RxLen;
                     /* Pass to upper layer */
                    skb->protocol= eth_type_trans(skb, dev);
                     netif_rx(skb);
                     dev->stats.rx_packets++;
              }else {
                     /*need to dump the packet's data */
                     (db->dumpblk)(db->io_data,RxLen);
              }

中断处理相关函数

  DM9000的驱动程序采用了中断方式而非轮询方式。触发中断的时机发生在:1)DM9000接收到一个包以后。2)DM9000发送完了一个包以后。

   中断处理函数在open的时候被注册进内核。代码清单如下:

static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
{

进中断都要使用自旋锁?

屏蔽所有中断,读取中断状态,清楚中断,如果打印中断信息,则打印

       /*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 ISRstatus */
       if(netif_msg_intr(db))
              dev_dbg(db->dev,"interrupt status %02x/n", int_status);

如果发生收到包中断,那么接收数据

如果发送包中断,那么调用dm9000_tx_done

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

中断恢复

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

dm9000_tx_done

如果queue中有3个,那么第1个发送完毕,触发中断,调用done,发送第2个,第2个发送完毕,触发中断,调用done,继续发送第3个。

注:dm9000可以发送两个数据包,当发送一个数据包产生中断后,要确认一下队列中有没有第2个包需要发送。

   (1)读取dm9000寄存器NSR(Network Status Register)获取发送的状态,存在变量tx_status中;

   (2)如果发送状态为NSR_TX2END(第2个包发送完毕)或者NSR_TX1END(第1个包发送完毕),则将待发送的数据包数量(db->tx_pkt_cnt )减1,已发送的数据包数量(dev->stats.tx_packets)加1;

   (3)检查变量db->tx_pkt_cnt(待发送的数据包)是否大于0(表明还有数据包要发送),则调用函数dm9000_send_packet发送队列中的数据包;

   (4)调用函数netif_wake_queue(dev)通知内核可以将待发送的数据包进入发送队列。

/*
 *DM9000 interrupt handler
 *receive the packet to upper layer, free the transmitted packet
 */
static void dm9000_tx_done(structnet_device *dev, board_info_t *db)
{
       inttx_status = ior(db, DM9000_NSR);    /* Got TXstatus */

       if(tx_status & (NSR_TX2END |NSR_TX1END)) {
              /*One packet sent complete */
              db->tx_pkt_cnt--;
              dev->stats.tx_packets++;
              if(netif_msg_tx_done(db))
                     dev_dbg(db->dev,"tx done, NSR %02x/n", tx_status);
              /*Queue packet check & send */
              if(db->tx_pkt_cnt > 0) {
                     iow(db,DM9000_TXPLL, db->queue_pkt_len);
                     iow(db,DM9000_TXPLH, db->queue_pkt_len >> 8);
                     iow(db,DM9000_TCR, TCR_TXREQ);
                     dev->trans_start= jiffies;
              }
              netif_wake_queue(dev);
       }
}

7.一些操作硬件细节的函数。

   在看函数之前还是先来看一下DM9000 CMD Pin和Processor并行总线的连接关系。CMD管脚用来设置命令类型。

当CMD管脚拉高时,这个命令周期访问DATA_PORT。

如果拉低, 则这个命令周期访问ADDR_PORT。见下图:

当然,内存映射的I/O空间读写还是采用最基本的readb(), readw(), readl(), writeb(), writew(), writel() , readsb(),readsw(), readsl(), writesb(), writesw(), writesl()。

在DM9000的驱动中还自定义了几个函数,方便操作。

从IO端口读一个字节。代码清单如下:

/*
 *   Reada byte from I/O port
 */
static u8
ior(board_info_t * db, int reg)
{
       writeb(reg,db->io_addr); /*写reg到ADDR_PORT,用来选择寄存器*/
       returnreadb(db->io_data); /*从DATA_PORT读一个字节,用来读寄存器*/
}

向IO端口写一个字节。代码清单如下:

/*
 *  Write a byte to I/O port
 */
static void
iow(board_info_t * db, int reg, int value)
{
       writeb(reg,db->io_addr);
       writeb(value,db->io_data);
}



   

推荐:Linux-2.6.32.2内核在mini2440上的移植----移植DM9000网卡驱动

1、设备资源初始化 Linux-2..6.32.2 已经自带了完善的DM9000网卡驱动驱动(源代码位置:linux-2.6.32.2/drivers/net/dm9000.c),它也是一个平台设备,因此在目标

网络接口DM9000驱动学习: /drivers/input/touchscreen/s3c2410_ts.c /drivers/input/s3c2410_ts.c 参考: http://blogold.chinaunix.net/u3/118227/showart_2353723.html http://blog.csdn.net

相关阅读排行


相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。