linux usb usbip驱动详解(四)

2023-05-16

      我们先讲解vhci-hcd驱动(linux-4.20.14的usbip驱动)。

      usb主机控制器驱动hcd学习心得:可以阅读某款SOC的主机控制器驱动代码,譬如TI的am3358芯片,可以看musb驱动代码(drivers/usb/musb/musb_host.c),或者阅读虚拟主机控制器(代码在drivers/usb/gadget/udc/dummy_hcd.c),该虚拟USB主机控制器驱动内还包含一个虚拟USB设备控制器驱动(dummy_udc)供写gadget驱动时测试使用。但无论你阅读hcd、udc还是gadget都好,都要阅读usb core代码。

      vhci_hcd是一个platform驱动:

static struct platform_driver vhci_driver = {
    .probe    = vhci_hcd_probe,
    .remove    = vhci_hcd_remove,
    .suspend = vhci_hcd_suspend,
    .resume    = vhci_hcd_resume,
    .driver    = {
        .name = driver_name,
    },
};
...
...
ret = platform_driver_register(&vhci_driver);
//为了方便说明,代码做了简化,vhci_num_controllers固定设为1,错误处理忽略
static int __init vhci_hcd_init(void)
{
  int i, ret;
  if (usb_disabled())
   return -ENODEV;

  vhci_num_controllers = 1;

  vhcis = kcalloc(vhci_num_controllers, sizeof(struct vhci), GFP_KERNEL);
  if (vhcis == NULL)
    return -ENOMEM;

  vhcis->pdev = platform_device_alloc(driver_name, i);
  if (!vhcis->pdev) {
    ...
  }

  ret = platform_device_add_data(vhcis->pdev, &vhcis, sizeof(void *));
  ...
  
  ret = platform_driver_register(&vhci_driver);
  ...
  
  ret = platform_device_add(vhcis->pdev);
	...

	return ret;
}

      为了方便说明,vhci_hcd_init()代码做了简化,vhci_num_controllers固定设为1,错误处理忽略。但platform驱动不是本文的重点,我们可以认为,在vhci_hcd_init()中先malloc一块内存,用来存储struct vhci结构,该结构是记录了本驱动平台设备指针(struct platform_device)和本驱动的重要数据结构指针(struct vhci_hcd),用于相互关联,便于利用container_of()找到对方:

struct vhci {
	spinlock_t lock;

	struct platform_device *pdev;

	struct vhci_hcd *vhci_hcd_hs;
	struct vhci_hcd *vhci_hcd_ss;
};

        因为struct vhci结构是创建“平台设备”(platform_device_alloc())时添加进去作为“私有数据”(platform_device_add_data)的,所以能很方便与平台驱动关联起来,有现成接口获取平台私有数据。底层platform驱动框架会利用driver_name进行匹配(match),找到具体的平台驱动实例,也就是我们的vhci-hcd驱动,最后框架会回调我们定义的vhci_hcd_probe(struct platform_device *pdev),其中参数pdev就是我们自己在vhci_hcd_init()中分配的,从pdev->dev我们可以很轻易拿到struct vhci结构体指针,这一步就是“从平台设备找struct vhci对象”,反过来更容易,struct vhci结构本身包含平台设备指针pdev,初始化时填充进去就ok了。Linux内核是面向对象的,实例(对象)不止一个,而且是分层的,有很多core层或者框架层,经常被“某驱动框架”回调,所以阅读linux内核代码最主要的是搞清楚各个结构体指针究竟是从哪里分配的,到哪里去,怎么根据这个结构体找另外一个关联的结构体等。

       我们从入口函数vhci_hcd_probe()开始分析:

static int vhci_hcd_probe(struct platform_device *pdev)
{
	struct vhci             *vhci = *((void **)dev_get_platdata(&pdev->dev));
	struct usb_hcd		*hcd_hs;
	struct usb_hcd		*hcd_ss;
	int			ret;

	usbip_dbg_vhci_hc("name %s id %d\n", pdev->name, pdev->id);

	/*
	 * Allocate and initialize hcd.
	 * Our private data is also allocated automatically.
	 */
	hcd_hs = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev));
	if (!hcd_hs) {
		pr_err("create primary hcd failed\n");
		return -ENOMEM;
	}
	hcd_hs->has_tt = 1;

	/*
	 * Finish generic HCD structure initialization and register.
	 * Call the driver's reset() and start() routines.
	 */
	ret = usb_add_hcd(hcd_hs, 0, 0);
	if (ret != 0) {
		pr_err("usb_add_hcd hs failed %d\n", ret);
		goto put_usb2_hcd;
	}
        
        //USB super speed本文不关注,有兴趣自己阅读linux-4.20.14源码
	hcd_ss = usb_create_shared_hcd(&vhci_hc_driver, &pdev->dev,
				       dev_name(&pdev->dev), hcd_hs);
	
	...
        ...
}

