S3C2410 vivi閱讀筆記

S3C2410 vivi閱讀筆記

建議讀一讀《嵌入式系統Boot Loader技術內幕》(詹榮開著),google一下就會找到一片。什麼是Bootloader就不再這裏廢話了,看看上面的文章就明瞭了。

Bootloader有很多種,如本文將要閱讀的vivi,除此之外還有uboot,redboot,lilo等等。Vivi 是韓國mizi公司專門爲三星s3c2410芯片設計的Bootloader。
先來看看vivi的源碼樹:
vivi-+-arch-+-s3c2410
|-Documentation
|-drivers-+-serial
|           ‘-mtd-+-maps
|                  |-nor
|                  ‘-nand
|-include-+-platform
|           |-mtd
|           ‘-proc
|-init
|-lib-+-priv_data
|-scripts-+-lxdialog
|-test
|-util
可以google一下,搜到源碼vivi.tar.gz。
前面提到的文件已經系統的分析了bootloader的,這裏就按源代碼來具體說事。vivi也可以分爲2個階段,階段1的代碼在arch/s3c2410/head.S中,階段2的代碼從init/main.c的main函數開始。

階段1

階段1從程序arch/s3c2410/head.S開始,按照head.S的代碼執行順序,一次完成了下面幾個任務:
1、關WATCH DOG (disable watch dog timer)
上電後,WATCH DOG默認是開着的
2、禁止所有中斷 (disable all interrupts)
vivi中不會用到中斷,中斷是系統的事,bootloader可不能去幹這事的(不過這段代碼實在多餘,上電後中斷默認是關閉的)
3、初始化系統時鐘(initialise system clocks)
啓動MPLL,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz,“CPU bus mode”改爲“Asynchronous bus mode”。
4、初始化內存控制寄存器(memsetup)
S3c2410共有15個寄存器,在此開始初始化13個寄存器。
5、檢查是否從掉電模式喚醒(Check if this is a wake-up from sleep)
若是,則調用WakeupStart函數進行處理。
6、點亮所有LED (All LED on)
點一下燈,通知外面的同志,告訴他們有情況發生。
7、初始化UART0 (set GPIO for UART & InitUART)
a.設置GPIO,選擇UART0使用的引腳
b.初始化UART0,設置工作方式(使用FIFO)、波特率115200 8N1、無流控等。這可是使用串口與s3c2410通信的條件啊,在終端也要如此設置。
8、跳到內存測試函數(simple memory test to find some DRAM flaults)
當然要定義了CONFIG_BOOTUP_MEMTEST這個參數纔會跳到內存測試。
9、如果定義了以Nand flash方式啓動(#ifdef CONFIG_S3C2410_NAND_BOOT),則此時要將vivi所有代碼(包括階段1和階段2)從Nand flash複製到SDRAM中(因爲在Nand
flash中是不能執行程序的,它只能做爲程序和數據的存儲器,而Nor flash可就不同了,Nor flash可以執行程序,但貴是它發展得瓶頸):
a.設置nand flash控制寄存器
b.設置堆棧指針
c.設置即將調用的函數nand_read_ll的參數:r0=目的地址(SDRAM的地址),r1=源地址(nand
flash的地址),r2=複製的長度(以字節爲單位)
d.調用nand_read_ll進行復制
10、跳到bootloader的階段2運行,亦即調用init/main.c中的main函數(get read to call C functions)
a.重新設置堆棧
b.設置main函數的參數
c.調用main函數
head.S有900多行,都是些arm彙編,看的雲山霧罩,彙編看來是忘的差不多了,所以這部分代碼也看的相當糙,只知道大概在幹什麼,至於箇中緣由就不是很瞭解。先學學arm彙編再回來看。

階段2

從init/main.c中的main函數開始,終於步入C語言的世界了。Main函數總共有8步(8 steps),先看看源代碼:
int main(int argc, char *argv[])
{
        int ret;

/*
        * Step 1:
        */
        putstr("/r/n");
        putstr(vivi_banner);   
//vivi_banner是vivi執行開始的顯示,vivi_banner在文件version.c中定 義        reset_handler();

        /*
         * Step 2:
         */
        ret = board_init();
        if (ret) {
                putstr("Failed a board_init() procedure/r/n");
                error();
        }

        /*
        * Step 3:
        */
        mem_map_init();
        mmu_init();
        putstr("Succeed memory mapping./r/n");

        /*
         * Now, vivi is running on the ram. MMU is enabled.
         * Step 4:
*/
        /* initialize the heap area*/
        ret = heap_init();
        if (ret) {
                putstr("Failed initailizing heap region/r/n");
                error();
        }

        /* Step 5:
         * MTD
         */
        ret = mtd_dev_init();

        /* Step 6:
         */
        init_priv_data();

        /* Step 7:
*/
misc();
init_builtin_cmds();

        /* Step 8:
         */
        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  }
在上電後,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  }
先寫到這兒吧。
(未完待續)

S3C2410 bootloader ----VIVI閱讀筆記2(續筆記1)

2、Step 2:board_init()    
board_init調用2個函數用於初始化定時器和設置各GPIO引腳功能,代碼在arch/s3c2410/smdk.c中:
[main(int argc, char *argv[]) > board_init()]
1  int board_init(void)
2  {
3      init_time();  /*arch/s3c2410/proc.c*/
4      set_gpios();  /*arch/s3c2410/smdk.c */
5      return 0;
6  }
init_time() 這個函數對寄存器進行了簡單的操作:
void init_time(void)
{
        TCFG0 = (TCFG0_DZONE(0) | TCFG0_PRE1(15) | TCFG0_PRE0(0));
        /*s3c2410 data sheet P298*/
        /*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,查找s3c2410用戶手冊可知,該參數將GPACON的23位全部置1。各位功能需察看s3c2410用戶手冊

3、Step 3:建立頁表和啓動MMU
mem_map_init();
mmu_init();

mem_map_init函數用於建立頁表,vivi使用段式頁表,只需要一級頁表。它調用3個函數,代碼在arch/s3c2410/mmu.c中:
[main(int argc, char *argv[]) > mem_map_init(void)]
1        void mem_map_init(void)
2        {
3        #ifdef CONFIG_S3C2410_NAND_BOOT        
/*CONFIG_S3C2410_NAND_BOOT = y ,在文件include/autoconf.h中定義*/
4                mem_map_nand_boot();      
/* 最終調用mem_mepping_linear, 建立頁表 */
5        #else
6                mem_map_nor();
7        #endif
8                cache_clean_invalidate();/* 清空cache,使無效cache */ 
9                tlb_invalidate();        /* 使無效快表TLB */
10        }
第9、10行的兩個函數可以不用管它,他們做的事情在下面的mmu_init函數裏又重複了一遍。對於本開發板,在.config中定義了 CONFIG_S3C2410_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/s3c2410/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=0xC0000000,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 }

[未完]

S3C2410 bootloader ----VIVI閱讀筆記3

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步,還有三步就完成了啓動了,同時我的這篇閱讀筆記也就OVER了。

發佈了1 篇原創文章 · 獲贊 2 · 訪問量 29萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章