《网蜂A8实战演练》——10.Linux 网络设备驱动

时间:2022-05-02 09:35:21

第12章 Linux 网络设备驱动


想象一下,当今社会如果没有了网络,这个世界将会变成怎样?再想象一下,如果古代有网络,那将会是怎样的呢?唐僧就不需要到西天取经啦,直接 EMAIL就可以啦,干嘛非把自己累的像个孙子,您说对吧? 好啦,说到 EMAIL,很自然的就要联系到常用的电子邮件协议有 SMTP、 POP3、 IMAP4,它们都隶属于TCP/IP 协议簇。咦,好像跑偏了,我们今天要讲的可不是 TCP/IP 协议,也不是网络驱动, 看清楚了, 是网络设备驱动。

12.1 Linux 网络驱动层次框架


12.1.1 网络设备简介


网络设备是完成用户数据包在网络媒介上发送和接收的设备,它将上层协议传递下来的数据包以特定的媒介访问控制方式进行发送,并将接收到的数据包传递给上层协议。 很官方的语言,对吧? webee 打个比方,其实网络设备完成的工作就是搬砖。 还不明白的话,可以去面壁三分钟了。


12.1.2 Linux 网络设备驱动程序体系结构 


Linux 系统对网络设备驱动程序体系分四层, 从上到下:网络协议接口层-->网络设备接口层-->设备驱动功能层-->网络设备与媒介层。
《网蜂A8实战演练》——10.Linux 网络设备驱动

我们可以理解为三层:

1 、最上面理解为我们用的网络传输方法,就是网络协议;

2、最下面就是物理硬件,即网络设备层;

3、中间是一层,设备驱动,然后拆成 2 部分,上部分是结构(层),下部分是结构中函数的实现(层)。


网 络 协 议 接 口 层 向 网 络 层 协 议 提 供 统 一 的 数 据 包 收 发 接 口 , 通 过dev_queue_xmit()函数发送数据,并通过 netif_rx()函数接收数据。


网络设备结构层向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体 net_device。


设备驱动功能层各函数是网络设备结构层 net_device 数据结构的具体成员,通过 hard_start_xmit()函数发送,通过中断触发接收函数。


网络设备媒介层就是完成数据包发送和接收的物理实体,对于 Linux 系统而言,网络设备和媒介都可以是虚拟的。

12.2 Linux 网络协议接口层


Linux 大牛们在很早很早以前就已经帮我们写出了一套完美的网络协议接口体系,驱动工程通常不用去修改这个层次的代码,直接使用就可以了。所以,这一层,我们在本教程不会涉及的太深,这并不是我们本章的学习的重点。 但是,理解网络协议接口层,能对我们学习网络设备驱动起到很好的辅助作用。


12.2.1 发送接口函数
(dev_queue_xmit) 


网络洗衣接口层最核心的工作就是提供了发送和接收接口给上层,当上层需要发送数据包时,它将调研网络协议接口层的 dev_queue_xmit()函数发送该数据包,其函数原型如下:


int dev_queue_xmit(struct sk_buff*skb)


12.2.2 接收接口函数(netif_rx) 


上面讲了发送函数,自然地,接下来就应该是接收函数了。当网络设备发出包到达网络协议接口层时,使用 netif_rx()函数来接收数据包。其函数原型如下:


int netif_rx(struct sk_buff *skb)


12.2.3 套接字缓冲区(sk_buff) 


Linux 使用 socket 做为连接用户和网络设备的一个桥梁。用户可以通过read / write 等函数操作 socket,然后通过 socket 与具体的网络设备进行交互,从而进行实际的数据收发工作。


Linux 提供了一个被称为 sk_buff 的数据接口类型,用户传给 socket 的数据 首 先 会 保 存 在 sk_buff 对 应 的 缓 冲 区 中 , sk_buff 的 结 构 定 义 在include/linux/skbuff.h 文件中。


当发送数据时, Linux 内核的网络处理模块必须建立一个包含要传输的数据包 sk_buff,然后将 sk_buff 递交给下层,各层在 sk_buff 中添加不同的协议头直至交给网络设备发送。同样,当网络设备从网络媒介上接收数据包后,它必须将接收到的数据转化为 sk_buff 数据结构并传递给上层,各层剥去相应的协议头,直至交给用户。


skb 有四个指针, head 和 end 分别指向数据缓冲区的启始地址和结尾地址,而 data 和 tail 分别指向有效数据的开始地址和结尾地址,如图 12.2。
《网蜂A8实战演练》——10.Linux 网络设备驱动

sk_buff 中还定义了 3 个协议头用于网络协议的不同层次,传输层 TCP/IP 协议头:th,网络层协议头: nh,链路层协议头 mac。


sk_buff 成员很多,这里仅列举一些驱动工程师比较关心的成员。


/* 参考 include/linux/skbuff.h */
struct sk_buff {
struct sk_buff *next;
struct sk_buff *prev;
struct net_device *dev;
unsigned int len, data_len;
__u16 mac_len, hdr_len;
/* 分别指向传输层、网络层、 MAC 层的包头位置 */
sk_buff_data_t transport_header;
sk_buff_data_t network_header;
sk_buff_data_t mac_header;
sk_buff_data_t tail;
sk_buff_data_t end;

unsigned char *head,*data;
unsigned int truesize;
};


