记一次libusb库函数“libusb_bulk_transfer”的调用关系追踪

记一次libusb库函数“libusb_bulk_transfer”的调用关系追踪

一次使用libusb库函数“libusb_bulk_transfer”对外部USB设备进行读取数据的时候,出现了无数据返回的情况,但是使用USB分析仪,看到USB总线是进行了一次完整的IN令牌的transfer,如下图,感觉像是中间某一个节点没有将收到的device数据包返回上来,所以对代码进行下追踪,定位下问题大致位置。
在这里插入图片描述
这里记录下追踪到的函数调用关系:

libusb库内部调用关系

先看下大致调用流程:

> libusb_bulk_transfer(sync.c)
----> do_sync_bulk_transfer(sync.c)
--------> libusb_submit_transfer(io.c)
------------> usbi_backend->submit_transfer(core.c)
------------> linux_usbfs_backend.submit_transfer(linux_usbfs.c)
------------> op_submit_transfer(linux_usbfs.c)
----------------> submit_bulk_transfer(linux_usbfs.c)
--------------------> ioctl

可以看到,最终实际是调用到了系统的“ioctl”函数。
简单分析一下:

  1. do_sync_bulk_transfer(sync.c)中有两个重要的函数,如下:
static int do_sync_bulk_transfer(struct libusb_device_handle *dev_handle,
	unsigned char endpoint, unsigned char *buffer, int length,
	int *transferred, unsigned int timeout, unsigned char type)
{
	struct libusb_transfer *transfer = libusb_alloc_transfer(0);
	int completed = 0;
	int r;

	if (!transfer)
		return LIBUSB_ERROR_NO_MEM;
	//这个函数用来填充一个用于bulk传输的struct libusb_transfer
	libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, length,
		bulk_transfer_cb, &completed, timeout);
	transfer->type = type;
	//这里提交上面填充好的transfer
	r = libusb_submit_transfer(transfer);
	if (r < 0) {
		libusb_free_transfer(transfer);
		return r;
	}

	......

	libusb_free_transfer(transfer);
	return r;
}
  1. libusb_submit_transfer(io.c)会调用不同平台的usbi_backend->submit_transfer,如下:
int API_EXPORTED libusb_submit_transfer(struct libusb_transfer *transfer)
{
	struct libusb_context *ctx = TRANSFER_CTX(transfer);
	struct usbi_transfer *itransfer =
		LIBUSB_TRANSFER_TO_USBI_TRANSFER(transfer);
	int r;
	int first;

	......

	first = add_to_flying_list(itransfer);
	//根据编译平台的不同,调用对应平台的 struct usbi_os_backend 成员 submit_transfer
	r = usbi_backend->submit_transfer(itransfer);
	if (r) {
		usbi_mutex_lock(&ctx->flying_transfers_lock);
		list_del(&itransfer->list);
		usbi_mutex_unlock(&ctx->flying_transfers_lock);
	}

	......

out:
	usbi_mutex_unlock(&itransfer->lock);
	return r;
}

不同平台的区分在core.c文件,如下:

#if defined(OS_LINUX)
const struct usbi_os_backend * const usbi_backend = &linux_usbfs_backend;
#elif defined(OS_DARWIN)
const struct usbi_os_backend * const usbi_backend = &darwin_backend;
#elif defined(OS_OPENBSD)
const struct usbi_os_backend * const usbi_backend = &openbsd_backend;
#elif defined(OS_WINDOWS)
const struct usbi_os_backend * const usbi_backend = &windows_backend;
#else
#error "Unsupported OS"
#endif

我们使用的是linux,那就是用这个结构体“linux_usbfs_backend”,在文件linux_usbfs.c,如下:

const struct usbi_os_backend linux_usbfs_backend = {
	.name = "Linux usbfs",
	.init = op_init,
	.exit = NULL,
	......
	.open = op_open,
	.close = op_close,
	......
	.submit_transfer = op_submit_transfer,	//这个就是被调用到的函数
	.cancel_transfer = op_cancel_transfer,
	.clear_transfer_priv = op_clear_transfer_priv,
	......
};
  1. op_submit_transfer(linux_usbfs.c)比较简单,就是根据不同的transfer类型,调用不同的函数,这里我们是“bulk”类型,调用的是 submit_bulk_transfer(linux_usbfs.c),如下:
