移植Linux(ucLinux)到GBA的記錄

移植Linux(ucLinux)到GBA的記錄 v1.0
[email protected]


1.感謝
向所有直接和間接幫助過我的的朋友們表示衷心的感謝,因爲人數太多我就不一一提到了。



2.寫在最前面
《移植uclinux到GBA的記錄 v0.0》可以在下面的地址找到。
http://www.linuxforum.net/forum/showthreaded.php?Cat=&Board=embedded&Number=489216

上次以後這個東西一直就放下了,突然某天(上個月吧)想到,既然Kernel都是直接在flash上跑的,文件系統還放在blkmem上幹什麼,白白浪費寶貴內存,於是自己做了一個只讀文件系統norfs,弄起來一跑,仍然不行,還是在run_init_process內存不夠了。我正在鬱悶,突然想到mmnommu中有一個page_alloc2.c,是另一套頁管理的代碼,於是偶將其換上一跑,一下就跑了過去,果然厲害。又改了exec的一點錯誤,現在已經把init跑起來了,當然一跑init裏面的swi就會讓模擬器報錯,忽略過去的話就能看見init在屏幕上顯示出了顏色。再往後的工作就是對libc的修改了,不過我可能暫時不會再弄了。

本修改都以Linux kernel 2.4.24+uclinux針對其的補丁爲基礎做的。
在把補丁打好後,將我提供的linux-2.4.24中的文件覆蓋進目錄。然後將norfs目錄中的linux-2.4.24目錄中覆蓋Kernel目錄中相應文件就可以。然後make menuconfig,在System Type中設置ARM system type爲GBA,打開General setup中的Support uClinux FLAT format binaries,打開File systems中的NOR file system support。然後make dep,make clean,make,將生成的elf文件linux拷貝到gbarom目錄中。
然後進入norfs目錄中的gennorfs目錄,輸入make,make install,將生成norfs鏡象的gennorfs文件裝進系統中。
最後進入make,則將生成linux.gba,這就是給GBA模擬器使用的2進制文件。



3.GBA的簡單介紹以及其優點和缺點
GBA是一種掌上遊戲機,用的ARM7TDMI,256K內存(有的介紹將其32K內存也算在其中的,這是完全不對的,因爲那些內存是有專門作用的),顯存、聲道、多用串口等等一堆遊戲機需要的東西,rom是nor flash,詳細的介紹網上非常多,我也就不詳細介紹了。
GBA的優點:開發資源豐富。因爲對GBA遊戲開發有興趣的人不少,所以對其介紹的資料相當多,還有不少專門的論壇對其的開發進行討論。同時GBA有多種模擬器可以使用,而且功能都很不錯。
GBA的缺點:有點BT的環境。256K內存是無法將整個KERNEL放入其中的。最主要的問題是GBA將頭16K寫入了他自己的代碼,也就是說GBA自己處理了svc到fiq,其中irq的處理會在GBA自己處理過後跳到指定的代碼(這將在後面做詳細介紹),其他的以我現有的認識都是無法取到的,GBA都用自己的代碼處理掉了。



4.介紹一下我寫的bootloader
GBA的執行是從0x8000000開始的,這也是flash的開始地址,但是實際上從0x8000004一直到0x80000c0以前的部分是有一套格式的,有什麼圖標字符、校驗位等等一大堆東西,不過這些東西我都沒能夠在我的GBA鏡象生成程序creatrom中完成,只是簡單的按照規矩在bootloader中直接從0x8000000跳到了0x80000c0開始執行具體的bootloader代碼。在bootloader中我主要做的工作是將存儲在flash中的init節和data節放到內存中。下面我就來介紹一下bootloader代碼bl.s:
首先將mess的地址設置到r4,這個mess中存儲了init和data在flash中的位置、長度、應在內存中的位置以及text的開始地址,這個部分是在creatrom程序中填寫的。
然後就是先init然後data的拷貝過程,其中具體的拷貝用的是gba_dma3_copy這個函數,這裏用的是GBA的DMA3,GBA共有4個DMA,在實際的kernel中我還沒能將這幾個東西用上。
在2次拷貝結束的時候,有這麼2句:
mov r0,#0
mov r1,#210
這是設置啓動參數,在arch/armnommu/kernel/head-armv.S中是這樣寫的
/*
* Kernel startup entry point.
*
* The rules are:
* r0 - should be 0
* r1 - unique architecture number
* MMU - off
* I-cache - on or off
* D-cache - off
*
* See linux/arch/arm/tools/mach-types for the complete list of numbers
* for r1.
*/
注意這個210是我後加的,具體我將在下面介紹對kernel的修改的時候進行介紹。
設置好啓動參數以後就是將mess中的text開始位置讀出,跳到那個位置開始執行。



5.我對kernel的修改的介紹
我對kernel的修改除了我自己增加的目錄arch/armnommu/mach-gba/和include/asm-armnommu/arch-gba/以外,我都用類似
//teawater add for gba 2004.2.28------------------------------------------------
#ifdef CONFIG_ARCH_GBA
#else
#endif
//AJ2D--------------------------------------------------------------------------
標記了出來。


