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;  //连接检测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;
}

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以太网W5300芯片是一款用于实现以太网通信的专用集成电路芯片。在使用W5300芯片进行驱动时,需要进行以下几个步骤。 首先,我们需要准备一个W5300芯片的驱动程序。这个驱动程序可以从官方网站或者其他可信的资源中获取。下载后,我们需要将其解压并保存到一个合适的位置。 接下来,我们需要将W5300芯片连接到我们的目标设备上。这包括将芯片插入到适当的插槽中,并连接相应的引脚,如电源和通讯接口等。 一旦W5300芯片与设备连接好,我们就可以开始加载驱动程序了。这通常需要在操作系统中进行设置。具体的设置方法可以在驱动程序提供的文档中找到。 一旦驱动程序加载成功,我们就可以开始使用W5300芯片进行以太网通信了。这包括发送和接收数据包,建立和维护连接等操作。我们可以使用编程语言中的相关库或者API来进行这些操作。 在使用W5300芯片进行驱动时,我们还需要注意一些常见的问题。例如,需要确保设备的硬件连接正确,驱动程序的版本与芯片兼容等。此外,我们还需要遵循相关的网络协议和程式设计规范,以确保通信的正确性和稳定性。 总之,驱动以太网W5300芯片需要准备合适的驱动程序,连接芯片到设备,加载驱动程序,使用相关的库或API进行通信操作,同时注意常见问题和规范。这样一来,我们就可以成功地使用W5300芯片实现以太网通信了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值