USB网卡驱动分析(rt8152)

USB设备驱动程序分析

最近一直在搞zynq的PL部分,为了保持对驱动程序的敏感度,看着源码分析一下rt8152的驱动程序。之前学单片机一直想着给单片机装一个USB网卡,但是一直没有思路。今天突然想到之前的想法,就带着这个想法加上对内核驱动的怀念,写一下,写点东西有时候能让人的心平静点。

USB总线和USB设备

usb总线是一种常见的高速总线,其实也不算高,usb2.0规定没记错应该是480Mbit/s。物理层上,usb high使用的是差分电压型串行数据线。usb full采用的是差分电流型串行串行数据线。差分抗干扰源于模拟电路的一个差动放大器,放大差模信号,抑制共模信号。再来说一下为什么高速数据总线喜欢用电流来传输信号。我们知道当导线距离增加时,其电阻会增加,那么从发送端到接收端的电压就会发生变化。使用电流传输,在发送端放置一个电流源,无论电阻怎么变换,流过电阻的电流总是恒定的。只要在接收端的端接电阻保持稳点就可以了。
简单介绍完物理层以后,在说一下USB通信的过程。很重要的一点是每次USB通信的发起者都是主机。这点很重要,至于USB的中断传输模式、块传输。。。网上好多,这里只写一些实用的、简单的。
在linux设备驱动中,把usb控制器的驱动程序可以看成是usb总线驱动程序,在设备树中定义了usb控制器的设备节点,控制器通过和设备树匹配,获取到usb控制器的基地址、中断号等资源。这部分程序一般不需要驱动工程师去修改,一般芯片厂商会写好。一般情况下,需要关心的是USB设备驱动程序,也就是挂在USB总线上的设备。通俗的讲就是u盘,usb声卡,usb暖手宝(这算吗☺)。

USB设备匹配方式

usb设备的匹配不需要在设备树中进行定义,而是通过vid和pid。这是usb控制器在在完成热插拔以后做的一项检测工作,会从usb的端点0中读取usb的vid和pid,然后在和已经装在的usb驱动程序进行匹配。

static struct usb_device_id rtl8152_table[] = {
	{REALTEK_USB_DEVICE(VENDOR_ID_REALTEK, 0x8152)},
	{REALTEK_USB_DEVICE(VENDOR_ID_REALTEK, 0x8153)},
	{REALTEK_USB_DEVICE(VENDOR_ID_SAMSUNG, 0xa101)},
	{REALTEK_USB_DEVICE(VENDOR_ID_LENOVO,  0x7205)},
	{REALTEK_USB_DEVICE(VENDOR_ID_LENOVO,  0x304f)},
	{REALTEK_USB_DEVICE(VENDOR_ID_NVIDIA,  0x09ff)},
	{}
};

这是usb网卡的设备ID匹配表。当usb设备接到usb总线上,如果正确匹配,probe函数就会被执行。

思考

  1. usb设备驱动的PID和VID能不能动态配置?
  2. usb设备驱动程序中的VID和PID万一重复怎么办?

网络设备驱动程序分析

网卡设备驱动分析

之前写过一篇 linux网卡设备驱动(任意传输介质传输(与FPGA交互)) 大致说了下网卡驱动程序的结构以及怎么实现一个网络驱动程序。
这里在赘述一下,网卡驱动程序在probe函数中:

  1. struct net_device *netdev;定义网络设备结构体;
  2. netdev = alloc_etherdev(sizeof(struct r8152));分配空间
  3. 填充该结构体;
  4. ret = register_netdev(netdev);注册该设备;
  5. 实现传输函数以及中断接收网络数据。(也可以是轮询,取决于并发量,目前有一种自动检测的驱动程序,第一次是中断形式,之后是轮询);

USB+网卡驱动

标题其实已经说明了,USB网卡驱动程序就是USB驱动程序加上网卡驱动程序。说说题外话吧,如何用带usb控制器的单片机来和usb网卡进行数据交互呢?之前做过STM32上的USB自定义设备程序,是从机的(上位机用的是libusb+qt)。所以对单片机的usb程序有一些记忆,基本都是对端点的操作。接下来去分析linux中的usb网卡驱动程序,以此来构想下单片机如何驱动一个usb网卡。

linux 源码中rt8152驱动程序分析

源码在drivers\net\usb\r8152.c下
先看下入口函数:

static int rtl8152_probe(struct usb_interface *intf,const struct usb_device_id *id)
{
	struct usb_device *udev = interface_to_usbdev(intf);
	struct r8152 *tp;
	struct net_device *netdev;
	int ret;
	usb_driver_set_configuration(udev, 1);
	usb_reset_device(udev);
	
	netdev = alloc_etherdev(sizeof(struct r8152));
	
	tp->mii.dev = netdev;
	tp->mii.mdio_read = read_mii_word;
	tp->mii.mdio_write = write_mii_word;
	tp->mii.phy_id_mask = 0x3f;
	tp->mii.reg_num_mask = 0x1f;
	tp->mii.phy_id = R8152_PHY_ID;

	ret = register_netdev(netdev);
}

