1. 程式人生 > >kernel移植——從三星官方核心開始移植

kernel移植——從三星官方核心開始移植

以下內容源於朱有鵬嵌入式課程的學習,如有侵權,請告知刪除。

一、核心移植初體驗

1、三星官方移植版核心獲取

  • 原始碼包來於三星的SMDKV210開發板附帶的光碟資料,下載地址

2、構建移植環境

(1)Windows下建立SI工程;

  • 建立前刪除不必要的目錄和檔案:刪除arch目錄下非arm架構的目錄檔案;刪除arch/arm目錄下不是三星的mach-xxx、plat-xxx目錄。

(2)ubuntu下解壓;

3、配置、編譯

(1)檢查Makefile中ARCH =arm 和CROSS_COMPILE =/usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-;

(2)make xx_defconfig;(如何確定xx_defconfig?這裡是smdkv210_android_defconfig)

  • 在arch/arm/configs下有很多,找到三星相關的。這裡找一個最像的:smdkv210_android_defconfig

(3)make menuconfig;(目前還不知道修整哪些)

(4)make -j4(直接make,則會直接單執行緒編譯;如果make -j4,則會4執行緒編譯)。

4、將得到的zImage下載執行看結果

5、現象分析

(1)結果

  • 只見到uboot打印出的下圖資訊,看不到linux自解壓程式碼打印出的“Uncompressing Linux... done, booting the kernel.”


(2)分析

  • 說明zImage根本沒有被解壓成功,核心程式碼根本就沒有被執行。因此問題出在解壓相關的部分。
  • 問題出在解壓後代碼放置的記憶體地址處(該地址可以由核心配置的,如果不對,則核心不能執行)。
  • 核心配置的解壓地址應該等於連線地址,否則自解壓之後核心無法執行。
  • 現在問題變成:第一,核心的連線地址等於多少?第二,核心中配置的解壓地址是多少
  • 這裡面還有個問題:核心的連線地址是一個虛擬地址,而自解壓程式碼解壓核心時需要實體地址(此時mmu還沒開,因為核心還沒有開始執行),因此連線地址對應的實體地址等於自解壓地址
  • 連線地址和對應的實體地址在head.S中可以查到,經分析(在.config中查巨集TEXT_OFFSET,合成)得知分別是0xC0008000和0x30008000,那麼自解壓程式碼配置的解壓地址應該是30008000。



(3)修改操作

  • 自解壓程式碼對應的自解壓地址在mach-s5pv210/Makefile.boot檔案中。
  • 在此檔案後面追加入下面內容。
  • (實踐中這樣好像並沒有預期的結果。因此我直接賦值不使用巨集。問題是,為什麼這裡的巨集不起作用?因為我寫錯了,尷尬……)
# override for SMDKV210
zreladdr-$(CONFIG_MACH_SMDKV210)	:= 0x30008000
params_phys-$(CONFIG_MACH_SMDKV210)	:= 0x30000100

(4)同步、編譯,重新下載執行檢視結果

  • 因為只修改了一些連結引數,不需要重新配置編譯,所以make很快就完成了。
  • 自解壓程式碼解壓列印資訊已經出來;
  • 核心還是沒有執行;

(5)問題分析

  • 因為我們在uboot中配置的空間為3000 0000開始(而不是2000 0000開始),因此這裡定義的實體地址不對,從20000000改到30000000即可。


(6)重新編譯,執行結果:核心打印出很多執行資訊。

二、核心中機器碼的確定(知識課程)

核心支援什麼架構、支援哪款cpu是怎麼確定的。 核心中儲存了一份機器碼,會和uboot傳過來的機器碼比對(在head.s中比對uboot傳過來的機器碼),比對成功說明uboot和核心是匹配的。 之前講過uboot如何給核心傳機器碼。本課講核心的機器碼是怎麼確定的,怎麼來的。 每個mach-xx資料夾代表一種cpu,其內可能包含cpu相同的不同開發板。比如mach-s5pv210資料夾內有mach-smdkc110.c和mach-smdkv210.c等開發板。

1、MACHINE_START巨集

  • 這個巨集用來定義一個機器碼的資料結構。
  • 即用來定義一個結構體型別為machine_desc型別的結構體變數,名為__mach_desc_SMDKV210。
  • 這個結構體變數會被定義到一個特定段.arch.info.init,因此這個結構體變數將來會被連結器連結到這個.arch.info.init段中。

比如 MACHINE_START(SMDKV210, "SMDKV210")解釋為下面內容。

