linux usb gadget驅動詳解(五)

        現從fsg_bind()講起。

//不失一般性,刪掉錯誤處理和configfs相關代碼
static int fsg_bind(struct usb_configuration *c, struct usb_function *f)
{
	struct fsg_dev		*fsg = fsg_from_func(f);
	struct fsg_common	*common = fsg->common;
	struct usb_gadget	*gadget = c->cdev->gadget;
	int			i;
	struct usb_ep		*ep;
	unsigned		max_burst;
	int			ret;
	struct fsg_opts		*opts;

	/* Don't allow to bind if we don't have at least one LUN */
	ret = _fsg_common_get_max_lun(common);

	opts = fsg_opts_from_func_inst(f->fi);

	if (!common->thread_task) {
		common->state = FSG_STATE_IDLE;
		common->thread_task =
			kthread_create(fsg_main_thread, common, "file-storage");
		if (IS_ERR(common->thread_task)) {
			...
		}
		wake_up_process(common->thread_task);
	}

	fsg->gadget = gadget;

	/* New interface */
	i = usb_interface_id(c, f);

	fsg_intf_desc.bInterfaceNumber = i;
	fsg->interface_number = i;

	/* Find all the endpoints we will use */
	ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc);
	fsg->bulk_in = ep;

	ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc);
	fsg->bulk_out = ep;

	/* Assume endpoint addresses are the same for both speeds */
	fsg_hs_bulk_in_desc.bEndpointAddress =
		fsg_fs_bulk_in_desc.bEndpointAddress;
	fsg_hs_bulk_out_desc.bEndpointAddress =
		fsg_fs_bulk_out_desc.bEndpointAddress;

	/* Calculate bMaxBurst, we know packet size is 1024 */
	max_burst = min_t(unsigned, FSG_BUFLEN / 1024, 15);

	fsg_ss_bulk_in_desc.bEndpointAddress =
		fsg_fs_bulk_in_desc.bEndpointAddress;
	fsg_ss_bulk_in_comp_desc.bMaxBurst = max_burst;

	fsg_ss_bulk_out_desc.bEndpointAddress =
		fsg_fs_bulk_out_desc.bEndpointAddress;
	fsg_ss_bulk_out_comp_desc.bMaxBurst = max_burst;

	ret = usb_assign_descriptors(f, fsg_fs_function, fsg_hs_function,
			fsg_ss_function);
...
	return 0;
}

可以看到該函數主要是通過kthread_create+wake_up_process的組合創建了一個內核線程fsg_main_thread,名稱是"file-storage",通過shell的ps可以看到。另外就是利用usb_interface_id()分配一個接口號,填充進接口描述符,以便在設備枚舉時返回給usb host,最後利用composite.c框架所創建的gadget對象對U盤的IN/OUT端點初始化:

//storage_common.c

/*
 * Three 
full-speed endpoint descriptors: bulk-in, bulk-out, and
 * interrupt-in.
 */
struct usb_endpoint_descriptor fsg_fs_bulk_in_desc = {
	.bLength =		USB_DT_ENDPOINT_SIZE,
	.bDescriptorType =	USB_DT_ENDPOINT,

	.bEndpointAddress =	USB_DIR_IN,
	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
	/* wMaxPacketSize set by autoconfiguration */
};
struct usb_endpoint_descriptor fsg_fs_bulk_out_desc = {
	.bLength =		USB_DT_ENDPOINT_SIZE,
	.bDescriptorType =	USB_DT_ENDPOINT,

	.bEndpointAddress =	USB_DIR_OUT,
	.bmAttributes =		USB_ENDPOINT_XFER_BULK,
	/* wMaxPacketSize set by autoconfiguration */
};
	
    /* Find all the endpoints we will use */
	ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_in_desc);
	fsg->bulk_in = ep;

	ep = usb_ep_autoconfig(gadget, &fsg_fs_bulk_out_desc);
	fsg->bulk_out = ep;

        因爲只有端點(fifo)初始化完,未來纔可以利用由usb_ep_queue()傳輸usb數據,而我們的U盤gadget驅動就利用usb_ep_queue()封裝而成以下兩個函數用於傳輸U盤數據:

