Linux网络设备驱动分析,以W5300以太网驱动为例
前言
本文是笔者在分析Linux网络驱动时记录的笔记,在这里分享给大家。因为笔者目前也属于学习阶段,因此可能会存在分析不清楚甚至分析错误的地方,欢迎大家评判指正!!
版本说明
Linux内核版本:4.1.15
驱动源码路径:drivers/net/ethernet/wiznet
设备私有数据
在分析驱动之前,需要先了解一下W5300自定义的私有数据w5300_priv都有哪些内容
struct w5300_priv {void __iomem *base; //内存基地址spinlock_t reg_lock; //自旋锁bool indirect; //是间接读写还是直接读写u16 (*read) (struct w5300_priv *priv, u16 addr); //读函数void (*write)(struct w5300_priv *priv, u16 addr, u16 data); //写函数int irq; //中断号int link_irq; //连接检测中断,当有新连接或者连接断开触发此中断int link_gpio; //连接检测IOstruct napi_struct napi; //napi_struct,此驱动使用NAPIstruct net_device *ndev; //网络设备结构体指针bool promisc; //混杂接收模式标志u32 msg_enable;
};
驱动入口和出口
static SIMPLE_DEV_PM_OPS(w5300_pm_ops, w5300_suspend, w5300_resume);static struct platform_driver w5300_driver = {.driver = {.name = DRV_NAME,.pm = &w5300_pm_ops,},.probe = w5300_probe,.remove = w5300_remove,
};module_platform_driver(w5300_driver);
1. 其中module_platform_driver是一个宏,展开来包含了驱动的注册platform_driver_register和注销platform_driver_unregister
2. w5300_driver中的driver成员的pm成员,和电源管理有关,用于管理设备的挂起和恢复。其由宏SIMPLE_DEV_PM_OPS定义,这个宏的格式如下
#define SIMPLE_DEV_PM_OPS(name, suspend_fn, resume_fn)
probe函数
接下来看看驱动的probe函数。由上面的w5300_driver可以看出,驱动和设备应该是通过name来进行匹配的,当驱动和设备匹配的时候,w5300_probe就会执行。w5300_probe函数的内容在源码里做了相应的注释,大家自己看就行。
static int w5300_probe(struct platform_device *pdev)
{/*设备私有数据,包含net_device、napi_struct、中断号、基地址等内容*/struct w5300_priv *priv;struct net_device *ndev;int err;/*使用alloc_etherdev申请网络设备,大小为sizeof(net_device+w5300_priv+32bytes对齐部分),返回net_device*/ndev = alloc_etherdev(sizeof(*priv));if (!ndev)return -ENOMEM;/* #define SET_NETDEV_DEV(net, pdev)((net)->dev.parent = (pdev) *//* 将网络设备的基类dev父设备指向了平台设备的设备基类dev */SET_NETDEV_DEV(ndev, &pdev->dev);/*设置platform设备的私有数据为net_device*/platform_set_drvdata(pdev, ndev);/*使用alloc_etherdev申请net_device的时候,为私有数据w5300_priv也申请了内存,*其位置在net_device之后,netdev_priv(ndev)只是简单的return (char*)ndev+ALIGN(*sizeof(struct net_device),NETDEV_ALIGN) (中间和结尾做了32byte对齐) */priv = netdev_priv(ndev);/* w5300_priv里面有个net_device指针 */priv->ndev = ndev;/* net_device操作集和ethtool操作集 */ndev->netdev_ops = &w5300_netdev_ops;ndev->ethtool_ops = &w5300_ethtool_ops;ndev->watchdog_timeo = HZ;/*使用NAPI方式,注册中断发生后用于轮询网卡的poll函数w5300_napi_poll */netif_napi_add(ndev, &priv->napi, w5300_napi_poll, 16);/* This chip doesn't support VLAN packets with normal MTU,* so disable VLAN for this device.*/ndev->features |= NETIF_F_VLAN_CHALLENGED;/*注册网络设备*/err = register_netdev(ndev);if (err < 0)goto err_register;/*将硬件相关的probe独立出来*/err = w5300_hw_probe(pdev);if (err < 0)goto err_hw_probe;return 0;err_hw_probe:unregister_netdev(ndev);
err_register:free_netdev(ndev);return err;
}
接下来就是分析w5300_hw_probe了
static int w5300_hw_probe(struct platform_device *pdev)
{struct wiznet_platform_data *data = dev_get_platdata(&pdev->dev);/*在probe中platform_set_drvdata了,提取出来*/struct net_device *ndev = platform_get_drvdata(pdev);struct w5300_priv *priv = netdev_priv(ndev);const char *name = netdev_name(ndev);struct resource *mem;int mem_size;int irq;int ret;/*拷贝wiznet_platform_data下的mac_addr给net_device的dev_addr*/if (data && is_valid_ether_addr(data->mac_addr)) {memcpy(ndev->dev_addr, data->mac_addr, ETH_ALEN);} else {eth_hw_addr_random(ndev);}/*获取控制器基地址*/mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);/*内存映射*/priv->base = devm_ioremap_resource(&pdev->dev, mem);if (IS_ERR(priv->base))return PTR_ERR(priv->base);mem_size = resource_size(mem);spin_lock_init(&priv->reg_lock);/*根据W5300内存空间(由设备树给出)大小判断是直接读写还是间接读写,因为直接读写和间接读写需要的内存不同*/priv->indirect = mem_size < W5300_BUS_DIRECT_SIZE;if (priv->indirect) {/*间接读写函数*/priv->read = w5300_read_indirect;priv->write = w5300_write_indirect;} else {/*直接读写函数*/priv->read = w5300_read_direct;priv->write = w5300_write_direct;}/*硬件复位*/w5300_hw_reset(priv);if (w5300_read(priv, W5300_IDR) != IDR_W5300) /*判断ID是不是5300*/return -ENODEV;/*获取和请求中断w5300_interrupt*/irq = platform_get_irq(pdev, 0);if (irq < 0)return irq;ret = request_irq(irq, w5300_interrupt,IRQ_TYPE_LEVEL_LOW, name, ndev);if (ret < 0)return ret;priv->irq = irq;/*获取link_gpio,由此得到irq号,并申请连接中断w5300_detect_link。此中断用于提示有新连接或者连接断开*/priv->link_gpio = data ? data->link_gpio : -EINVAL;if (gpio_is_valid(priv->link_gpio)) {char *link_name = devm_kzalloc(&pdev->dev, 16, GFP_KERNEL);if (!link_name)return -ENOMEM;snprintf(link_name, 16, "%s-link", name);priv->link_irq = gpio_to_irq(priv->link_gpio);if (request_any_context_irq(priv->link_irq, w5300_detect_link,IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,link_name, priv->ndev) < 0)priv->link_gpio = -EINVAL;}netdev_info(ndev, "at 0x%llx irq %d\n", (u64)mem->start, irq);return 0;
}
设备操作集和工具集
在w5300_probe中,对网络设备的操作集和工具集进行了指定
ndev->netdev_ops = &w5300_netdev_ops;
ndev->ethtool_ops = &w5300_ethtool_ops;
这两个集合的定义如下,主要包含了W5300的打开、关闭、发送接收、设置MAC地址相关的内容,这里就不一一进行分析了。
static const struct ethtool_ops w5300_ethtool_ops = {.get_drvinfo = w5300_get_drvinfo, //获取驱动信息,包含名字、版本信息和总线信息.get_msglevel = w5300_get_msglevel, //获取消息等级.set_msglevel = w5300_set_msglevel, //设置消息等级.get_link = w5300_get_link, //获取连接状态.get_regs_len = w5300_get_regs_len, //获取寄存器长度.get_regs = w5300_get_regs, //获取所有寄存器的值
};static const struct net_device_ops w5300_netdev_ops = {.ndo_open = w5300_open, //打开设备.ndo_stop = w5300_stop, //关闭设备.ndo_start_xmit = w5300_start_tx, //将sk_buff发送给上层协议栈.ndo_tx_timeout = w5300_tx_timeout, //发送超时,重启,记录超时次数、时间.ndo_set_rx_mode = w5300_set_rx_mode,//设置接收模式,混杂还是非混杂模式.ndo_set_mac_address = w5300_set_macaddr, //设置MAC地址.ndo_validate_addr = eth_validate_addr,.ndo_change_mtu = eth_change_mtu,
};
操作集中的重要函数
1. w5300_open函数,其中主要是通过w5300_hw_start来打开W5300,并使能NAPI:napi_enable和打开网卡队列netif_start_queue,使能NAPI和打开网卡队列以后,上层网络协议就可以把数据包发送到这个设备(W5300)
static int w5300_open(struct net_device *ndev)
{struct w5300_priv *priv = netdev_priv(ndev);netif_info(priv, ifup, ndev, "enabling\n");w5300_hw_start(priv);napi_enable(&priv->napi);netif_start_queue(ndev);if (!gpio_is_valid(priv->link_gpio) ||gpio_get_value(priv->link_gpio) != 0)netif_carrier_on(ndev);return 0;
}
2. w5300_start_tx,这个函数主要内容是通过w5300_write_frame将sk_buff发送给上层协议栈。当然,还需要记录发送数据的大小,释放已经发送完成的sk_buff等
static int w5300_start_tx(struct sk_buff *skb, struct net_device *ndev)
{struct w5300_priv *priv = netdev_priv(ndev);netif_stop_queue(ndev);w5300_write_frame(priv, skb->data, skb->len);mmiowb();ndev->stats.tx_packets++;ndev->stats.tx_bytes += skb->len;dev_kfree_skb(skb);netif_dbg(priv, tx_queued, ndev, "tx queued\n");w5300_command(priv, S0_CR_SEND);return NETDEV_TX_OK;
}
3. w5300_write_frame的实现如下,主要是通过写W5300的发送FIFO,将数据发送出去
static void w5300_write_frame(struct w5300_priv *priv, u8 *buf, int len)
{u16 fifo;int i;for (i = 0; i < len; i += 2) {fifo = *buf++ << 8;fifo |= *buf++;w5300_write(priv, W5300_S0_TX_FIFO, fifo);}w5300_write32(priv, W5300_S0_TX_WRSR, len);
}
中断
回过头来,我们来看一下之前在w5300_probe中申请的中断,分别是w5300_interrupt发送接收中断,以及w5300_detect_link连接检测中断,另外,还有使用netif_napi_add 添加的用于接收中断发生以后用于轮询网卡数据的poll函数w5300_napi_poll
w5300_detect_link
w5300_detect_link主要是根据link_gpio这个引脚的状态来判断新连接加入和连接断开
static irqreturn_t w5300_detect_link(int irq, void *ndev_instance)
{struct net_device *ndev = ndev_instance;struct w5300_priv *priv = netdev_priv(ndev);if (netif_running(ndev)) {if (gpio_get_value(priv->link_gpio) != 0) {netif_info(priv, link, ndev, "link is up\n");netif_carrier_on(ndev);} else {netif_info(priv, link, ndev, "link is down\n");netif_carrier_off(ndev);}}return IRQ_HANDLED;
}
w5300_interrupt
接下来是发送和接收中断w5300_interrupt
static irqreturn_t w5300_interrupt(int irq, void *ndev_instance)
{struct net_device *ndev = ndev_instance;struct w5300_priv *priv = netdev_priv(ndev);/*读取中断寄存器*/int ir = w5300_read(priv, W5300_S0_IR);if (!ir)return IRQ_NONE;、/*写回读出的值,应该是写1清零*/w5300_write(priv, W5300_S0_IR, ir);mmiowb();/*发送完成中断*/if (ir & S0_IR_SENDOK) {netif_dbg(priv, tx_done, ndev, "tx done\n");netif_wake_queue(ndev);/*通知上层协议,可以向网卡发送数据包*/}/*接收中断*/if (ir & S0_IR_RECV) {/*调用__napi_schedule前的检查,判断NAPI是否可以调度,如果NAPI没有被禁止且不存起已经调度的NAPI则允许调度*/if (napi_schedule_prep(&priv->napi)) {w5300_write(priv, W5300_IMR, 0); /*poll前,先关接收中断,接收完再打开*/mmiowb();/*调度到NAPI的poll进行轮询*/__napi_schedule(&priv->napi);}}return IRQ_HANDLED;
}
w5300_napi_poll
w5300_napi_poll,是NAPI技术的核心。NAPI技术使用中断+轮询的方式,当接收中断发生以后,会关闭中断,使用poll的方式处理网络数据,当数据处理完成以后,再重新打开中断。使用这种方法,可以避免突发网络数据导致频繁进入中断而影响到其他进程的执行。在w5300_napi_poll里处理数据接收,将网卡数据转换成skb_buff,最终发往上层协议栈。
static int w5300_napi_poll(struct napi_struct *napi, int budget)
{struct w5300_priv *priv = container_of(napi, struct w5300_priv, napi);struct net_device *ndev = priv->ndev;struct sk_buff *skb;int rx_count;u16 rx_len;for (rx_count = 0; rx_count < budget; rx_count++) {/*W5300_S0_RX_RSR是数据大小寄存器*/u32 rx_fifo_len = w5300_read32(priv, W5300_S0_RX_RSR); if (rx_fifo_len == 0)break;//读RX FIFO中的数据rx_len = w5300_read(priv, W5300_S0_RX_FIFO);/*netdev_alloc_skb_ip_align会申请一个sk_buff结构,同时申请存放报文数据的buffer空间,并将他们关联起来*/skb = netdev_alloc_skb_ip_align(ndev, roundup(rx_len, 2));if (unlikely(!skb)) {u32 i;for (i = 0; i < rx_fifo_len; i += 2)w5300_read(priv, W5300_S0_RX_FIFO);ndev->stats.rx_dropped++;return -ENOMEM;}/*skb_put() -- 扩展缓冲区中数据区域的大小;增加len个字节*/skb_put(skb, rx_len);/*扩展完把数据读到skb_buff*/w5300_read_frame(priv, skb->data, rx_len);skb->protocol = eth_type_trans(skb, ndev);/*netif_receive_skb(),这个函数是内核收包的入口,驱动收到的数据包通过这个函数进入内核协议栈进行处理*/netif_receive_skb(skb);ndev->stats.rx_packets++;ndev->stats.rx_bytes += rx_len;}if (rx_count < budget) {napi_complete(napi); /* NAPI poll完成,将napi设备从轮询列表删除*/w5300_write(priv, W5300_IMR, IR_S0); /*重新开启接收中断*/mmiowb();}return rx_count;
}
Linux网络设备驱动分析,以W5300以太网驱动为例相关推荐
- 【linux驱动分析】之dm9000驱动分析
[linux驱动分析]之dm9000驱动分析(一):dm9000原理及硬件分析 [linux驱动分析]之dm9000驱动分析(一):dm9000原理及硬件分析 [linux驱动分析]之dm9000驱动 ...
- 【linux驱动分析】之dm9000驱动分析(三):sk_buff结构分析
[linux驱动分析]之dm9000驱动分析(一):dm9000原理及硬件分析 [linux驱动分析]之dm9000驱动分析(二):定义在板文件里的资源和设备以及几个宏 [linux驱动分析]之dm9 ...
- Linux spi驱动分析(四)----SPI设备驱动(W25Q32BV)
一.W25Q32BV芯片简介 W25X是一系列SPI接口Flash芯片的简称,它采用SPI接口和CPU通信,本文使用的W25Q32BV容量为32M,具体特性如下: 1.1.基本特性 该芯片最大支持10 ...
- 【linux驱动分析】misc设备驱动
misc设备驱动.又称混杂设备驱动. misc设备驱动共享一个设备驱动号MISC_MAJOR.它在include\linux\major.h中定义: #define MISC_ ...
- 免费分享:5本安卓开发经典书籍,Android 7编程入门经典(第4版),Android底层驱动分析和移植,底层驱动分析和移植
1.Android 7编程入门经典(第4版) 使用Android Studio 2 PDF 下载 下载地址: http://www.askwinds.com/r-c/down-info-02/579 ...
- 触摸屏驱动分析(eeti源码为例)
module_init(egalax_i2c_ts_init)–>表示驱动加载时首先执行的函数是egalax_i2c_ts_init,下面看egalax_i2c_ts_init函数源码: sta ...
- android 触摸屏驱动分析,rk3188--6.android 触摸屏驱动分析
在drivers/input/touchscreen/ft5302_tp/ft5302_ts.c中 module_init(ft5x0x_ts_init); static int __init ft5 ...
- 树莓派zero w安装linux,树莓派 Zero W 的USB/以太网应用一例
来自 Archer 的投稿,感谢~ 前些日子突发奇想,想用树莓派 Zero W 做成S*P*Y无线网卡来玩,用Openwrt来做,不过怎么做都不成功,于是我就换个思路,做成以太网卡如何?我就用树莓派连 ...
- Linux中断子系统(二)中断控制器GIC驱动分析
Linux中断子系统(二)中断控制器GIC驱动分析 备注: 1. Kernel版本:5.4 2. 使用工具:Source Insight 4.0 3. 参考博客: Linux中断子系统(一 ...
最新文章
- DecimalFormat 的使用
- SQL语法中drop,delete与truncate的区别
- 面向对象的本质是算法的上下文封装,是同一类属的行为接口的一致性
- android wear升级方法,LG G Watch官方工具包刷Android wear5.1.1教程(附刷机包)
- Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(二)之Introduction to Objects...
- [2018湖南省队集训] 6.28 T3 simulate
- html文本改,编辑html格式文本可改成txt格式(可以替换或更换某文本)新手
- 因果推断笔记——自整理因果推断理论解读(七)
- SQL Server分页3种方案比拼[转]
- Java/Spring/SpringBoot利用itextpdf将JPG/PNG/TIF等输出为PDF(解决TIF多页合并问题)
- 速卖通关键词挖掘工具_谷歌优化关键词挖掘工具大全
- 内燃机 vs 外燃机
- javascript入门及基础语法结构
- 拥有一本CISP证书,我的工资会翻倍吗?
- 高盛:DeFi 的互操作性可能会增加系统性风险
- 实现html页面的倒计时
- 朱xx 现在开始有点不知道正义的一方是哪边了
- java并发原理实战(8)-- lock接口使用和认识
- 惊呼!一枚程序缓,竟能开发出如此劲爆的僵尸游戏!
- 第二章(第三部分) 出发之前