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)講起!

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