/*
** 这个函数主要做了3件事情:
** 1.根据 transfer 创建并初始化 urb
** 2.通过 ioctl 提交 urb
** 3.判断 urb 提交结果
*/
static int submit_bulk_transfer(struct usbi_transfer *itransfer,
	unsigned char urb_type)
{
	struct libusb_transfer *transfer =
		USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer);
	struct linux_transfer_priv *tpriv = usbi_transfer_get_os_priv(itransfer);
	struct linux_device_handle_priv *dpriv =
		_device_handle_priv(transfer->dev_handle);
	struct usbfs_urb *urbs;
	int is_out = (transfer->endpoint & LIBUSB_ENDPOINT_DIR_MASK)
		== LIBUSB_ENDPOINT_OUT;
	int r;
	int i;
	size_t alloc_size;

	if (tpriv->urbs)
		return LIBUSB_ERROR_BUSY;

	if (is_out && transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET &&
	    !supports_flag_zero_packet)
		return LIBUSB_ERROR_NOT_SUPPORTED;

	/* usbfs places a 16kb limit on bulk URBs. we divide up larger requests
	 * into smaller units to meet such restriction, then fire off all the
	 * units at once. it would be simpler if we just fired one unit at a time,
	 * but there is a big performance gain through doing it this way. */
	int num_urbs = transfer->length / MAX_BULK_BUFFER_LENGTH;
	int last_urb_partial = 0;

	if (transfer->length == 0) {
		num_urbs = 1;
	} else if ((transfer->length % MAX_BULK_BUFFER_LENGTH) > 0) {
		last_urb_partial = 1;
		num_urbs++;
	}
	usbi_dbg("need %d urbs for new transfer with length %d", num_urbs,
		transfer->length);
	alloc_size = num_urbs * sizeof(struct usbfs_urb);
	urbs = malloc(alloc_size);			//创建urb
	if (!urbs)
		return LIBUSB_ERROR_NO_MEM;
	memset(urbs, 0, alloc_size);
	tpriv->urbs = urbs;
	tpriv->num_urbs = num_urbs;
	tpriv->num_retired = 0;
	tpriv->reap_action = NORMAL;
	tpriv->reap_status = LIBUSB_TRANSFER_COMPLETED;

	for (i = 0; i < num_urbs; i++) {
		struct usbfs_urb *urb = &urbs[i];
		urb->usercontext = itransfer;			//初始化urb
		urb->type = urb_type;
		urb->endpoint = transfer->endpoint;
		urb->buffer = transfer->buffer + (i * MAX_BULK_BUFFER_LENGTH);
		if (supports_flag_bulk_continuation && !is_out)
			urb->flags = USBFS_URB_SHORT_NOT_OK;
		if (i == num_urbs - 1 && last_urb_partial)
			urb->buffer_length = transfer->length % MAX_BULK_BUFFER_LENGTH;
		else if (transfer->length == 0)
			urb->buffer_length = 0;
		else
			urb->buffer_length = MAX_BULK_BUFFER_LENGTH;

		if (i > 0 && supports_flag_bulk_continuation)
			urb->flags |= USBFS_URB_BULK_CONTINUATION;

		/* we have already checked that the flag is supported */
		if (is_out && i == num_urbs - 1 &&
		    transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET)
			urb->flags |= USBFS_URB_ZERO_PACKET;

		r = ioctl(dpriv->fd, IOCTL_USBFS_SUBMITURB, urb);		//提交urb
		if (r < 0) {											//判断urb提交是否成功
			if (errno == ENODEV) {
				r = LIBUSB_ERROR_NO_DEVICE;
			} else {
				usbi_err(TRANSFER_CTX(transfer),
					"submiturb failed error %d errno=%d", r, errno);
				r = LIBUSB_ERROR_IO;
			}
	
			......
			return 0;
		}
	}

	return 0;
}

到这里,应用发起的的一次transfer,就通过USB设备文件节点的ioctl系统调用,进入了kernel层。

USB设备文件节点

当device设备插入之后,host端主机会创建一个“USB设备文件节点”,提供给应用层使用,这个节点位置如下图:
在这里插入图片描述
可以看到,这些节点的主设备号为189,创建这些节点的源码在:kernel/drivers/usb/core/devio.c,如下:

#define USB_DEVICE_DEV		MKDEV(USB_DEVICE_MAJOR, 0)

int __init usb_devio_init(void)
{
	int retval;

	retval = register_chrdev_region(USB_DEVICE_DEV, USB_DEVICE_MAX,
					"usb_device");
	if (retval) {
		printk(KERN_ERR "Unable to register minors for usb_device\n");
		goto out;
	}
	cdev_init(&usb_device_cdev, &usbdev_file_operations);
	retval = cdev_add(&usb_device_cdev, USB_DEVICE_DEV, USB_DEVICE_MAX);
	if (retval) {
		printk(KERN_ERR "Unable to get usb_device major %d\n",
		       USB_DEVICE_MAJOR);
		goto error_cdev;
	}
	usb_register_notify(&usbdev_nb);
out:
	return retval;

error_cdev:
	unregister_chrdev_region(USB_DEVICE_DEV, USB_DEVICE_MAX);
	goto out;
}

这里面的宏 USB_DEVICE_MAJOR 的值就是189,另外文件节点对应的驱动 struct file_operations 就是 usbdev_file_operations,如下:

const struct file_operations usbdev_file_operations = {
	.owner =	  THIS_MODULE,
	.llseek =	  usbdev_lseek,
	.read =		  usbdev_read,
	.poll =		  usbdev_poll,
	.unlocked_ioctl = usbdev_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl =   usbdev_compat_ioctl,
#endif
	.open =		  usbdev_open,
	.release =	  usbdev_release,
};

