linux usb gadget驱动详解(四)

        现从msg_bind()函数(drivers/usb/gadget/legacy/mass_storage.c)开始讲起。

        U盘的gadget驱动比较复杂,因为它包含几部分,包括gadget驱动、U盘相关的处理(SCSI命令的处理、lun概念等)以及虚拟文件系统的读写操作(因为我们虚拟出来的U盘是用文件系统来保存的)等。

//不失一般性,删除一些错误处理代码
static int msg_bind(struct usb_composite_dev *cdev)
{
	static const struct fsg_operations ops = {
		.thread_exits = msg_thread_exits,
	};
	struct fsg_opts *opts;
	struct fsg_config config;
	int status;

	fi_msg = usb_get_function_instance("mass_storage");
	fsg_config_from_params(&config, &mod_data, fsg_num_buffers);
	opts = fsg_opts_from_func_inst(fi_msg);

	opts->no_configfs = true;
	status = fsg_common_set_num_buffers(opts->common, fsg_num_buffers);

	fsg_common_set_ops(opts->common, &ops);

	status = fsg_common_set_cdev(opts->common, cdev, config.can_stall);

	fsg_common_set_sysfs(opts->common, true);
	status = fsg_common_create_luns(opts->common, &config);

	fsg_common_set_inquiry_string(opts->common, config.vendor_name,
				      config.product_name);

	status = usb_string_ids_tab(cdev, strings_dev);

	msg_device_desc.iProduct = strings_dev[USB_GADGET_PRODUCT_IDX].id;

	if (gadget_is_otg(cdev->gadget) && !otg_desc[0]) {
		struct usb_descriptor_header *usb_desc;

		usb_desc = usb_otg_descriptor_alloc(cdev->gadget);
		if (!usb_desc)
			goto fail_string_ids;
		usb_otg_descriptor_init(cdev->gadget, usb_desc);
		otg_desc[0] = usb_desc;
		otg_desc[1] = NULL;
	}

	status = usb_add_config(cdev, &msg_config_driver, msg_do_config);

	usb_composite_overwrite_options(cdev, &coverwrite);

	return 0;
}

       内核有个特点,就是驱动分层,gadget也不例外,它有一系列的function驱动(源码在drivers/usb/gadget/function目录下),譬如f_mass_storage.c(usb_f_mass_storage.ko)就是实现U盘的功能,而f_hid.c(usb_f_hid.ko)则是实现HID键鼠功能,为什么这么做呢?因为一个USB设备不一定只有一种功能,譬如"U盘+网卡"、“摄像头+串口”等等这种复合设备,其中drivers/usb/gadget/legacy/multi.c就是一个复合设备的例子。只是我们现在分析的mass_storage.c刚好只有一种功能(U盘),你觉得没必要分层而已,毕竟驱动分层后变复杂了很多。你看 fi_msg = usb_get_function_instance("mass_storage"),不跟进去根本不知道它做了什么,除非你比较熟悉gadget的api接口。它其实是会得到由drivers/usb/gadget/function/f_mass_storage.c这个驱动(usb_f_mass_storage.ko)所注册的“usb功能实例”(struct usb_function_instance *), 而在f_mass_storage.c这边是预先通过DECLARE_USB_FUNCTION_INIT宏(在include/linux/usb/composite.h中定义)进行注册的:

// f_mass_storage.c
DECLARE_USB_FUNCTION_INIT(mass_storage, fsg_alloc_inst, fsg_alloc);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Michal Nazarewicz");

        每调用DECLARE_USB_FUNCTION_INIT宏注册一个function实例,就会add进一个全局链表变量的尾部(list_add_tail),所以当在其他驱动中调用usb_get_function_instance("mass_storage"),就可以通过“mass_storage”名称在全局链表中找到对应的“usb功能实例”(struct usb_function_instance *)。

        好了,知道这一层关系后,调用usb_get_function_instance("mass_storage")其实就是调用f_mass_storage.c的fsg_alloc_inst()函数:

//去掉错误处理代码
static struct usb_function_instance *fsg_alloc_inst(void)
{
	struct fsg_opts *opts;
	struct fsg_lun_config config;
	int rc;

	opts = kzalloc(sizeof(*opts), GFP_KERNEL);
...
	mutex_init(&opts->lock);
	opts->func_inst.free_func_inst = fsg_free_inst;
	opts->common = fsg_common_setup(opts->common);
...
	rc = fsg_common_set_num_buffers(opts->common,
					CONFIG_USB_GADGET_STORAGE_NUM_BUFFERS);
...
	memset(&config, 0, sizeof(config));
	config.removable = true;
	rc = fsg_common_create_lun(opts->common, &config, 0, "lun.0",
			(const char **)&opts->func_inst.group.cg_item.ci_name);
...
	opts->lun0.lun = opts->common->luns[0];
	opts->lun0.lun_id = 0;
	config_group_init_type_name(&opts->lun0.group, "lun.0", &fsg_lun_type);
	opts->default_groups[0] = &opts->lun0.group;
	opts->func_inst.group.default_groups = opts->default_groups;