12.2. 4 套接字缓冲区操作函数


alloc_skb()分配一个套接字缓冲区和一个数据缓冲区,原型如下:


static inline struct sk_buff *alloc_skb(unsigned int size,gfp_t priority)


kree_skb()进行套接字缓冲区的释放,原型如下:


void kfree_skb(struct sk_buff *skb)


skb_push()将 data 指针上移,主要用于添加协议头部,原型如下:


unsigned char *skb_push(struct sk_buff *skb, unsigned int len)


skb_pull()将 data 指针下移,用于剥去头部,原型如下:


unsigned char *skb_pull(struct sk_buff *skb, unsigned int len)


12.2.5 数据发送流程


当网络子系统上层有数据包要发送时,通过调用网络设备驱动中的实现的ndo_start_xmit 函数,将要发送的数据包封装在套接字缓冲区 skb 参数中。在驱动程序的发送数据包函数的具体实现中,它将首先在 skb 数据包所在主存中的数据块和网络设备内存之间建立一个 DMA 通道,然后启动该 DMA 通道将数据包由主存传输到设备内存,之后由网络设备硬件通过网络接口或者天线将数据包发送出去。数据包发送成功后会向处理器发出一个硬件中断,在中断处理程序里做一些善后处理工作, 流程图 12.3 如下所示。
《网蜂A8实战演练》——10.Linux 网络设备驱动

12.2.6 数据接收流程(中断方式) 


数据包的接收是一个异步的过程,正因为这样,出于系统性能的考虑,绝大部分网络设备都支持数据接收中断,因此在驱动程序中是通过中断处理程序来接收数据包的。由于系统主存与网络设备之间已经建立好 DMA 通道,所有当有数据包到达网络设备时,数据包会被自动传输到系统主存,此时将产生一个中断信号,从而进入驱动程序的中断处理函数,在中断处理函数里驱动首先会分配一个套接字缓冲区 skb 来容纳收到的数据包,然后将 skb 传递到网络子系统的上层代码中,具体传递的过程是驱动程序通过调用 netif_rx(skb)函数实现的,上层代码负责释放该 skb 所占用的内存。 用户调用 read 之类的读函数,从 sk_buff 缓冲区中读出数据,同时释放该缓冲区, 流程图 12.4 如下所示。
《网蜂A8实战演练》——10.Linux 网络设备驱动

12.3 Linux 网络设备接口/功能层


网络设备接口层的主要功能是为千变万化的网络设备定义了统一,抽象的数据结构 net_device 结构体,以不变应万变,实现多种硬件在软件层次上的统一。net_deivce 结构体非常庞大,这里仅列举一些重要的成员。


/* 参考 include/linux/skbuff.h */
struct net_device
{
char name[IFNAMSIZ]; /* 网络设备名 */
unsigned long mem_end; /* 共享内存的结束地址 */
unsigned long mem_start; /* 共享内存的起始地址 */
unsigned long base_addr; /* 网络设备 I/O 基地址 */
unsigned int irq; /* 中断号 */
unsigned char if_port; /* 用于多端口设备选择具体端口 */
unsigned char dma; /* DMA 通道 */

/* 操作方法接口,后面分析*/
const struct net_device_ops *netdev_ops;
const struct ethtool_ops *ethtool_ops;
unsigned int flags; /* 网络接口标志 */
unsigned mtu; /* 最大传输单元 */
unsigned short type; /* 硬件接口类型 */
/* 网络设备的硬件头长度,一般设置为 14 */
unsigned short hard_header_len;
/* 存放设备的硬件地址(6 个字节),如 MAC 地址 */
unsigned char dev_addr[MAX_ADDR_LEN];
/* 存放设备的广播地址(6 个字节),一般设置为 0xFF */
unsigned char broadcast[MAX_ADDR_LEN];
void *priv;
struct device dev;
......
};


12.3.1 操作方法接口 (net_device_ops) 


打开和关闭网络设备


/* 当用户输入 ifconfig eth0 up 命令时 open 函数被调用 */
int (*ndo_open)(structnet_device *dev);
/* 当用户输入 ifconfig eth0 down 命令时 stop 函数被调用 */
int (*ndo_stop)(structnet_device *dev);


数据包发送函数


/* sk_buff 是从上层传递下来的 */
int (*ndo_start_xmit) (struct sk_buff *skb,struct net_device *dev);


其他几个重要的函数


/* 当设备的组播列表改变或设备标志改变时,该方法被调用 */
void (*ndo_set_multicast_list)(struct net_device *dev);
/* 用于设置设备的 MAC 地址 */
int (*ndo_set_mac_address)(struct net_device *dev,void *addr);
/* 用于对设备特定的 I/O 控制 */
int (*ndo_do_ioctl)(struct net_device *dev,struct ifreq *ifr, int cmd);

/* 当用户输入 ifconfig eth0 up 命令时 open 函数被调用 */
int (*ndo_open)(structnet_device *dev);
/* 当用户输入 ifconfig eth0 down 命令时 stop 函数被调用 */

int (*ndo_stop)(structnet_device *dev);


数据包发送函数