static const struct machine_desc __mach_desc_SMDKV210	\
 __used							\
 __attribute__((__section__(".arch.info.init"))) = {	\
	.nr		= MACH_TYPE_SMDKV210,//機器碼2456		\
	.name		= "SMDKV210",
	.phys_io	= S3C_PA_UART & 0xfff00000,
	.io_pg_offst	= (((u32)S3C_VA_UART) >> 18) & 0xfffc,
	.boot_params	= S5P_PA_SDRAM + 0x100,
	.init_irq	= s5pv210_init_irq,
	.map_io		= smdkv210_map_io,
	.init_machine	= smdkv210_machine_init,
	.timer		= &s5p_systimer,
};

(1)mach-xxx.c檔案定義了一個機器碼的開發板的machine_desc結構體變數(一個機器碼對應一個開發板,一個cpu可以對應多個開發板)

  • 這個結構體變數放到.arch.info.init段中後,表示當前核心可以支援這個機器碼的開發板。

(2)我們所移植的目標開發板使用S5PV210的CPU,開發板名字叫X210。

  • 但在三星官方版本的核心中找不到mach-x210.c;
  • 但不想從零開始移植,因此在三星移植的mach-s5pv210目錄下找一個mach-xx.c,使得該開發板和X210開發板最為接近(同一款cpu),然後以此為基礎來移植。

(3)經過檢視,發現mach-s5pc110.c和mach-s5pv210.c與X210開發板最為接近。

  • 尋找原則:開發板和三星官方的哪個開發板最為相似。
  • X210開發板抄的是三星的SMDKV210,因此要找SMDKV210對應的檔案(按理應該是mach-smdkv210.c,但實際是嗎?)

(4)結合mach-s5pv210目錄下的Makefile來分析,得知.config中定義CONFIG_MACH_SMDKV210後,實際繫結的是mach-smdkc110.c這個檔案。

  • 因此實際上mach-smdkv210.c這個檔案根本沒用到。

2、硬體驅動的載入和初始化函式執行

.init_machine	= smdkc110_machine_init,
  • 定義了一個機器硬體初始化函式;
  • 這個函式非常重要,這個函式中綁定了開發板linux核心啟動過程中會初始化的各種硬體的資訊。
  • 在此函式中新增的才會被初始化,否則不會被初始化。
  • 這篇視訊末尾的知識,可以強化核心分析的學習。

三、解決核心啟動中的錯誤

1、認識核心啟動OOPS(核心死亡資訊)

(1)核心啟動後會有列印資訊,列印資訊中隱藏了問題所在。

  • 認真的去分析這個列印資訊,從中找到對的或者錯誤的一些資訊片段,才能幫助我們找到問題,從而解決問題。

(2)核心啟動中的錯誤資訊的特徵

  • 由PC和LR的值可以看出,程式執行到dev_driver_string或者max8698_pmic_probe(這兩個是函式或者彙編中的標號)符號部分的時候出錯了。
  • 我們從這兩個符號出發去尋找、思考可能出錯的地方然後試圖去解決。

2、錯誤追溯及問題解決

(1)max8698_pmic_probe

  • max8698這個電源管理IC的驅動安裝函式部分出錯;
  • 開發板系統中配置了支援這個電源管理IC,於是啟動時去載入它的驅動,結果驅動在載入執行的過程中出錯。

(2)這個驅動載入時為什麼會出錯?

  • 結合X210開發板的硬體實際情況來分析:X210開發板上根本就沒有max8698這個電源管理IC,既然硬體都沒有,執行驅動肯定會出錯。
  • 移植三星版本的uboot時,在uboot的lowlevel_init.S中也有呼叫電源管理IC初始化函式(PMIC_init),結果會報錯,遮蔽掉該函式的呼叫,uboot就可以成功執行下去。

(5)為什麼uboot和核心中,都預設呼叫這個電源管理IC的初始化程式碼?

  • 因為三星的SMDKV210開發板中用了max8698這個電源管理IC,因此三星的uboot和kernel中都預設支援這個。
  • 但是X210中是沒用的,因此uboot和核心中都需要去掉該程式碼模組。

(6)怎麼解決?

  • 在uboot中,是直接改原始碼,即遮蔽掉那個初始化函式解決的;
  • 在kernel中,不能直接修改原始碼。
  • 因為linux kernel是高度模組化高度可配置的,核心中每一個模組都被配置項條件編譯;
  • 因此要去掉對某個模組的支援,需要重新配置,配置時去掉選項即可,不用改原始碼。
  • 因此關鍵就是要找它對應的配置項。