5.1.編譯相關的修改
arch/armnommu/Makefile,這裏增加了一個GBA相關的條目,主要是設置連接腳本,其他也跟別的項目沒什麼不同,TEXTADDR這個部分本來是在連接腳本用指定kernel開始執行地址的地方,不過在我修改的代碼中沒什麼作用,只是原來臨時用保留下來的。
arch/armnommu/config.in,這裏是menuconfig的時候生成菜單以及進行編譯配置的地方,在這其中:
DRAM_BASE 內存的起始地址
DRAM_SIZE 內存的長度
FLASH_MEM_BASE flash的起始地址
FLASH_SIZE flash的長度
CONFIG_NO_PGT_CACHE 表示Disable pgtable cache
CONFIG_CPU_WITH_CACHE 表示CPU有CACHE 啓動的時候要clean
CONFIG_CPU_WITH_MCR_INSTRUCTION 表示系統是否有MCR指令
arch/armnommu/tools/mach-types,實際上這個文件是用來生成文件include/asm-armnommu/mach-types.h的,不過因爲關聯的問題,修改過這個文件後注意刪除一下mach-types.h這個文件,保證修改的生效。


5.2.連接腳本
我使用了單獨的連接腳本arch/armnommu/mach-gba/romlinux.lds。
在這裏比較主要的就是設置init和data的開始地址設置在了0x2001000,內存開始的部分在0x2000000,而這0x1000是交給kernel讓其寫中斷處理代碼的。而在0x8040d00是text的開始地址。
如果仔細觀察的話你可以發現我將*(.text.init)也放進了text,這樣的目的就是爲了節省內存。
其中有1個地方比較關鍵:
. = ALIGN(4096);
__init_end = .;
因爲init節實際上在init函數中將要被釋放掉的,而且其後跟着的就是init_task_union,所以這裏一定要保證init節的長度跟頁長度對齊,同時要保證這個__init_end生成的地址是跟task的長度對齊。而前面將開始地址設置在0x2001000也是出於這個目的。


5.3.kernel在start_kernel以前的代碼
arch/armnommu/kernel/head-armv.S,這是kernel最開始啓動的代碼,這裏體系相關的代碼很多,通用的啓動部分在448行,也就是我修改後代碼的第464行。
一上來就是函數__lookup_processor_type,這個函數是用來根據芯片的類型ID從.proc.info中取出相應的proc_info_list結構,這個結構定義在include/asm-armnommu/procinfo.h文件中,這個段中的數據是arch/armnommu/mm/proc-*.S文件中的,如果你的芯片比較特殊,可以定義自己的芯片結構以及編寫自己的文件,arm7tdmi在arch/armnommu/mm/proc-arm6,7.S文件中。這個函數中,我們可以看到在arch/armnommu/config.in中定義的CONFIG_CPU_WITH_MCR_INSTRUCTION,我也嘗試跑過mrc指令取得芯片ID,不過代碼馬上就跑到了未定義指令上去了,所以我估計用這個是不行的,就定義了不支持mcr。在下面也寫明瞭
# warning "FIXME: Get Processor ID without MCR Instruction"
還舉出了使用的例子
@ A possible code
@ldr r9, PROCESSOR_ID_MEM_LOCATION
@ldr r9, [r9]
看一下arch/armnommu/mm/proc-arm6,7.S文件,就可以找到定義arm7tdmi的結構的是729行開始的,所以代碼應該寫成
ldr r9, __arm7tdmi_proc_info
ldr r9, [r9]
不過我開始是沒仔細看這段註釋的,直接就
@set r9 to arm7tdmi id(0x41007700)
mov r9,#0x7700
add r9,r9,#0x41000000
結果都是一樣的,當然建議大家按uclinux提供的方式來弄比較好,我嘛,以後再改。
然後就是一大段根據這個ID比較的過程,最後返回,一些查找結構失敗的判斷,然後就是函數__lookup_architecture_type。
__lookup_architecture_type這個函數的作用是在.arch.info段中尋找相應的machine_desc結構,這個結構定義在include/asm-armnommu/mach/arch.h中,具體針對每個arch的定義就在arch/armnommu/kernel/arch.c以及arch/armnommu/mach-*/arch.c中,而GBA的就在arch/armnommu/mach-gba/arch.c中。其中具體的每個宏都在include/asm-armnommu/mach/arch.h中,都很明確我就不詳細介紹了。
往後還有一些初始化工作,不過因爲跟沒有什麼改動,就不詳細介紹了。


5.3.start_kernel

5.3.1.setup_arch
這個函數用來做體系相關的初始化的,armnommu的在arch/armnommu/kernel/setup.c。注意一下540行,這裏是默認的對物理內存結構meminfo的初始化,這個結構將在後面的內存初始化中起很重要的作用。在這其中,nr_banks指定了內存塊的數量,bank是指定了每塊內存的範圍。而在這裏用來指定塊開始和長度的PAGE_OFFSET和MEM_SIZE,都定義在include/asm-armnommu/arch-gba/memory.h中,PAGE_OFFSET是內存的開始地址,我設置這個值爲0x2001000,就是前面介紹的init和data的開始地址,結束地址自然就是內存的結束地址。後面函數就將根據meminfo進行內存結構初始化。
其中還有一個地方的修改,bootmem_init函數中調用的函數reserve_node_zero,這個函數的作用是保留一些內存以便使值不能被動態分配。看一下這個函數的內部,原來的代碼是:
reserve_bootmem_node(pgdat, __pa(&_stext), &_end - &_stext);
這行的意思就是從_stext到_end都保留起來不做動態分配,而看我寫的連接腳本就能知道這麼寫是不行的,因爲_stext在flash中,所以我將這段代碼改爲:
reserve_bootmem_node(pgdat, __pa(&__init_begin), &_end - &__init_begin);
後面對init_arch_irq進行了初始化,這個將在後面用到。

