由於項目需要,需要將Fl2440實現模擬U盤功能。這種功能在生活中很常見,比如我們的手機用USB線連接上電腦的時候,電腦會自動識別爲U盤,讀取手機(Nandflash)裏的文件。
Gadget驅動
在做移植之前我們需要先了解一個驅動——gadget驅動
USB驅動分爲主機側驅動(USB host驅動)和設備側驅動(gadget驅動)。顧名思義,主機側驅動一般是將開發板作爲主機,可以外接USB設備如U盤,鼠標,鍵盤等外設。其接口通常爲USB typeA接口。同樣,設備側驅動就是將開發板當做一個外設使用,比如我們即將要做的將開發板模擬成爲一個U盤,可以在PC上讀寫開發板 Nandflash 的文件。其接口一般爲USB typeB,miniUSB或microUSB接口。由於USB device驅動在在內核中已經有定義了,所以將USB設備側驅動叫做gadget驅動。
USB gadget驅動的三層
如圖,Linux內核中usb設備側驅動程序分成3個層次:UDC驅動程序、Gadget API和Gadget驅動程序。UDC驅動程序(USB控制器)直接訪問硬件,控制USB設備和主機間的底層通信,向上層提供與硬件相關操作的回調函數。Gadget API是UDC驅動程序回調函數的簡單包裝,這部分程序內核都已經寫好。Gadget驅動程序具體控制USB設備功能的實現,使設備表現出“U盤”、“虛擬串口”等特性。理解這部分含義非常重要,對於驅動的調試和查錯非常有用。
修改內核
對gadget驅動有了一定了解之後,開始移植工作。Linux內核對於各種驅動已經支持得比較完善,所以我們只需要將內核稍作修改並且在 make menuconfig 選項中添加相應的配置選項就可以了。
修改內核源碼
1.修改drivers/usb/gadget/file_storage.c
...
static void start_transfer(struct fsg_dev *fsg, struct usb_ep *ep, struct usb_request *req, int *pbusy, enum fsg_buffer_state *state)
{
int rc;
udelay(800); //增加延時800 us
……
}
......
/*修改mod_data初始值爲如下*/
mod_data = {
……
.removable = 1,
.can_stall = 0,
……
};
2.在mach-smdk2440.c中添加gadget設備結構體初始化和 USB device上拉電阻控制,從原理圖可以看到GPG9引腳控制上拉電阻。
/*添加udc頭文件支持*/
#include <plat/udc.h>
...
static struct platform_device *smdk2440_devices[] __initdata
{
...
&s3c_device_usbgadget,/*Add usb gadget by liwannneg*/
...
}
...
/*設置上拉引腳爲GPG9*/
static struct s3c2410_udc_mach_info s3c_udc_cfg __initdata = {
.pullup_pin = S3C2410_GPG(9),
};
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_fb_info);
s3c_i2c0_set_platdata(NULL);
/* modify usb gad my liwanneng 2017-4-30 15:40:08 */
s3c24xx_udc_set_platdata(&s3c_udc_cfg);/*添加上拉電阻控制*/
platform_add_devices(smdk2440_devices,ARRAY_SIZE(smdk2440_devices));
smdk_machine_init();
}
3.配置 make menuconfig 選項
因爲加載file-backed驅動的時候,要添加鏡像文件,所以這裏我們要選擇模塊編譯
Device Drivers --->
[*] USB support --->
<*> USB Gadget Support --->
[*] S3C2410 udc debug messages(打印內核調試信息)
<M> USB Gadget Drivers (必須選)
<M> Gadget Zero (DEVELOPMENT)
< > Audio Gadget (EXPERIMENTAL)
< > Ethernet Gadget (with CDC Ethernet support)
< > Network Control Model (NCM) support
< > Gadget Filesystem (EXPERIMENTAL)
< > Function Filesystem (EXPERIMENTAL)
<M> File-backed Storage Gadget(必須選)
[ ] File-backed Storage Gadget testing version
< > Mass Storage Gadget
< > Serial Gadget (with CDC ACM and CDC OBEX support)
編譯測試
完成以上修改之後重新編譯內核,將在driver/usb/gadget目錄下生成g_file_storage.ko模塊文件,將其和內核燒錄到開發板,測試運行。
- 製作FAT32文件系統鏡像,可以直接在開發板上執行,亦可在虛擬機創建鏡像文件後下載到開發板
dd if=/dev/zero of=udisk32M.img bs=1k count = 32768
mkfs.vfat udisk32M.img
2 . 加載g_file_storage.ko驅動,與鏡像文件建立關聯,掛載loop設備
我們將其掛載到media目錄下,如果沒有該目錄則新建一個media目錄
insmod g_file_storage.ko file=udisk32M.img stall=0 removable=1
mount -o loop /udisk32M.img /media/
完成上述操作在,現在我們用USB線將開發板接上PC,在開發板上會有打印信息:
g_file_storage gadget: full speed config #1
在windows的資源管理器中可以看到有一個大小爲32M的U盤
測試U盤是否正常工作
- 開發板往模擬U盤寫文件,windows系統上可以訪問該文件;
在開發板上往 /media目錄創建test 1.txt 文件,重新拔插USB線之後,可以在windows上看到新文件
2.windows系統往模擬U盤寫文件,開發板可以訪問該文件;
在windows系統上往U盤寫test2.txt文件,重新掛載文件系統映像到 /media目錄
到此 我們的模擬U盤已經制作完成,現在可以將FL2440的Nandflash當做U盤使用。- 開發板往模擬U盤寫文件,windows系統上可以訪問該文件;
遇到的問題及解決
下面來分享分享做gadget驅動的『艱難歷程』
問題1.在使用命令mount -o loop /udisk32M.img /media 掛載loop設備的時候提示掛載不成功。
分析:在開發板掛載loop設備失敗,於是在虛擬機上嘗試着掛載試試,驚訝的發現在虛擬機能夠掛載成功。上網瞭解了一下loop設備。下面摘自某網友博客的片段。
在類Unix系統中,/dev/loop(或稱vnd (vnode disk)、lofi(循環文件接口))是一種僞設備,這種設備使得文件可以如同塊設備一般被訪問。在使用之前,循環設備必須與現存文件系統上的文件相關聯。這種關聯將提供給用戶一個應用程序接口,接口將允許文件視爲塊特殊文件(參見設備文件系統)使用。因此,如果文件中包含一個完整的文件系統,那麼這個文件就能如同磁盤設備一般被掛載。
上面提到的文件就是前面在開發板上製作的FAT32文件系統映像udisk32M.img,我們將該鏡像文件與loop僞設備關聯,從而使得該鏡像文件能夠像磁盤一樣被訪問。
根據這一點,我到開發板的dev目錄下去查看沒有發現有loop設備(虛擬機的dev目錄有loop0設備),可以知道是我的內核沒有添加支持loop設備。
解決辦法:在內核的make menconfig中添加loop支持
Device Drivers --->
[*] Block devices --->
<*> Loopback device support
內核添加loop支持,重新編譯加載內核,在開發板dev目錄下發現有了loop設備,掛載成功,問題得到解決。
問題2.修改完內核,添加相關make menuconfig配置選項之後,連接上USB嵌入式設備和電腦都沒有任何反應,即沒有識別到USB設備。
分析.通過最開始對於gadget驅動的學習瞭解到gadget驅動分爲三層,與硬件信息建立關係的在最底層udc驅動。在內核啓動信息中發現有如下信息,說明gadget驅動加載成功。初步猜測問題出現在底層UDC驅動。
s3c2440-usbgadget s3c2440-usbgadget: S3C2440: increasing FIFO to 128 bytes
linux3.0內核對於FL2440的gadget驅動支持不是很完全。開啓USB Gadget功能之後,不能使得主機發現USB硬件。這個問題主要是USB接口的上拉電阻的問題,從上面的原理圖可以看到,Fl2440使用GPG9來上拉USB,使得主機集線器發現有USB設備鏈接從而枚舉設備。但是在linux3.0內核中,沒有設置GPG9的代碼。所以導致不能識別到USB設備。
因此參考網上的代碼,在內核中添加了上拉控制的程序。但是問題依然存在,待會兒來分析爲什麼會依然不能識別。
高能預警:添加以下代碼在linux3.0中不能解決該問題,在此僅作分析使用。
/* USB device(gadget) UDC support add by liwanneng */
static void s3c_udc_pullup(enum s3c2410_udc_cmd_e cmd)
{
switch (cmd)
{
case S3C2410_UDC_P_ENABLE :
(S3C2410_GPG(9), S3C2410_GPIO_OUTPUT);
s3c2410_gpio_setpin(S3C2410_GPG(9), 1);
break;
case S3C2410_UDC_P_DISABLE :
s3c2410_gpio_cfgpin(S3C2410_GPG(9), S3C2410_GPIO_OUTPUT);
s3c2410_gpio_setpin(S3C2410_GPG(9), 0);
break;
case S3C2410_UDC_P_RESET :
break;
default:
break;
}
}
static struct s3c2410_udc_mach_info s3c_udc_cfg __initdata = {
.udc_command = s3c_udc_pullup,
};
最初我的上拉程序是上面這樣寫的
static int s3c2410_udc_set_pullup(struct s3c2410_udc *udc, int is_on)
{
dprintk(DEBUG_NORMAL, "%s()\n", __func__);
if (udc_info && (udc_info->udc_command ||
gpio_is_valid(udc_info->pullup_pin))) {
if (is_on)
s3c2410_udc_enable(udc);
else {
if (udc->gadget.speed != USB_SPEED_UNKNOWN) {
if (udc->driver && udc->driver->disconnect)
udc->driver->disconnect(&udc->gadget);
}
s3c2410_udc_disable(udc);
}
}
else
return -EOPNOTSUPP;
return 0;
}
在s3c_udc.c中有上面這樣一段代碼,可以看到,當udc_info並且udc_info->udc_command有值或者udc_info->pullup_pin有值的情況下,調用s3c2410_udc_enable(udc)使能UDC。 udc_info在代碼中被初始化爲如下
udc_info = pdev->dev.platform_data
很顯然udc_command我已經初始化爲s3c_udc_pullup,說明條件成立。但是通調試發現上拉沒有使能。通過追蹤代碼發現問題出現在s3c2410_gpio_cfgpin函數,在調用該函數設置GPG9引腳爲輸出的時候,並沒有申請該GPIO。所以導致上拉控制失敗。
解決辦法
換一種上拉設置方式,直接設置pullup_pin引腳爲GPG9。
/*設置上拉引腳爲GPG9*/
static struct s3c2410_udc_mach_info s3c_udc_cfg __initdata = {
.pullup_pin = S3C2410_GPG(9),
};
更改爲上面的程序後,問題得到了解決。USB模擬U盤成功完成。