	config_group_init_type_name(&opts->func_inst.group, "", &fsg_func_type);

	return &opts->func_inst;
}

在fsg_alloc_inst()中我们看到它先malloc了一个struct fsg_opts对象,该对象在fsg(File Storage Gadget)中算是比较重要的:

struct fsg_opts {
	struct fsg_common *common;
	struct usb_function_instance func_inst;
	struct fsg_lun_opts lun0;
	struct config_group *default_groups[2];
	bool no_configfs; /* for legacy gadgets */

	/*
	 * Read/write access to configfs attributes is handled by configfs.
	 *
	 * This is to protect the data from concurrent access by read/write
	 * and create symlink/remove symlink.
	 */
	struct mutex			lock;
	int				refcnt;
};

        因为它里面保存了struct fsg_common * common和struct usb_function_instance func_inst结构,而no_configfs字段则是指明是否使用configfs配置,这个参数在msg_bind()会配置,我们在第二篇文章中有介绍过使用configfs进行配置gadget驱动,很是方便,只是代码讲解我选用了legacy gadgets来分析,就不细说configfs相关的代码了。U盘gadget的主要全局变量都是聚集在struct fsg_common结构体中,f_mass_storage.c大部分函数都是传入struct fsg_common *参数进去,是本驱动最重要的结构体!struct fsg_common * common就是在这个时候分配好空间(fsg_common_setup())的,并且把common用到的自旋锁、完成量(completion)等等初始化一遍,方便后续使用。然后分配buffer,譬如32个16KB的缓冲区,用于U盘数据传输,多个缓冲区能增加数据读写吞吐量。最后调用fsg_common_create_lun()创建逻辑单元(logical units/LUNs),也是为了后续U盘操作做准备。最后return &opts->func_inst返回struct usb_function_instance *。

        后面两个函数是关于configfs的,毕竟要支持从configfs配置驱动,肯定要有相应的api支持,后面遇到configs相关的代码我都直接忽略。

        ok,回到msg_bind(),我们这时得到了上述由fsg_alloc_inst()负责填充好并返回的struct usb_function_instance *fi_msg(全局变量),另外,我们都知道可以根据struct usb_function_instance *通过container_of()找回struct fsg_opts对象,方便后续继续配置(初始化)。

        另外在msg_bind()中我们还出现了一个struct fsg_config config结构体,该结构体主要用于配置U盘gadget支持的功能,譬如我们可以配置该U盘是否removable的,是否CD/ROM,是否只读,是否nofua,有多少个逻辑块(luns),是否支持端点挂起(stall),以及为各个lun指定映射的文件路径,因为一个lun代表一个逻辑分区,既然我的U盘的底层存储是基于文件系统,自然要为每个分区都指定文件路径。这些参数从哪定义?自然是加载驱动时把参数带进去,譬如(modprobe g_mass_storage luns=2 file=/var/sdcard/disk.img,/var/sdcard/disk1.img 等等。为什么我会知道使用file=filename[,filename...],原因是内核有个文档专门描述怎么配置,具体在内核源码目录下Documentation/usb/mass-storage.txt)。struct fsg_config的具体定义如下:

struct fsg_config {
	unsigned nluns;
	struct fsg_lun_config luns[FSG_MAX_LUNS];

	/* Callback functions. */
	const struct fsg_operations	*ops;
	/* Gadget's private data. */
	void			*private_data;

	const char *vendor_name;		/*  8 characters or less */
	const char *product_name;		/* 16 characters or less */

	char			can_stall;
	unsigned int		fsg_num_buffers;
};

其实就是在msg_bind()->fsg_config_from_params()里解释参数,不深入了。在这里又调用fsg_common_set_num_buffers()一次,之前在f_mass_storage.c的fsg_alloc_inst()也有初始化过,但没有释放之前的,貌似有内存泄漏了。后面通过fsg_common_create_luns()根据用户设置的luns数量重新创建lun。fsg_common_set_inquiry_string()函数有意思,我们可以改变里面的内容,让PC插入我们的U盘时,下发SCSI INQUIRY命令时,返回的字符串。这个可以用Bus Hound或者USBlyzer抓包看到。usb_string_ids_tab(cdev, strings_dev)是利用strings_dev结构生成对应的字符串描述符,用于usb枚举阶段返回给usb host。接着是otg相关,我们跳过。最重要的函数来了,usb_add_config(cdev, &msg_config_driver,  msg_do_config)。因为调用完它之后,U盘就算初始化完毕!我们看下它的定义,它的具体实现我们不分析,有兴趣的朋友可以自行研究:

//composite.c
/**
 * usb_add_config() - add a configuration to a device.
 * @cdev: wraps the USB gadget
 * @config: the configuration, with bConfigurationValue assigned
 * @bind: the configuration's bind function
 * Context: single threaded during gadget setup
 *
 * One of the main tasks of a composite @bind() routine is to
 * add each of the configurations it supports, using this routine.
 *
 * This function returns the value of the configuration's @bind(), which
 * is zero for success else a negative errno value.  Binding configurations
 * assigns global resources including string IDs, and per-configuration
 * resources such as interface IDs and endpoints.
 */
