歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Linux2.6.39下DM9K驅動源碼分析

Linux2.6.39下DM9K驅動源碼分析

日期:2017/3/1 10:51:16   编辑:Linux編程

本文基於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