5.3.2.parse_options和calibrate_delay
我的代碼跑到這2行以後進去就跑不出來了,正好看一個文章寫有什麼問題,而且2個函數也沒什麼作用,所以我就直接將其去掉了。

5.3.3.trap_init
這個函數用來做體系相關的中斷處理的初始化,armnommu的在arch/armnommu/kernel/traps.c。vectors_base的值是比較關鍵的,因爲其的寫中斷向量的開始地址,他的設置在include/asm-armnommu/proc-armv/system.h,地址0因爲GBA的BIOS的原因自然是寫不進去的,所以我將其設置爲0x2000000。然後就調用arch/armnommu/kernel/entry-armv.S中的函數__trap_init將中斷處理代碼寫入vectors_base指定地址。

5.3.4.init_IRQ
這個函數用來做體系相關的irq處理的初始化,armnommu的在arch/armnommu/kernel/irq.c。一開始就是對根據定義在include/asm-armnommu/arch-gba/irqs.h中斷數量NR_IRQS對中斷結構irq_desc的初始化。
在默認的初始化完成後調用前面初始化的函數init_arch_irq,先到了arch/armnommu/kernel/irq-arch.c中的函數genarch_init_irq,然後就執行include/asm-armnommu/arch-gba/irq.h中的inline函數irq_init_irq,在這裏對irq_desc進行了實質的初始化。其中mask用阻塞中斷;unmask用來取消阻塞;mask_ack的作用是第一是阻塞中斷,同時還發ack給板表示這個中斷已經被處理了,否則板將會自動再次發生同一個中斷,當然不是所有板需要這個ack,所以有的扳子的mask_ack跟mask用的是一個函數。後面3行語句,第1行設置對irq全部mask,第2行是對irq的另一個標誌設置爲irq可以發生,第3行是設置irq向量處理的位置,GBA將在接到irq自己處理後跳到這裏指定的位置執行。
下面執行的函數是init_dma,前面我已經介紹過我沒有在kernel使用GBA的DMA,所以就設置include/asm-armnommu/arch-gba/dma.h中的MAX_DMA_CHANNELS爲0,這樣在arch/armnommu/kernel/dma.c文件中你可以看到根據這個定義的不同會使用不同的函數。

5.3.5.time_init
這個函數用來做體系相關的timer的初始化,armnommu的在arch/armnommu/kernel/time.c。這裏調用了在include/asm-armnommu/arch-gba/time.h中的inline函數setup_timer,首先設置timer的中斷處理函數爲gba_timer_interrupt,然後設置已經初始化好的結構timer_irq用setup_arm_irq爲GBA的timer3的中斷處理結構。
然後調用gba_enable_timer3啓動timer3,其中LATCH是每個時鐘中斷用的時鐘滴答的次數,這個值定義在include/linux/timex.h中:
#define LATCH ((CLOCK_TICK_RATE + HZ/2) / HZ)
其中CLOCK_TICK_RATE是定義在include/asm-armnommu/arch-gba/timex.h中,這個值是輸入時鐘脈衝的頻率。

5.3.6.fork_init
因爲GBA的內存太小,計算出的max_threads數量太小,所以我將其改成我定義的GBA_MAX_THREADS。

5.3.7.kernel_thread
這裏調用了arch/armnommu/kernel/process.c中的函數arch_kernel_thread,在這個函數中kernel第一次使用了swi指令,在:
__syscall(clone)
我將其換成了
bl gba_sys_clone
gba_sys_clone這個函數定義在arch/armnommu/mach-gba/entry_gba.S中,sys_clone函數的最後一個參數是struct pt_regs *regs,這是一個寄存器的列表,原來是在swi的處理過程中存儲進棧的,然後在返回的時候再將寄存器恢復成原來的值,在gba_sys_clone中模仿了這個過程。gba_save_user_regs是前面定義的宏,是模仿save_user_regs寫的,作用就是將寄存器存入棧。然後將sp值放進r2,也就是作爲sys_clone函數的最後一個參數。函數sys_clone執行結束後,將r0的值放入寄存器列表中r0的位置,這樣做是保證函數的返回值最後可以返回,最後 用宏gba_restore_user_regs將寄存器值從棧中恢復到寄存器中。
其實如果是一般的系統調用,直接bl到相應函數就可以了,只是因爲sys_clone函數還需要一個寄存器列表的函數,所以使用的上面的方法,類似的還有下面用到的sys_execve,只是


5.4.init
5.4.1.do_basic_setup
在這裏的sock_init我覺得佔內存太多,所以就把這個函數的執行去掉了。

