linux usb usbip驱动详解(四)

    技术2022-07-13  85

          我们先讲解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盘。

     

    Processed: 0.017, SQL: 9