Linux2.6.39下dm9k驱动源码分析(一)

本文基于linux2.6.39内核

CPU:S3C2440

一、s3c2440和dm9k的电路连接如下图:

从上图可以看出dm9k引用了16条数据线(sd0-sd15)和s3c2440(ldata0-ldata15)相连,引用了一条地址线(CMD)和S3C2440(ADDR2)相连。CPU就是通过CMD这条地址线来判断LDATA0-LDATA15这16条数据线传送的究竟是地址还是数据的。dm9k的片选信号AEN(Address enable a low activie signal used to select the dm9k)位和S3C2440的LnGCS4(BANK4)相连,BANK4的访问地址[0x2000 0000 - 0x2800 0000)

在dm9k芯片手册的5.1节有(TXD[2:0] is also used as the strap of  IO  base address IO base = (strap pin TXD[2:0]) * 10h + 300h),而mini2440开发板中对于dm9k电路TXD[3:0]引脚都未接的所以得出IO base=300h     中断使用了EINT7(GPF7)

这些放在移植的时候来分析再好不过了 其实

2、dm9000.c源码分析

分析之前先看看驱动程序中几个重要的结构

/**************************************************************************************************************************************************************/

* dm9000_driver变量。是platform_driver结构体变量,其中包含了重要的:驱动的名字(内核中用来匹配的唯一标识)和几个重要操作函数。

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_netdev_ops变量。是net_device_ops结构体变量, 其中定义了操作net_device的重要函数,我们在驱动程序中根据需要的操作要填充这些函数

static const struct net_device_ops dm9000_netdev_ops = {
    .ndo_open        = dm9000_open,//打开网络接口设备
    .ndo_stop        = dm9000_stop,//停止网络接口设备
    .ndo_start_xmit        = dm9000_start_xmit,//开始发送数据包
    .ndo_tx_timeout        = dm9000_timeout,//当数据包发送超时时该函数被调用
    .ndo_set_multicast_list    = dm9000_hash_table,
    .ndo_do_ioctl        = dm9000_ioctl,//进行设备特定的IO控制
    .ndo_change_mtu        = eth_change_mtu,
    .ndo_validate_addr    = eth_validate_addr,
    .ndo_set_mac_address    = eth_mac_addr,//设置MAC地址
#ifdef CONFIG_NET_POLL_CONTROLLER
    .ndo_poll_controller    = dm9000_poll_controller,//采用轮询的方式接收数据
#endif
};

/**************************************************************************************************************************************************************/

  * dm9000_ethtool_ops变量。是ethtool_ops结构体变量,为了支持ethtool,其中的函数主要是用于查询和设置网卡参数(有的驱动程序可能不支持ethtool)

static const struct ethtool_ops dm9000_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_wol        = dm9000_get_wol,
    .set_wol        = dm9000_set_wol,
     .get_eeprom_len        = dm9000_get_eeprom_len,
     .get_eeprom        = dm9000_get_eeprom,
     .set_eeprom        = dm9000_set_eeprom,
    .get_rx_csum        = dm9000_get_rx_csum,
    .set_rx_csum        = dm9000_set_rx_csum,
    .get_tx_csum        = ethtool_op_get_tx_csum,
    .set_tx_csum        = dm9000_set_tx_csum,
};

/**************************************************************************************************************************************************************/

  * board_info结构体。用来保存芯片相关的一些信息的,差不多每个芯片级驱动里面都有一个类似的结构体

typedef struct board_info {
   
    void __iomem    *io_addr;    /* Register I/O base address *///虚拟的通过IO映射的
    void __iomem    *io_data;    /* Data I/O address */
    u16         irq;        /* IRQ */
/**********************************都通过映射后的**********************************************************************************************************/
    u16        tx_pkt_cnt;
    u16        queue_pkt_len;
    u16        queue_start_addr;
    u16        queue_ip_summed;
    u16        dbug_cnt;
    u8        io_mode;        /* 0:word, 2:byte */
    u8        phy_addr;
    u8        imr_all;

    unsigned int    flags;
    unsigned int    in_suspend :1;
    unsigned int    wake_supported :1;
    int        debug_level;

    enum dm9000_type type;
    //IO模式
    void (*inblk)(void __iomem *port, void *data, int length);
    void (*outblk)(void __iomem *port, void *data, int length);
    void (*dumpblk)(void __iomem *port, int length);

    struct device    *dev;         /* parent device */
    //平台设备资源
    struct resource    *addr_res;   /* 地址资源resources found */
    struct resource *data_res;   /**IO数据资源**/
    struct resource    *addr_req;   /* 分配后的地址内存资源*/
    struct resource *data_req;  /* 分配后的数据资源*/
    struct resource *irq_res;    /**中断资源***/

    int         irq_wake;

    struct mutex     addr_lock;    /* phy and eeprom access lock */

    struct delayed_work phy_poll;
    struct net_device  *ndev;

    spinlock_t    lock;

    struct mii_if_info mii;
    u32        msg_enable;
    u32        wake_state;

    int        rx_csum;
    int        can_csum;
    int        ip_summed;
} board_info_t;

/**************************************************************************************************************************************************************/

2.1、 注册平台驱动。

    将驱动添加到总线上,完成驱动和设备的匹配,并执行驱动的probe函数。

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

static int __init dm9000_init(void)
{
    printk(KERN_INFO "%s Ethernet Driver, V%s\n", CARDNAME, DRV_VERSION);

    return platform_driver_register(&dm9000_driver);
}

2.2、dm9000_probe探测函数的分析