所以,上一节 libusb 最后调用的 ioctl ,会直接进到驱动的“usbdev_ioctl”,然后进一步调用“usbdev_do_ioctl”,在这个函数里,会使用 libusb 里面调用时候传入的参数“IOCTL_USBFS_SUBMITURB”来做判断,在kernel里面相同的定义是“USBDEVFS_SUBMITURB”,然后再进一步调用“proc_submiturb”,将用户空间的urb,传递给内核,代码如下:

static int proc_submiturb(struct dev_state *ps, void __user *arg)
{
	struct usbdevfs_urb uurb;

	if (copy_from_user(&uurb, arg, sizeof(uurb)))
		return -EFAULT;

	return proc_do_submiturb(ps, &uurb,
			(((struct usbdevfs_urb __user *)arg)->iso_frame_desc),
			arg);
}

接下来调用“proc_do_submiturb”,这个函数主要就是对用户空间给过来的urb进行分析,然后组织成 usb core 需要的urb,再调用“usb_submit_urb”,将urb传递给 usb core,如下代码:

static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb,
			struct usbdevfs_iso_packet_desc __user *iso_frame_desc,
			void __user *arg)
{
	......

	if (usb_endpoint_xfer_bulk(&ep->desc)) {
		spin_lock_irq(&ps->lock);

		......

		/* Don't accept continuation URBs if the endpoint is
		 * disabled because of an earlier error.
		 */
		if (ps->disabled_bulk_eps & (1 << as->bulk_addr))
			ret = -EREMOTEIO;
		else
			ret = usb_submit_urb(as->urb, GFP_ATOMIC);
		spin_unlock_irq(&ps->lock);
	} else {
		ret = usb_submit_urb(as->urb, GFP_KERNEL);
	}

	......
}

usb驱动层调用关系

usb_submit_urb”是USB驱动里面一个非常重要的函数,所有的urb,最终都是要通过它提交到usb core来处理,源码在:kernel/drivers/usb/core/urb.c
urb”是“USB request block”的缩写,与USB设备通讯要用的所有数据,都由urb来传递。

usb core

这里的调用关系比较简单,如下:

> usb_submit_urb(urb.c)
----> usb_hcd_submit_urb(hcd.c)
--------> hcd->driver->urb_enqueue

最后调用主机控制器驱动“struct usb_hcd”结构体的,“struct hc_driver”类型driver成员的,urb_enqueue函数指针所对应的函数。
这里“struct usb_hcd”描述了一个USB主机控制器驱动信息,就是 Host Controller Driver(HCD);“struct hc_driver”描述了这个主机控制器驱动的硬件操作相关的信息;而 urb_enqueue 就是控制器硬件IO请求的一个函数指针。

HCD

每个主机控制器都需要创建个HCD,不同的硬件平台,注册不同的HCD,通过使用函数“usb_create_hcd”,例如:

	struct usb_hcd	*hcd;

	hcd = usb_create_hcd(&musb_hc_driver, dev, dev_name(dev));
	if (!hcd)
		return NULL;

这里的“musb_hc_driver”就是一个“struct hc_driver”类型结构体,创建“struct usb_hcd”的时候,会将这个“musb_hc_driver”结构体变量的指针,给到“struct usb_hcd”的成员“driver”。
这个“musb_hc_driver”定义如下,这里都是硬件平台和厂商对应的东西了:

const struct hc_driver musb_hc_driver = {
	.description		= "musb-hcd",
	.product_desc		= "MUSB HDRC host driver",
	.hcd_priv_size		= sizeof(struct musb),
	.flags			= HCD_USB2 | HCD_MEMORY,

	/* not using irq handler or reset hooks from usbcore, since
	 * those must be shared with peripheral code for OTG configs
	 */

	.start			= musb_h_start,
	.stop			= musb_h_stop,

	.get_frame_number	= musb_h_get_frame_number,

	.urb_enqueue		= musb_urb_enqueue,
	.urb_dequeue		= musb_urb_dequeue,
	.endpoint_disable	= musb_h_disable,

	.hub_status_data = musb_hub_status_data,
	.hub_control = musb_hub_control,
	.bus_suspend = musb_bus_suspend,
	.bus_resume = musb_bus_resume,
	/* .start_port_reset    = NULL, */
	/* .hub_irq_enable      = NULL, */
};

可以看到这个结构体的成员“urb_enqueue”,就是函数“musb_urb_enqueue”的指针。
所以,最终的调用,就是函数“musb_urb_enqueue”,这里面会去操作主机控制器硬件,在USB总线上收发数据,来和device设备通讯,然后将收到的数据,通过urb里面的buffer,再返回给设备驱动或应用!

最终,我一开始的问题,追踪之后,发现“musb_urb_enqueue”返回的时候,urb的buffer是空的,推测是厂商控制硬件获取数据可能出错了,导致没有拿到设备返回的数据!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值