usbip_dbg_vhci_hc()是用于调试使用的,可在make menuconfig时开启usbip的debug功能,开启了之后,会影响usb数据传输的速度,仅作调试使用。

hcd_hs = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev));

这个就是hcd的重要接口。用于注册一个USB主机控制器驱动实例:

static const struct hc_driver vhci_hc_driver = {
	.description	= driver_name,
	.product_desc	= driver_desc,
	.hcd_priv_size	= sizeof(struct vhci_hcd),

	.flags		= HCD_USB3 | HCD_SHARED,

	.reset		= vhci_setup,
	.start		= vhci_start,
	.stop		= vhci_stop,

	.urb_enqueue	= vhci_urb_enqueue,
	.urb_dequeue	= vhci_urb_dequeue,

	.get_frame_number = vhci_get_frame_number,

	.hub_status_data = vhci_hub_status,
	.hub_control    = vhci_hub_control,
	.bus_suspend	= vhci_bus_suspend,
	.bus_resume	= vhci_bus_resume,

	.alloc_streams	= vhci_alloc_streams,
	.free_streams	= vhci_free_streams,
};

因为框架已经有了,我们的工作归结为:“为struct hc_driver对象注册回调函数”,以实现多态,不同实例有不同实现。

我们只关注vhci-hcd使用到的、比较重要的成员函数和成员变量:

.flags        = HCD_USB3 | HCD_SHARED代表vhci-hcd支持usb3.0设备,同时指定usb2.0和usb3.x的两个hcd使用相同的硬件。因为此处(上面的vhci_hcd_probe)定义了两个hcd:/* Each VHCI has 2 hubs (USB2 and USB3), each has VHCI_HC_PORTS ports */

hcd_hs = usb_create_hcd(&vhci_hc_driver, &pdev->dev, dev_name(&pdev->dev));
...
hcd_ss = usb_create_shared_hcd(&vhci_hc_driver, &pdev->dev,
				       dev_name(&pdev->dev), hcd_hs);

hcd_hs->has_tt = 1;开启root hub的TT(Integrated TT),TT应该是高速HUB也能支持传输低速和全速的usb设备,详情阅读CH11章节,看hub底层数据包的传输原理。

      很例牌,调用完usb_add_hcd()后,系统就认为存在一个主机控制器了,我主要讲usb2.0部分(hcd_hs),usb3.0部分有兴趣的朋友自行阅读代码。

      我们发现整个vhci_hcd_probe函数都没有看到创建的struct usb_hcd  *hcd_hs保存到哪,毕竟我们后面肯定会用到这个usb主机控制器驱动,但怎么找回来呢?它没有单纯使用全局变量保存,而是利用了平台驱动的“私有数据”来传递,以后用到struct usb_hcd对象也能找回来。它是在usb_create_hcd()中调用了dev_set_drvdata(dev, hcd);

static inline void dev_set_drvdata(struct device *dev, void *data)
{
	dev->driver_data = data;
}

      放到平台驱动的driver_data中了,到时可以根据struct vhci找到平台设备(struct platform_device *pdev),就能找到对应的平台驱动,最后找到struct usb_hcd ,或者反过来,即可以从struct usb_hcd找到struct vhci指针,container_of()功能强大。

      我们继续struct hc_driver结构体的其他成员说明,看usb_add_hcd()的官方注释可以知道,reset和start这个成员函数会在usb_add_hcd()中被回调,用于基本的初始化工作。其中,reset比start先执行。

/**
 * usb_add_hcd - finish generic HCD structure initialization and register
 * @hcd: the usb_hcd structure to initialize
 * @irqnum: Interrupt line to allocate
 * @irqflags: Interrupt type flags
 *
 * Finish the remaining parts of generic HCD initialization: allocate the
 * buffers of consistent memory, register the bus, request the IRQ line,
 * and call the driver's reset() and start() routines.
 * If it is an OTG device then it only registers the HCD with OTG core.
 *
 */
int usb_add_hcd(struct usb_hcd *hcd,
		unsigned int irqnum, unsigned long irqflags)