把结构性的代码留下了,可以看出在probe函数中,完成了usb操作接口的获取、网络设备的注册、pyh相关设置。

static int rtl_ops_init(struct r8152 *tp)
{
	struct rtl_ops *ops = &tp->rtl_ops;
	int ret = 0;

	switch (tp->version) {
	case RTL_VER_01:
	case RTL_VER_02:
		ops->init		= r8152b_init;
		ops->enable		= rtl8152_enable;
		ops->disable		= rtl8152_disable;
		ops->up			= rtl8152_up;
		ops->down		= rtl8152_down;
		ops->unload		= rtl8152_unload;
		ops->eee_get		= r8152_get_eee;
		ops->eee_set		= r8152_set_eee;
		ops->in_nway		= rtl8152_in_nway;
		ops->hw_phy_cfg		= r8152b_hw_phy_cfg;
		ops->autosuspend_en	= rtl_runtime_suspend_enable;
		break;

	case RTL_VER_03:
	case RTL_VER_04:
	case RTL_VER_05:
	case RTL_VER_06:
		ops->init		= r8153_init;
		ops->enable		= rtl8153_enable;
		ops->disable		= rtl8153_disable;
		ops->up			= rtl8153_up;
		ops->down		= rtl8153_down;
		ops->unload		= rtl8153_unload;
		ops->eee_get		= r8153_get_eee;
		ops->eee_set		= r8153_set_eee;
		ops->in_nway		= rtl8153_in_nway;
		ops->hw_phy_cfg		= r8153_hw_phy_cfg;
		ops->autosuspend_en	= rtl8153_runtime_enable;
		break;

	default:
		ret = -ENODEV;
		netif_err(tp, probe, tp->netdev, "Unknown Device\n");
		break;
	}

	return ret;
}

这段代码完成了所有网卡的操作函数的注册。

static const struct net_device_ops rtl8152_netdev_ops = {
	.ndo_open		= rtl8152_open,
	.ndo_stop		= rtl8152_close,
	.ndo_do_ioctl		= rtl8152_ioctl,
	.ndo_start_xmit		= rtl8152_start_xmit,
	.ndo_tx_timeout		= rtl8152_tx_timeout,
	.ndo_set_features	= rtl8152_set_features,
	.ndo_set_rx_mode	= rtl8152_set_rx_mode,
	.ndo_set_mac_address	= rtl8152_set_mac_address,
	.ndo_change_mtu		= rtl8152_change_mtu,
	.ndo_validate_addr	= eth_validate_addr,
	.ndo_features_check	= rtl8152_features_check,
};

rtl8152_start_xmit()函数很重要,完成了网络数据包的发送。实现如下:


static netdev_tx_t rtl8152_start_xmit(struct sk_buff *skb,
				      struct net_device *netdev)
{
	struct r8152 *tp = netdev_priv(netdev);

	skb_tx_timestamp(skb);

	skb_queue_tail(&tp->tx_queue, skb);

	if (!list_empty(&tp->tx_free)) {
		if (test_bit(SELECTIVE_SUSPEND, &tp->flags)) {
			set_bit(SCHEDULE_NAPI, &tp->flags);
			schedule_delayed_work(&tp->schedule, 0);
		} else {
			usb_mark_last_busy(tp->udev);
			napi_schedule(&tp->napi);
		}
	} else if (skb_queue_len(&tp->tx_queue) > tp->tx_qlen) {
		netif_stop_queue(netdev);
	}

	return NETDEV_TX_OK;
}

再到usb中断服务函数中找到接收大代码:

static void tx_bottom(struct r8152 *tp)
{
	int res;

	do {
		struct tx_agg *agg;

		if (skb_queue_empty(&tp->tx_queue))
			break;

		agg = r8152_get_tx_agg(tp);
		if (!agg)
			break;

		res = r8152_tx_agg_fill(tp, agg);
		if (res) {
			struct net_device *netdev = tp->netdev;

			if (res == -ENODEV) {
				set_bit(RTL8152_UNPLUG, &tp->flags);
				netif_device_detach(netdev);
			} else {
				struct net_device_stats *stats = &netdev->stats;
				unsigned long flags;

				netif_warn(tp, tx_err, netdev,
					   "failed tx_urb %d\n", res);
				stats->tx_dropped += agg->skb_num;

				spin_lock_irqsave(&tp->tx_lock, flags);
				list_add_tail(&agg->list, &tp->tx_free);
				spin_unlock_irqrestore(&tp->tx_lock, flags);
			}
		}
	} while (res == 0);
}