int usb_add_config(struct usb_composite_dev *cdev,
		struct usb_configuration *config,
		int (*bind)(struct usb_configuration *))
{
...
}

功能是“为一个usb设备增加配置”,我们还记得usb的描述符结构吧?usb设备描述符->配置描述符->接口描述符->端点描述符....,其中,usb_add_config()的int (*bind)(struct usb_configuration *)函数指针就是用于“为配置增加接口”,它会在调用usb_add_config()时被回调,对应我们的U盘gadget就是msg_do_config()。我们先来看两个实参的定义:

static struct usb_configuration msg_config_driver = {
	.label			= "Linux File-Backed Storage",
	.bConfigurationValue	= 1,
	.bmAttributes		= USB_CONFIG_ATT_SELFPOWER,
};

static int msg_do_config(struct usb_configuration *c)
{
	struct fsg_opts *opts;
	int ret;

	if (gadget_is_otg(c->cdev->gadget)) {
		c->descriptors = otg_desc;
		c->bmAttributes |= USB_CONFIG_ATT_WAKEUP;
	}

	opts = fsg_opts_from_func_inst(fi_msg);

	f_msg = usb_get_function(fi_msg);
	if (IS_ERR(f_msg))
		return PTR_ERR(f_msg);

	ret = usb_add_function(c, f_msg);
	if (ret)
		goto put_func;

	return 0;

put_func:
	usb_put_function(f_msg);
	return ret;
}

msg_config_driver结构主要是指定.bConfigurationValue    = 1,使用第一个配置,因为到时usb host(譬如PC)在枚举本U盘阶段,会下发SetConfiguration() 进行配置,这时U盘事实上要准备好usb传输相关的东西,如端点分配等,因为紧接着PC就会用端点描述符上指定的端点进行通信,你得提前准备好。

        上面的代码片段msg_do_config中的fi_msg变量就是在msg_bind()中通过usb_get_function_instance("mass_storage")得到的全局变量。它先是struct usb_function *f_msg = usb_get_function(fi_msg),该函数事实上是调用了f_mass_storage.c注册好的fsg_alloc():

static struct usb_function *fsg_alloc(struct usb_function_instance *fi)
{
	struct fsg_opts *opts = fsg_opts_from_func_inst(fi);
	struct fsg_common *common = opts->common;
	struct fsg_dev *fsg;

	fsg = kzalloc(sizeof(*fsg), GFP_KERNEL);
	if (unlikely(!fsg))
		return ERR_PTR(-ENOMEM);

	mutex_lock(&opts->lock);
	opts->refcnt++;
	mutex_unlock(&opts->lock);

	fsg->function.name	= FSG_DRIVER_DESC;
	fsg->function.bind	= fsg_bind;
	fsg->function.unbind	= fsg_unbind;
	fsg->function.setup	= fsg_setup;
	fsg->function.set_alt	= fsg_set_alt;
	fsg->function.disable	= fsg_disable;
	fsg->function.free_func	= fsg_free;

	fsg->common               = common;

	return &fsg->function;
}

这个函数超级重要,很明显它的作用是填充一些回调函数,以及返回struct usb_function* f_msg,在我们前面的代码分析就已经知道struct fsg_opts *opts和struct fsg_common *common结构早在fsg_alloc_inst()时就已经分配好空间了,这里只是拿回指针进行填充(初始化)而已。其中fsg_setup和fsg_bind回调十分重要,未来会被composite.c框架回调,用于处理usb host下发usb枚举以及U盘的实际读写操作。后面再单独分析。

        msg_do_config最后调用的一个函数是usb_add_function(c, f_msg),这样就把上面填充的fsg_setup和fsg_bind等回调注册到composite.c框架了。其中参数c是在static struct usb_configuration msg_config_driver基础上通过usb_add_config()->usb_add_config_only()进行进一步配置,剩下就是composite.c框架处理的事情了。那fsg_setup()和fsg_bind()在什么情况下会被回调:

/**
 * usb_add_function() - add a function to a configuration
 * @config: the configuration
 * @function: the function being added
 * Context: single threaded during gadget setup
 *
 * After initialization, each configuration must have one or more
 * functions added to it.  Adding a function involves calling its @bind()
 * method to allocate resources such as interface and string identifiers
 * and endpoints.
 *
 * This function returns the value of the function's bind(), which is
 * zero for success else a negative errno value.
 */
int usb_add_function(struct usb_configuration *config,
		struct usb_function *function)
{
...
...
    /* REVISIT *require* function->bind? */
	if (function->bind) {
		value = function->bind(config, function);
		...
	} else
		value = 0;
...
...
}

        我们发现msg_do_config()在调用usb_add_function()后,fsg_bind()就会被回调,这时就越来越接近我们的初衷:“为啥它能让PC识别成“可移动磁盘”,以及它可以像市面上的U盘一样能读写文件”。

        好了,下一篇文章我们从fsg_bind()函数(usb/gadget/function/f_mass_storage.c)讲起!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章