static bool start_in_transfer(struct fsg_common *common, struct fsg_buffhd *bh);
static bool start_out_transfer(struct fsg_common *common, struct fsg_buffhd *bh);

        當然現在只是初始化,U盤還不能正常工作,畢竟現在連fsg_setup()都沒有調用!也就是說還沒被usb host枚舉到,也沒有SetConfiguration()等操作。那究竟什麼時候調用fsg_setup()回調??

        事實上,我們無需關心,因爲在composite.c(libcomposite.ko)框架已經幫我們處理好細節了,在composite_setup()函數中被處理,該函數處於中斷上下文中,不要放入sleep或者切換調度之類的代碼。相當於當我們插入我們的U盤到PC上,它就會在composite_setup()回調我們的fsg_setup()。

        fsg_setup()中主要處理了兩個Mass Storage Class相關的請求:US_BULK_RESET_REQUEST和US_BULK_GET_MAX_LUN,這些請求都是由usb host(電腦的U盤驅動)下發給U盤的,U盤只有按要求處理即可。

        想要深入理解gadget,還是需要仔細閱讀libcomposite.c(libcomposite.ko)的實現,否則我們就只會調調gadget的api,以後我再講解libcomposite.ko和udc驅動的流程。

        下面主要分析fsg_main_thread();基本上U盤的所有讀寫操作都是靠它完成,十分重要的一個函數!

static int fsg_main_thread(void *common_)
{
	struct fsg_common	*common = common_;

	/*
	 * Allow the thread to be killed by a signal, but set the signal mask
	 * to block everything but INT, TERM, KILL, and USR1.
	 */
	allow_signal(SIGINT);
	allow_signal(SIGTERM);
	allow_signal(SIGKILL);
	allow_signal(SIGUSR1);

	/* Allow the thread to be frozen */
	set_freezable();

	/*
	 * Arrange for userspace references to be interpreted as kernel
	 * pointers.  That way we can pass a kernel pointer to a routine
	 * that expects a __user pointer and it will work okay.
	 */
	set_fs(get_ds());

	/* The main loop */
	while (common->state != FSG_STATE_TERMINATED) {
		if (exception_in_progress(common) || signal_pending(current)) {
			handle_exception(common);
			continue;
		}

		if (!common->running) {
			sleep_thread(common, true);
			continue;
		}

		if (get_next_command(common))
			continue;

		spin_lock_irq(&common->lock);
		if (!exception_in_progress(common))
			common->state = FSG_STATE_DATA_PHASE;
		spin_unlock_irq(&common->lock);

		if (do_scsi_command(common) || finish_reply(common))
			continue;

		spin_lock_irq(&common->lock);
		if (!exception_in_progress(common))
			common->state = FSG_STATE_STATUS_PHASE;
		spin_unlock_irq(&common->lock);

		if (send_status(common))
			continue;

		spin_lock_irq(&common->lock);
		if (!exception_in_progress(common))
			common->state = FSG_STATE_IDLE;
		spin_unlock_irq(&common->lock);
	}

	spin_lock_irq(&common->lock);
	common->thread_task = NULL;
	spin_unlock_irq(&common->lock);

	if (!common->ops || !common->ops->thread_exits
	 || common->ops->thread_exits(common) < 0) {
		int i;

		down_write(&common->filesem);
		for (i = 0; i < ARRAY_SIZE(common->luns); --i) {
			struct fsg_lun *curlun = common->luns[i];
			if (!curlun || !fsg_lun_is_open(curlun))
				continue;

			fsg_lun_close(curlun);
			curlun->unit_attention_data = SS_MEDIUM_NOT_PRESENT;
		}
		up_write(&common->filesem);
	}

	/* Let fsg_unbind() know the thread has exited */
	complete_and_exit(&common->thread_notifier, 0);
}

        它先是聲明可以被信號kill調該內核線程,以及能凍結,譬如kiill -STOP、kill -CONT之類的。它主要是靠如下幾個函數工作:get_next_command(common)

do_scsi_command(common) || finish_reply(common)

和send_status(common)

        Bulk only 的傳輸協議可閱讀《usbmassbulk_10.pdf》文檔,下面只是截取其中一部分:

以及閱讀SCSI命令文檔。本U盤gadget只是實現其中一些常用的SCSI命令子集而已,我們就挑讀(READ_10)和寫(WRITE_10)這兩個操作:

        可以看到主要是do_read和do_write。因爲流程比較繁雜,這裏只簡單描述,有興趣的朋友可以逐行代碼分析研究,do_write()是通過start_out_transfer()從usb host獲取到文件數據,然後調用vfs_write()寫入文件系統,完成了將文件寫入U盤的過程;而do_read()則是先通過vfs_read()從文件系統(加載驅動時指定的文件路徑file=filename[,filename...])中讀取文件,然後調用start_in_transfer()寫入usb host,完成了讀取U盤內的文件到PC。

        終於把U盤gadget驅動講解了一遍,當然只是粗略走讀了一下,代碼細節上還是需要大家仔細研究,譬如沒有深入到composite.c(libcomposite.ko)gadget框架的具體實現,U盤方面也沒有細節到每個SCSI命令的講解,以及沒有講解CBW/CSW的細節處理(有興趣可以對照《usbmassbulk_10.pdf》閱讀代碼)等。

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