static int __devinit dm9000_probe(struct platform_device *pdev)
{

    //定义局部变量用来保存数据
    struct dm9000_plat_data *pdata = pdev->dev.platform_data;
    struct board_info *db;    /* Point a board information structure */
    struct net_device *ndev;
    const unsigned char *mac_src;
    int ret = 0;
    int iosize;
    int i;
    u32 id_val;
    /**内核用net_device结构来描述一个网络设备并使用alloc_etherdev或者alloc_netdev函数来分配一个net_device结构
    /* Init network device */
    ndev = alloc_etherdev(sizeof(struct board_info));
    if (!ndev) {
        dev_err(&pdev->dev, "could not allocate device.\n");
        return -ENOMEM;
    }
    //platform_device与net_device关联起来
    SET_NETDEV_DEV(ndev, &pdev->dev);//通过这一步网络设备和平台设备即关联起来了

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

    /* setup board info structure */
 
    db = netdev_priv(ndev);/**获取net_device结构的私有成员保存到struct board_info *db中**/
    
    db->dev = &pdev->dev;
    db->ndev = ndev;
     
    spin_lock_init(&db->lock);/**初始化自旋锁**/
    mutex_init(&db->addr_lock);
    //初始化延迟等待队列并传入dm9000_poll_work该函数将在设备被打开的时候被调度
    INIT_DELAYED_WORK(&db->phy_poll, dm9000_poll_work);
    //获取平台资源?从哪里获取?
    db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);//dm9k平台设备所所使用的IO地址资源
    db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);//dm9k平台设备所所使用的IO数据资源
    db->irq_res  = platform_get_resource(pdev, IORESOURCE_IRQ, 0);   //dm9k平台设备所所使用中断资源

    if (db->addr_res == NULL || db->data_res == NULL ||
        db->irq_res == NULL) {
        dev_err(db->dev, "insufficient resources\n");
        ret = -ENOENT;
        goto out;
    }
    //获取dm9k平台设备所使用的中断号
    db->irq_wake = platform_get_irq(pdev, 1);
    if (db->irq_wake >= 0) {
        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 = 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;
            }
        }
    }
    /*计算上面所获取到的IO地址平台资源的大小*/
    iosize = resource_size(db->addr_res);
    //为IO地址空间分配IO内存
    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;
    }
    //在访问IO内存之前必须映射IO内存
    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;
    }
    /* fill in parameters for net-dev structure */
    ndev->base_addr = (unsigned long)db->io_addr;//初始化IO基地址
    ndev->irq    = db->irq_res->start;    //初始化irq      

    /**根据DM9000的数据位宽,初始化读写数据帧的函数指针初始化net_device给其结构中的成员变量和成员函数赋值*/
    /* ensure at least we have a default set of IO routines */
    dm9000_set_io(db, iosize);
    /*根据platform的定义(16bit),再次初始化读写数据帧的函数指针*/
    /* check to see if anything is being over-ridden */
    if (pdata != NULL) {
        /* check to see if the driver wants to over-ride the
         * default IO width */
        /****IOctl flag在驱动加载的时候被传入********/
        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 */
        /*检查看看是否有任何IO常规越权*/
        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;
    }

/**************************************************************************************************************************************

*到此为止总结下prob究竟做了些什么事,那些结构里面多了什么

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

3、获得dm9k所使用的平台资源并将其保存在board_info变量db中。关键系统函数:netdev_priv(),  platform_get_resource()

4、根据资源信息分配内存,申请中断等等, 并将申请后的资源信息也保存到db中,并且填充ndev中的参数。 关键系统函数:request_mem_region(),  ioremap()。

 resource_size(),自定义函数:dm9000_set_io(db, iosize);

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号

***************************************************************************************************************************************/

#ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL
    db->flags |= DM9000_PLATF_SIMPLE_PHY;
#endif
    //复位芯片
    dm9000_reset(db);
    //读取芯片ID号并判断是否为0x90000A46**/
    /* try multiple times, DM9000 sometimes gets the read wrong */
    for (i = 0; i < 8; i++) {
        id_val  = ior(db, DM9000_VIDL);//供应商ID低8位
        id_val |= (u32)ior(db, DM9000_VIDH) << 8;//供应商ID高8位
        id_val |= (u32)ior(db, DM9000_PIDL) << 16;//产品ID低8位
        id_val |= (u32)ior(db, DM9000_PIDH) << 24;//产品ID高8位

        if (id_val == DM9000_ID)//芯片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 */
    //读DM9000_CHIPR寄存器判断网卡类型
    id_val = ior(db, DM9000_CHIPR);//读chip revision寄存器
    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 = 1;
        db->rx_csum = 1;
        ndev->features |= NETIF_F_IP_CSUM;
    }

    /* from this point we assume that we have found a DM9000 */
    //初始化以太网ndev的部分成员
    /* driver system function */
    /**
    *以太网设置
    **/
    ether_setup(ndev);

 //手动初始化ndev的ops和db的mii部分。
    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";
    //从EEPROM中读取MAC
    /* 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);
    //判断MAC是否合法
    if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) {
        mac_src = "platform data";
        memcpy(ndev->dev_addr, pdata->dev_addr, 6);
    }

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

    if (!is_valid_ether_addr(ndev->dev_addr)) {
        dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please "
             "set using ifconfig\n", ndev->name);

        random_ether_addr(ndev->dev_addr);
        mac_src = "random";
    }


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

    return 0;

/*****************************************************************************************************************************************

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

6、 读一下生产商和制造商的ID,应该是0x9000 0A46。 关键函数:ior()

7、 读一下芯片类型。

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

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

9、手动初始化ndev的ops和db的mii部分。

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

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

12、使用register_netdev()注册ndev。

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

    dm9000_release_board(pdev, db);
    free_netdev(ndev);

    return ret;
}








  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值