2011年12月9日 星期五

Linux Kernel 開機大追蹤


前言
多年之前大家都在談論所謂後PC時代,在當時大家無法很確定什麼是後PC時代,在今天因為智慧型手機及平板電腦的興起,己使大家可以很清楚地了解什麼是後PC時代,就是嵌入式系統,而在這些嵌入式系統中,會需要一個作業系統,而Linux的免費,以及它的穩定、open source code、Linux 已成為現在嵌入式首選的作業系統,也因此現在已有越來越多人投入開發。通常若要了解一個作業作統,可以從其開機流程來了解,若清楚地了解其開機流程程,將有助於你了解整個作業系統。
Kernel原始碼
Linux將其所有的程式碼全部公開,用以讓所有世界高手來修改,其實你可以將其視做一個framework的原始碼,所以你若了解其原始碼目錄的擺放將會有助於你開發的速度。以下是kernel source code根目錄的內容:
圖一Linux Kernel source code根目錄
其中幾個比較重要的目錄說明如下:
  • arch
這是一個HAL層的原始碼,和CPU以及各個板子有關,通常這部分會由CPU廠商所提供,另外程式起始包含在這裏面,後面將會給予說明。
  • drivers
這是各個元件的驅動元件,而各個元件的驅動程式,將會依照各分門別類給予不同的目錄而放置。
  • mm
這是有關記憶體管理的部分程式碼,這部分是和硬件無關的程式,其底層則是呼叫HAL層的程式碼,而這部分則是前面所提的arch目錄。
  • fs
在這個目錄所放置的則是各種檔案系統的程式,通常檔案系統和硬體沒有關係,而其底層的IDE或者SCSI等等則是放在前面所提的driver目錄之中。對於各種的虛擬檔案系統也是在這裏。
  • kernel
這是系統微核心的地方,而這部分通常和硬體無關,排程的管理也是在這裏。
  • net
有關各種網路協定的實做,都在這裏,從網路的link層至應用層都在這裏,以及和應用程式之間的socket層介面也是在此目錄中。
  • include
kernel中用到C和組合語言的include file放在這個目錄。
  • init
