AT91SAM9260
U-BOOT
OHCI
對於 U 盤啓動 kernel, 先通過了解整個框架,在細說 USB 枚舉(包括 HUB ) ,OHCI 等內容
一. 總體流程
在 U-BOOT 中, USB HOST 是可以不支持的,如果需要 U 盤啓動內核時,會使用此功能。
在 U_BOOT_CMD 中有 usb host 的命令結構,具體調用的是 do_usb ,若使用 USB storage ( U 盤),則同時會有 do_usbboot
使用 U 盤啓動內核的過程一般是,(舉例)
在 do_usb 函數中添加如下代碼,
if (strcmp(argv[1], "boot") == 0) {
char *s;
s=getenv("boot");
run_command(s,0);
return 0;
}
插上 U 盤之後,進入命令行模式,先寫命令 usb start 啓動 HUB 和 U 盤設備,然後配置環境變量,如 boot=usbboot ,接着寫命令 usb boot ,如上代碼所示,取得環境變量之後,就會調用 do_usbboot 完成 U 盤啓動 kernel image
首先分析 do_usb , do_usbboot 在 USB storage 部分分析
do_usb--------usb_init,usb_init 主要調用 usb_lowlevel_init 和 usb_scan_devices
識別設備之後,如果配置了 USB storage ,則會調用 usb_stor_scan 配置 U 盤,接着輸入命令 usb boot 就會調用 do_usbboot
二. Usb_low_init
usb_low_init 主要流程如下,
a) 配置外部時鐘,使能時鐘,寄存器爲 PCER 和 SCER
b) 配置 host controller 相關變量,包括
gohci,ghcca[0],phcca,ohci_dev,gtd,ptd,urb_priv 等
c) hc_reset
寫命令到 host controller 的 control 寄存器, reset ,等待 reset 結束 ( 不懂爲啥用兩次 reset)
d) hc_start
三. Usb device 的識別
1 . Usb_scan_devices
usb_scan_devices 主要調用了兩個函數, usb_alloc_new_device 和 usb_new_device
usb_alloc_new_device 初始化 usb_device *dev, 並將初始化成功的 dev 存放在數組 usb_dev 中,便於以後的查找
usb_new_device 先設置 Dev 的地址爲 0 ,然後通過枚舉過程得到新的 USB 設備的地址,設備地址一般從 1 開始分配, roothub 的地址就爲 1 , u 盤的地址爲 2
USB 設備枚舉過程,
get device descriptor
set address
get device descriptor
get configuration descriptor
set configuration
get string
由於 root hub 是直接與 host controller 相連的,所以枚舉過程一定存在 ( 至少有一個 USB device)
最後調用 usb_hub_probe 識別連接上的設備是否爲 hub
2. 枚舉過程
具體的, scan devices 枚舉包括兩個部分,一個是 ROOT HUB 的枚舉過程,一個 u 盤的枚舉過程
a) ROOT HUB 的枚舉過程: 上述描述的過程識別處 ROOT HUB 這一 USB 設備後,還未確定是否是 HUB 設備,則調用 usb_hub_probe 識別,識別出是 HUB 設備後,調用 usb_hub_configure 配置 ROOT HUB ,由於是 HUB ,會重新進行 HUB 的枚舉過程
如同前面所說的數組 usb_dev 一樣,對於所有的 HUB 設備也有一個數組 hub_dev , 在 usb_hub_configure 中,先分配一個 usb_hub_device *hub, 然後對 hub 進行枚舉,過程包括,(更詳細的枚舉見 uboot 之 USB 枚舉)
usb_get_hub_descriptor 得到 hub descriptor length
usb_get_hub_descriptor 用新得到的 length 獲取對應長的描述符
usb_get_hub_status 獲取 port 數等
usb_set_port_feature 根據 Port 數調用相應次數,對每個 Port 進行 set feature
usb_get_port_status 根據 Port 數調用相應次數,獲取 Port 是否有設備插入的信息等
usb_hub_port_connect_change 對於 Port 端口有設備插入的,調用此函數,對新設備進行檢測枚舉,此函數中又包括如下函數操作,
aa)usb_clear_port_feature
ab)disconnect any existing devices under this port wai_ms(200)
ac)hub_port_reset 這裏需要 reset 的原因在於, usb 設備速度的識別,對於以前的 1.0 系列 USB ,只有全速和低速,所以無需 reset 也能識別(通過兩根數據線的上拉電阻識別),對於高速和全速,插上之後先識別爲全速設備, hub_port_reset 之後再通過電路時序識別是否是高速設備
ad) 建立設備的樹結構, dev->children[port] 和 usb->parent 實現
ae) usb_new_device 這裏繼續調用此函數識別 USB 設備,如果連接到 root hub 上的設備仍然爲 HUB 設備,則會一直延續着調用此函數,直至 usb_hub_probe 識別出連接到 HUB Port 上的設備不爲 HUB ,對於 U 盤啓動,這裏 usb_new_device 在調用一次之後就不再調用, usb_hub_probe 識別出 U 盤設備不爲 HUB 設備
b)U 盤的枚舉過程(假設已經連接上): 這裏由於在 usb_hub_port_connect_change 中檢測到 Port 上有 USB device ,所以會調用 usb_new_device 進行枚舉,從而 USB device 得到識別,接着在 調用 usb_stor_scan 配置 U 盤
(具體見 uboot 之 usb 枚舉)
四. USB storage
u-boot 中如果配置 usb storage ,則會調用函數 usb_stor_scan
主要流程如下:
( 1 ) 初始化結構 usb_dev_desc[], 該結構爲 block_dev_desc ,包括 target SCSI ID,type of the interface(usb),device number,part_type( 未知 ) ,以及初始化 block_read 方法
( 2 )調用函數 usb_get_dev_index 找到 usb_dev[] 數組下標得到 u 盤設備的 usb_device * 結構指針
( 3 )調用 usb_storage_probe ,取得 us_data usb_stor[]
該函數包括三個重要數據結構 usb_device , usb_interface_descriptor , us_data ,主要目的就是根據 usb_device 和 usb_interface_descriptor 來填充 usb_data 結構,如下圖所示,
這裏一些賦值操作主要還是枚舉中獲得的接口信息,所以熟悉枚舉過程是很必要的。
( 4 )最後調用 usb_stor_get_info 取得 block_dev_desc_t usb_dev_desc[]
該函數主要是 U 盤的一些操作,如 INQUIRY,READ CAPACITY ,要來填充結構 static block_dev_desc_t usb_dev_desc[USB_MAX_STOR_DEV] ,便於後面引導 kernel 中所需的信息。(這裏許多 UFI 命令可以參考 USB 枚舉過程) , 這裏涉及的 CCB 結構就不講了,枚舉中會細說
流程大致如下,
a) 對於一些特定的 USB 設備,不需要 transport_reset, 否則調用 transport_reset 方法。
b) 發送 USB_INQUIRY 命令,獲取 U 盤基本信息 ( 這裏涉及的可以看 U-BOOT 之 USB 枚舉 ) (調用 transport 方法)
c) 調用 usb_test_unit_ready 查看設備是否準備就緒(調用 transport 方法)
d) 調用 usb_read_capacity 讀取 U 盤容量(返回信息包括塊數目,塊大小,設備類型,例如 DOS 盤等)(調用 transport 方法)
e) 調用 init_part 初始化分區,對於 DOS 盤,則調用 test_part_dos 讀取分區頭信息,檢測是否正確(調用 block_read 方法) , 對於 DOS 盤,讀取出的第一塊的信息,要求 0x1fe 偏移處值爲 0x55,0x1ff 爲 0xaa
下面主要講講涉及到的三個方法, transport_reset , transport , block_read
i) tranport_reset
transport_reset 指向的函數爲 usb_stor_BBB_reset , reset 的步驟需要嚴格支持以下步驟,
/*
* Reset recovery (5.3.4 in Universal Serial Bus Mass Storage Class)
*
* For Reset Recovery the host shall issue in the following order:
* a) a Bulk-Only Mass Storage Reset
* b) a Clear Feature HALT to the Bulk-In endpoint
* c) a Clear Feature HALT to the Bulk-Out endpoint
*
* This is done in 3 steps.
*
* If the reset doesn't succeed, the device should be port reset.
*
* This comment stolen from FreeBSD's /sys/dev/usb/umass.c.
*/
所以調用的分別是 usb_control_msg ,
usb_clear_halt, usb_clear_hatl
ii) transport
transport 指向的函數 usb_stor_bbb_transport ,該函數主要調用函數 usb_stor_BBB_comdat 填充 CBW ,接着調用 usb_bulk_msg 發送 CBW, 調用 usb_bulk_msg 返回數據,調用 usb_bulk_msg 接收 CSW ,進行結構 CSW 解析,查看是否發送成功,如果有誤,則 reset ,即 usb_stor_BBB_reset
iii) block_read
該部分實際調用的是 UFI 命令,實際上仍然是 transport 方法,讀取指定塊數據,略
五. USB 引導 kernel
在最後一步,做 U 盤引導 kernel 時,最好格式化 U 盤,然後將 kernel.img 存入 U 盤進行引導。
這部分的原理與 UBOOT 最後檢查 img 的合法性,然後調用 do_bootm 方法實現引導 kernel 原理一致(只不過多了一步分析 U 盤的文件系統的步驟)
當然這裏所說的啓動 kernel 的方式還使用命令行,實際上稍微修改下代碼是可以實現自動檢測 kernel ,如果存在且合法則啓動,否則使用默認 kernel
該部分調用函數 do_usbboot, 首先通過環境變量 bootdevice 得到引導設備的下標,從而找到引導設備。
這裏可以設置 bootdevice=0, 則爲獲取得的 block_dev_desc 的 * stor_dev 指針 .
具體的流程爲,(這裏主要調用的方法是 block_read )
a) get_partition_info 獲取分區信息,分析分區信息
b) 讀取 img 的 img_head ,分析合法性,如果合法,則調用 do_bootm 引導 kernel (這裏設置 autostart=yes 這一環境變量則合法直接引導)
該函數的重點在於分析 U 盤的文件系統。事實上, uboot 中的對於 u 盤啓動 kernel 的方法是不完善的(支持引導 DOS 盤),特別是對與普通的 U 盤。(查看源代碼可以發現這一不足,本人用 U 盤引導 kernel 時也出現一些問題,主要是文件系統方面的)
下面對 FAT32 文件系統簡單分析( FAT16 類似,如果需要完善 U 盤引導 kernel 這部分,需要對大多數的 U 盤支持,所以最好對大多數的文件系統熟悉,並且對需要用到的信息封裝結構體,這樣在便於實現的同時也實現對不同文件系統的支持)
這裏只分析與引導 kernel 部分有關的,具體的可以參照《 4.5 萬字透視 FAT32 文件系統》
由於 U 盤是經過格式化的,且一般 img 的大小不會超過第一分區的大小,所以通過計算 img 起始位置,然後讀取 img 對應的塊數數據即可
下圖是 FAT 文件系統的部分結構圖
一般而言只要分析出 DBR 中相關數據,即可計算起始位置
下圖是 U 盤的 DBR 截圖(用的是 WINHEX )
可以看到該 U 盤使用的是 FAT32 文件系統
參照 FAT32 的 BPB 說明,根據下圖信息即可找到對應的起始地址,具體的就分析略。