/* sk_buff 是从上层传递下来的 */
int (*ndo_start_xmit) (struct sk_buff *skb,struct net_device *dev);


其他几个重要的函数


/* 当设备的组播列表改变或设备标志改变时,该方法被调用 */
void (*ndo_set_multicast_list)(struct net_device *dev);
/* 用于设置设备的 MAC 地址 */
int (*ndo_set_mac_address)(struct net_device *dev,void *addr);
/* 用于对设备特定的 I/O 控制 */
int (*ndo_do_ioctl)(struct net_device *dev,struct ifreq *ifr, int cmd);

/* sk_buff 是从上层传递下来的 */

int (*ndo_start_xmit) (struct sk_buff *skb,struct net_device *dev);


其他几个重要的函数


/* 当设备的组播列表改变或设备标志改变时,该方法被调用 */
void (*ndo_set_multicast_list)(struct net_device *dev);
/* 用于设置设备的 MAC 地址 */
int (*ndo_set_mac_address)(struct net_device *dev,void *addr);
/* 用于对设备特定的 I/O 控制 */
int (*ndo_do_ioctl)(struct net_device *dev,struct ifreq *ifr, int cmd);

/
int (*ndo_set_config)(struct net_device *dev,struct ifmap *map);
/* 数据超时处理函数,一般采取重发 */
void (*ndo_tx_timeout) (struct net_device *dev);
/* 用于获取网络设备的状态信息,如:流量统计信息,收发数据包数等*/
struct net_device_stats* (*ndo_get_stats)(struct net_device *dev);


12.3.2 Linux 网络设备功能层


驱动工程师需要完成的就是这一层了,而功能层使用网络设备接口层提供的net_device 结构体成员来完成。 对于具体的设备 xxx,驱动工程师应该编写设备驱动功能层的函数,如: xxx_open()、 xxx_stop()、 xxx_tx()、 xxx_timeout()等等。由于网络数据包的接收会触发中断的发生,设备去、驱动功能层的另一个主体部分应该是中断处理函数。


12.4 DM9000 驱动分析


12.4.1 DM9000 硬件原理图

《网蜂A8实战演练》——10.Linux 网络设备驱动

12.4.2 DM9000 的移植


这里,为了方便学习 DM9000 驱动,我们再次把移植部分的代码贴出来。关于 DM9000 的移植,在前面的 uboot, kernel 教程都已经讲解过二次了,还不懂的朋友们,请回到前面教程去学习吧。


/* 参考 arch\arm\mach-s5pv210\mach-smdkv210.c */
static struct resource smdkv210_dm9000_resources[] = {
[0] = DEFINE_RES_MEM(0x88001000, 1),
[1] = DEFINE_RES_MEM(0x88001000 + 0x300c, 1),
[2] = DEFINE_RES_NAMED(IRQ_EINT(7), 1, NULL,IORESOURCE_IRQ | IORESOURCE_IRQ_HIGHLEVEL),
};
static struct dm9000_plat_data smdkv210_dm9000_platdata = {
.flags =DM9000_PLATF_16BITONLY|DM9000_PLATF_NO_EEPROM,
.dev_addr = { 0x00, 0x09, 0xc0, 0xff, 0xec, 0x48 }, /* MAC 地址 */
};
static struct platform_device smdkv210_dm9000 = {
.name = "dm9000",
.id = -1,
.num_resources = ARRAY_SIZE(smdkv210_dm9000_resources),
.resource = smdkv210_dm9000_resources,
.dev = {
.platform_data = &smdkv210_dm9000_platdata,
},
};
static struct platform_device *smdkv210_devices[] __initdata = {
……
&smdkv210_dm9000,
……
};


12.4.3 DM9000.c 源码分析


webee210 开发板(核心板)上的网卡芯片是 dm9000e,是一款高度集成低功耗快速以太网处理器,该芯片集成了 MAC 和 PHY。 DM9000 可以和 CPU 直接连接,支持 8 位、 16 位和 32 位数据总线宽度。该芯片支持 10M 和 100M 自适应以太网接口,内部有 16K 的 FIFO 以及 4K 双字节 SRAM,支持全双工工作。DM9000 内部还集成了接收缓冲区,可以在接收到数据的时候把数据存放到缓冲区中,链路层可以直接把数据从缓冲区取走。

12.4.3.1 入口函数


通常来说,分析一个驱动,都是从它的入口函数开始分析的。


/* 参考 drivers\net\ethernet\davicom\dm9000.c */
static int __init dm9000_init(void)
{
printk(KERN_INFO "%s Ethernet Driver, V%s\n",
CARDNAME, DRV_VERSION);
return platform_driver_register(&dm9000_driver);
}


12.4.2 小节移植了一个平台设备,很自然的,在设备驱动里,就是注册一个平台驱动,这种架构在 Linux 内核里到处可见。那它的 platform_driver 具体的定义又是怎么样的呢?


static struct platform_driver dm9000_driver = {
.driver = {
.name = "dm9000",
.owner = THIS_MODULE,
.pm = &dm9000_drv_pm_ops,
},
.probe = dm9000_probe,
.remove = dm9000_drv_remove,
};