這是系統開機時的初始化動作,這會呼叫arch之下以及各個driver的初始化動作。
Kernel編譯模式
我們在編譯kernel有兩種模式可以產生,一種是有壓縮的形式,這種形式檔案會較小很多,但是在啓動時會先做自我解壓縮,這樣會造成開機時會較慢一點,另外一種是直接產生一個binary (位元)檔,這是一個kernel的執行檔,這個檔案會大很多,但其實就是前一種解壓縮完之後的內容。如何編譯這兩種kernel呢? 其實很簡單,若要產生壓縮的程式碼,在根目錄執行 ’make zImage’就可以了,它會產生一個檔案 zImage ,放在目錄arch/arm/boot之內,另外要產生沒有壓縮的kernel執行 ’make linux’就可以了,而其產生的檔名為 vmlinux 在原始檔的根目錄之下,最後要說明的是要產生壓縮的 zImage 一定會先產生沒有壓縮的 vmlinux,再由其做壓縮成 zImage。
自我解壓縮
壓縮的zImage檔其前面有一個自我解壓縮的程式碼,這段程式碼在arch/arm/boot/compressed目錄之內,第一個執行的程式為該目錄之下的head.S,原則上bootloader可以傳遞參數給kernel,它將透過暫存器r1及r2來傳送,其中r1是CPU的ID,這個值其實是由CP15的指令所讀來的,而r2則是存於一些tag的指標位置,通常在embedded系統中我們都會省略而不使用。這是一小段解壓縮的程式碼,它可以放在記憶中任何位置來執行,也就是為independent code,其實它是將kernel壓縮後利用linker及objdump公用程式,將其結合成為一個可執行檔或者是firmware,其實是將壓縮過的核心程式當做是資料和head.S的程式結合在一起,另外值得一提的是,這個解壓縮程式可放在記憶體中任何位置執行,所以它會自動判別解壓縮後的程式是否會覆蓋自己而給予避開。
第一個指令
如果是有壓縮的第一個指令如前所提的,放在arch/arm/boot/compressed/head.S,若不是壓縮檔則是放在arch/arm/kernel/head.S之內,應該說這才是真正kernel的第一個指令,一樣的在解壓縮之後會將bootloader所傳送過來的r1 & r2暫存器原封不動的再傳給kernel,而 r1 則是傳遞 CPU 的代碼,而kernel則會使用cp15的指令讀回做檢查,並且查核那一個ARM core,則會使用相對的cache指令將cache打開,通常打開cache時需要啓動MMU才能打開cache,而這時通常會設定虛擬位址和真實位址是一樣的,而打開cache是為了要加快解壓縮的速度,而在解壓縮之前會將r1 & r2分別拷貝至r7 & r8暫存器,而在解壓縮完之後,會將這兩個暫存器存回原先的r1 & r2,而r2則是存放參數位置,而這些參數是有關開機時的選項,而這些說明目前已經寫在程式的開頭地方,當你看其原始碼時即可看到,而第一個指令則是在標示在.setion “.text.head”的地方。
接著它會檢查從bootloader傳來的CPU ID是否正確,在kernel中可以撰寫支援多顆CPU的程式碼,因此可以透過這個代碼來取得相對應的程式碼,主要是cache以及CP15指令的不同,另外還有關MMU方面的操作也有所不同,當確認讓CPU ID是有被支援的,就會繼續執行下去。
再來的動作則取回該CPU的訊息,以及該CPU在kernel己經準備好的一些初始化程式碼,我們在porting kernel時會建立一個錄在arch/arm之下,然後以mach-當開頭的目錄,像是mach-ixp4xx,在這目錄之內將會建立一個C程式碼,在這之內會有一個巨集指令MACHINE_START,在這之內會有一些call back function的設定,在初始化的過程中會被呼叫,其主要是設定那些硬體需要被mapping至那些virtual address,以及timer & interrupt controller的初始化程式碼,當這些初始化的程式碼都可以正確無誤地被執行,它最後則會執會init/main.c中的start_kernel,而在它執行的當中它會檢查從bootloader傳來的command line,若沒有會有一個警告訊息,說這個tag有問題,若你確定没有傳,那你可以忽略掉它。
當它初始都没有問題,則會開始載入各個驅動程式,以及kernel本身的初始化動作,如記憶體管理,當這些都正常執行完之後,就會開始執行第一支AP,它會先執行由bootloader或者在kernel中設定的command line所指定的第一支程式,若没有則會尋找/sbin/init這支程式,若無法執行,則行bash,還是無法執行則會panic系統,並會說明找不到程式可執行,若第一支程式可以正常執行,則其它的程式則會由這第一支程式去執行其它程式,若它沒有執行其它程式,則將不會執行其它程式,另外第一支程式可以是一支shell script,以上則是kernel開機的順序。

2011年11月8日 星期二

如何製作一個Debian安裝磁碟在USB storage上

Debian是在linux中常被使用的發行版本,它可以從多種的儲存謀體來做安裝的動作,這裹要介紹從USB stroage (俗稱大姆哥)做安裝的動作,以下則是裝作USB安裝碟的流程:
1.將USB format成FAT or FAT32檔案系統,因為只有這兩種系統才能做為開機系統,而且這兩種檔案系統有可能會讓USB可使用的大小比實際硬體還要小。
2.從網路下載一個公共程式 syslinux
3.執行指令 'syslinux /dev/sdb1' (其中/dev/sdb1則是USB磁碟機代碼),完成之後將其掛載起來
       mount /dev/sdb1 /mnt/usbdisk
4.下載網路安裝檔 netboot.sio 至PC並將其掛載起來如下
       mount -o loop /home/netboot.iso /mnt/isodisk