5.4.2.blkmem相關的修改
下面這段在使用norfs的時候不需要,不過我還是保留了下來。
我將include/linux/blkdev.h中的MAX_NR_REQUESTS從1024換成了16,在blkmem_init中,調用了blk_init_queue,這裏調用了blk_init_free_list,這裏調用blk_grow_request_list,在這個函數中會kmem_cache_alloc出nr_requests個request結構,而nr_requests的值爲MAX_NR_REQUESTS,如果是1024個request結構則將用掉過多的內存,實際GBA的全部內存也不夠,所以我將其替換成了16。
在include/asm-armnommu/arch-gba/blkmem.h中的一行
#define FIXUP_ARENAS arena[0].address = (unsigned long)0x8240d1c;
其中0x8240d1c就是romfs在flash中的地址。

5.4.3.free_initmem
這個函數在arch/armnommu/mm/init.c中,就是前面提到的對init節的釋放,如果你的體系不想釋放init,可以用其中其他體系類似的方式指定不釋放。

5.4.4.run_init_process
在這其中有一個execve,我將其換成了gba_sys_execve,基本過程跟上面的gba_sys_clone過程類似。不同的地方在sys_execve返回後,先判斷r0的值是否大於等於0;如果不是則表示sys_execve的執行發生了錯誤,則將r0的值設置進積存器列表中;如果是則設置USR_MODE進寄存器列表,可能你要奇怪我爲什麼這麼做了,因爲寄存器列表應該已經在sys_execve被設置好了的,我這麼做的原因是這樣的:
先看一下實際對寄存器列表進行設置的宏start_thread
#define start_thread(regs,pc,sp) /
({ /
unsigned long *stack = (unsigned long *)sp; /
set_fs(USER_DS); /
memzero(regs->uregs, sizeof(regs->uregs)); /
if (current->personality & ADDR_LIMIT_32BIT) /
regs->ARM_cpsr = USR_MODE; /
else /
regs->ARM_cpsr = USR26_MODE; /
regs->ARM_pc = pc; /* pc */ /
regs->ARM_sp = sp; /* sp */ /
regs->ARM_r2 = stack[2]; /* r2 (envp) */ /
regs->ARM_r1 = stack[1]; /* r1 (argv) */ /
regs->ARM_r0 = stack[0]; /* r0 (argc) */ /
})
可以很清楚的看到,依賴了current->personality纔將cpsr設置爲32位user模式,不然就是26位user模式。
而personality來自哪裏呢,繼承自init_task,但是在其的初始化宏INIT_TASK並沒這個元素,應該是被設置爲了0,這樣最後被設成了 USR26_MODE,我覺得有點莫名其妙。
然後我試圖用一種通用的辦法解決這個問題:
發現在 CONFIG_ARCH_C5471 中有一個
static void __init
fixup_c5471(struct machine_desc *desc, struct param_struct *params,
char **cmdline, struct meminfo *mi)
{
/* This is probably not necessary for the init process. But, all
* normal C5471 processes must be marked as 32-bit processes. */

init_task_union.task.personality = PER_LINUX_32BIT;
}
俺一看馬上模仿了寫了一個,一跑還是不行,開始還都一直PER_LINUX_32BIT跑的滿好,可最後到了binfmt_flat.c中,load_flat_file函數中有一句,set_personality(PER_LINUX); 被清成0了,偶延着這個set_personality走了一下,發現被設置成0是肯定的。
這下偶就暈了,別人的代碼應該是跑的好好的,可爲什麼到我這裏就不行了呢?只好在最後做了個不久工作,如果哪位知道指點一下我,多謝了。


5.5.irq的處理
剛纔介紹了irq的初始化,這裏來介紹發生irq後的處理。
發生中斷以後,首先是GBA的BIOS對中斷進行處理,然後到咱們前面在irq初始化時候指定的地址繼續執行,那些代碼在arch/armnommu/kernel/entry-armv.S中的第1360行開始也就是我修改過代碼的1382行開始。在一上來我就增加了一行:
ldmia r13!,{r0-r3,r12,r14}
因爲GBA的BIOS會將這幾個寄存器存入棧,爲了讓這些寄存器保持一個irq才發生時候的值。而在後面設置r13爲0x3007fa0,這樣BIOS處理irq的時候的棧指針,這樣可以保證下次再發生irq以後BIOS可以做正常處理。
這裏的處理結束後就會根據當前進程所處的情況分別執行__irq_usr或者__irq_svc,在這其中get_irqnr_and_base就是取得當前中斷的體系相關的取得當前中斷號的宏。這個宏的幾個參數代表的意思爲:
irqnr: 中斷號碼,也就是取得返回的中斷號碼。
irqstat:用來存儲irq當前狀態的臨時寄存器。
base:中斷優先級別,不過在這裏好象沒起實際作用,所以我也不太瞭解具體做什麼的。
tmp:給宏中使用的臨時寄存器。
宏中整個的過程就是將GBA_IRQ_STAT位置中的中斷狀況,然後循環比較,最後將返回。