当内核有同名(dm900)的平台设备时,就会调用平台驱动里的 probe 函数,这里将会调用 dm9000_probe()函数。

12.4.3.2 出口函数


static void __exit dm9000_cleanup(void)
{
platform_driver_unregister(&dm9000_driver);
}


有入口就有出口,这里,出口函数注销一个平台驱动。12.4.3.3 完全剖析 dm9000_probe 函数


12.4.3.3.1 board_info结构体


在分析 dm9000_probe 函数之前,我们很有必要先认识一下 board_info 结构体,它保存了 dm9000 芯片相关的私有信息。 board_info 结构体的定义如下,根据简单的英文注释就可知其用途。


/* 参考 drivers\net\ethernet\davicom\dm9000.c */

typedef structboard_info {
void __iomem *io_addr; /* Register I/O base address */
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;
enum dm9000_type type;
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;
struct resource *addr_req; /* resources requested */
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 ip_summed;
} board_info_t;


12.4.3.3.2 dm9000_probe 


函数源码完全注释


static int dm9000_probe(structplatform_device *pdev)
{
/* 获取 smdkv210_dm9000_platdata 数据 */
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;
/* Init network device */
/* 分配一个名为 eth%d 的网络设备(net_device),同时分配一个私有数据区,
* 这片多分配的私有数据空间就是用来存放 board_info 结构体成员的信息
*/
ndev = alloc_etherdev(sizeof(structboard_info));
if (!ndev)
return -ENOMEM;
/* 相当于 ndev->dev.parent = &pdev->dev
* 把网络设备的基类 dev 的父指针设为平台设备的基类 dev
*/
SET_NETDEV_DEV(ndev, &pdev->dev);
dev_dbg(&pdev->dev, "dm9000_probe()\n");
/* setup board info structure */
/* 获得 net_device 的私有信息指针 */
db = netdev_priv(ndev);
/* 将 platform_device 里的 device 成员
* 赋给 board_info 里面的 device 成员
*/
db->dev = &pdev->dev;


static int dm9000_probe(structplatform_device *pdev)
{
/* 获取 smdkv210_dm9000_platdata 数据 */
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;
/* Init network device */
/* 分配一个名为 eth%d 的网络设备(net_device),同时分配一个私有数据区,
* 这片多分配的私有数据空间就是用来存放 board_info 结构体成员的信息
*/
ndev = alloc_etherdev(sizeof(structboard_info));
if (!ndev)
return -ENOMEM;
/* 相当于 ndev->dev.parent = &pdev->dev
* 把网络设备的基类 dev 的父指针设为平台设备的基类 dev
*/
SET_NETDEV_DEV(ndev, &pdev->dev);
dev_dbg(&pdev->dev, "dm9000_probe()\n");
/* setup board info structure */
/* 获得 net_device 的私有信息指针 */
db = netdev_priv(ndev);
/* 将 platform_device 里的 device 成员
* 赋给 board_info 里面的 device 成员
*/
db->dev = &pdev->dev;

/* 将刚生成的 net_device 成员赋给 board_info 里面的 net_device 成员 */
db->ndev = ndev;
/* 初始化一个自旋锁和一个互斥体 */
spin_lock_init(&db->lock);
mutex_init(&db->addr_lock);
/* 往工作队列插入一个工作,随后我们调用
* schedule_delayed_work 就会执行传递的函数
*/
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);
/* 判断是否成功获取资源 */
if (db->addr_res == NULL || db->data_res == NULL || db->irq_res == NULL) {
dev_err(db->dev, "insufficient resources\n");
ret = -ENOENT;
goto out;
}
/* 平台设备里的资源只有一个 IRQ 资源,所以这个 if 分支不会执行 */
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;
}
}
}
/* 获取 dm9000 内存资源的大小,然后申请内存 */
iosize = resource_size(db->addr_res);
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;
}
/* 映射地址寄存器 I/O 基地址 */
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;
}
/* 映射地址寄存器 I/O 基地址 */
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 */
/* 填充 net_dev 结构体, 映射后的地址寄存器基地址*/
ndev->base_addr = (unsigned long)db->io_addr;
ndev->irq = db->irq_res->start; /* 中断号 */
/* ensure at least we have a default set of IO routines
* 设置 board_info 里三个函数指针,选择 IO 读写函数,我们的是 16 位的
*/
dm9000_set_io(db, iosize);
/* 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 */
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 */
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;
}
#ifdef CONFIG_DM9000_FORCE_SIMPLE_PHY_POLL
db->flags |= DM9000_PLATF_SIMPLE_PHY;
#endif