5.拷貝上一個動作所掛載的網路安裝檔的所有內容至USB disk
       cp -a /mnt/isodisk/* /mnt/usbdisk
6.將在USB disk中根目錄之下的isolinux目錄名稱改為syslinux
7.將在USB disk中的syslinux/isolinux.bin檔名改為syslinux/syslinux.bin
8.將在USB disk中的syslinux/isolinux.cfg檔名改為syslinux/syslinux.cfg
到此就大功告成,最後重新開機進入BIOS設定從USB disk開機即可

2011年9月27日 星期二

Serial驅動程式中的Flow Control問題

什麼是Flow Control
Flow Control簡單講就是流量控制,其主要目的就是避免通訊的雙方資料被掉包,那為何資料會掉包呢?我們想像一個情境,通訊一定是至少兩人以上互相傳送資料,互相傳送資料雙方硬體不一定會一樣,所以在效能上也會不一樣,若當接收端來不及處理已接收到資料時,若沒有通知對方暫停傳送資料,因而造成對方持續送資料,這樣就會產一種現象,就是接收端會將收進來的資料丟棄,或者不接收對方送過來的資料,因而造成資料的留失,所以我們就使用流量控制的部分來確保資料不要留失。其實其中的做法也不是太難,就是利用一個機制來通知對方不要送資料就好了,而問題在於以前這種的控制都由軟體來做,但是最後還是控制使用硬體來通知對方,在控制硬體送出訊號給對方會需要一些時間處理,而這段時間長短是無法預測的,並且對方還是在繼續傳送資料,假如這段時間過長,就會造成和没有流量控制一樣的情形,也因此為了解決這個問題,現在就有人改用硬體來處理這個問題,就可以大大降低這個問題的產生,但是若要真正有效發揮硬體的功能是需要修改軟體的,所以以下我們會來說明如何修改軟體。
Serial Port的流量控制方式
RS232的流量控制方式有兩種,一種稱為軟體流量控制方式,另外一種就是所謂的硬體流量方式,什麼是軟體流量控制方式,其控制方式就是將控制訊息加到資料之中,用另外一個方式說明,就是當接收端無法收資料時就送一個字元Xoff (0x13)給對方,可以收的時候就送一個字元Xon (0x11)給對方,所以軟體控制的方式有幾種限制,首先是在傳送的資料不可以包含這兩個字完,否則將會嚴重破壞流量控制的正常運行,另外一個限制就這兩個字元必須是要即時被送出,若和要送給對方的資料一起排隊的話,有可能會延誤流量控制的時間點,因而造成流量控制的失敗。
另外一種的流量控制方式為硬體方式,其作法是必需另外再接兩條線,一條為RTS至對方的CTS,因為是雙向的所以另外一條就是我的CTS接至對方的RTS,另外的說法雙方都會增加兩隻腳為RTS及CTS,其做法很簡單就是當接收端資料快滿的時候,將RTS設為low,一直到可以接收資料時再將RTS拉為high,相對的傳送端會隨時檢查CTS,當它為low時就停止送資料,當它為high時就開始送資料。
RS232的上層控制驅動程式為tty_io,而這層則會負責流量控制,當需要做相對流量控制的行為時,它會呼叫你的RS232驅動程式作動作,所以只要做相對的動作即可,而這個動作則是前一篇文章-serial port device driver中所提的throttle & unthrottle這兩個call back function。
有什麼問題嗎?
若按照前面所提的,在寫串列式驅動程式的時候有什麼問題?看是沒有問題,但問題就在後面,因為現在的UART越做越好,所以就將流量控制的動作直接由IC本身實現,就不需要軟體來處理,IC實現有它的好處,因為它是由硬體來處理,所以反應的時間會非常快,能夠避免軟體處理時需要冗長的時間,可能會造成反應不及而讓流量控制無效。在找出問題之前我們先看以下的圖:

以上為驅動程式的架構圖,通常每一層驅程式都會有自已的buffer來存放收送的資料,而前面有說過在tty_io層會做流量控制,而它是如何判斷何時需要做流量控制呢?當然是依照它自已的buffer來做判斷,快滿的時候就叫對方不要送資料,buffer已被讀完則通知對方可以送資料,問題在於當上層的buffer滿並不代表低層IC上的buffer已滿,如此一來有一種情境將會發生,底層的IC buffer已滿,因為有支援流量控制,所以IC已經主動通知對方不要送資料,可是這時候因為上層的資料未滿,或者剛到低水位,所以通知低層送出通知對方可以送資料,如此一來將會弄亂底層的流量控制,簡單的說明就是上層的buffer水位和底層是不會同步的,用另外一種方式來看待這個問題,其實這麼多層的驅動程式,只要其中有一層做流量控制即可,這時候當然讓最低層的IC來處理即可,因為可以反應最快,並且因為由IC自行處理,所以可以減輕CPU的負擔,所以在有IC支援流量控制時RS232的驅動程式,應該如以下的寫法將會是比較好的:
static void uart_throttle(struct tty_struct *tty)
{
…………
disable_IC_receiving_and_receiving_interrupt();
………………..
}
static void uart_unthrottle(struct tty_struct *tty)
{
………………
enable_IC_receiving_and_receiving_interrupt();
…………………
}
没錯一個簡單的動作,就是在throttle的callback function中停止收資料就好了,因為如此一來將會造成IC buffer到達高水位而讓IC自動作成流量控制的行為即可,另外unthrottle的callback function中再一次啓動收資料即可,因為將IC中的buffer讀走而達低水位時,IC將會再次自動做出流量控制,如此一來上層可以不用知道低層IC的buffer使用情形,也不會弄亂IC的流量控制,這是在IC支援流量控制時所要做的修正。

2011年9月12日 星期一

驅動程式中的DMA問題

前言
什麼是DMA(directly memory access)? 簡單的講就直接記憶體存取,或許你還是無法明白,這樣說好了,在一個電腦系統中,通常都是由CPU (中央控制單元) 來對記憶體做存取的動作,也就是在各個電腦週邊元件中,若要對記憶體做存取動作,則必須經由 CPU 來做仲介的角色,而DMA則可以取代CPU這個工作,但僅至於資料而已無法對指令做這個動作,因為記憶體在系統中是唯一的,若要讓CPU及DMA可以很順暢的工作而不會互相干擾,則必需在軟體上做一些協調的動作,另外因為CPU有MMU及Cache的元件,也使得為了要讓DMA取得正確的資料,所以在寫這方面的驅動程式時需要特別注意,以下就來說明這方面的作法及問題。
硬體架構
圖一
問題點
從上圖中我們可以清楚地了解以下幾件事:
  • 當CPU要向DRAM或者是週邊元件存取時,所以放出來的位置將會經過cache及MMU的處理。
    • 在cache中的處理主要是提高對記憶體的存取速度。
    • 當作業啓動時所有程式均使用虛擬記憶體,所以一定會經過MMU的位址轉換,因為當實際要對週邊元件或者記憶體存取時一定會使用實體位址。
  • 當DMA要存取記憶體時並不會經過MMU模組的轉換,換句話說就是DMA會直接使用實體位址來存取記憶體。
    • 另外一個問題是DMA也沒有經過cache模組,所以存取的資直接從記憶體存取。
解決方案
由以上的問題描述,我們可以看一個很基本的問題,就是CPU和DMA之間對記憶體的存取可能會有不相符的現象產生。首先我們先來看週邊元件要送資料,也就是從CPU寫入資料至記憶體,然後DMA從記憶體取得資料給週邊元件,進而週邊元件送出資料,這裡問題在於CPU寫入資料時,可能不會一定寫入記憶體,有可能保留至cache中,若這時DMA來取資料將會是不對的資料,因此在DMA存取之前,一定要讓CPU的cache單元做一次sync的動作,將若有還有cache中的資料,真的寫入記憶體中,如此一來DMA將可以取得正確資料。換一個簡單的說法,就是週邊元件要送資料時必須對cache做sync的動作,確認最後的資料是有寫入記憶體。
另一個資料方向就是當週邊元件收到資料時,將會由DMA直接寫入記憶體,完作接收後,週邊元件會產生一個中斷通知CPU來讀取資料,這時的問題是在於存放收到資料,有可能是已被cache住,若CPU不做任何動作而直接來讀取資料,一樣會產生資料不正確的情形,這時在CPU讀取時必須對該區塊作一個cache invalid的動作,讓CPU在讀取該區塊資料,會實際至記憶體做讀取的動作,因此重新讓cache元件,針對該區域重新做cache的動作,如此一來就不會有資料不正確的情形產生。所以說週邊元件在收資料時,必需對cache做invalid的動作。
另外有一個簡單的作法,就是在準備這些記憶體時,就取得讓記憶體是不會做cache的動作,如此一來就不需要做資料同步動作。
Kernel API
我們從以上知道問題,也知道該如何解決,現在的問題是有多少的kernel API可供使用,以下我們就來看:
dma_addr_t dma_map_single(struct device *, void *, size_t, enum dma_data_direction);
void dma_unmap_single(struct device *, dma_addr_t, size_t, enum dma_data_direction);
dma_addr_t dma_map_page(struct device *, struct page *,unsigned long, size_t, enum dma_data_direction);
void dma_unmap_page(struct device *, dma_addr_t, size_t,enum dma_data_direction);
void dma_cache_maint(const void *kaddr, size_t size, int rw);
void dma_cache_maint_page(struct page *page, unsigned long offset,size_t size, int rw);
enum dma_data_direction {
        DMA_BIDIRECTIONAL = 0,
        DMA_TO_DEVICE = 1,
        DMA_FROM_DEVICE = 2,
        DMA_NONE = 3,
};
以上的kernel API主要是針對將一般取得的記憶體(例如由kmalloc所取得),是會被cache的記憶體,將cache中的資料和實體記憶體資料做同步的動作,其DMA_TO_DEVICE就是做invalidate的動作,而DMA_FROM_DEVICE就是synchronize的動作,這些API必需要在呼叫DMA之前完成。
void *dmam_alloc_coherent(struct device *dev, size_t size,dma_addr_t *dma_handle, gfp_t gfp);
void dmam_free_coherent(struct device *dev, size_t size, void *vaddr,dma_addr_t dma_handle); 
以上的kernel API是在要allocate記憶體時就取得不會被cache的記憶體,如此一來對這塊記憶體的存取時就不需要前面所提的動作 – invalidate or synchronize,另外也可取得該記憶體的實體記憶體,這個實體記憶體將會給DMA controller所使用。

2011年7月28日 星期四

SD/MMC驅動程式面面談

前言
MMC/SD在現在崁入式系統常被使用,因為它的體積小而且目前最新的版本可以支援到2TB,所以現在的崁入式的系統中自然而然會選用它來做儲存媒介。而SD/MMC其標準定義了實體層的硬體介面訊號,以及第二層為指令及資料讀寫的定義。它可分為SD/MMC為第一版只支援至2G,而SDHC則是第二版,可以支援至32G,它和第一版的差異,除了實體層的工作頻率增加,可以較快速的讀取之,在第二層也有擴充其功能,使其可以存取更大的檔案系統,而目前最新的為SDXC它可以支援至2TB,它又將工件往上提升,可是可以向下相容,也就是舊規格的SD卡也可以用,現在我們來介紹如何在driver中撰這些程式碼。
MMC/SD程式目錄
MMC/SD在Linux Kernel原始程式碼中的擺放位置為drivers/mmc,在其中又分為三個子目錄,分別為host、core及card,而這三個目錄其所包含內容如下:
host 
則是各家SD controller晶片廠所實現的部分,其主要是控制硬體,並將上層的所下的指令給轉換至硬體層,我們所要撰寫的驅動程式也是在這個目錄之下。
core
這是SD/MMC的管理及核心程式的地方,其中包含了第二層大家共通的部分,也是提供實體註冊及管理的部分,這個部分是大家一起共用,所以不用修改。
card
這是kernel中的檔案系統和管理中心溝通部分,也就是將檔案系統的需求轉換至核心管理程式,而核心管理再將其轉換至實體層,這個部分也是大家一起共用,所以也不需要修改。
在2.6.18之後有支援SDHC其主要是第二層的指令增加,所以舊的SD硬體 (SD host) 可以經軟體的改變而可以支援SDHC,只是因為硬體沒有改變,因此無法支援較高速的SD card。而在載入驅動程式需如下:
insmod mmc_core.ko
insmod mmc_block.ko
insmod your_hardware_SD_driver.ko
因為這是一個底層的驅動程式,所以你可以在上層載入任何的檔案系統,因為檔案系統是一個純軟體的驅動程式,所以和硬體無關,只是FAT32最大所能支援的檔案系統就只有32G,所以若要使用較大的檔案系統則必需使用其它的檔案系統。
系統驅動程式結構
我們先來了解一下linux kernel其中驅動程式的結構,在linux kernel的驅動程式中會將各種元件給予分門別類,並且在各種類別中會給予建構一個管理層的驅動程式,其主要目的是要建立和應用程式之間的標準介面,並且管理相同類別中的各家不同的硬體廠商,如此一來即可規範硬體廠商如何撰寫廠商,避免因不同的廠商而產生不同的驅動程式的撰寫格式不同,或者造成應用程式的介面,進而造成使用者因不同的廠商而要修改軟體。其架構圖如下:
              圖一:驅動程式系統架構
圖一清楚地表示前面所說明的各個目錄所負責的事情,而我們今天所要實現就是在host的目錄之下,向core註冊一個硬體,如此一來就可很輕易地連接到整個檔案系統,而檔案系統維護的部分,則是由上層的檔案所維護,對硬體層而言則是執行上層的指令,並且是完全是被動的,不會主動自行執行任何指令。
MMC core所提供的API
在介紹如何撰寫前,先來了解有那些MMC core API可供使用,其所用的include file是,而API如下:
struct mmc_host *mmc_alloc_host(int extra, struct device *)
這是allocate MMC註冊時所需的資料結構記憶體,另外可以多要一些記憶體來存放驅動程式的私有資料,其input/output說明如下:
Input :
int extra – 需要額外記憶體的大小,其存放位置的指標將會放在return回來的mmc資料結構中的private指標,它將會是緊接在mmc資料結構的最後面,或者經由呼叫mmc_priv(struct mmc_host *)來取得其指標。
struct device * – 為驅動程式進入時系統會傳過來的一個參數,只要將其再轉傳過去即可。
Output :
其返回值為一個mmc_host資料結構的位址point,這個資料結構會在後面介紹,這個point位址將會給予保留,因為我們需要在其中幾個call back欄位填入我們要實現的功能,也就是提供給上層驅動程式如何溝通的介面。
void *mmc_priv(struct mmc_host *host)
這是取回私有資料的指標,通常會再給予強迫轉型給local變數。
Input :
struct mmc_host *host – 是mmc_alloc_host API所return回來的class pointer。
Output :
其返回值為一個指標位址。
int mmc_add_host(struct mmc_host *)
 這是增加一個SD host controller,而其所需要的資料結構則是由上一個API所取得回來的,而登錄加入時上層的管理者會掃瞄一次是否有SD卡已經插上來,若有則增加一個SD disk,若没有也會設定該SD host所支援的電壓及頻率。
Iuput:
struct mmc_host * - 是由mmc_alloc_host API所return回來的class pointer。
Output:
0代表成功,非0值代表錯誤。
void mmc_remove_host(struct mmc_host *)
這是移除一個SD host controller,其做的事情和mmc_add_host()是相反的事。
Input:
struct mmc_host * - 是由mmc_alloc_host API所return回來的class pointer。
Output:
void mmc_free_host(struct mmc_host *)
這是將mmc_alloc_host()所取得的資料結構記憶體空間歸還給系統,另外也會將多取得的私有空間也歸還給系統,所以在這之後將不可再使用該資料結構的記憶體空間。
Input:
struct mmc_host * - 是由mmc_alloc_host API所return回來的class pointer。
Output:
void mmc_detect_change(struct mmc_host *, unsigned long delay)
在控制器偵測到有SD卡的插入或拔出都會呼叫這個API通知上層管理層加入一張SD卡或者移除一張SD卡,請不要搞亂並沒有移除或增加一個控制器,只是SD卡而己,但在移除時未完成的SD動作,都需要給予放棄。
Input:
struct mmc_host * - 是由mmc_alloc_host API所return回來的class pointer。
unsigned long delay – 當是插入時通常會設定一個值,以等待插入的SD卡電氣訊號穩定,而移除時通常其值會是0。
Output:
void mmc_request_done(struct mmc_host *, struct mmc_request *)
這是一個非常也常用到的API,原則上層管理層下達指令給控制器時都是透過mmc_request,而當控制器完成該指令時則需要呼叫這個API來通知上層已完成該指令,而在SD的控制指令則是半雙工的,一個指令完成之後才會有下一個,不會有連續好幾個指令產生。另外指令是包含讀寫資料。
Input:
struct mmc_host * - 是由mmc_alloc_host API所return回來的class pointer。
struct mmc_request * - 是由上層所下的指令資料結構。
Output:
資料結構
如前面所提,其中最重要的資料結構宣告如下:
struct mmc_ios {
        unsigned int    clock;                  /* clock rate */
        unsigned short  vdd;
        unsigned char   bus_mode;    /* command output mode */
        unsigned char   chip_select;            /* SPI chip select */
        unsigned char   power_mode;   /* power supply mode */
        unsigned char   bus_width;        /* data bus width */
        unsigned char   timing;      /* timing specification used */
};
這是一個SD卡的基本設定值,其中要支援的電壓值、使用的頻率等等,其使用時機當有SD卡插上會要求設定這些以啓動插入的SD卡。
struct mmc_host_ops {
        void (*request)(struct mmc_host *host, struct mmc_request *req);
        void    (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
        int     (*get_ro)(struct mmc_host *host);
        int     (*get_cd)(struct mmc_host *host);
        void    (*enable_sdio_irq)(struct mmc_host *host, int enable);
};
這是設定讓SD host所能提供功能,而其中的request最為重要,它是SD要對SD卡任何的動作需求,包含讀寫資料,以其它SD控制指令下達,set_ios則是設定以上資料結構的call back function,get_ro則是回覆這個SD卡是否可以寫入。
struct mmc_host {
        struct device           *parent;
        struct device           class_dev;
        int                     index;
        const struct mmc_host_ops *ops;
        unsigned int            f_min;
        unsigned int            f_max;
        u32                     ocr_avail;
        unsigned long           caps;           /* Host capabilities */
        /* host specific block data */
unsigned int max_seg_size;   /* see blk_queue_max_segment_size */
unsigned short max_hw_segs;/* see blk_queue_max_hw_segments */
unsigned short max_phys_segs; /* see blk_queue_max_phys_segments */
        unsigned short          unused;
   unsigned int max_req_size; /* maximum number of bytes in one req */
        unsigned int max_blk_size; /* maximum size of one mmc block */
 unsigned int max_blk_count; /* maximum number of blocks in one req */
        /* private data */
        spinlock_t              lock;    /* lock for claim and bus ops */
        struct mmc_ios          ios;     /* current io bus settings */
        u32                     ocr;     /* the current OCR setting */
        /* group bitfields together to minimize padding */
        unsigned int            use_spi_crc:1;
        unsigned int    claimed:1;      /* host exclusively claimed */
        unsigned int   bus_dead:1;     /* bus has been released */
#ifdef CONFIG_MMC_DEBUG
        unsigned int     removed:1;   /* host is being removed */
#endif
        struct mmc_card    *card; /* device attached to this host */
        wait_queue_head_t       wq;
        struct delayed_work     detect;
        const struct mmc_bus_ops *bus_ops;   /* current bus driver */
        unsigned int            bus_refs;       /* reference counter */
        unsigned int            sdio_irqs;
        struct task_struct      *sdio_irq_thread;
        atomic_t                sdio_irq_thread_abort;
#ifdef CONFIG_LEDS_TRIGGERS
        struct led_trigger      *led;           /* activity led */
#endif
        struct dentry           *debugfs_root;
#ifdef CONFIG_MMC_EMBEDDED_SDIO
        struct {
                struct sdio_cis                 *cis;
                struct sdio_cccr                *cccr;
                struct sdio_embedded_func    *funcs;
                int                             num_funcs;
        } embedded_sdio_data;
#endif
        unsigned long           private[0] ____cacheline_aligned;
};
這是記錄SD host的一些資訊,SD中所有驅動程式將會隨時使用這個資訊。
struct mmc_command {
        u32                     opcode;
        u32                     arg;
        u32                     resp[4];
        unsigned int            flags;    /* expected response type */
        unsigned int            retries;     /* max number of retries */
        unsigned int            error;          /* command error */
/*
 * Standard errno values are used for errors, but some have specific
 * meaning in the MMC layer:
 *
 * ETIMEDOUT    Card took too long to respond
 * EILSEQ       Basic format problem with the received or sent data
 *              (e.g. CRC check failed, incorrect opcode in response
 *              or bad end bit)
 * EINVAL       Request cannot be performed because of restrictions
 *              in hardware and/or the driver
 * ENOMEDIUM    Host can determine that the slot is empty and is
 *              actively failing requests
 */
        struct mmc_data  *data; /* data segment associated with cmd */
        struct mmc_request   *mrq;       /* associated request */
};
這個資料結構只有在下指令時會用到,但是有一個很重要的概念是,讀寫資料也是指令的一種,所以意思就是SD的管理層軟體全部使用command和實體的驅動程式做溝通。
struct mmc_data {
        unsigned int  timeout_ns;  /* data timeout (in ns, max 80ms) */
        unsigned int     timeout_clks;   /* data timeout (in clocks) */
        unsigned int            blksz;          /* data block size */
        unsigned int            blocks;         /* number of blocks */
        unsigned int            error;          /* data error */
        unsigned int            flags;
        unsigned int            bytes_xfered;
        struct mmc_command      *stop;     /* stop command */
        struct mmc_request      *mrq;       /* associated request */
        unsigned int            sg_len;       /* size of scatter list */
        struct scatterlist      *sg;            /* I/O scatter list */
};
這是當指令為讀寫資料時會用到的資料結構,其中有包含要存放的buffer,但有點很重要,就是這個buffer是使用page的管理,而不是raw buffer的方式。
struct mmc_request {
        struct mmc_command      *cmd;
        struct mmc_data         *data;
        struct mmc_command      *stop;
        void                    *done_data;     /* completion data */
        void  (*done)(struct mmc_request *);/* completion function */
};
這是來存放指令的資料結構。
範例程式
static void vicsd_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct vicsd_host *host=mmc_priv(mmc);
struct mmc_command *cmd;
spin_lock(&host->lock);
host->mrq = mrq;
cmd = mrq->cmd;
// if no card inserted, return timeout error
if ( readl(&host->reg->status) & MSD_CARD_DETECT ) { // card is removed
cmd->error = MMC_ERR_TIMEOUT;
goto request_done;
}
// request include data or not
if ( cmd->data ) {
vicsd_prepare_data(host, cmd->data);
}
// do request command
vicsd_send_command(host, cmd);
if ( cmd->data && cmd->error == MMC_ERR_NONE ) {
spin_unlock(&host->lock);
return;
}
request_done:
vicsd_request_done(host);
spin_unlock(&host->lock);
}
static void vicsd_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct vicsd_host *host=mmc_priv(mmc);
spin_lock(&host->lock);
if (ios->clock) {
// set SD host controller frequency
} else if ( !(readl(&host->reg->clock_control) & MSD_CLK_DIS) ) {
/*
* Ensure that the clock is off.
*/
}
if ( ios->power_mode == MMC_POWER_OFF ) {
// power off host controller
} else {
// power on host controller
}
#if 1
// set bus width
if ( ios->bus_width == MMC_BUS_WIDTH_1 ) {
………………………….
} else {
…………………………..
}
#endif
spin_unlock(&host->lock);
}
static int vicsd_get_ro(struct mmc_host *mmc)
{
struct vicsd_host *host=mmc_priv(mmc);
if ( readl(&host->reg->status) & MSD_WRITE_PROT )
return 1;
else
return 0;
}
static struct mmc_host_ops vicsd_ops = {
.request = vicsd_request,
.set_ios = vicsd_set_ios,
.get_ro = vicsd_get_ro,
};
static int vicsd_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mmc_host *mmc;
int ret;
// allocate a MMC host data structure
mmc = mmc_alloc_host(sizeof(struct vicsd_host), dev);
if (!mmc) {
ret = -ENOMEM;
goto out;
}
// initialize the MMC host data structure
mmc->ops = &vicsd_ops;
mmc->f_min = 400000;
mmc->f_max = 25000000;
mmc->mode = MMC_MODE_SD;
#if 1
mmc->ocr_avail = 0xffff00; // support 2.0v - 3.6v power
#else
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
mmc->caps = MMC_CAP_4_BIT_DATA;
mmc->max_hw_segs = 128;
mmc->max_phys_segs = 128;
mmc->max_sectors = 128;
mmc->max_seg_size = mmc->max_sectors * 512;
#endif
host = mmc_priv(mmc);
host->mmc = mmc;
…………………………………………………..
/*
* Ensure that the host controller is shut down, and setup
* with our defaults.
*/
// reset the SD card controller
………………………………………………
// to check any card inserted or not
……………………………………………
// initialize the SD card host register
…………………………………………..
dev_set_drvdata(dev, mmc);
mmc_add_host(mmc);
return 0;
 out:
if (mmc)
mmc_free_host(mmc);
return ret;
}
static struct platform_driver vicsd_driver = {
.probe          = vicsd_probe,
.remove         = vicsd_remove,
.suspend = vicsd_suspend,
.resume = vicsd_resume,
.driver = {
.name = "victor-sd",
},
};
static int __init vicsd_init(void)
{
int ret;
ret=platform_driver_register(&vicsd_driver);
if ( ret ) {
printk("fail !\n");
platform_driver_unregister(&vicsd_driver);
} else {
vicsd_device = platform_device_alloc("moxart-sd", -1);
if (!vicsd_device) {
platform_driver_unregister(&vicsd_driver);
return -ENOMEM;
}
ret = platform_device_add(vicsd_device);
if (ret) {
platform_device_put(vicsd_device);
platform_driver_unregister(&vicsd_driver);
return ret;
}
printk("OK.\n");
}
return ret;
}
static void __exit vicsd_exit(void)
{
platform_driver_unregister(&vicsd_driver);
}
module_init(vicsd_init);
module_exit(vicsd_exit);