本文基於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_POLLreturn 0;
/*****************************************************************************************************************************************
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。
***************************************************************************************************************************************/