/* 写 DM900 的 00H 地址,软件复位 dm9000 */
dm9000_reset(db);
/* try multiple times, DM9000 sometimes gets the read wrong
* Vendor ID 的地址为 0x28-0x29, Prodouct ID 的地址为 0x2A-0x2B
* 先读出 dm9000 的供应商 ID 和产品 ID,然后判断 ID 是否正确
*/
for (i = 0; i < 8; i++) {
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;
/* DM9000 的厂家 ID 应为 0A46H,产品
* ID 应为 9000H (datasheet page11)
*/
if (id_val == DM9000_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
* CHIP Revision 的地址为 0x2C,读 CHIP Revision 的值,应为 00H
*/
id_val = ior(db, DM9000_CHIPR);
dev_dbg(db->dev, "dm9000 revision 0x%02x\n", id_val);
/* 经过上面设置后 id_val = 0,所以类型应该为默认的 DM9000E */
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) {
ndev->hw_features = NETIF_F_RXCSUM | NETIF_F_IP_CSUM;
ndev->features |= ndev->hw_features;
}
/* from this point we assume that we have found a DM9000 */
/* driver system function */
/* 初始化以太网设备的基本参数 */
ether_setup(ndev);
/* 设置 net_device 结构体里的 ops */
ndev->netdev_ops = &dm9000_netdev_ops;
ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
ndev->ethtool_ops = &dm9000_ethtool_ops;
/* 填充 board_info 结构体的部分成员 */
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_src = "eeprom";
/* 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);
/* 执行这个 if 分支,将 MAC 地址读出存到 dev_addr[] */
if (!is_valid_ether_addr(ndev->dev_addr) && pdata != NULL) {
mac_src = "platform data";
memcpy(ndev->dev_addr, pdata->dev_addr, 6);
}
/* 不执行这个 if 分支 */
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 分支 */
if (!is_valid_ether_addr(ndev->dev_addr)) {
dev_warn(db->dev, "%s: Invalid ethernet MAC address. Please "
"set using ifconfig\n", ndev->name);
eth_hw_addr_random(ndev);
mac_src = "random";
}
/* 将分配设置好的 ndev 存放到平台私有数据里,以供其他函数获取 ndev
* 其他函数使用 platform_get_drvdata(pdev, ndev)获得 ndev
*/
platform_set_drvdata(pdev, ndev);
/* 注册 net_device 结构体 */
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;
out:
dev_err(db->dev, "not found (%d).\n", ret);
/* 没有找到设备要解除映射、释放资源、内存 */
dm9000_release_board(pdev, db);
free_netdev(ndev);
return ret;

}


dm9000_probe 函数代码量算是比较大,这里总结一下,主要干了哪些事:

第一、 分配 board_info 结构占用的私有资源,使用 alloc_etherdev()函数分配网卡驱动使用的私有资源。

第二、 分配资源成功后, 初始化 board_info 结构体,然后获取资源,映射资源。

第三、 DM9000 硬件相关的设置。

第四、 设 置 net_device 结 构 体 , 主 要 包 括 二 个 ops 的 设 置 , 其 中dm9000_netdev_ops 结构体里定义了 net_device 里的 open、 stop、hard_start_xmit、 tx_timeout、 do_ioctl 等成员函数,其中在 open 函数里申请了中断,这个中断用于数据包的接收处理;而 dm9000_ethtool_ops主要是用于查询和设置网卡参数(当然也有的驱动程序可能不支持ethtool)。

第五、 最后使用 register_netdev()函数向内核注册一个网络设备。

12.4.3.3.3 dm9000_open 


函数当使用命令 ifconfig eth0 up 时,网卡被打开, 最终会调用到 dm9000_open()函数,传递的参数正是 probe 函数分配设置好的 net_deivce 结构体。