因为框架(drivers/usb/core/hcd.c)比较复杂,就不跟进去usb_add_hcd()分析了,有兴趣的朋友可以深入研究。

      那对于vhci-hcd驱动实例,它的reset和start究竟做了什么呢?下面我们来一一分析。我们先看reset回调:

static int vhci_setup(struct usb_hcd *hcd)
{
	struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller));
	if (usb_hcd_is_primary_hcd(hcd)) {
		vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd);
		vhci->vhci_hcd_hs->vhci = vhci;
		/*
		 * Mark the first roothub as being USB 2.0.
		 * The USB 3.0 roothub will be registered later by
		 * vhci_hcd_probe()
		 */
		hcd->speed = HCD_USB2;
		hcd->self.root_hub->speed = USB_SPEED_HIGH;
	} else {
		...
	}
	return 0;
}

      上面也说了,struct vhci结构可以由struct usb_hcd找到,struct vhci *vhci = *((void **)dev_get_platdata(hcd->self.controller));

我们主要看primary_hcd那个分支,因为从vhci_hcd_probe()中我们知道hcd_hs是作为primary_hcd,而hcd_ss作为shared_hcd的,usb3.0我们不看。

      这里有个比较有意思的函数hcd_to_vhci_hcd():

static inline struct vhci_hcd *hcd_to_vhci_hcd(struct usb_hcd *hcd)
{
	return (struct vhci_hcd *) (hcd->hcd_priv);
}

其中hcd_priv在struct usb_hcd中是一个0数组:

/* The HC driver's private data is stored at the end of
	 * this structure.
	 */
	unsigned long hcd_priv[0]
			__attribute__ ((aligned(sizeof(s64))));

早在vhci_hcd_probe()中注册vhci_hc_driver(.hcd_priv_size    = sizeof(struct vhci_hcd))时,就已经分配好空间了。所以在vhci_setup()被回调时,是可以访问该空间的,在这里初始化了vhci_hcd_hs(struct vhci_hcd结构):

vhci->vhci_hcd_hs = hcd_to_vhci_hcd(hcd);
vhci->vhci_hcd_hs->vhci = vhci;

这样就关联起来了struct vhci结构中的vhci_hcd_hs,同时也关联了struct vhci_hcd结构与struct vhci结构,方便后续相互找对方。

    ok,我们再来看start回调:

static void vhci_device_init(struct vhci_device *vdev)
{
	memset(vdev, 0, sizeof(struct vhci_device));

	vdev->ud.side   = USBIP_VHCI;
	vdev->ud.status = VDEV_ST_NULL;
	spin_lock_init(&vdev->ud.lock);

	INIT_LIST_HEAD(&vdev->priv_rx);
	INIT_LIST_HEAD(&vdev->priv_tx);
	INIT_LIST_HEAD(&vdev->unlink_tx);
	INIT_LIST_HEAD(&vdev->unlink_rx);
	spin_lock_init(&vdev->priv_lock);

	init_waitqueue_head(&vdev->waitq_tx);

	vdev->ud.eh_ops.shutdown = vhci_shutdown_connection;
	vdev->ud.eh_ops.reset = vhci_device_reset;
	vdev->ud.eh_ops.unusable = vhci_device_unusable;

	usbip_start_eh(&vdev->ud);
}
...
...
static int vhci_start(struct usb_hcd *hcd)
{
	struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd);
	int id, rhport;
	int err;

	usbip_dbg_vhci_hc("enter vhci_start\n");

	if (usb_hcd_is_primary_hcd(hcd))
		spin_lock_init(&vhci_hcd->vhci->lock);

	/* initialize private data of usb_hcd */

	for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) {
		struct vhci_device *vdev = &vhci_hcd->vdev[rhport];

		vhci_device_init(vdev);
		vdev->rhport = rhport;
	}

	atomic_set(&vhci_hcd->seqnum, 0);

	hcd->power_budget = 0; /* no limit */
	hcd->uses_new_polling = 1;

#ifdef CONFIG_USB_OTG
	hcd->self.otg_port = 1;
#endif

	id = hcd_name_to_id(hcd_name(hcd));
	if (id < 0) {
		pr_err("invalid vhci name %s\n", hcd_name(hcd));
		return -EINVAL;
	}

	/* vhci_hcd is now ready to be controlled through sysfs */
	if (id == 0 && usb_hcd_is_primary_hcd(hcd)) {
		err = vhci_init_attr_group();
		if (err) {
			pr_err("init attr group\n");
			return err;
		}
		err = sysfs_create_group(&hcd_dev(hcd)->kobj, &vhci_attr_group);
		if (err) {
			pr_err("create sysfs files\n");
			vhci_finish_attr_group();
			return err;
		}
		pr_info("created sysfs %s\n", hcd_name(hcd));
	}

	return 0;
}

      vhci_start()函数主要是继续初始化本驱动的两个重要结构体struct vhci_hcd和struct vhci_device:

/* a common structure for stub_device and vhci_device */
struct usbip_device {
	enum usbip_side side;
	enum usbip_device_status status;

	/* lock for status */
	spinlock_t lock;

	int sockfd;
	struct socket *tcp_socket;

	struct task_struct *tcp_rx;
	struct task_struct *tcp_tx;

	unsigned long event;
	wait_queue_head_t eh_waitq;

	struct eh_ops {
		void (*shutdown)(struct usbip_device *);
		void (*reset)(struct usbip_device *);
		void (*unusable)(struct usbip_device *);
	} eh_ops;
};

struct vhci_device {
	struct usb_device *udev;

	/*
	 * devid specifies a remote usb device uniquely instead
	 * of combination of busnum and devnum.
	 */
	__u32 devid;

	/* speed of a remote device */
	enum usb_device_speed speed;

	/* vhci root-hub port to which this device is attached */
	__u32 rhport;

	struct usbip_device ud;

	/* lock for the below link lists */
	spinlock_t priv_lock;

	/* vhci_priv is linked to one of them. */
	struct list_head priv_tx;
	struct list_head priv_rx;

	/* vhci_unlink is linked to one of them */
	struct list_head unlink_tx;
	struct list_head unlink_rx;

	/* vhci_tx thread sleeps for this queue */
	wait_queue_head_t waitq_tx;
};

/* for usb_hcd.hcd_priv[0] */
struct vhci_hcd {
	struct vhci *vhci;

	u32 port_status[VHCI_HC_PORTS];

	unsigned resuming:1;
	unsigned long re_timeout;

	atomic_t seqnum;

	/*
	 * NOTE:
	 * wIndex shows the port number and begins from 1.
	 * But, the index of this array begins from 0.
	 */
	struct vhci_device vdev[VHCI_HC_PORTS];
};

包括初始化里面的队列和自旋锁,原子序列号等,以及最重要的是通过sysfs_create_group(&hcd_dev(hcd)->kobj, &vhci_attr_group)注册了一个sysfs界面,上层就可以用usbip工具进行配置vhci-hcd驱动了,在sysfs中,usbip工具主要配置了如下几个参数:

devid
speed
sockfd

后面再说这几个参数的作用。我们只需要知道vhci_start()只是一个初始化函数即可。

终于把vhci_hcd_probe()流程讲清楚了。完成了vhci-hcdq驱动的全部初始化过程!

 