5.6.對page和task大小的修改
因爲GBA內存的問題,實際還沒運行到init函數內存就不夠了,我採取的解決辦法是將page和task改小。
首先我將include/asm-armnommu/proc-armv/page.h中頁的大小改小。
然後我修改了include/asm-armnommu/processor.h中的THREAD_SIZE的頁大小改小。不過在代碼中實際幾乎沒有人使用THREAD_SIZE,而都用了8192,所以我都將他們改成了THREAD_SIZE,包括一些別的體系的代碼,個人還是希望kernel中的東西多用宏,方便查看也能方便修改。還有修改了include/asm-armnommu/proc-armv/processor.h中的ll_alloc_task_struct宏和ll_free_task_struct宏,這兩個宏是在do_fork的時候alloc_task_struct調用的,用來分配task的空間,既然同時改變了page和task的大小,自然也要修改。
還有就是要修改根據sp取得task開始地址的函數,這裏一共有2個。一個是include/asm-armnommu/current.h中的inline函數get_current,主要是將sp的值最後的幾位設置爲0,所以這也是前面一直要內存對齊的原因。還有一個是arch/armnommu/kernel/entry-header.S中的宏get_current_task,剛纔那個函數是給c代碼用的,而這個是給asm代碼用的。它的作用跟剛纔那個函數一樣,也是將最後幾位設置爲0,不過他使用了移位的方法,而前面的函數使用了位清除,這2段代碼的作用是一樣的,希望對這裏有理解的朋友指點這樣使用各自的好處。


5.7.關於大量使用宏ifdef的時候需要注意的東西
移植的代碼爲了保證能不影響別的體系代碼的使用,我大量的使用了
#ifdef CONFIG_ARCH_GBA
#else
#endif
但是如果前面沒有
#include <linux/config.h>
則將導致不管定義了什麼選項,編譯中只使用#else後面的部分,這自然是要發生錯誤的,我使用的檢查方法是,在打開了GBA選項以後,在#else後定義一個#error,這樣在發生定義錯誤的時候編譯馬上會終止,這樣就能找到需要增加#include <linux/config.h>的位置,在include/asm-armnommu/proc-armv/page.h中就是這樣的情況。


5.8.爲了讓Kernel支持norfs作爲根文件系統對Kernel進行的修改
在do_mounts.c文件中的mount_block_root函數是用來mount根分區的函數,但是因爲norfs不是FS_REQUIRES_DEV類型的文件系統,所以不會在其循環mount中出現,我爲了方便在其全部失敗後的位置增加了單獨的mount norfs的代碼。 
移植uclinux到GBA的記錄 v0.0
[email protected]


1.感謝
向所有直接和間接幫助過我的的朋友們表示衷心的感謝,因爲人數太多我就不一一提到了。

 

2.寫在最前面
本來是計劃一直等到應用層的init跑起來以後才總結這個文章的(現在到了run_init_process,不過好象因爲內存不夠還是沒跑起來),可是因爲拖的時間比較長了(我自己記錄的開始時間是2004.2.28),前幾天整理代碼的時候也發覺不少修改是怎麼個事情都忘記了,所以本着好腦子不如爛筆頭的精神,把大部分過程以及其中一些體會想法都記錄下來,望大家多多指點。
本修改都以Linux kernel 2.4.24+uclinux針對其的補丁爲基礎做的。在把補丁打好後,將我提供的linux-2.4.24.zip解壓縮,將解出的文件覆蓋進目錄,然後make menuconfig,在System Type中設置ARM system type爲GBA,打開General setup中的Support uClinux FLAT format binaries,打開Block devices中的ROM disk memory block device (blkmem),打開File systems中的ROM file system support。然後make dep,make clean,make。將生成的elf文件linux拷貝到gbarom目錄中,然後make,則將生成linux.gba,這就是給GBA模擬器使用的2進制文件。

 

3.GBA的簡單介紹以及其優點和缺點
GBA是一種掌上遊戲機,用的ARM7TDMI,256K內存(有的介紹將其32K內存也算在其中的,這是完全不對的,因爲那些內存是有專門作用的),顯存、聲道、多用串口等等一堆遊戲機需要的東西,rom是nor flash,詳細的介紹網上非常多,我也就不詳細介紹了。
GBA的優點:開發資源豐富。因爲對GBA遊戲開發有興趣的人不少,所以對其介紹的資料相當多,還有不少專門的論壇對其的開發進行討論。同時GBA有多種模擬器可以使用,而且功能都很不錯。
GBA的缺點:有點BT的環境。256K內存是無法將整個KERNEL放入其中的。最主要的問題是GBA將頭16K寫入了他自己的代碼,也就是說GBA自己處理了svc到fiq,其中irq的處理會在GBA自己處理過後跳到指定的代碼(這將在後面做詳細介紹),其他的以我現有的認識都是無法取到的,GBA都用自己的代碼處理掉了。

 

4.介紹一下我寫的bootloader
GBA的執行是從0x8000000開始的,這也是flash的開始地址,但是實際上從0x8000004一直到0x80000c0以前的部分是有一套格式的,有什麼圖標字符、校驗位等等一大堆東西,不過這些東西我都沒能夠在我的GBA鏡象生成程序creatrom中完成,只是簡單的按照規矩在bootloader中直接從0x8000000跳到了0x80000c0開始執行具體的bootloader代碼。在bootloader中我主要做的工作是將存儲在flash中的init節和data節放到內存中。下面我就來介紹一下bootloader代碼bl.s:
首先將mess的地址設置到r4,這個mess中存儲了init和data在flash中的位置、長度、應在內存中的位置以及text的開始地址,這個部分是在creatrom程序中填寫的。
然後就是先init然後data的拷貝過程,其中具體的拷貝用的是gba_dma3_copy這個函數,這裏用的是GBA的DMA3,GBA共有4個DMA,在實際的kernel中我還沒能將這幾個東西用上。
在2次拷貝結束的時候,有這麼2句:
mov r0,#0
mov r1,#210
這是設置啓動參數,在arch/armnommu/kernel/head-armv.S中是這樣寫的
/*
* Kernel startup entry point.
*
* The rules are:
* r0 - should be 0
* r1 - unique architecture number
* MMU - off
* I-cache - on or off
* D-cache - off
*
* See linux/arch/arm/tools/mach-types for the complete list of numbers
* for r1.
*/
注意這個210是我後加的,具體我將在下面介紹對kernel的修改的時候進行介紹。
設置好啓動參數以後就是將mess中的text開始位置讀出,跳到那個位置開始執行。

 

