uboot硬件驱动
uboot與linux驅動
1.uboot本身是裸機程序
(1)在裸機中本來是沒有驅動概念的(狹義的驅動概念是指在操作系統中用來具體操控硬件的那部分代碼叫驅動)。
(2)裸機程序是直接操控硬件的,操作系統必須通過驅動來操控硬件。這兩個有什么區別?本質區別就是分層。
2.uboot虛擬地址對硬件操作的影響
(1)操作系統(指的是linux)下MMU肯定是開啟的,也就是說linux驅動中使用的都是虛擬地址,而純裸機程序中根本不會開MMU,全部使用的是物理地址。這是裸機和操作系統中操控硬件的一個重要區別。
(2)uboot早期也是工作在純物理地址下,但是現在的uboot開啟了MMU,做了虛擬地址映射,因此做驅動時必須考慮到這一點。
通過查看uboot中的虛擬地址映射表,發現除了0x30000000~0x3FFFFFFF映射到0xC0000000~0xCFFFFFFF之外,其余的地址空間全部是原樣映射,而在驅動中主要是操控硬件寄存器,而s5pv210的SFR都在0xEXXXXXXX地址空間,因此在驅動不需要考慮虛擬地址。
3.uboot借用(移植)linux驅動
(1)linux驅動本身做了模塊化設計,linux驅動本身和linux內核不是強耦合的,這是linux驅動可以被uboot借用(移植)的關鍵。
(2)uboot移植了linux驅動源代碼,uboot是從源代碼級別去移植linux驅動的,這就是linux系統的開源性。
(3)uboot中的硬件驅動比linux簡單,linux驅動本身有復雜的框架,需要實現更多的附帶功能,而uboot本質上只是個裸機程序,uboot移植linux驅動時,只是借用了linux驅動的一部分而已。
iNand/SD卡驅動
1.從start_armboot開始
(1)驅動整體比較龐大,涉及很多文件夾下的很多文件,函數很多,貿然插入根本不知道看哪里,學習時必須有時序。因此這里的iNand/SD卡驅動從start_armboot開始。
(2)在start_armboot函數調用mmc_initialize函數實現mmc的初始化,該函數在lib_arm/board.c的603行被調用。
2.mmc_initialize
代碼:1177 ~ 1208行(drivers/mmc/mmc.c)
int mmc_initialize(bd_t *bis) {struct mmc *mmc;int err;INIT_LIST_HEAD(&mmc_devices);cur_dev_num = 0;if (board_mmc_init(bis) < 0)cpu_mmc_init(bis);#if defined(DEBUG_S3C_HSMMC)print_mmc_devices(','); #endif#ifdef CONFIG_CHECK_X210CV3mmc = find_mmc_device(1);//lqm #elsemmc = find_mmc_device(0); #endifif (mmc) {err = mmc_init(mmc);if (err)err = mmc_init(mmc);if (err) {printf("Card init fail!\n");return err;}}printf("%ldMB\n", (mmc->capacity/(1024*1024/(1<<9))));return 0; }(1)從名字可以看出,這個函數的作用就是初始化開發板上的MMC系統。MMC系統的初始化應該包含這幾部分:SoC中的MMC控制器初始化(MMC系統時鐘的初始化、SFR初始化)、SoC中的MMC相關的GPIO的初始化、iNand/SD卡芯片的初始化。
3.mmc_devices
(1)mmc_devices是一個鏈表(全局變量),用來記錄系統中所有已注冊的iNand/SD設備。所以向系統中插入一個iNand/SD設備,則系統驅動就會向mmc_devices鏈表中插入一個數據結構表示這個設備。
4.cpu_mmc_init
代碼:232 ~ 241行(cpu/s5pc11x/cpu.c)
/** Initializes on-chip MMC controllers.* to override, implement board_mmc_init()*/ int cpu_mmc_init(bd_t *bis) { #ifdef CONFIG_S3C_HSMMCsetup_hsmmc_clock();setup_hsmmc_cfg_gpio();return smdk_s3c_hsmmc_init(); #elsereturn 0; #endif }(1)該函數調用了3個函數:setup_hsmmc_clock、setup_hsmmc_cfg_gpio、smdk_s3c_hsmmc_init。
(2)setup_hsmmc_clock函數:位于cpu/s5pc11x/setup_hsmmc.c中,從名字上可以看出該函數是用來初始化SoC中MMC控制器的時鐘部分的。
(3)setup_hsmmc_cfg_gpio函數:位于cpu/s5pc11x/setup_hsmmc.c中,從名字上可以該函數是用來配置SoC中MMC相關的GPIO的。
(4)smdk_s3c_hsmmc_init函數:位于drivers/mmc/s3c_hsmmc.c中,在它的內部通過宏定義USE_MMCx來決定是否調用s3c_hsmmc_initialize函數來進行具體的初始化操作。
(5)s3c_hsmmc_initialize函數:位于drivers/mmc/s3c_hsmmc.c中,代碼如下所示:
static int s3c_hsmmc_initialize(int channel) {struct mmc *mmc;mmc = &mmc_channel[channel];sprintf(mmc->name, "S3C_HSMMC%d", channel);mmc->priv = &mmc_host[channel];mmc->send_cmd = s3c_hsmmc_send_command;mmc->set_ios = s3c_hsmmc_set_ios;mmc->init = s3c_hsmmc_init;mmc->voltages = MMC_VDD_32_33 | MMC_VDD_33_34;mmc->host_caps = MMC_MODE_4BIT | MMC_MODE_HS_52MHz | MMC_MODE_HS; #if defined(USE_MMC0_8BIT) || defined(USE_MMC2_8BIT)mmc->host_caps |= MMC_MODE_8BIT; #endifmmc->f_min = 400000;mmc->f_max = 52000000;mmc_host[channel].clock = 0;switch(channel) {case 0:mmc_host[channel].ioaddr = (void *)ELFIN_HSMMC_0_BASE;break;case 1:mmc_host[channel].ioaddr = (void *)ELFIN_HSMMC_1_BASE;break;case 2:mmc_host[channel].ioaddr = (void *)ELFIN_HSMMC_2_BASE;break; #ifdef USE_MMC3case 3:mmc_host[channel].ioaddr = (void *)ELFIN_HSMMC_3_BASE;break; #endifdefault:printk("mmc err: not supported channel %d\n", channel);}return mmc_register(mmc); }函數解析:
第1點,定義并且實例化一個struct mmc類型的對象(定義了一個指針,并且給指針指向有意義的內存,或者給指針分配內存),然后填充它的各種成員,最后調用mmc_register函數來向驅動框架注冊這個mmc設備驅動。
第2點,mmc_register功能是進行mmc設備的注冊,注冊方法其實就是將當前這個struct mmc使用鏈表連接到mmc_devices這個全局變量中去。
(6)在x210_sd.h中定義了USE_MMC0和USE_MMC2,因此在uboot初始化時,會調用2次s3c_hsmmc_initialize函數,傳遞參數分別是0和2,因此在調用完成之后,系統中會注冊上2個mmc設備,表示當前系統中有2個mmc通道在工作。
5.find_mmc_device
代碼:63 ~ 78行(drivers/mmc/mmc.c)
struct mmc *find_mmc_device(int dev_num) {struct mmc *m;struct list_head *entry;list_for_each(entry, &mmc_devices) {m = list_entry(entry, struct mmc, link);if (m->block_dev.dev == dev_num)return m;}printf("MMC Device %d not found\n", dev_num);return NULL; }(1)這個函數其實就是通過mmc設備編號在系統中查找對應的mmc設備(struct mmc的對象,根據上面的分析可知,系統中有2個,分別是0和2)
(2)函數的工作原理就是通過遍歷mmc_devices鏈表,依次尋找系統中注冊的mmc設備,然后對比設備編號和我們當前要查找的設備編號,如果相同,就表示找到要找的設備。找到了之后,調用mmc_init函數來初始化它。
6.mmc_init
代碼:1112 ~ 1144行(drivers/mmc/mmc.c)
int mmc_init(struct mmc *host) {int err;err = host->init(host);if (err)return err;/* Reset the Card */err = mmc_go_idle(host);if (err)return err;/* Test for SD version 2 */err = mmc_send_if_cond(host);/* Now try to get the SD card's operating condition */err = mmc_send_app_op_cond(host);/* If the command timed out, we check for an MMC card */if (err == TIMEOUT) {err = mmc_send_op_cond(host);if (err)return UNUSABLE_ERR;} elseif (err)return UNUSABLE_ERR;return mmc_startup(host); }(1)該函數的功能是初始化mmc卡。
(2)函數的調用關系為:
mmc_init
? ? ? ? mmc_go_idle
? ? ? ? ? ? ? ? mmc_send_cmd
? ? ? ? mmc_send_if_cond
? ? ? ? ? ? ? ? mmc_send_cmd
······
從以上的分析可以看出,mmc_init函數內部就是依次通過向mmc卡發送命令碼(CMD0、CMD2...)來初始化iNand/SD卡內部的控制器,以達到初始化SD卡的目的。
總結
1.整個MMC的初始化分為2部分:SoC這一端的MMC控制器的初始化、SD卡這一端的本身的初始化。前一部分主要是在cpu_mmc_init函數中完成,后一部分主要是在mmc_init函數中完成。
2.整個初始化完成后去使用iNand/SD卡時,操作方法和mmc_init函數中初始化SD卡的的操作一樣。讀寫SD卡時也是通過總線向SD卡發送命令、讀取/寫入數據來完成的。
3.順著操作追下去,到了mmc_send_cmd函數處就斷了,真正的向SD卡發送命令的硬件操作的函數找不到。這就是學習驅動的麻煩之處。
4.struct mmc結構體是關鍵,兩部分初始化之間是用mmc結構體來鏈接的,初始化完了之后對mmc卡的常規讀寫操作也是通過mmc結構體來鏈接的。
驅動結構體分析
1.數據結構
(1)驅動設計中有一個關鍵數據結構,例如MMC驅動的結構體struct mmc,這些結構體中包含一些變量和一些函數指針,變量用來記錄驅動相關的一些屬性,函數指針用來記錄驅動相關的一些操作方法,這些變量和函數指針加起來就構成了驅動,驅動就被抽象為這個結構體。
(2)驅動工作的時候主要分為兩部分:驅動構建(構建一個struct mmc,然后填充它)、驅動運行(調用結構體中變量和函數指針)
2.分離思想
(1)分離思想就是說在驅動中將操作方法和數據分開。
(2)操作方法就是函數,數據就是變量。所謂操作方法和數據分離的意思是:在不同的地方來存儲和管理驅動的操作方法和變量,這樣的優勢就是驅動便于移植。
3.分層思想
(1)分層思想是指一個整體的驅動分為好多個層次,簡單理解就是驅動分為很多個源文件,放在很多個文件夾中。例如本課程講的mmc的驅動設計到drivers/mmc下面的2個文件和cpu/s5pc11x下面的幾個文件。
(2)以mmc驅動為例來分析各個文件的作用:
drivers/mmc/mmc.c:本文件的主要內容是和MMC卡操作有關的方法,例如MMC卡設置為空閑狀態、卡讀寫數據等,但是本文件中并沒有具體的硬件操作函數,操作最終指向的是struct mmc結構體中的函數指針,這些函數指針是在驅動構建的時候和真正的硬件操作函數掛接的(真正的硬件操作函數在別的文件中)
drivers/mmc/s3c_hsmmc.c:本文件是SoC內部MMC控制器的硬件操作方法,例如向SD卡發送命令的函數(s3c_hsmmc_send_command)、SD卡讀寫數據的函數(s3c_hsmmc_set_ios)等,這些函數就是具體操作硬件的函數,也就是mmc.c中需要的那些硬件操作函數。這些函數在mmc驅動初始化構建時(s3c_hsmmc_initialize函數中)和struct mmc掛接起來備用。
分析:mmc.c和s3c_hsmmc.c構成了一個分層,mmc.c中調用了s3c_hsmmc.c中的函數,所以mmc.c在上層,s3c_hsmmc.c在下層。有這個分成后,可以發現mmc.c中不涉及具體硬件的操作,s3c_hsmmc.c中不涉及時序操作。因此移植的時候就有好處:例如要把這一套mmc驅動移植到別的SoC上的,那mmc.c就不用動,s3c_hsmmc.c動就可以了;又例如SoC沒變,但是SD卡升級了,這時候只需要更換mmc.c,不需要更換s3c_hsmmc.c。
(3)cpu/s5pc11x下還有一個setup_hsmmc.c,也和MMC驅動有關,但是這些代碼為什么不放到drivers目錄下,而是放到cpu目錄下?因為這里面的兩個函數(setup_hsmmc_clock和setup_hsmmc_cfg_gpio)都是和SoC有關的初始化函數,這兩個函數不能放到drivers目錄下,實際上如果非把這兩個函數放在drivers/mmc/s3c_hsmmc.c中也能湊合說得過去。
總結
- 上一篇: 成为被 BAT 疯抢的数据分析师,要如何
- 下一篇: 重磅!阿里首次全面公开展示AI布局(附布