歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

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;
}
Copyright © Linux教程網 All Rights Reserved