5.我對kernel的修改的介紹
我對kernel的修改除了我自己增加的目錄arch/armnommu/mach-gba/和include/asm-armnommu/arch-gba/以外,我都用類似
//teawater add for gba 2004.2.28------------------------------------------------
#ifdef CONFIG_ARCH_GBA
#else
#endif
//AJ2D--------------------------------------------------------------------------
標記了出來。


5.1.編譯相關的修改
arch/armnommu/Makefile,這裏增加了一個GBA相關的條目,主要是設置連接腳本,其他也跟別的項目沒什麼不同,TEXTADDR這個部分本來是在連接腳本用指定kernel開始執行地址的地方,不過在我修改的代碼中沒什麼作用,只是原來臨時用保留下來的。
arch/armnommu/config.in,這裏是menuconfig的時候生成菜單以及進行編譯配置的地方,在這其中:
DRAM_BASE 內存的起始地址
DRAM_SIZE 內存的長度
FLASH_MEM_BASE flash的起始地址
FLASH_SIZE flash的長度
CONFIG_NO_PGT_CACHE 表示Disable pgtable cache
CONFIG_CPU_WITH_CACHE 表示CPU有CACHE 啓動的時候要clean
CONFIG_CPU_WITH_MCR_INSTRUCTION 表示系統是否有MCR指令
arch/armnommu/tools/mach-types,實際上這個文件是用來生成文件include/asm-armnommu/mach-types.h的,不過因爲關聯的問題,修改過這個文件後注意刪除一下mach-types.h這個文件,保證修改的生效。


5.2.連接腳本
我使用了單獨的連接腳本arch/armnommu/mach-gba/romlinux.lds。
在這裏比較主要的就是設置init和data的開始地址設置在了0x2001000,內存開始的部分在0x2000000,而這0x1000是交給kernel讓其寫中斷處理代碼的。而在0x8040d00是text的開始地址。
如果仔細觀察的話你可以發現我將*(.text.init)也放進了text,這樣的目的就是爲了節省內存。
其中有1個地方比較關鍵:
. = ALIGN(4096);
__init_end = .;
因爲init節實際上在init函數中將要被釋放掉的,而且其後跟着的就是init_task_union,所以這裏一定要保證init節的長度跟頁長度對齊,同時要保證這個__init_end生成的地址是跟task的長度對齊。而前面將開始地址設置在0x2001000也是出於這個目的。


5.3.kernel在start_kernel以前的代碼
arch/armnommu/kernel/head-armv.S,這是kernel最開始啓動的代碼,這裏體系相關的代碼很多,通用的啓動部分在448行,也就是我修改後代碼的第464行。
一上來就是函數__lookup_processor_type,這個函數是用來根據芯片的類型ID從.proc.info中取出相應的proc_info_list結構,這個結構定義在include/asm-armnommu/procinfo.h文件中,這個段中的數據是arch/armnommu/mm/proc-*.S文件中的,如果你的芯片比較特殊,可以定義自己的芯片結構以及編寫自己的文件,arm7tdmi在arch/armnommu/mm/proc-arm6,7.S文件中。這個函數中,我們可以看到在arch/armnommu/config.in中定義的CONFIG_CPU_WITH_MCR_INSTRUCTION,我也嘗試跑過mrc指令取得芯片ID,不過代碼馬上就跑到了未定義指令上去了,所以我估計用這個是不行的,就定義了不支持mcr。在下面也寫明瞭
# warning "FIXME: Get Processor ID without MCR Instruction"
還舉出了使用的例子
@ A possible code
@ldr r9, PROCESSOR_ID_MEM_LOCATION
@ldr r9, [r9]
看一下arch/armnommu/mm/proc-arm6,7.S文件,就可以找到定義arm7tdmi的結構的是729行開始的,所以代碼應該寫成
ldr r9, __arm7tdmi_proc_info
ldr r9, [r9]
不過我開始是沒仔細看這段註釋的,直接就
@set r9 to arm7tdmi id(0x41007700)
mov r9,#0x7700
add r9,r9,#0x41000000
結果都是一樣的,當然建議大家按uclinux提供的方式來弄比較好,我嘛,以後再改。
然後就是一大段根據這個ID比較的過程,最後返回,一些查找結構失敗的判斷,然後就是函數__lookup_architecture_type。
__lookup_architecture_type這個函數的作用是在.arch.info段中尋找相應的machine_desc結構,這個結構定義在include/asm-armnommu/mach/arch.h中,具體針對每個arch的定義就在arch/armnommu/kernel/arch.c以及arch/armnommu/mach-*/arch.c中,而GBA的就在arch/armnommu/mach-gba/arch.c中。其中具體的每個宏都在include/asm-armnommu/mach/arch.h中,都很明確我就不詳細介紹了。
往後還有一些初始化工作,不過因爲跟沒有什麼改動,就不詳細介紹了。


