最近一直在看linux网络相关的东西, 做为提高准备自己动手写个网卡驱动, 手上刚好有一块mini2440, 所以准备以mini2440的DM9000下刀。当然本人也是第一次写网卡驱动, 所以希望大家看到不足的地方多海函和指教。
今天, 我们先搭一个网卡驱动的框架, 并不设计实际硬件。
首先先包含今天会用到的必要的头文件:
#include <linux/module.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/netdevice.h> #include <linux/etherdevice.h>
定义调试和打印用的宏:
#undef LYDM9K_DEBUG #define LYDM9K_DEBUG #ifdef LYDM9K_DEBUG #define printdbg(fmt, args...) \ printk("lydm9k[%s]:\t" fmt, __func__, ##args) #else #define printdbg(fmt, args...) \ do {} while(0) #endif /* LYDM9K_DEBUG */ #define dm9kmsg(fmt, args...) \ printk("lydm9k[%s]:\t" fmt, __func__, ##args)
编写module注册函数和卸载函数:
static int __init lydm9k_init(void) { dm9kmsg("insert module lydm9k\n"); platform_device_register(&lydm9k_device); return platform_driver_register(&lydm9k_driver); } static void __exit lydm9k_exit(void) { platform_device_unregister(&lydm9k_device); platform_driver_unregister(&lydm9k_driver); dm9kmsg("remove module lydm9k\n"); } MODULE_LICENSE("GPL"); module_init(lydm9k_init); module_exit(lydm9k_exit);
初始化网卡的device结构体:
struct lydm9k_plat_data { unsigned char mac[6]; unsigned int watchdog_timeo_msecs; };
static struct lydm9k_plat_data lydm9k_plat_data = { .mac = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, .watchdog_timeo_msecs = 5000, }; static struct resource lydm9k_resource[] = { }; static void lydm9k_device_release(struct device *dev) { printdbg("invoked\n"); } static struct platform_device lydm9k_device = { .name = "lydm9k", .id = -1, .num_resources = ARRAY_SIZE(lydm9k_resource), .resource = lydm9k_resource, .dev = { .platform_data = &lydm9k_plat_data, .release = lydm9k_device_release, }, };
初始化网卡的driver结构体:
static struct platform_driver lydm9k_driver = { .driver = { .name = "lydm9k", .owner = THIS_MODULE, }, .probe = lydm9k_probe, .remove = __devexit_p(lydm9k_remove), };
网卡驱动的probe函数和remove函数:
static int __devinit lydm9k_probe(struct platform_device *pdev) { int ret; struct net_device *ndev; struct lydm9k_priv *priv; struct lydm9k_plat_data *pdata = pdev->dev.platform_data; printdbg("detect a dm9k device\n"); // 申请ndev空间 ndev = alloc_etherdev(sizeof(struct lydm9k_priv)); if (NULL == ndev) { dm9kmsg("fail to alloc_etherdev\n"); return -ENOMEM; } // 设置ndev的福设备为pdev SET_NETDEV_DEV(ndev, &pdev->dev); // 设置ndev和pdev直接的关联 platform_set_drvdata(pdev, ndev); priv = netdev_priv(ndev); priv->pdev = pdev; // 设置操作函数集/mac/watchdog_timeo ndev->netdev_ops = &lydm9k_ops; ndev->watchdog_timeo = msecs_to_jiffies(pdata->watchdog_timeo_msecs); memcpy(ndev->dev_addr, pdata->mac, 6); // 注册网卡 ret = register_netdev(ndev); if(0 > ret) { dm9kmsg("fail to register netdev\n"); return -ENOMEM; } // 设置ndev的父设备为pdev SET_NETDEV_DEV(ndev, &pdev->dev); // 设置ndev和pdev直接的关联 platform_set_drvdata(pdev, ndev); priv = netdev_priv(ndev); priv->pdev = pdev; // 设置操作函数集/mac/watchdog_timeo ndev->netdev_ops = &lydm9k_ops; ndev->watchdog_timeo = msecs_to_jiffies(pdata->watchdog_timeo_msecs); memcpy(ndev->dev_addr, pdata->mac, 6); // 注册网卡 ret = register_netdev(ndev); if(0 > ret) { dm9kmsg("fail to register netdev\n"); goto failure_register_netdev; } return 0; failure_register_netdev: platform_set_drvdata(pdev, NULL); free_netdev(ndev); return ret; } static int __devexit lydm9k_remove(struct platform_device *pdev) { struct net_device *ndev = platform_get_drvdata(pdev); unregister_netdev(ndev); platform_set_drvdata(pdev, NULL); free_netdev(ndev); printdbg("remove ok\n"); return 0; }
其中网卡的私有数据结构需要包含的数据先不深入分析,等后续用到了再追加,为了简单先定义成如下:
struct lydm9k_priv { struct platform_device *pdev; };
以及网卡的相关操作函数也都先定义几个成空的示意用:
static int lydm9k_open(struct net_device *ndev) { printdbg("invoked\n"); return 0; } static int lydm9k_stop(struct net_device *ndev) { printdbg("invoked\n"); return 0; } static int lydm9k_start_xmit( struct sk_buff *skb, struct net_device *ndev) { printdbg("invoked\n"); return 0; } static void lydm9k_timeout(struct net_device *ndev) { printdbg("invoked\n"); } static struct net_device_ops lydm9k_ops = { .ndo_open = lydm9k_open, .ndo_stop = lydm9k_stop, .ndo_start_xmit = lydm9k_start_xmit, .ndo_tx_timeout = lydm9k_timeout, };
OK,简单的网卡驱动框架就搭好了, 然后是Makgefile:
ifneq ($(KERNELRELEASE),) obj-m := lydm9k.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif clean: rm -rf *.mod.c *.mod.o *.o *.order *.symvers rm -rf .*.ko.cmd .*.o.cmd .tmp_versions
make, 装载驱动insmod lydm9k.ko, 然后dmesg, 可以看到如下打印信息(在PC机上即可):
[ 3949.452062] lydm9k[lydm9k_init]: insert module lydm9k [ 3949.453609] lydm9k[lydm9k_probe]: detect a dm9k device [ 3949.469968] lydm9k[lydm9k_open]: invoked [ 3949.484108] lydm9k[lydm9k_start_xmit]: invoked [ 3949.920047] lydm9k[lydm9k_start_xmit]: invoked [ 3950.928047] lydm9k[lydm9k_start_xmit]: invoked [ 3951.020927] lydm9k[lydm9k_start_xmit]: invoked [ 3951.081567] lydm9k[lydm9k_start_xmit]: invoked [ 3951.095994] lydm9k[lydm9k_start_xmit]: invoked [ 3951.332173] lydm9k[lydm9k_start_xmit]: invoked [ 3951.583247] lydm9k[lydm9k_start_xmit]: invoked [ 3951.783516] lydm9k[lydm9k_start_xmit]: invoked [ 3952.021169] lydm9k[lydm9k_start_xmit]: invoked [ 3952.208043] lydm9k[lydm9k_start_xmit]: invoked
ifconfig, 可以看到系统里多出了一快网卡(MAC地址是01:02:03:04:05:06, 即我们在lydm9k_plat_data里设置的地址):
eth1 Link encap:以太网 硬件地址 01:02:03:04:05:06 inet6 地址: fe80::302:3ff:fe04:506/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 跃点数:1 接收数据包:0 错误:0 丢弃:0 过载:0 帧数:0 发送数据包:0 错误:0 丢弃:0 过载:0 载波:0 碰撞:0 发送队列长度:1000 接收字节:0 (0.0 B) 发送字节:0 (0.0 B)
OK, 今天就到此为止, 明天继续。