(7)操作

  • 先make menuconfig;
  • 然後/搜尋"MAX8698"這幾個關鍵字,然後看到這個配置項的路徑,然後到路徑下去按N鍵去掉這個模組的支援,儲存,重新編譯即可。

(8)重新編譯、下載執行

  • 此問題被解決了;
  • 核心再次啟動後直接執行到掛載rootfs才出錯。

3、分析及總結

分析:根本原因在於CONFIG_MFD_MAX8698這個配置巨集。這個配置巨集決定了很多東西

  • 第一:這個配置巨集決定了drivers目錄下的max8698對應的驅動程式原始碼是否被編譯;
  • 第二:這個配置巨集決定了kernel啟動過程中是否會呼叫一些max8698的相關的程式碼;

總結:kernel是高度模組化和可配置化的,所以在核心中做任何事情(新增一個模組、更改一個模組、去掉一個模組)都必須按照核心設定的方案和流程。

四、iNand的問題和安排

1、錯誤分析

(1)核心錯誤資訊:Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(0,0)。

  • 核心試圖掛載根檔案系統時失敗,失敗的原因是unknown-block(不能識別的塊裝置)


(2)backstrace分析,可以得知錯誤資訊的來源,再結合之前的核心啟動流程分析,就更加確定出錯的地方。


(3)為什麼unknown-block(0,0)?

  • 在kernel啟動時,uboot給核心傳遞一個cmdline。
  • 其中root=xx指定rootfs在哪個裝置上,核心會到相應的地方去掛載rootfs。
  • 譬如root=/dev/mmcblk0p2,這裡的/dev/mmcblk0p2就是rootfs所在的裝置的地址。
  • 它表示mmc裝置0的第2個分割槽(裝置0,即在SD0通道上的裝置,即iNand)。
  • 這裡的問題就是沒找到mmc裝置0的第2分割槽。

(4)為什麼沒找到mmc裝置0的第2分割槽?


  • 一定是因為kernel啟動過程中載入mmc驅動的時候有問題,驅動沒有發現mmc裝置0。
  • 問題定位在MMC相關的驅動方面。

(5)對比九鼎版本的核心啟動資訊

下圖是九鼎版本的核心啟動資訊部分截圖


  • 可發現待移植的核心啟動時,沒有找到MMC裝置(內建的iNand和外接的SD卡都沒找到);
  • 沒找到肯定是驅動的問題,因此要移植MMC驅動。

2、知識補充

(1)SD/iNand都是由一個一個的扇區組成的

  • BL1從SD卡的1扇區開始往後存放;
  • SD卡的0扇區是不用的,SD卡的0扇區用來放置MBR(主引導記錄)。

(2)MBR用來描述塊裝置的分割槽資訊

  • 事先定義了一個通用的資料結構來描述塊裝置的分割槽;
  • 只要將分割槽資訊寫入MBR中,即可完成對該裝置的分割槽;
  • MBR預設存放在塊裝置的第0個扇區。

(3)核心如何知道iNand分了4個分割槽?哪裡對inand進行了分割槽?

  • uboot中有一個命令fdisk,fdisk -c 0時,對iNand進行分割槽;
  • fdisk命令對iNand的分割槽已經寫死,核心通過讀取MBR,就可以知道分割槽資訊了;
  • 由iNand本身通過MBR給核心傳遞分割槽資訊因此uboot給核心傳參時,不用傳遞分割槽表資訊

(4)如果開發板使用的是nandFlash,分割槽表一般是在核心中用程式碼構建的。

  • 因此nand版本的核心移植時,一般都需要移植、更改nand分割槽表。

3、解決安排

  • 暫時解決不了這個問題……

五、網絡卡驅動的移植和新增實驗

1、移植標準

(1)網絡卡驅動移植ok時,啟動資訊為

[    1.452008] dm9000 Ethernet Driver, V1.31
[    1.455870] eth0: dm9000c at e08f4300,e08f8304 IRQ 42 MAC: 00:09:c0:ff:ec:48 (platform data)
(2)當前核心中網絡卡驅動尚未移植,因此核心啟動時有錯誤的列印資訊:
[    1.130308] dm9000 Ethernet Driver, V1.31
[    1.133113] ERROR : resetting 
[    1.135700] dm9000 dm9000.0: read wrong id 0x2b2a2928
[    1.140915] dm9000 dm9000.0: read wrong id 0x2b2a2928
[    1.145941] dm9000 dm9000.0: read wrong id 0x2b2a2928
[    1.150963] dm9000 dm9000.0: read wrong id 0x2b2a2928
[    1.155992] dm9000 dm9000.0: read wrong id 0x2b2a2928
[    1.161018] dm9000 dm9000.0: read wrong id 0x2b2a2928
[    1.166041] dm9000 dm9000.0: read wrong id 0x2b2a2928
[    1.171070] dm9000 dm9000.0: read wrong id 0x2b2a2928
[    1.176092] dm9000 dm9000.0: wrong id: 0x2b2a2928
[    1.180774] dm9000 dm9000.0: not found (-19).

