現在的Linux內核中,mmc不僅是一個驅動,而是一個子系統。這里通過分析Linux3.2.0內核,結合TI的arm335x平台及omap_hsmmcd host分析下mmc子系統,重點關注sdio及架構在其上的具體sdio IP驅動實現。
1. General overview
1.1 源碼概覽
Linux kernel把mmc,sd以及sdio三者的驅動代碼整合在一起,俗稱mmc子系統。源碼位於drivers/mmc下。其下有三個子目錄,分別是:
其中,card用於構建一個塊設備作為上層與mmc子系統溝通的橋梁;core抽象了mmc,sd,sdio三者的通用操作;host則是各類平台上的host驅動代碼,包括如TI Omap的omap_hsmmc,三星的s3cmci等。
1.2 硬件層IP對象間的聯系
即,cpu要訪問slave必須通過host進行,包括slave的中斷。omap host的具體組成及其與slave之間的連線如圖1.1所示:
圖1.1
和別的中斷控制器一樣,host的MPU中斷子系統有一個仲裁機制,分別使用IE和ISE控制是否向CPU報中斷以及是否care slave報出來的中斷。
Host在數據傳輸時支持8bit模式,但slave是sdio時僅支持1-bit & 4-bit模式,當支持4-bit模式時,data1和IRQ線復用。
注意:MMCHS的smart-idle wake up line(SWAKEUP)沒有連出去,在別的一些host中,它是和PRCM子系統連接,用於在MMCHS處於suspend時自動提醒PRCM給MMCHS提供clock。
2. 從bus,driver,device看mmc子系統
Linux下的任何驅動在內核中最終都抽象為bus, driver以及device三者間的相互作用。
2.1 mmc下的bus,driver,device模型
Mmc子系統涉及到三條總線。
Host驅動相應的driver和device掛載在Linux內核內置的虛擬抽象總線platform_bus_type。兩者的匹配采用名稱匹配的方式,即driver和device兩者的name一樣則認為該device對應該driver,這里是”omap_hsmmc”。
Card驅動相應的driver和device掛載在mmc自己創建的虛擬總線mmc_bus_type下,直接匹配。
Sdio驅動相應的driver和device掛載在mmc自己創建的虛擬總線sdio_bus_type下,ID匹配。
注意:Linux內核中,匹配函數默認使用bus注冊的匹配函數,如果bus沒有注冊則使用driver注冊的匹配函數。所以,一般自己創建虛擬總線時,其匹配函數和driver的匹配函數都是一致的。
2.2 按時間順序觀察mmc中各bus,driver,device對象初始化流程
2.2.1 Host device對象
Host device對象首先被初始化並掛載到platform_bus_type,但是這個過程不在mmc子系統源碼下,它在平台初始化過程中init了,具體的流程參考5.1。
2.2.2 mmc_bus,sdio_bus對象
core初始化時,件core.c中的subsys_initcall(mmc_init)創建這兩條mmc自己的虛擬總線。
2.2.3 card driver對象
Card初始化時,文件block.c中module_init(mmc_blk_init);函數創建mmcblk driver對象,並將之掛載到mmc_bus_type總線上。
2.2.4 host driver對象
Host初始化時,文件omap_hsmmc.c中module_init(omap_hsmmc_init);函數創建”omap_hsmmc”driver對象。
2.2.5 card device對象
到2.2.4為止,host的device和driver都已經創建,匹配后調用host對應的probe函數:omap_hsmmc_probe()。該函數將進行檢測slave、初始化slave等操作。
檢測sdio並創建card device對象。換言之,這個device對象並沒有對應的硬件設備,它只是抽象出來用於和他人交互的一個虛擬device。具體的流程參考5.1。
2.2.6 sdio device對象
緊接着2.2.5后面,在函數sdio_init_func()函數中將創建一個device對象並掛載到sdio_bus_type上。具體的流程參考5.1。
2.2.7 小結
到此為止,mmc下三條總線、六個device/driver對象只差一個sdio driver了。這個driver對象對應着具體的sdio IP驅動,比如sdio_wifi, sdio_uart等。這樣來看sdio IP驅動其實是構建在mmc子系統之上的。
3. 上層與mmc以及mmc內部模塊之間的交互方式
從軟件層面看mmc內部模塊交互以及外部訪問mmc的方式,如圖3.1所示,。
圖3.1
如之前所言,mmc內部的core模塊是各類host、sd及sdio操作的抽象,訪問host的通用行為都放在其中,而各類host將其各自獨特的行為注冊到host規定的接口上。Mmc提供了兩種方式供外部訪問,mmc子系統本身對外呈現的是一個塊設備,應用層通過VFS到具體的FS再到塊設備逐層訪問,塊設備通過core讓host和slave進行通信,sd卡的訪問就是這種典型方式。另外一種方式是不通過塊設備訪問core,而是在驅動層新建一個驅動,這個驅動構建在mmc之上,讓它有權限訪問core,具有sdio接口的IP驅動就可以這么做。至於這個sdio的IP驅動對外以何種方式呈現由它自己決定,抽象為一個塊設備或者字符設備都可以。
需要注意的是:core內部訪問host的操作有睡眠動作,一般的訪問行為是這樣的:
1) 獲取host資源(mmc_claim_host),可能sleep;
2) 發送訪問請求(如讀/寫);
3) 等待(block)本次訪問結果(無論slave是否response,host都會通過中斷給出返回值);
4) 釋放host資源(mmc_release_host)。
4. sdio
4.1 sdio和sd/mmc的差異
sdio事件通過card interrupt通知host,在host已有的中斷處理過程中進行,如圖4.1所示。
圖4.1
但是sdio的中斷事件(以CIRQ bit位表示)的處理和別的事件有點差異。Sdio中斷事件的處理可能需要sleep。
比如讀操作,它的流程是:1)來一個CIRQ表明sdio中的FIFO已經達到閾值;2)host告訴上層這個事件;3)sdio中斷處理例程將去sdio FIFO中讀取數據,它是通過host的標准操作進行的,如之前所言,這個過程會阻塞。而同樣的讀操作對sd卡則不需要,它的流程是:1)host發出讀操作;2)slave把准備好的數據放到host的FIFO中並assert中斷;3)host處理自己的FIFO並通知上層讀操作結束。
所以對於sdio而言,host只是充當sdio和上層的溝通媒介,它不是sdio slave的controller,sdio IP有自己的controller,有自己的FIFO。
4.2 TI Omap平台下的sdio中斷處理實現
截至3.12內核版本,Sdio在TI的Omap Soc下仍然沒有實現以中斷方式處理sdio事件。具體點說,即mmc子系統軟件框架已經考慮了sdio中斷,但是針對omap平台的host驅動(omap_hsmmc.c)沒有支持,它直接忽略了中斷寄存器SD_STAT中的CIRQ。
4.2.1為什么host不支持sdio中斷?
究其原因,應該是因為某些平台的host沒有swakeup line(smart-idle-wakeup),比如arm335x。看一下圖4.1所示的mmc子系統的大致硬件架構和host的硬件組成就大致明白了。
Host包括hsmmc和PRCM等子模塊,其中PRCM提供clock,電源管理時host可能會進入suspend模式,該模式下PRCM不提供clock,所以此時host將無法處理CIRQ中斷,除非上層強制host wake up,否則host不會智能恢復正常工作。而如果有swakeup line的時候,當有CIRQ時,通過swakeup line,PRCM將自動供clock。這一點在圖1.1中可以看的更清楚。
4.2.2 mmc子系統如何實現sdio中斷方式
假設不考慮上述沒有swakeup line,進入suspend狀態的hsmmc無法在有卡中斷時自動開始恢復工作的情況,或者說我們已經找到了某種解決辦法后,考慮下host,sdio等mmc子系統模塊如何配合實現使用中斷方式處理sdio事件。
圖4.2表明了在當前mmc子系統軟件架構下可行的中斷處理方式。
圖4.2
請注意圖中給出的注意事項。
5. Appendix
5.1 平台初始化流程
5.1.1 Linux內核平台初始化
Linux內核將平台相關的具體數據和模塊的特定初始化函數分別放在兩個平台相關的文件中。內核提供一個框架,並調用各平台提供的注冊函數。
以omap arm33xx為例,板級初始化相關函數放在文件board-am335xevm.c中,由kernel框架調用;板級各模塊特定信息放在omap_hwmod_33xx_data.c中,並由omap_hwmod.c中提供的函數進行調用。
注意:現在Linux下已經不用這種方式存儲板級信息,取而代之的是device tree,它用一種更簡潔的方式來描述這些特定平台信息,從而讓kernel代碼更加清晰,具體的可以參考http://blog.csdn.net/21cnbao/article/details/8457546。
5.1.2 平台環境文件
平台環境文件board-am335xevm.c定義了各模塊的初始化函數,以及提供內核初始化必要的平台初始化函數:
1 MACHINE_START(AM335XEVM, "xxxx") 2 3 /* Maintainer: Texas Instruments */ 4 5 .atag_offset = 0x100, 6 7 .map_io = am335x_evm_map_io, 8 9 .init_early = am33xx_init_early, 10 11 .init_irq = ti81xx_init_irq, 12 13 .handle_irq = omap3_intc_handle_irq, 14 15 .timer = &omap3_am33xx_timer, 16 17 .init_machine = am335x_evm_init, 18 19 MACHINE_END
上面這段代碼注冊了內核在start_kernel()中會調用的函數,如map_io提供將各IP寄存器地址到內核虛擬地址的轉換,init_early()函數由start_kernel()--->setup_arch()--->mdesc->init_early()調用,其余的init_irq,handle_irq()等會在start_kernel()中分別調用。其中init_machine()函數用於初始化各IP模塊的device抽象對象,熟悉Linux驅動架構的應該就清楚了,這就是bus,driver,device三劍客之一的device初始化的地方。
舉例來說,init_machine()這個函數被調用流程是:start_kernel()--->rest_init()--->kernel_init()-àdo_basic_setup()-àdo_initcalls()--->customize_machine()--->init_machine()。
5.1.3 平台數據文件及其調用者
可以看到init_machine()函數在do_initcalls()中處於第三優先級,第一優先級的是core_init(),它會使用omap_hwmod.c提供的相關函數在device初始化前作一些操作。
omap_hwmod_33xx_data.c大部分內容是定義板子各模塊的資源信息如clock、irq號、模塊寄存器相應的物理地址等。
do_initcalls()時,core_initcall(omap_hwmod_setup_all)---> _setup()(對每個模塊依次作此操作)。
所以平台初始化操作除MACHINE_START提供的幾個函數外,還有一個地方就是這里的omap_hwmod_setup_all。
以各模塊的sysconfig寄存器初始配置為例:
core_initcall(omap_hwmod_setup_all);
omap_hwmod_for_each(_setup, NULL);//枚舉平台omap_hwmod_33xx_data.c中定義的omap_hwmod(每個模塊的寄存器設定值)
_setup
++++++++++++ 以下是clock相關 +++++++++
_enable
_enable_sysc
_set_clockactivity
_write_sysconfig
omap_hwmod_write(v, oh, oh->class->sysc->sysc_offs);
注意:omap相關模塊寄存器默認值一般情況下並非都為0,以mmc的sysconfig為例,其默認值是0x2015。
5.2 slave檢測流程
如2.2.5所言,host的device和driver創建並匹配后調用host對應的probe函數:omap_hsmmc_probe()。其中一個重要的步驟是啟用一個工作隊列進行slave檢測並創建后續相關device/driver對象。
Host驅動為了兼容多種host controller,有些操作對於SDIO是不需要的(下面用刪除線mark了),但是給SDIO這樣的操作,SDIO不應報錯。具體流程如下所示:
mmc_alloc_host
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
mmc_add_host(mmc);
mmc_start_host(host);
mmc_power_off(host);
mmc_detect_change(host, 0);
mmc_schedule_delayed_work(&host->detect, delay);
mmc_rescan
mmc_rescan_try_freq
sdio_reset(host); //使用CMD52設置slave寄存器6(CCCR)的RES bit
mmc_go_idle(host);
mmc_send_if_cond(host, host->ocr_avail);
mmc_attach_sdio(host)
mmc_send_io_op_cond(host, 0, &ocr);//使用CMD5命令,獲取OCR值(slave支持的電壓范圍)
mmc_attach_bus(host, &mmc_sdio_ops);//設置host的bus操作函數是mmc_sdio_ops
mmc_select_voltage(host, ocr);//設置電壓為選取電壓(slave本身支持的電壓范圍 & 平台環境選取的電壓ocr_avail==ocr_mask)的最小值
mmc_sdio_init_card(host, host->ocr, NULL, 0);
mmc_alloc_card(host, NULL);//device_init,創建card device,指定為mmc_bus_type
host->ops->init_card == omap_hsmmc_init_card //沒設置,為空
mmc_send_relative_addr(host, &card->rca);//CMD3,獲取slave的rca
mmc_select_card(card);//CMD7,選中rca對應的slave
sdio_read_cccr(card);//read CCCR
sdio_read_common_cis(card);// read CIS
// set high speed, set bus width and so on...
sdio_init_func
sdio_alloc_func//創建device,指定為sdio_bus_type
sdio_read_fbr
sdio_read_func_cis
mmc_add_card//把card device掛載到mmc_bus_type總線
sdio_add_func//把sdio device掛載到sdio_bus_type總線
6. Reference
- Linux kernel source code(version3.2.0)
- Linux kernel 最新patch
- AM335x ARM® Cortex™-A8 Microprocessors(MPUs) Technical Reference Manual
- SDIO Simplified Specification
- SD Host Controller Simplified Specification