前言

本文是笔者在分析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以太网驱动为例相关推荐

  1. 【linux驱动分析】之dm9000驱动分析

    [linux驱动分析]之dm9000驱动分析(一):dm9000原理及硬件分析 [linux驱动分析]之dm9000驱动分析(一):dm9000原理及硬件分析 [linux驱动分析]之dm9000驱动 ...

  2. 【linux驱动分析】之dm9000驱动分析(三):sk_buff结构分析

    [linux驱动分析]之dm9000驱动分析(一):dm9000原理及硬件分析 [linux驱动分析]之dm9000驱动分析(二):定义在板文件里的资源和设备以及几个宏 [linux驱动分析]之dm9 ...

  3. Linux spi驱动分析(四)----SPI设备驱动(W25Q32BV)

    一.W25Q32BV芯片简介 W25X是一系列SPI接口Flash芯片的简称,它采用SPI接口和CPU通信,本文使用的W25Q32BV容量为32M,具体特性如下: 1.1.基本特性 该芯片最大支持10 ...

  4. 【linux驱动分析】misc设备驱动

        misc设备驱动.又称混杂设备驱动. misc设备驱动共享一个设备驱动号MISC_MAJOR.它在include\linux\major.h中定义:         #define MISC_ ...

  5. 免费分享:5本安卓开发经典书籍,Android 7编程入门经典(第4版),Android底层驱动分析和移植,底层驱动分析和移植

    1.Android 7编程入门经典(第4版) 使用Android Studio 2  PDF 下载 下载地址: http://www.askwinds.com/r-c/down-info-02/579 ...

  6. 触摸屏驱动分析(eeti源码为例)

    module_init(egalax_i2c_ts_init)–>表示驱动加载时首先执行的函数是egalax_i2c_ts_init,下面看egalax_i2c_ts_init函数源码: sta ...

  7. android 触摸屏驱动分析,rk3188--6.android 触摸屏驱动分析

    在drivers/input/touchscreen/ft5302_tp/ft5302_ts.c中 module_init(ft5x0x_ts_init); static int __init ft5 ...

  8. 树莓派zero w安装linux,树莓派 Zero W 的USB/以太网应用一例

    来自 Archer 的投稿,感谢~ 前些日子突发奇想,想用树莓派 Zero W 做成S*P*Y无线网卡来玩,用Openwrt来做,不过怎么做都不成功,于是我就换个思路,做成以太网卡如何?我就用树莓派连 ...

  9. Linux中断子系统(二)中断控制器GIC驱动分析

    Linux中断子系统(二)中断控制器GIC驱动分析 备注:   1. Kernel版本:5.4   2. 使用工具:Source Insight 4.0   3. 参考博客: Linux中断子系统(一 ...

最新文章

  1. DecimalFormat 的使用
  2. SQL语法中drop,delete与truncate的区别
  3. 面向对象的本质是算法的上下文封装,是同一类属的行为接口的一致性
  4. android wear升级方法,LG G Watch官方工具包刷Android wear5.1.1教程(附刷机包)
  5. Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(二)之Introduction to Objects...
  6. [2018湖南省队集训] 6.28 T3 simulate
  7. html文本改,编辑html格式文本可改成txt格式(可以替换或更换某文本)新手
  8. 因果推断笔记——自整理因果推断理论解读(七)
  9. SQL Server分页3种方案比拼[转]
  10. Java/Spring/SpringBoot利用itextpdf将JPG/PNG/TIF等输出为PDF(解决TIF多页合并问题)
  11. 速卖通关键词挖掘工具_谷歌优化关键词挖掘工具大全
  12. 内燃机 vs 外燃机
  13. javascript入门及基础语法结构
  14. 拥有一本CISP证书,我的工资会翻倍吗?
  15. 高盛:DeFi 的互操作性可能会增加系统性风险
  16. 实现html页面的倒计时
  17. 朱xx 现在开始有点不知道正义的一方是哪边了
  18. java并发原理实战(8)-- lock接口使用和认识
  19. 惊呼!一枚程序缓,竟能开发出如此劲爆的僵尸游戏!
  20. 第二章(第三部分) 出发之前

热门文章

  1. PostgreSQL数据库集簇
  2. css在开发中禁止使用通配符
  3. 高清精美壁纸:2013年5月桌面日历壁纸免费下载
  4. Linux DHCP配置IP
  5. 批处理调用cacls修改文件权限
  6. foreign 磁盘阵列_RAID 磁盘状态为foreign,怎么变成ready
  7. 聊天界面html+css+javascript
  8. 程序员的涅槃重生之路
  9. 腾讯数十亿广告的秘密武器:利用大数据实时精准推荐
  10. Python猜拳游戏(五局三胜)