5.3.start_kernel

5.3.1.setup_arch
這個函數用來做體系相關的初始化的,armnommu的在arch/armnommu/kernel/setup.c。注意一下540行,這裏是默認的對物理內存結構meminfo的初始化,這個結構將在後面的內存初始化中起很重要的作用。在這其中,nr_banks指定了內存塊的數量,bank是指定了每塊內存的範圍。而在這裏用來指定塊開始和長度的PAGE_OFFSET和MEM_SIZE,都定義在include/asm-armnommu/arch-gba/memory.h中,PAGE_OFFSET是內存的開始地址,我設置這個值爲0x2001000,就是前面介紹的init和data的開始地址,結束地址自然就是內存的結束地址。後面函數就將根據meminfo進行內存結構初始化。
其中還有一個地方的修改,bootmem_init函數中調用的函數reserve_node_zero,這個函數的作用是保留一些內存以便使值不能被動態分配。看一下這個函數的內部,原來的代碼是:
reserve_bootmem_node(pgdat, __pa(&_stext), &_end - &_stext);
這行的意思就是從_stext到_end都保留起來不做動態分配,而看我寫的連接腳本就能知道這麼寫是不行的,因爲_stext在flash中,所以我將這段代碼改爲:
reserve_bootmem_node(pgdat, __pa(&__init_begin), &_end - &__init_begin);
後面對init_arch_irq進行了初始化,這個將在後面用到。

5.3.2.parse_options和calibrate_delay
我的代碼跑到這2行以後進去就跑不出來了,正好看一個文章寫有什麼問題,而且2個函數也沒什麼作用,所以我就直接將其去掉了。

5.3.3.trap_init
這個函數用來做體系相關的中斷處理的初始化,armnommu的在arch/armnommu/kernel/traps.c。vectors_base的值是比較關鍵的,因爲其的寫中斷向量的開始地址,他的設置在include/asm-armnommu/proc-armv/system.h,地址0因爲GBA的BIOS的原因自然是寫不進去的,所以我將其設置爲0x2000000。然後就調用arch/armnommu/kernel/entry-armv.S中的函數__trap_init將中斷處理代碼寫入vectors_base指定地址。

5.3.4.init_IRQ
這個函數用來做體系相關的irq處理的初始化,armnommu的在arch/armnommu/kernel/irq.c。一開始就是對根據定義在include/asm-armnommu/arch-gba/irqs.h中斷數量NR_IRQS對中斷結構irq_desc的初始化。
在默認的初始化完成後調用前面初始化的函數init_arch_irq,先到了arch/armnommu/kernel/irq-arch.c中的函數genarch_init_irq,然後就執行include/asm-armnommu/arch-gba/irq.h中的inline函數irq_init_irq,在這裏對irq_desc進行了實質的初始化。其中mask用阻塞中斷;unmask用來取消阻塞;mask_ack的作用是第一是阻塞中斷,同時還發ack給板表示這個中斷已經被處理了,否則板將會自動再次發生同一個中斷,當然不是所有板需要這個ack,所以有的扳子的mask_ack跟mask用的是一個函數。後面3行語句,第1行設置對irq全部mask,第2行是對irq的另一個標誌設置爲irq可以發生,第3行是設置irq向量處理的位置,GBA將在接到irq自己處理後跳到這裏指定的位置執行。
下面執行的函數是init_dma,前面我已經介紹過我沒有在kernel使用GBA的DMA,所以就設置include/asm-armnommu/arch-gba/dma.h中的MAX_DMA_CHANNELS爲0,這樣在arch/armnommu/kernel/dma.c文件中你可以看到根據這個定義的不同會使用不同的函數。

5.3.5.time_init
這個函數用來做體系相關的timer的初始化,armnommu的在arch/armnommu/kernel/time.c。這裏調用了在include/asm-armnommu/arch-gba/time.h中的inline函數setup_timer,首先設置timer的中斷處理函數爲gba_timer_interrupt,然後設置已經初始化好的結構timer_irq用setup_arm_irq爲GBA的timer3的中斷處理結構。
然後調用gba_enable_timer3啓動timer3,其中LATCH是每個時鐘中斷用的時鐘滴答的次數,這個值定義在include/linux/timex.h中:
#define LATCH ((CLOCK_TICK_RATE + HZ/2) / HZ)
其中CLOCK_TICK_RATE是定義在include/asm-armnommu/arch-gba/timex.h中,這個值是輸入時鐘脈衝的頻率。

5.3.6.fork_init
因爲GBA的內存太小,計算出的max_threads數量太小,所以我將其改成我定義的GBA_MAX_THREADS。

5.3.7.kernel_thread
這裏調用了arch/armnommu/kernel/process.c中的函數arch_kernel_thread,在這個函數中kernel第一次使用了swi指令,在:
__syscall(clone)
我將其換成了
bl gba_sys_clone
gba_sys_clone這個函數定義在arch/armnommu/mach-gba/entry_gba.S中,sys_clone函數的最後一個參數是struct pt_regs *regs,這是一個寄存器的列表,原來是在swi的處理過程中存儲進棧的,然後在返回的時候再將寄存器恢復成原來的值,在gba_sys_clone中模仿了這個過程。gba_save_user_regs是前面定義的宏,是模仿save_user_regs寫的,作用就是將寄存器存入棧。然後將sp值放進r2,也就是作爲sys_clone函數的最後一個參數。函數sys_clone執行結束後,用宏gba_restore_user_regs將寄存器值從棧中恢復到寄存器中。