(3)移植的目標

  • 讓此版本的核心可以打印出正確情況下的啟動資訊。

2、make menuconfig中新增DM9000支援

  • 搜尋/DM9000,找到所在路徑。
  • menuconfig中選擇Y。

3、mach-smdkc110.c中邏輯分析

(1)mach-smdkc110.c中的smdkc110_machine_init是整個開發板的所有硬體的初始化函式

  • 在這裡被載入的硬體,在將來啟動時就會被初始化;
  • 在這裡沒被載入的硬體,在將來啟動時就不管。
(2)smdkc110_devices和smdkc110_dm9000_set()這兩個地方是和DM9000有關的,要分別去做移植。
  • smdkc110_dm9000_set(),是和DM9000相關的SROM bank的暫存器設定;
  • 其相當於uboot中dm9000移植時的dm9000_pre_init函式,只是讀寫暫存器的函式名稱不同了。
  • smdkc110_dm9000_set()函式的更改,直接拷貝九鼎的移植好的。

4、修改相應的配置引數

(1)DM9000相關的資料配置在arch/arm/plat-s5p/devs.c中;


(2)在arch/arm/mach-s5pv210/include/mach/map.h中定義了DM9000的IO基地址,和DM9000接在哪個bank有關。

  • 根據實際情況,將其改為0x8800 0300。

(3)+2改成+4

 截圖是x210的配置,可見裡面是+4,因此下面要改成+4



(4)IRQ_EINT9改成10即可

5、同步、編譯、下載,檢視啟動資訊

六、核心啟動第一階段(彙編階段)的除錯方法

1、除錯的原因

(1)核心啟動在head.S中首先進行三個校驗(CPU id的校驗、機器碼的校驗、tag的校驗),然後建立頁表,然後做了一些不太會出錯的事情,然後b start_kernel。

  • 基本上能執行到start_kernel,核心移植就不太會出問題了。

(2)有時候移植的核心啟動後的現象是:根本沒有啟動資訊出來(下面所述主要針對這個問題)。

  • 有可能是核心啟動運行了,但是執行出錯了沒啟動起來所以沒有列印資訊;
  • 有可能是核心根本沒執行;
  • 希望能有一種除錯手段來確定問題所在

2、除錯方法和原理

(1)除錯方法:在核心啟動的第一階段,新增彙編操作led點亮/熄滅的方法來標明程式碼執行的軌跡。

(2)將led點亮和熄滅的程式碼,複製貼上到head.S中合適位置,然後核心啟動後根據led的表現來標明程式碼有無執行。

3、動手測試

(1)在head.S中合適的地方(比如函式集中域處)新增(定義)led這個函式,然後在head.S的核心起始執行階段新增呼叫led函式。然後重新編譯核心,執行核心看這段程式碼有無被執行。

(3)分析思路

  • 如果被執行,證明在呼叫led的步驟之前的部分都是沒問題的,如果有錯,錯誤肯定在後邊;
  • 如果沒有被執行則證明錯誤在之前,那麼就要去之前的部分debug。

4、典型led函式

//移植核心的led除錯方法
led:
	// 第一步:把0x11111111寫入0xE0200240(GPJ0CON)位置
	ldr r3, =0x11111111			// 從後面的=可以看出用的是ldr偽指令,因為需要編譯器來判斷這個數
	ldr r4, =0xE0200240			// 是合法立即數還是非法立即數。一般寫程式碼都用ldr偽指令
	str r3, [r4]				// 暫存器間接定址。功能是把r0中的數寫入到r1中的數為地址的記憶體中去

	// 第二步:把0xff寫入0xE0200244(GPJ0DAT)位置
	ldr r3, =0xff               //開發板啟動時,led半亮,這裡賦值為ff,則三顆全滅了,一顆半亮。
	ldr r4, =0xE0200244
	str r3, [r4]				// 把ff寫入到GPJ0DAT暫存器中,引腳即輸出高電平,LED熄滅
        mov pc,lr          //函式一定要返回
//r0,r1,r2在head.s中,是用來給核心傳參的,類似於全域性變數。
//因此不能用這些暫存器,我們可以使用那些沒有被使用的暫存器。
//因此可以r0改為r3,r1改為r4