static int dm9000_open(structnet_device *dev)
{
/* 获得 net_device 的私有信息指针后赋给 db 指针 */
board_info_t *db = netdev_priv(dev);
/* IRQF_TRIGGER_MASK 为中断触发方式 */
unsigned long irqflags = db->irq_res->flags & IRQF_TRIGGER_MASK;
/* 使能设备 */
if (netif_msg_ifup(db))
dev_dbg(db->dev, "enabling %s\n", dev->name);
/* If there is no IRQ type specified, default to something that
* may work, and tell the user that this is a problem */
/* 若未指定触发方式,则出现警告 */
if (irqflags == IRQF_TRIGGER_NONE)
dev_warn(db->dev, "WARNING: no IRQ resource flags set. \n");
/* 共享中断 */
irqflags |= IRQF_SHARED;
/* GPIO0 on pre-activate PHY, Reg 1F is not set by reset */
iow(db, DM9000_GPR, 0); /* REG_1F bit0 activate phyxcer */
mdelay(1); /* delay needs by DM9000B */
/* Initialize DM9000 board */
dm9000_reset(db);
/* 初始化 dm9000 芯片 */
dm9000_init_dm9000(dev);


static int dm9000_open(structnet_device *dev)
{
/* 获得 net_device 的私有信息指针后赋给 db 指针 */
board_info_t *db = netdev_priv(dev);
/* IRQF_TRIGGER_MASK 为中断触发方式 */
unsigned long irqflags = db->irq_res->flags & IRQF_TRIGGER_MASK;
/* 使能设备 */
if (netif_msg_ifup(db))
dev_dbg(db->dev, "enabling %s\n", dev->name);
/* If there is no IRQ type specified, default to something that
* may work, and tell the user that this is a problem */
/* 若未指定触发方式,则出现警告 */
if (irqflags == IRQF_TRIGGER_NONE)
dev_warn(db->dev, "WARNING: no IRQ resource flags set. \n");
/* 共享中断 */
irqflags |= IRQF_SHARED;
/* GPIO0 on pre-activate PHY, Reg 1F is not set by reset */
iow(db, DM9000_GPR, 0); /* REG_1F bit0 activate phyxcer */
mdelay(1); /* delay needs by DM9000B */
/* Initialize DM9000 board */
dm9000_reset(db);
/* 初始化 dm9000 芯片 */
dm9000_init_dm9000(dev);

/* 申请中断并注册中断服务程序,中断处理函数为 dm9000_interrupt */
if (request_irq(dev->irq,dm9000_interrupt, irqflags, dev->name, dev))
return -EAGAIN;
/* Init driver variable */
db->dbug_cnt = 0;
/* 检测 mii 口的状态 */
mii_check_media(&db->mii, netif_msg_link(db), 1);
/* 表示允许上层进行发包 */
netif_start_queue(dev);
/* 表示开始周期性的调用 dm9000_poll_work.前面
* 在 probe 函数中,只是初始化了 phy_poll 函数
*/
dm9000_schedule_poll(db);
return 0;
}


dm9000_open()函数的首先复位 dm9000,然后初始化 dm9000 芯片,接着申请中断,检查完 mii 接口后,最后调用 netif_start_queue()告诉上层,我已经准备好了,你可以发数据了。


12.4.3.3.4 dm9000_stop 


函数当 使 用命 令 ifconfig eth0 down 时 , 网 卡 被关 闭 , 最终 就 会调 用 到dm9000_stop ()函数,传递的参数也是 probe 函数分配设置好的 net_deivce 结构体。


static int dm9000_stop(structnet_device *ndev)
{
board_info_t *db = netdev_priv(ndev);
/* 检测 ndev 是否停止 */
if (netif_msg_ifdown(db))
dev_dbg(db->dev, "shutting down %s\n", ndev->name);
/* 取消延时工作队列 phy_poll */
cancel_delayed_work_sync(&db->phy_poll);
/* 停止发送数据包 */
netif_stop_queue(ndev);
netif_carrier_off(ndev);

/* free interrupt */
free_irq(ndev->irq, ndev);
/* 重启设备,然后通过设置网络控制器的控制寄存器
* 关闭芯片的电源和中断,并且停止接收数据包
*/
dm9000_shutdown(ndev);
return 0;
}


dm9000_stop()函数当检测到网络设备已经停止工作,就会停止数据包的发送、释放中断、重启设备,然后通过设置网络控制器的控制寄存器关闭芯片的电源和中断,并且停止接收数据包。

12.4.3.3.5 dm9000_start_xmit 


函数当 上 层 有 数 据 包 需 要 发 送 时 , dm9000 网 络 设 备 驱 动 就 会 调 用dm9000_start_xmit()函数将数据包发送到硬件上的网卡设备里。


static int dm9000_start_xmit(structsk_buff *skb, structnet_device *dev)
{
unsigned long flags;
board_info_t *db = netdev_priv(dev);
dm9000_dbg(db, 3, "%s:\n", __func__);
/* 如果数据包个数大于 1 ,则返回忙状态 */
if (db->tx_pkt_cnt > 1)
return NETDEV_TX_BUSY;
spin_lock_irqsave(&db->lock, flags);
/* Move data to DM9000 TX RAM */
/* 将发送数据包移至 DM9000 的 TX RAM */
writeb(DM9000_MWCMD, db->io_addr);
/* 将数据从 sk_buff 中输出到网卡的 TX SRAM 中 */
(db->outblk)(db->io_data, skb->data, skb->len);
/* 统计发送的字节数 */
dev->stats.tx_bytes += skb->len;
/* 待发送计数 */
db->tx_pkt_cnt++;

/* TX control: First packet immediately send, second packet queue */
if (db->tx_pkt_cnt == 1) {
/* 如果计数为 1 ,直接发送 */
dm9000_send_packet(dev, skb->ip_summed, skb->len);
} else {
/* 如果数据包大于 1 ,则先记录状态,然后告诉上层停止发送 */
db->queue_pkt_len = skb->len;
db->queue_ip_summed = skb->ip_summed;
netif_stop_queue(dev);
}
spin_unlock_irqrestore(&db->lock, flags);
/* free this SKB */
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}


设置发送数据包后,可以认为数据包已经发送出去,而发送的状态需要通过中断得到。接下来,程序释放已经发送数据包的 sk_buff,然后检查 tx_pkt_cnt,判断数据包是否已经发送。如果数据包已经发送,则通过 netif_wake_queue()函数重新开启接收队列。最后,在程序中写入 DM9000 的命令打开中断响应,如果数据包已经发送,驱动程序会收到 DM9000 控制器发送的中断。数据包发送 完 毕 后 , 内 核 会 调 用 后 续 的 处 理 函 数 , DM9000 驱 动 程 序 提 供 了dm9000_tx_done()函数

12.4.3.3.6 dm9000_tx_done 函数


static void dm9000_tx_done(struct net_device *dev, board_info_t *db)
{
int tx_status = ior(db, DM9000_NSR); /* Got TX status */
/* 如果第一个或第二个数据包发送完毕 */
if (tx_status & (NSR_TX2END | NSR_TX1END)) {
/* One packet sent complete */
db->tx_pkt_cnt--; /* 待发送的数据包个数减 1 */
dev->stats.tx_packets++; /* 已经发送的数据包加 1 */
if (netif_msg_tx_done(db))
dev_dbg(db->dev, "tx done, NSR %02x\n", tx_status);
/* Queue packet check & send */
/* 如果还有数据包待发,那么调用 dm9000_send_packet 发送数据包 */
if (db->tx_pkt_cnt > 0)


static void dm9000_tx_done(struct net_device *dev, board_info_t *db)
{
int tx_status = ior(db, DM9000_NSR); /* Got TX status */
/* 如果第一个或第二个数据包发送完毕 */
if (tx_status & (NSR_TX2END | NSR_TX1END)) {
/* One packet sent complete */
db->tx_pkt_cnt--; /* 待发送的数据包个数减 1 */
dev->stats.tx_packets++; /* 已经发送的数据包加 1 */
if (netif_msg_tx_done(db))
dev_dbg(db->dev, "tx done, NSR %02x\n", tx_status);
/* Queue packet check & send */
/* 如果还有数据包待发,那么调用 dm9000_send_packet 发送数据包 */
if (db->tx_pkt_cnt > 0)

dm9000_send_packet(dev, db->queue_ip_summed,
db->queue_pkt_len);
/* 告诉内核,将数据包放入发生那个队列 */
netif_wake_queue(dev);
}

}


dm9000_tx_done()函数首先判断是否已经有一个数据包被成功发送,如果已经有数据包功能发送,则进入第二个数据包处理。程序通过判断缓冲区是否有未发送的数据包,如果有,则通知 DM9000 控制器数据包的长度,然后写入命令发送数据包。数据包发送完毕后,程序开启内核接收数据包队列。


12.4.3.3.7 dm9000_rx 函数


当有数据的时候,就需要通过 dm9000_rx()函数将数据接收进来,并上报数据,传递数据给上层。


static void dm9000_rx(structnet_device *dev)
{
/* 得到网卡私有信息数据结构的首地址 */
board_info_t *db = netdev_priv(dev);
/* 该结构体封装了 dm9000 接收的数据包信息 */
struct dm9000_rxhdr rxhdr;
struct sk_buff *skb;
u8 rxbyte, *rdptr;
bool GoodPacket;
int RxLen;
/* Check packet ready or not */
do {
/* 从 RX SRAM 里读数据 */
ior(db, DM9000_MRCMDX); /* Dummy read */
/* Get most updated data */
rxbyte = readb(db->io_data);
/* Status check: this byte must be 0 or 1 */
if (rxbyte & DM9000_PKT_ERR) {
dev_warn(db->dev, "status check fail: %d\n", rxbyte);
iow(db, DM9000_RCR, 0x00); /* Stop Device */
iow(db, DM9000_ISR, IMR_PAR); /* Stop INT request */
return;
}

if (!(rxbyte & DM9000_PKT_RDY))
return;
/* A packet ready now & Get status/length */
GoodPacket = true;
writeb(DM9000_MRCMD, db->io_addr);
/* 读取数据,从 RX_SRAM 到 rxhdr 结构体中 */
(db->inblk)(db->io_data, &rxhdr, sizeof(rxhdr));
RxLen = le16_to_cpu(rxhdr.RxLen);
if (netif_msg_rx_status(db))
dev_dbg(db->dev, "RX: status %02x, length %04x\n",rxhdr.RxStatus, RxLen);
/* Packet Status check */
if (RxLen < 0x40) {
GoodPacket = false;
if (netif_msg_rx_err(db))
dev_dbg(db->dev, "RX: Bad Packet (runt)\n");
}
/* 判断数据包是否大于 1536 */
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++;
}
}
/* Move data from DM9000 */
/* 如果是好的数据包,并且分配 skb 缓冲区和数据
* 缓冲区成功就会从 DM9000 读取数据包
*/
if (GoodPacket &&
((skb = netdev_alloc_skb(dev, RxLen + 4)) != NULL)) {
/* 将 skb->data 和 skb->tail 同时后移 2 个长度 */
skb_reserve(skb, 2);
/* 在缓冲区尾部添加数据 */
rdptr = (u8 *) skb_put(skb, RxLen - 4);
/* Read received packet from RX SRAM */
/* 读 RxLen 长的 rdptr 指针指向的数据到 db->io_data */
(db->inblk)(db->io_data, rdptr, RxLen);
/* 记录总共接收的字节数 */
dev->stats.rx_bytes += RxLen;
/* Pass to upper layer */
skb->protocol = eth_type_trans(skb, dev);/* 获取网络协议 ID */
if (dev->features & NETIF_F_RXCSUM) {
if ((((rxbyte & 0x1c) << 3) & rxbyte) == 0)
skb->ip_summed = CHECKSUM_UNNECESSARY;
else
skb_checksum_none_assert(skb);
}
/* 网络协议接口层数据包接收 */
netif_rx(skb);
/* 总共接收包计数加 1 */
dev->stats.rx_packets++;
} else {
/* need to dump the packet's data */
(db->dumpblk)(db->io_data, RxLen);
}
} while (rxbyte & DM9000_PKT_RDY);
}


dm9000_rxhdr 结构的 RxStatus 成员变量存放接收数据包的状态, RxLen存放接收到的数据包长度。 首先获取网络控制器状态,然后判断网络控制器状态是否正确。如果网络控制器状态不正确,则停止网络控制器,并且屏蔽中断请求。如果网络控制器处理"准备好"的状态,则向网络控制器发起读数据包命令。然后读取数据包头,然后取出包长。再判断数据包长度是否小于 64 字节,因为以太网协议规定,小于 64 字节的数据包是错误的。


在从网络控制器接收数据包内容之前,程序首先在使用 netdev_alloc_skb ()函数分配了一个 sk_buff 缓冲区,用于存放数据包,然后把数据包从网络控制器的 SRAM 复制到 sk_buff,再更新字节计数器。 新的数据包收到后,就可以通知上层协议栈处理了,调用 eth_type_trans()函数把数据包丢给协议栈,然后更新包计数器。

12.4.3.3.8 dm9000_interrupt函数


dm9000_interrup()中断处理函数只 处理网络控制器发送的接收数据包和发送数据包请求。


static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
{
struct net_device *dev = dev_id;
board_info_t *db = netdev_priv(dev);
int int_status;
unsigned long flags;
u8 reg_save;
dm9000_dbg(db, 3, "entering %s\n", __func__);
/* A real interrupt coming */
/* 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 ISR status */
if (netif_msg_intr(db))
dev_dbg(db->dev, "interrupt status %02x\n", int_status);

/* 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);
if (db->type != TYPE_DM9000E) {
if (int_status & ISR_LNKCHNG) {
/* fire a link-change request */
schedule_delayed_work(&db->phy_poll, 1);
}
}
/* 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);
return IRQ_HANDLED;
}


dm9000_interrupt()函数首先取出当前中断寄存器的值保存,关闭中断请求,并处理 DM9000 的 ISR。 然后判断是否是接收到数据包中断,如果是则调用dm9000_rx()函数接收数据包,再判断是否发送数据包中断,如果是则调用dm9000_tx_done()函数进行处理。 处理完所有的中断以后,程序重新打开中断请求,然后恢复中断处理之前中断寄存器的值。最后对临界资源解锁,整个中断处理流程结束。


到这里, dm9000.c 就基本分析完毕了,至少重点内容已经全部分析完了。至于 dm9000 寄存器的初始化那些不是我们重点关心的内容,有兴趣的读者,请结合 DM9000 数据手册研究研究。

12.4.4 DM9000 驱动配置 


在内核目录下使用 make menuconfig 命令将 DM9000 驱动编译进内核,配置结果导向流程如下:


Device Drivers --->
    [*] Network device support --->
          [*] Ethernet driver support --->
              <*> DM9000 support


12.4. 5 DM9000 驱动的测试


将平台设备相关代码移植好后,根据 12.4.4 小节将 dm9000 驱动配置进内核后,使用 make uImage 命令编译内核,将新的 uImage 内核镜像烧到开发板的 NAND FLASH,使用 NFS 网络文件系统启动内核,如果能成功启动,那么dm9000 驱动是好用的,成功启动后,内核输出信息应该有下面二句信息的出现。


dm9000 Ethernet Driver, V1.31
eth0: dm9000a at a084c000,a084e00c IRQ 39 MAC: 00:09:c0:ff:ec:48
(platform data)
.
Please press Enter to activate this console.


如果还要进一步测试 dm9000 驱动是否真的好用,那么先使用 ifconfig 命令配置好 IP 地址,然后与 PC 机进行互 ping。示例如下:


ifconfig eth0 192.168.1.2
ping 192.168.1.1 23


如果能 ping 通,那么 dm9000 驱动测试成功。

12.5 本章小结学完


本章知识,您是否可以回答出本章开头的那二个问题呢?您的答案又是什么呢?可以到 QQ 群上发表一下你的意见哟。


Linux 系统其实最强大之处就在于它的网络、服务器这一块做的特别成功,学完本章知识,能够加强对 Linux 系统网络这一块的理解。 Linux 网络体系是层次化设计的,实现了上层协议接口的统一。使得驱动工程无需关心协议层是如何实现的,大大的减少了驱动工程师的工作量。驱动工程师只需要将工作重点集中到设备驱动功能层,使用网络设备接口层提供的 net_device 结构体来完成设备驱动,这个 net_device 结构体将千变万化的网络设备得以抽象。


最后本章还将内核自带的 dm9000 驱动源码详细的分析了一遍,让读者能够清晰明了的认识到网络设备驱动的编写流程、重点等内容。 建议读者在学习dm9000 源码时,不要将重点关注到每一个非常细的细节上去,应该要学会如何把握网络设备驱动的层次架构上去。 只要你学会了网络设备的编写步骤、流程、重点该如何编写,不管你的网卡芯片是 dm9000 还是 cx8900 或者是瑞昱 RTL系列的网卡驱动,都是差不多的,区别在于硬件上的设置不一样而已。


最后,如果对本章学习感觉太困难的读者, webee 建议您,先看看《Linux设备驱动第三版[中文]》的第十七章网络驱动,然后再来多次阅读本章内容。


路都是慢慢一步一步走出来的,学习也是一样,不能一步登天。 Webee 总相信一句话,厚积总有一天会薄发,共勉。