5.4.init
5.4.1.do_basic_setup
在這裏的sock_init我覺得佔內存太多,所以就把這個函數的執行去掉了。

5.4.2.blkmem相關的修改
我將include/linux/blkdev.h中的MAX_NR_REQUESTS從1024換成了16,在blkmem_init中,調用了blk_init_queue,這裏調用了blk_init_free_list,這裏調用blk_grow_request_list,在這個函數中會kmem_cache_alloc出nr_requests個request結構,而nr_requests的值爲MAX_NR_REQUESTS,如果是1024個request結構則將用掉過多的內存,實際GBA的全部內存也不夠,所以我將其替換成了16。
在include/asm-armnommu/arch-gba/blkmem.h中的一行
#define FIXUP_ARENAS arena[0].address = (unsigned long)0x8240d1c;
其中0x8240d1c就是romfs在flash中的地址。

5.4.3.free_initmem
這個函數在arch/armnommu/mm/init.c中,就是前面提到的對init節的釋放,如果你的體系不想釋放init,可以用其中其他體系類似的方式指定不釋放。

5.4.4.run_init_process
在這其中有一個execve,我將其換成了gba_sys_execve,基本過程跟上面的gba_sys_clone過程類似,所以就不詳細介紹了。


5.5.irq的處理
剛纔介紹了irq的初始化,這裏來介紹發生irq後的處理。
發生中斷以後,首先是GBA的BIOS對中斷進行處理,然後到咱們前面在irq初始化時候指定的地址繼續執行,那些代碼在arch/armnommu/kernel/entry-armv.S中的第1360行開始也就是我修改過代碼的1382行開始。在一上來我就增加了一行:
ldmia r13!,{r0-r3,r12,r14}
因爲GBA的BIOS會將這幾個寄存器存入棧,爲了讓這些寄存器保持一個irq才發生時候的值。而在後面設置r13爲0x3007fa0,這樣BIOS處理irq的時候的棧指針,這樣可以保證下次再發生irq以後BIOS可以做正常處理。
這裏的處理結束後就會根據當前進程所處的情況分別執行__irq_usr或者__irq_svc,在這其中get_irqnr_and_base就是取得當前中斷的體系相關的取得當前中斷號的宏。這個宏的幾個參數代表的意思爲:
irqnr: 中斷號碼,也就是取得返回的中斷號碼。
irqstat:用來存儲irq當前狀態的臨時寄存器。
base:中斷優先級別,不過在這裏好象沒起實際作用,所以我也不太瞭解具體做什麼的。
tmp:給宏中使用的臨時寄存器。
宏中整個的過程就是將GBA_IRQ_STAT位置中的中斷狀況,然後循環比較,最後將返回。


5.6.對page和task大小的修改
因爲GBA內存的問題,實際還沒運行到init函數內存就不夠了,我採取的解決辦法是將page和task改小。
首先我將include/asm-armnommu/proc-armv/page.h中頁的大小改小。
然後我修改了include/asm-armnommu/processor.h中的THREAD_SIZE的頁大小改小。不過在代碼中實際幾乎沒有人使用THREAD_SIZE,而都用了8192,所以我都將他們改成了THREAD_SIZE,包括一些別的體系的代碼,個人還是希望kernel中的東西多用宏,方便查看也能方便修改。還有修改了include/asm-armnommu/proc-armv/processor.h中的ll_alloc_task_struct宏和ll_free_task_struct宏,這兩個宏是在do_fork的時候alloc_task_struct調用的,用來分配task的空間,既然同時改變了page和task的大小,自然也要修改。
還有就是要修改根據sp取得task開始地址的函數,這裏一共有2個。一個是include/asm-armnommu/current.h中的inline函數get_current,主要是將sp的值最後的幾位設置爲0,所以這也是前面一直要內存對齊的原因。還有一個是arch/armnommu/kernel/entry-header.S中的宏get_current_task,剛纔那個函數是給c代碼用的,而這個是給asm代碼用的。它的作用跟剛纔那個函數一樣,也是將最後幾位設置爲0,不過他使用了移位的方法,而前面的函數使用了位清除,這2段代碼的作用是一樣的,希望對這裏有理解的朋友指點這樣使用各自的好處。


5.7.關於大量使用宏ifdef的時候需要注意的東西
移植的代碼爲了保證能不影響別的體系代碼的使用,我大量的使用了
#ifdef CONFIG_ARCH_GBA
#else
#endif
但是如果前面沒有
#include <linux/config.h>
則將導致不管定義了什麼選項,編譯中只使用#else後面的部分,這自然是要發生錯誤的,我使用的檢查方法是,在打開了GBA選項以後,在#else後定義一個#error,這樣在發生定義錯誤的時候編譯馬上會終止,這樣就能找到需要增加#include <linux/config.h>的位置,在include/asm-armnommu/proc-armv/page.h中就是這樣的情況。

 

 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章