從init/main.c中的main函數開始,終於步入C語言的世界了。Main函數總共有8步(8 steps),先看看程式碼:
int main(int argc, char *argv[])
{
int ret;
GPFDAT = 0x10;
/* NB: MMU off state */
/*
/*
* Step 1:
* print banner
*/
putstr("\r\n");
putstr(vivi_banner); //vivi_banner是vivi執行開始的顯示資訊,vivi_banner在檔version.c中定義
reset_handler();
/*
* Step 2:
* Board initialize
*/
ret = board_init();
GPFDAT = 0x20;
if (ret) {
putstr("Failed a board_init() procedure\r\n");
error();
}
/*
* Step 3:
* 4G linear mapping, flash mapping
* MMU on
*/
mem_map_init();
mmu_init();
putstr("Succeed memory mapping.\r\n");
/*
* Now, vivi is running on the ram. MMU is enabled.
*
* Step 4:
* dynamic memory can be used in bootloader
*/
/* initialize the heap area*/
ret = heap_init();
if (ret) {
putstr("Failed initailizing heap region\r\n");
error();
}
/* Step 5:
* MTD initialize
* read MTE partition info.
*/
ret = mtd_dev_init();
/* Step 6:
* read bootloader parameter
*/
init_priv_data();
/* Step 7:
* misc treatment
*/
misc();
init_builtin_cmds();
/* Step 8:
* boot or vivi.
*/
boot_or_vivi();
return 0;
}
下面按照上面的步驟逐步來分析一下。
1、Step 1:reset_handler()
reset_handler用於將記憶體清零,代碼在lib/reset_handle.c中。
1 void
2 reset_handler(void)
3 {
4 int pressed;
5 pressed = is_pressed_pw_btn(); /*判斷是硬體重定還是軟體重定*/
6 if (pressed == PWBT_PRESS_LEVEL) {
7 DPRINTK("HARD RESET\r\n");
8 hard_reset_handle(); /*調用clear_mem對SDRAM清0*/
9 } else {
10 DPRINTK("SOFT RESET\r\n");
11 soft_reset_handle(); /*此函數為空*/
12 }
13 }
在Power On後,reset_handler呼叫第8行的hard_reset_handle(),此函數在lib/reset_handle.c中:
[main(int argc, char *argv[]) -> reset_handler() -> hard_reset_handle()]
1 static void
2 hard_reset_handle(void)
3 {
4 #if 0
5 clear_mem((unsigned long)(DRAM_BASE + VIVI_RAM_ABS_POS), \
6 (unsigned long)(DRAM_SIZE - VIVI_RAM_ABS_POS));
7 #endif
/*lib/memory.c,將起始位址為USER_RAM_BASE,長度為USER_RAM_SIZE的記憶體清0*/
8 clear_mem((unsigned long)USER_RAM_BASE, (unsigned long) USER_RAM_SIZE);
9 }
2、Step 2:board_init()
board_init呼叫2個函數用於初始化計時器和設置各GPIO引腳功能,代碼在arch/s3c2440/smdk.c中:
[main(int argc, char *argv[]) > board_init()]
1 int board_init(void)
2 {
3 init_time(); /*arch/s3c2440/proc.c*/
4 set_gpios(); /*arch/s3c2440/smdk.c */
5 return 0;
6 }
init_time() 這個函數對寄存器進行了簡單的操作:
void init_time(void)
{
TCFG0 = (TCFG0_DZONE(0) | TCFG0_PRE1(15) | TCFG0_PRE0(0));
/*s3c2440 data sheet 可以查到*/
/*TCFG0 = 0 | 0xf00 | 0 */
}
寄存器TCFG0由三部分組成,prescaler0,prescaler1,deadzone和reserve四部分,前三部分分別對應
TCFG0_PRE0、TCFG0_PRE1、TCFG0_DZONE,TCFG0_PRE0(0)實際值為0x00,TCFG0_PRE1(15)實際
值為0x0f00,而TCFG0_DZONE(0)實際值為
0x000000。實際中,vivi並未使用計時器,這個函數就可以忽略。set_gpios()用於選擇GPA至GPH埠各引腳的功能及是否使用各引腳
的內部上拉電阻,並設置外部中斷源寄存器EXTINT0-2(vivi中未使用外部中斷)。
1 void set_gpios(void)
2 {
3 GPACON = vGPACON;
4 GPBCON = vGPBCON;
5 GPBUP = vGPBUP;
6 GPCCON = vGPCCON;
7 GPCUP = vGPCUP;
8 GPDCON = vGPDCON;
9 GPDUP = vGPDUP;
10 GPECON = vGPECON;
11 GPEUP = vGPEUP;
12 GPFCON = vGPFCON;
13 GPFUP = vGPFUP;
14 GPGCON = vGPGCON;
15 GPGUP = vGPGUP;
16 GPHCON = vGPHCON;
17 GPHUP = vGPHUP;
18 EXTINT0 = vEXTINT0;
19 EXTINT1 = vEXTINT1;
20 EXTINT2 = vEXTINT2;
21 }
以第三行為例,vGPACON的值為0x007fffff,查找s3c2440用戶手冊可知,該參數將GPACON的23位元全部置1。各位功能需察看s3c2440用戶手冊
3、Step 3:建立頁表和啟動MMU
mem_map_init();
mmu_init();
mem_map_init函數用於建立頁表,vivi使用段式頁表,只需要一級頁表。它調用3個函數,代碼在arch/s3c2440/mmu.c中:
[main(int argc, char *argv[]) > mem_map_init(void)]
1. void mem_map_init(void)
2. {
3. #ifdef CONFIG_S3C2440_NAND_BOOT
4. mem_map_nand_boot();
5. #else
6. mem_map_nor();
7. #endif
8. cache_clean_invalidate();
9. tlb_invalidate();
10. }
第8、9行的兩個函數可以不用管它,他們做的事情在下面的mmu_init函數裏又重複了一遍。對於本開發板,在.config中定義了
CONFIG_S3C2440_NAND_BOOT。mem_map_nand_boot()函數呼叫mem_mapping_linear()函數來最
終完成建立頁表的工作。頁表存放在SDRAM物理位址0x33dfc000開始處,共16K:一個頁表項4位元組,共有4096個頁表項;每個頁表項對應
1M位址空間,共4G。mem_map_init先將4G虛擬位址映射到相同的物理位址上,NCNB(不使用cache,不使用write
buffer)——這樣,對寄存器的操作跟未啟動MMU時是一樣的;再將SDRAM對應的64M空間的頁表項修改為使用cache。
mem_mapping_linear函數的代碼在arch/s3c2440/mmu.c中:
[main(int argc, char *argv[]) > mem_map_init(void) > mem_map_nand_boot( ) > mem_mapping_linear(void)]
1 static inline void mem_mapping_linear(void)
2 {
3 unsigned long pageoffset, sectionNumber;
4 putstr_hex("MMU table base address = 0x", (unsigned long)
mmu_tlb_base);
5 /* 4G 虛擬位址映射到相同的物理位址. not cacacheable, not bufferable */
6 /* mmu_tlb_base = 0x33dfc000*/
7 for (sectionNumber = 0; sectionNumber < 4096; sectionNumber++) {
8 pageoffset = (sectionNumber << 20);
9 *(mmu_tlb_base + (pageoffset >> 20)) = pageoffset |
MMU_SECDESC;
10 }
11 /* make dram cacheable */
12 /* SDRAM物理位址0x3000000-0x33ffffff,
13 DRAM_BASE=0x30000000,DRAM_SIZE=64M
14 */
15 for (pageoffset = DRAM_BASE; pageoffset < (DRAM_BASE+DRAM_SIZE); \
16 pageoffset += SZ_1M) {
17 //DPRINTK(3, "Make DRAM section cacheable: 0x%08lx\n", pageoffset);
18 *(mmu_tlb_base + (pageoffset >> 20)) = \
pageoffset | MMU_SECDESC | MMU_CACHEABLE;
19 }
20 }
mmu_init()函數用於啟動MMU,它直接調用arm920_setup()函數。arm920_setup()的代碼在arch/s3c2410/mmu.c中:
[main(int argc, char *argv[]) > mmu_init( ) > arm920_setup( )]
1 static inline void arm920_setup(void)
2 {
3 unsigned long ttb = MMU_TABLE_BASE;
/* MMU_TABLE_BASE = 0x33dfc000 */
4 __asm__(
5 /* Invalidate caches */
6 "mov r0, #0\n"
7 "mcr p15, 0, r0, c7, c7, 0\n" /* invalidate I,D caches on v4 */
8 "mcr p15, 0, r0, c7, c10, 4\n" /* drain write buffer on v4 */
9 "mcr p15, 0, r0, c8, c7, 0\n" /* invalidate I,D TLBs on v4 */
10 /* Load page table pointer */
11 "mov r4, %0\n"
12 "mcr p15, 0, r4, c2, c0, 0\n" /* load page table pointer */
13 /* Write domain id (cp15_r3) */
14 "mvn r0, #0\n" /* Domains 0b01 = client, 0b11=Manager*/
15 "mcr p15, 0, r0, c3, c0, 0\n"
/* load domain access register,write domain 15:0, 用戶手冊P548(access permissions)*/
16 /* Set control register v4 */
17 "mrc p15, 0, r0, c1, c0, 0\n" /* get control register v4 */
/*數據手冊P545:read control register */
18 /* Clear out 'unwanted' bits (then put them in if we need them) */
19 /* ..VI ..RS B... .CAM */ /*這些位元的含義在資料手冊P546*/
20 "bic r0, r0, #0x3000\n" /* ..11 .... .... .... */
/*I(bit[12])=0 = Instruction cache disabled*/
21 /*V[bit[13]](Base location of exception registers)=0 = Low addresses = 0x0000 0000*/
22 "bic r0, r0, #0x0300\n" /* .... ..11 .... .... */
23 /*R(ROM protection bit[9])=0*/
/*S(System protection bit[8])=0*/
/*由於TTB中AP=0b11(line141),所以RS位不使用(P579)*/
24 "bic r0, r0, #0x0087\n" /* 0x0000000010000111 */
/*M(bit[0])=0 = MMU disabled*/
/*A(bit[1])=0 =Data address alignment fault checking disable*/
/*C(bit[2])=0 = Data cache disabled*/
/*B(bit[7])=0= Little-endian operation*/
25 /* Turn on what we want */
26 /* Fault checking enabled */
27 "orr r0, r0, #0x0002\n" /* .... .... .... ..10 */
/*A(bit[1])=1 = Data address alignment fault checking enable*/
28 #ifdef CONFIG_CPU_D_CACHE_ON /*is not set*/
29 "orr r0, r0, #0x0004\n" /* .... .... .... .100 */
/*C(bit[2])=1 = Data cache enabled*/
30 #endif
31 #ifdef CONFIG_CPU_I_CACHE_ON /*is not set*/
32 "orr r0, r0, #0x1000\n" /* ...1 .... .... .... */
/*I(bit[12])=1 = Instruction cache enabled*/
33 #endif
34 /* MMU enabled */
35 "orr r0, r0, #0x0001\n" /* .... .... .... ...1 */
/*M(bit[0])=1 = MMU enabled*/
36 "mcr p15, 0, r0, c1, c0, 0\n" /* write control register */
/*數據手冊P545*/
37 : /* no outputs */
38 : "r" (ttb) );
39 }
4、Step 4:heap_init()
第4步調用了heap_init(void)函數,並返回值。該值是函數heap_init()調用的mmalloc_init()函數的返回值。其實,這步就是申請一塊記憶體區域。
[lib/heap.c->heap_init(void)]
1 int heap_init(void)
2 {
3 return mmalloc_init((unsigned char *)(HEAP_BASE), HEAP_SIZE);
4 }
記憶體動態分配函數mmalloc就是從heap(堆)中劃出一塊空閒記憶體。相應的mfree函數則將動態分配的某塊記憶體釋放回heap中。
heap_init函數在SDRAM中指定了一塊1M大小的記憶體作為heap(起始位址HEAP_BASE =
0x33e00000),並在heap的開頭定義了一個資料結構blockhead。事實上,heap就是使用一系列的blockhead資料結構來描述
和操作的。每個blockhead資料結構對應著一塊heap記憶體,假設一個blockhead資料結構的存放位置為A,則它對應的可分配記憶體位址為
“A + sizeof(blockhead)”到“A + sizeof(blockhead) + size -
1”。blockhead資料結構在lib/heap.c中定義:
1 typedef struct blockhead_t {
2 int32 signature; //固定為BLOCKHEAD_SIGNATURE
3 bool allocated; //此區域是否已經分配出去:0-N,1-Y
4 unsigned long size; //此區域大小
5 struct blockhead_t *next; //鏈表指針
6 struct blockhead_t *prev; //鏈表指針
7 } blockhead;
現在來看看heap是如何運作的(如果您不關心heap實現的細節,這段可以跳過)。vivi對heap的操作比較簡單,vivi中有一個總體變數
static blockhead
*gHeapBase,它是heap的鏈表頭指標,通過它可以遍曆所有blockhead資料結構。假設需要動態申請一塊sizeA大小的記憶體,則
mmalloc函數從gHeapBase開始搜索blockhead資料結構,如果發現某個blockhead滿足:
(1) allocated = 0 //表示未分配
(2) size > sizeA,則找到了合適的blockhead,
滿足上述條件後,進行如下操作:
a.allocated設為1
b.如果size – sizeA > sizeof(blockhead),則將剩下的記憶體組織成一個新的blockhead,放入鏈表中
c.返回分配的記憶體的首位址釋放記憶體的操作更簡單,直接將要釋放的記憶體對應的blockhead資料結構的allocated設為0即可。
heap_init函數直接調用mmalloc_init函數進行初始化,此函數代碼在lib/heap.c中,比較簡單,初始化gHeapBase即可:
[main(int argc, char *argv[]) > heap_init(void) > mmalloc_init(unsigned char *heap, unsigned long size)]
1 static inline int mmalloc_init(unsigned char *heap, unsigned long size)
2 {
3 if (gHeapBase != NULL) return -1;
4 DPRINTK("malloc_init(): initialize heap area at 0x%08lx, size = 0x%08lx\n", heap, size);
5 gHeapBase = (blockhead *)(heap);
6 gHeapBase->allocated=FALSE;
7 gHeapBase->signature=BLOCKHEAD_SIGNATURE;
8 gHeapBase->next=NULL;
9 gHeapBase->prev=NULL;
10 gHeapBase->size = size - sizeof(blockhead);
11 return 0;
12 }
static blockhead *gHeapBase = NULL; 這個就是上面稱讚的總體變數了,定義在lib/heap.c中。上面就是個鏈表操作,資料結構,看來搞這個也得好好學資料結構啊,不然記憶體搞的溢出、浪費可就哭都來不及了。
5、Step 5:mtd_dev_init()
所謂MTD(Memory Technology
Device)相關的技術。在linux系統中,我們通常會用到不同的存儲設備,特別是FLASH設備。為了在使用新的存儲設備時,我們能更簡便地提供它
的驅動程式,在上層應用和硬體驅動的中間,抽象出MTD設備層。驅動層不必關心存儲的資料格式如何,比如是FAT32、ETX2還是FFS2或其他。它僅
僅提供一些簡單的介面,比如讀寫、擦除及查詢。如何組織資料,則是上層應用的事情。MTD層將驅動層提供的函數封裝起來,向上層提供統一的介面。這樣,上
層即可專注於檔系統的實現,而不必關心存儲設備的具體操作。這段亂七八糟的話也許比較讓人暈,也可以這樣理解在設備驅動(此處指存儲設備)和上層應用之間
還存在著一層,共三層,這個中間層就是MTD技術的產物。通常可以將它視為驅動的一部分,叫做上層驅動,而那些實現設備的讀、寫操作的驅動稱為下層驅動,
上層驅動將下層驅動封裝,並且留給其上層應用一些更加容易簡單的介面。
在我們即將看到的代碼中,使用mtd_info資料結構表示一個MTD設備,使用nand_chip資料結構表示一個nand
flash晶片。在mtd_info結構中,對nand_flash結構作了封裝,向上層提供統一的介面。比如,它根據nand_flash提供的
read_data(讀一個位元組)、read_addr(發送要讀的磁區的位址)等函數,構造了一個通用的讀函數read,將此函數的指標作為自己的一
個成員。而上層要讀寫flash時,執行mtd_info中的read、write函數即可。
mtd_dev_init()用來掃描所使用的NAND
Flash的型號,構造MTD設備,即構造一個mtd_info的資料結構。對於S3C2410來說,它直接調用mtd_init(),mtd_init
又調用smc_init(),此函數在drivers/mtd/maps/s3c2410_flash.c中:
[main(int argc,char *argv[])>mtd_dev_init()>mtd_init()]
1 int mtd_init(void)
2 {
3 int ret;
4 #ifdef CONFIG_MTD_CFI /*is not set*/
5 ret = cfi_init();
6 #endif
7 #ifdef CONFIG_MTD_SMC9 /* =y */
8 ret = smc_init();
9 #endif
10 #ifdef CONFIG_S3C2410_AMD_BOOT /*is not set*/
11 ret = amd_init();
12 #endif
13 if (ret) {
14 mymtd = NULL;
15 return ret;
16 }
17 return 0;
18 }
顯而易見,該函數應取第二項,這項在autoconf.h中定義了。
[main(int argc, char *argv[]) > mtd_dev_init() > mtd_init() > smc_init()]
1 static int
2 smc_init(void)
3 {
/*struct mtd_info *mymtd,資料類型在include/mtd/mtd.h*/
/*strcut nand_chip在include/mtd/nand.h中定義*/
4 struct nand_chip *this;
5 u_int16_t nfconf;
/* Allocate memory for MTD device structure and private data */
6 mymtd = mmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip));
7 if (!mymtd) {
8 printk("Unable to allocate S3C2410 NAND MTD device structure.\n");
9 return -ENOMEM;
10 }
/* Get pointer to private data */
11 this = (struct nand_chip *)(&mymtd[1]);
/* Initialize structures */
12 memset((char *)mymtd, 0, sizeof(struct mtd_info));
13 memset((char *)this, 0, sizeof(struct nand_chip));
/* Link the private data with the MTD structure */
14 mymtd->priv = this;
/* set NAND Flash controller */
15 nfconf = NFCONF;
/* NAND Flash controller enable */
16 nfconf |= NFCONF_FCTRL_EN;
/* Set flash memory timing */
17 nfconf &= ~NFCONF_TWRPH1; /* 0x0 */
18 nfconf |= NFCONF_TWRPH0_3; /* 0x3 */
19 nfconf &= ~NFCONF_TACLS; /* 0x0 */
20 NFCONF = nfconf;
/* Set address of NAND IO lines */
21 this->hwcontrol = smc_hwcontrol;
22 this->write_cmd = write_cmd;
23 this->write_addr = write_addr;
24 this->read_data = read_data;
25 this->write_data = write_data;
26 this->wait_for_ready = wait_for_ready;
/* Chip Enable -> RESET -> Wait for Ready -> Chip Disable */
27 this->hwcontrol(NAND_CTL_SETNCE);
28 this->write_cmd(NAND_CMD_RESET);
29 this->wait_for_ready();
30 this->hwcontrol(NAND_CTL_CLRNCE);
31 smc_insert(this);
32 return 0;
33 }
6-14行構造了一個mtd_info結構和nand_flash結構,前者對應MTD設備,後者對應nand
flash晶片(如果您用的是其他類型的記憶體件,比如nor
flash,這裏的nand_flash結構應該換為其他類型的資料結構)。MTD設備是具體記憶體件的抽象,那麼在這些代碼中這種關係如何體現呢——第
14行的代碼把兩者連結在一起了。事實上,mtd_info結構中各成員的實現(比如read、write函數),正是由priv變數所指向的
nand_flash的各類操作函數(比如read_addr、read_data等)來實現的。
15-20行是初始化S3C2410上的NAND
FLASH控制器。前面分配的nand_flash結構還是空的,現在當然就是填滿它的各類成員了,這正是21-26行做的事情。27-30行對這塊
nand flash作了一下復位操作。最後,也是最複雜的部分,根據剛才填充的nand_flash結構,構造mtd_info結構,這由31行的
smc_insert函數調用smc_scan完成。
這才是VIVI啟動的第5步,還有三步就完成了啟動了,同時我的這篇閱讀筆記也就O了。
沒有留言:
張貼留言