这里使用了中断的下半部,说一下这个知识,在linux内核中,中断处理一般分为以下几种方式:

  1. 直接处理;
  2. 软中断
  3. tasklet;
  4. 中断线程化;
  5. 工作队列;

直接处理就是在中断服务函数中直接处理相应的逻辑,这种情况对应于十分简短的中断处理逻辑;
软中断和tasklet都是发生在中断上下文的,因此在处理函数中不能有休眠。工作队列和中断线程化可以用休眠,但是处理的实时性会差一些。
大体结构已经出来了,还有一个urb的知识没有说;在linux驱动中,通过驱动程序通过urb和设备进行通信。urb相当于网络设备驱动程序中的skb。是传输数据的载体。

static void write_bulk_callback(struct urb *urb)
{
	struct net_device_stats *stats;
	struct net_device *netdev;
	struct tx_agg *agg;
	struct r8152 *tp;
	int status = urb->status;

	agg = urb->context;
	if (!agg)
		return;

	tp = agg->context;
	if (!tp)
		return;

	netdev = tp->netdev;
	stats = &netdev->stats;
	if (status) {
		if (net_ratelimit())
			netdev_warn(netdev, "Tx status %d\n", status);
		stats->tx_errors += agg->skb_num;
	} else {
		stats->tx_packets += agg->skb_num;
		stats->tx_bytes += agg->skb_len;
	}

	spin_lock(&tp->tx_lock);
	list_add_tail(&agg->list, &tp->tx_free);
	spin_unlock(&tp->tx_lock);

	usb_autopm_put_interface_async(tp->intf);

	if (!netif_carrier_ok(netdev))
		return;

	if (!test_bit(WORK_ENABLE, &tp->flags))
		return;

	if (test_bit(RTL8152_UNPLUG, &tp->flags))
		return;

	if (!skb_queue_empty(&tp->tx_queue))
		napi_schedule(&tp->napi);
}

static void intr_callback(struct urb *urb)
{
	struct r8152 *tp;
	__le16 *d;
	int status = urb->status;
	int res;

	tp = urb->context;
	if (!tp)
		return;

	if (!test_bit(WORK_ENABLE, &tp->flags))
		return;

	if (test_bit(RTL8152_UNPLUG, &tp->flags))
		return;

	switch (status) {
	case 0:			/* success */
		break;
	case -ECONNRESET:	/* unlink */
	case -ESHUTDOWN:
		netif_device_detach(tp->netdev);
	case -ENOENT:
	case -EPROTO:
		netif_info(tp, intr, tp->netdev,
			   "Stop submitting intr, status %d\n", status);
		return;
	case -EOVERFLOW:
		netif_info(tp, intr, tp->netdev, "intr status -EOVERFLOW\n");
		goto resubmit;
	/* -EPIPE:  should clear the halt */
	default:
		netif_info(tp, intr, tp->netdev, "intr status %d\n", status);
		goto resubmit;
	}

	d = urb->transfer_buffer;
	if (INTR_LINK & __le16_to_cpu(d[0])) {
		if (!netif_carrier_ok(tp->netdev)) {
			set_bit(RTL8152_LINK_CHG, &tp->flags);
			schedule_delayed_work(&tp->schedule, 0);
		}
	} else {
		if (netif_carrier_ok(tp->netdev)) {
			set_bit(RTL8152_LINK_CHG, &tp->flags);
			schedule_delayed_work(&tp->schedule, 0);
		}
	}

resubmit:
	res = usb_submit_urb(urb, GFP_ATOMIC);
	if (res == -ENODEV) {
		set_bit(RTL8152_UNPLUG, &tp->flags);
		netif_device_detach(tp->netdev);
	} else if (res) {
		netif_err(tp, intr, tp->netdev,
			  "can't resubmit intr, status %d\n", res);
	}
}

这是在usb网卡驱动中操作urb的实例,看起来很像i2c驱动中的i2c_msg;
usb驱动驱动具体操作下次会具体的说明。这次重点是usb网卡的结构分析;
回到最初的愿望,用单片机去控制usb网卡,实现单片机上网可行吗?
答案是可行的,首先要让单片机上有一个简单的操作系统,这样方便网络协议栈的移植,然后就是把单片机的usb host 调好,通过读取usb设备的端点来实现usb网卡和单片机的数据交互。之后有时间做一下这个,实现下最初的理想。人有时候在不具备条件时,总是幻想着自己的梦想,当具备条件时,最初的梦想早已忘得一干二净。

总结

  1. 在linux中,对usb抽象做的比较好,记得当时调单片机的usb调了一个周才实现了基本的数据交互,linux下只要你插上usb就会识别到总线驱动,前提是控制器正常工作,不然可比单片机难搞。
  2. SDIO网卡有时间研究下,据说安卓好多用的是SDIO的网卡芯片。
  • 4
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值