下篇文章讲解“usbip attach -r <server端ip地址> -b <busid>”命令下发后,vhci-hcd驱动做了什么,导致能共享远端的真实U盘。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux usb usbip驱动详解(四) 的相关文章

  • 在 /dev/input/eventX 中写入事件需要哪些命令?

    我正在开发一个android需要将触摸事件发送到 dev input eventX 的应用程序 我知道C执行此类操作的代码结构如下 struct input event struct timeval time unsigned short
  • diff 文件仅比较每行的前 n 个字符

    我有2个文件 我们将它们称为 md5s1 txt 和 md5s2 txt 两者都包含a的输出 find type f print0 xargs 0 md5sum sort gt md5s txt 不同目录下的命令 许多文件被重命名 但内容保
  • PHP 致命错误:未找到“MongoClient”类

    我有一个使用 Apache 的网站 代码如下 当我尝试访问它时 我在 error log 中收到错误 PHP Fatal Error Class MongoClient not found 以下是可能错误的设置 但我认为没有错误 php i
  • QFileDialog::getSaveFileName 和默认的 selectedFilter

    我有 getSaveFileName 和一些过滤器 我希望当用户打开 保存 对话框时选择其中之一 Qt 文档说明如下 可以通过将 selectedFilter 设置为所需的值来选择默认过滤器 我尝试以下变体 QString selFilte
  • Unix 命令列出包含字符串但*不*包含另一个字符串的文件

    如何递归查看包含一个字符串且不包含另一个字符串的文件列表 另外 我的意思是评估文件的文本 而不是文件名 结论 根据评论 我最终使用了 find name html exec grep lR base maps xargs grep L ba
  • bluetoothctl 到 hcitool 等效命令

    在 Linux 中 我曾经使用 hidd connect mmac 来连接 BT 设备 但自 Bluez5 以来 这种情况已经消失了 我可以使用 bluetoothctl 手动建立连接 但我需要从我的应用程序使用这些命令 并且使用 blue
  • 为什么我收到“无法进行二进制日志记录”的信息。在我的 MySQL 服务器上?

    当我今天启动 MySQL 服务器并尝试使用以下命令进行一些更改时用于 MySQL 的 Toad http www quest com toad for mysql 我收到此消息 MySQL 数据库错误 无法进行二进制日志记录 消息 交易级别
  • 从 PL/SQL 调用 shell 脚本,但 shell 以 grid 用户而非 oracle 身份执行

    我正在尝试使用 Runtime getRuntime exec 从 Oracle 数据库内部执行 shell 脚本 在 Red Hat 5 5 上运行的 Oracle 11 2 0 4 EE CREATE OR REPLACE proced
  • Elasticsearch 无法写入日志文件

    我想激活 elasticsearch 的日志 当我运行 elasticsearch 二进制文件时 我意识到我在日志记录方面遇到问题 无法加载配置 这是输出 sudo usr share elasticsearch bin elasticse
  • 创建 jar 文件 - 保留文件权限

    我想知道如何创建一个保留其内容的文件权限的 jar 文件 我将源代码和可执行文件打包在一个 jar 文件中 该文件将在使用前提取 人们应该能够通过运行批处理 shell 脚本文件立即运行示例和演示 然后他们应该能够修改源代码并重新编译所有内
  • 无法加载 JavaHL 库。- linux/eclipse

    在尝试安装 Subversion 插件时 当 Eclipse 启动时出现此错误 Failed to load JavaHL Library These are the errors that were encountered no libs
  • 如何检测并找出程序是否陷入死锁?

    这是一道面试题 如何检测并确定程序是否陷入死锁 是否有一些工具可用于在 Linux Unix 系统上执行此操作 我的想法 如果程序没有任何进展并且其状态为运行 则为死锁 但是 其他原因也可能导致此问题 开源工具有valgrind halgr
  • 如何查明CONFIG_FANOTIFY_ACCESS_PERMISSIONS是否启用?

    我想利用fanotify 7 http man7 org linux man pages man7 fanotify 7 html我遇到的问题是在某些内核上CONFIG FANOTIFY ACCESS PERMISSIONS不起作用 虽然C
  • 为什么内核需要虚拟寻址?

    在Linux中 每个进程都有其虚拟地址空间 例如 32位系统为4GB 其中3GB为进程保留 1GB为内核保留 这种虚拟寻址机制有助于隔离每个进程的地址空间 对于流程来说这是可以理解的 因为有很多流程 但既然我们只有 1 个内核 那么为什么我
  • 如何在数组中存储包含双引号的命令参数?

    我有一个 Bash 脚本 它生成 存储和修改数组中的值 这些值稍后用作命令的参数 对于 MCVE 我想到了任意命令bash c echo 0 0 echo 1 1 这解释了我的问题 我将用两个参数调用我的命令 option1 without
  • 如何有效截断文件头?

    大家都知道truncate file size 函数 通过截断文件尾部将文件大小更改为给定大小 但是如何做同样的事情 只截断文件的尾部和头部呢 通常 您必须重写整个文件 最简单的方法是跳过前几个字节 将其他所有内容复制到临时文件中 并在完成
  • 如何使用GDB修改内存内容?

    我知道我们可以使用几个命令来访问和读取内存 例如 print p x 但是如何更改任何特定位置的内存内容 在 GDB 中调试时 最简单的是设置程序变量 参见GDB 分配 http sourceware org gdb current onl
  • linux perf:如何解释和查找热点

    我尝试了linux perf https perf wiki kernel org index php Main Page今天很实用 但在解释其结果时遇到了困难 我习惯了 valgrind 的 callgrind 这当然是与基于采样的 pe
  • arm64和armhf有什么区别?

    Raspberry Pi Type 3 具有 64 位 CPU 但其架构不是arm64 but armhf 有什么区别arm64 and armhf armhf代表 arm hard float 是给定的名称Debian 端口 https
  • 无法使用 wget 在 CentOS 机器上安装 oracle jdk

    我想在CentOS上安装oracle java jdk 8 我无法安装 java jdk 因为当我尝试使用命令安装 java jdk 时 root ADARSH PROD1 wget no cookies no check certific

随机推荐