前文回顾
《linux驱动开发(一)—环境搭建与hello world》
《linux驱动开发(二)—驱动与设备的分离设计》
《linux驱动开发(三)—设备树》
《linux驱动开发(四)—树莓派内核编译》
《linux驱动开发(五)—树莓派设备树配合驱动开发》
《linux驱动开发(六)—树莓派配合硬件进行字符驱动开发》
《linux驱动开发(七)—树莓派按键驱动开发》
《linux驱动开发(八)—树莓派sr04驱动开发》
《linux驱动开发(九)—树莓派i2c设备驱动开发(bme280)》
《linux驱动开发(十)—树莓派输入子系统学习(红外接收)》
《linux驱动开发(十一)—树莓派spi驱动学习(oled)》
《linux驱动开发(十二)—树莓派framebuffer学习(改造oled)》
《linux驱动开发(十三)—usb驱动hid开发学习(鼠标)》
《linux驱动开发(十四)—usb驱动开发学习(键盘+鼠标)》
《linux驱动开发(十五)—如何使用内核现有驱动(显示屏)》
《linux驱动开发(十六)—块设备驱动》
《linux驱动开发(十七)—树莓派pwm驱动》
今天来学一下驱动中的第三类设备,网络设备的驱动。边学边写,慢慢理解。
网络设备驱动程序结构
linux网络设备驱动程序的体系结构如图所示,从上到下可以划分为4层,依次为网络协议接口层、网络设备接口层、提供实际功能的设备驱动功能层以及网络设备与媒介层,这4层的作用如下所示:
- 网络协议接口层
向网络层协议提供统一的数据包收发接口,不论上层协议是arp还是ip,都通过dev_queue_xmit()函数发送数据,并通过netif_rx()数接收数据。这一层的存在使得上层协议独立于具体的设备。 - 网络设备接口层
向协议接口层提供统一的用于描述具体网络设备属性和操作的结构体net_device,该结构体是设备驱动功能层中各函数的容器。实际上,网络设备接口层从宏观上规划了具体操作硬件的设备驱动功能层的结构。 - 设备驱动功能层
设备驱动功能层的各函数是网络设备接口层net_device数据结构的具体成员,是驱使网络设备硬件完成相应动作的程序,它通过hard_start_xmit()函数启动发送操作,并通过网络设备上的中断触发接收操作。 - 网络设备与媒介层
网络设备与媒介层是完成数据包发送和接收的物理实体,包括网络适配器和具体的传输媒介,网络适配器被设备驱动功能层中的函数在物理上驱动。对于linux系统而言,网络设备和媒介都可以是虚拟的。
以上内容来自《linux设备驱动开发详解》,其中下面这句就是关键
在设计具体的网络设备驱动程序时,我们需要完成的主要工作是
编写设备驱动功能层的相关函数
以
填充net device数据结构
的内容
并将net device注册入内核。
看看,三个核心操作。
虚拟网卡
来看一个虚拟网卡的例子,就是直接构造软件网卡,来看一下实现过程
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/netdev_features.h>
static struct net_device *virt_net;
static void virt_rs_packet(struct sk_buff *skb, struct net_device *dev)
{
unsigned char *type;
struct iphdr *ih;
__be32 *saddr, *daddr, tmp;
unsigned char tmp_dev_addr[eth_alen];
struct ethhdr *ethhdr;
struct sk_buff *rx_skb;
int ret;
//对调ethhdr结构体 "源/目的"mac地址*/
ethhdr = (struct ethhdr *)skb->data;
memcpy(tmp_dev_addr, ethhdr->h_dest, eth_alen);
memcpy(ethhdr->h_dest, ethhdr->h_source, eth_alen);
memcpy(ethhdr->h_source, tmp_dev_addr, eth_alen);
//对调iphdr结构体"源/目的" ip地址
ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
saddr = &ih->saddr;
daddr = &ih->daddr;
tmp = *saddr;
*saddr = *daddr;
*daddr = tmp;
ih->check=0;
ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
//之前是发送ping包0x08,需要改为0x00,表示接收ping包
type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
*type = 0;
rx_skb = dev_alloc_skb(skb->len + 2);
skb_reserve(rx_skb, 2);
memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
rx_skb->dev = dev;
rx_skb->ip_summed = checksum_unnecessary;
rx_skb->protocol = eth_type_trans(rx_skb, dev);
ret=netif_rx(rx_skb);
dev->stats.rx_packets++;
dev->stats.rx_bytes += skb->len;
pr_info("rx_packets=%ld rx_bytes=%ld ret=%d\n",dev->stats.rx_packets,dev->stats.rx_bytes,ret);
}
static int virt_send_packet(struct sk_buff *skb, struct net_device *dev)
{
netif_stop_queue(dev);
virt_rs_packet(skb,dev);
dev_kfree_skb(skb);
dev->stats.tx_packets++;
dev->stats.tx_bytes+=skb->len;
pr_info("tx_packets=%ld tx_bytes=%ld\n",dev->stats.tx_packets,dev->stats.tx_bytes);
netif_wake_queue(dev);
return netdev_tx_ok;
}
static int set_mac_address(struct net_device *dev,void *p)
{
struct sockaddr *addr = p;
pr_info("set_mac_address\n");
if (netif_running(dev))
{
return -ebusy;
}
memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);
return 0;
}
void virt_tx_timeout(struct net_device *net,unsigned int txqueue)
{
pr_info("virt_tx_timeout\n");
}
static const struct net_device_ops net_ops =
{
.ndo_start_xmit = virt_send_packet,
.ndo_set_mac_address =set_mac_address,
.ndo_tx_timeout = virt_tx_timeout,
};
static int virt_net_init(void){
virt_net= alloc_netdev(sizeof(struct net_device), "virt_net", net_name_unknown,ether_setup);
virt_net->netdev_ops= &net_ops;
virt_net->flags = iff_noarp;
virt_net->dev_addr[0] = 0x88;
virt_net->dev_addr[1] = 0x88;
virt_net->dev_addr[2] = 0x88;
virt_net->dev_addr[3] = 0x88;
virt_net->dev_addr[4] = 0x88;
virt_net->dev_addr[5] = 0x88;
register_netdev(virt_net);
return 0;
}
static void virt_net_exit(void)
{
unregister_netdev(virt_net);
free_netdev(virt_net);
}
module_init(virt_net_init);
module_exit(virt_net_exit);
module_license("gpl");
编译加载之后,就有了这么一个网卡
不过这个169.254.106.62,为什么他会有这么一个ip
不过不知道为啥我这个linux也吃了这么一个瓜
不过还是可以ping通的
为啥能ping通,因为我们在代码里给它返回了正确应答。
虚拟网卡代码分析
其中,在模块初始化函数中就做了前面说的三个步骤,过程也很简单。
- 申请一个net_device结构的内存
virt_net= alloc_netdev(sizeof(struct net_device), "virt_net", net_name_unknown,ether_setup);
- 填充数据
包括操作netdev_ops和一些参数,例如下面的网卡mac
virt_net->netdev_ops= &net_ops;
virt_net->flags = iff_noarp;
virt_net->dev_addr[0] = 0x88;
virt_net->dev_addr[1] = 0x88;
virt_net->dev_addr[2] = 0x88;
virt_net->dev_addr[3] = 0x88;
virt_net->dev_addr[4] = 0x88;
virt_net->dev_addr[5] = 0x88;
- 注册网络设备到内核
register_netdev(virt_net);
net_device的数据结构很庞大,感兴趣的可以在netdevice.h中查看一下,本身就是网络设备的参数和状态信息。其中的操作结构net_device_ops也是一样的庞大,这个里面是操作处理函数,例如接收到数据的回调函数。
那在这个虚拟设备中,就只是封装了三个接口
static const struct net_device_ops net_ops =
{
.ndo_start_xmit = virt_send_packet,
.ndo_set_mac_address =set_mac_address,
.ndo_tx_timeout = virt_tx_timeout,
};
netdev_tx_t (*ndo_start_xmit)(struct sk_buff *skb, struct net_device *dev);
功能:当需要传输数据包时调用。返回netdev_tx_ok。可以返回netdev_tx_busy,但应在这之前停止队列;它适用于过时的冷门的设备,但如果您返回netdev_tx_busy,堆栈确实会做大量无用的工作。必填的;不能为空。
这翻译是不是有点那味
int (*ndo_set_mac_address)(struct net_device *dev, void *addr);
功能:当mac地址需要改变时,如果未定义此接口,则mac地址无法更改。
void (*ndo_tx_timeout)(struct net_device *dev, unsigned int txqueue);
功能:当发送器未对dev->watchdog ticks取得任何进展时使用的回调。
那么整体来看,这个例子的功能,就是在数据来临之后,一旦调用virt_send_packet函数,就把收到的数据,利用本地函数virt_rs_packet将数据的源目的地址调换,修改成应答,发送出去,在ping的时候,就会有应答的反应了。
参照icmp的格式。
把请求
变成了应答
所以ping的时候,就会有反应。
学习rtl8187驱动
不过前面的虚拟网卡比较简单,内核中已经有好多网卡的驱动,我这有一个usb的无线网卡,不妨来学习一下,型号是rtl8187,所有代码都在这个路径下
其中dev.c是模块的主文件,创建模块,probe函数等都在这里。
首先是作为usb设备进行注册。毕竟是usb模块嘛
在probe函数中,用到了一个ieee80211的子系统,具体学习可以参考《linux无线驱动简介及mac80211源码分析》
不过我看了半天,还是没看太懂,过于复杂,不做展示
在probe函数中,主要相关是下面三个函数的使用。
申请
dev = ieee80211_alloc_hw(sizeof(*priv), &rtl8187_ops);
配置
ieee80211_hw_set(dev, rx_includes_fcs);
……
注册
err = ieee80211_register_hw(dev);
在申请步骤,和rtl8187相关的操作都在rtl8187_ops结构中
这里面的大部分操作,都是和usb读写相关的,举个例子tx函数
* @tx: handler that 802.11 module calls for each transmitted frame.
* skb contains the buffer starting from the ieee 802.11 header.
* the low-level driver should send the frame out based on
* configuration in the tx control data. this handler should,
* preferably, never fail and stop queues appropriately.
* must be atomic.
最终调用到
static void rtl8187_tx(struct ieee80211_hw *dev,
struct ieee80211_tx_control *control,
struct sk_buff *skb)
最终也是通过usb结构urb发送出去
然后就是中间的相关配置整个模块,里面用到了usb传输方式配置寄存器,也用了eep配置网卡中的数据。
最终注册函数
ieee80211_register_hw->
ieee80211_if_add->
cfg80211_register_netdevice->
register_netdevice
完成了网卡的注册。
整个过程目前只捋顺了这些东西,要想看懂每一行,估计也是很困难,只是把关键的操作找出来了。
可以再来看一个更简单的协议
学习rtl8150驱动
首先也是注册一个usb设备
然后看probe函数
static int rtl8150_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
rtl8150_t *dev;
struct net_device *netdev;
netdev = alloc_etherdev(sizeof(rtl8150_t));
if (!netdev)
return -enomem;
dev = netdev_priv(netdev);
dev->intr_buff = kmalloc(intbufsize, gfp_kernel);
if (!dev->intr_buff) {
free_netdev(netdev);
return -enomem;
}
tasklet_setup(&dev->tl, rx_fixup);
spin_lock_init(&dev->rx_pool_lock);
dev->udev = udev;
dev->netdev = netdev;
netdev->netdev_ops = &rtl8150_netdev_ops;
netdev->watchdog_timeo = rtl8150_tx_timeout;
netdev->ethtool_ops = &ops;
dev->intr_interval = 100; /* 100ms */
if (!alloc_all_urbs(dev)) {
dev_err(&intf->dev, "out of memory\n");
goto out;
}
if (!rtl8150_reset(dev)) {
dev_err(&intf->dev, "couldn't reset the device\n");
goto out1;
}
fill_skb_pool(dev);
set_ethernet_addr(dev);
usb_set_intfdata(intf, dev);
set_netdev_dev(netdev, &intf->dev);
if (register_netdev(netdev) != 0) {
dev_err(&intf->dev, "couldn't register the device\n");
goto out2;
}
dev_info(&intf->dev, "%s: rtl8150 is detected\n", netdev->name);
return 0;
out2:
usb_set_intfdata(intf, null);
free_skb_pool(dev);
out1:
free_all_urbs(dev);
out:
kfree(dev->intr_buff);
free_netdev(netdev);
return -eio;
}
是不是和虚拟网卡的有点类似了,简单清楚
几个核心操作
- 申请设备的私有空间
netdev = alloc_etherdev(sizeof(rtl8150_t));
这里就是开发网络驱动的核心部分,这个netdev是一个私有空间,不同类型的网卡,通常结构不一样,里面存放的就是针对这个设备驱动的数据
例如
- 注册接收终端下半部
tasklet_setup(&dev->tl, rx_fixup);
- 配置操作函数
netdev->netdev_ops = &rtl8150_netdev_ops;
- 甚至于更相似的设置网卡mac地址
set_ethernet_addr(dev);
- 最后注册
if (register_netdev(netdev) != 0) {
dev_err(&intf->dev, "couldn't register the device\n");
goto out2;
}
这个感觉还是清楚多了。不过实际上,可能真的没什么机会去写一个完整的驱动,应该是以移植驱动到一个嵌入式平台为主吧。
结束语
最近的大事就是辅助驾驶出事故,辅助驾驶在目前来看还是不够成熟,辅助驾驶还得驾驶员紧盯着,也不知道在辅助什么,而且都知道无法识别静态物体,反正是做不到自动驾驶的阶段,还是都别用的比较好。
你们想想,你带着老婆,出了城,吃着火锅还唱着歌!突然就撞了人!
不过,就目前的人工智能来说,代替财务应该是没啥问题的。但是为啥没有财务被代替呢?因为人工智能,不会坐牢。
今天是中元节,还是要早点回家,本来阳气就不足,可别被偷走了。
发表评论