前言
本文是笔者在分析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; //连接检测IO
struct napi_struct napi; //napi_struct,此驱动使用NAPI
struct 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;
}