韦东山视频学习笔记。平台JZ2440。CPU为S3C2440A,NandFlash型号为K9F2G08U0M。本文如果有什么错误的地方,欢迎各位批评指正,不甚感激。
注:第一阶段部分函数未完成,还不能启动内核
一、剖析实现从零写bootloader我们所要做的工作:
要想从零写出自己的bootloader,我们得先知道bootloader的目的是干嘛。就好像我们做事,得先明确目的,才有具体的行动。
bootloader的目的:启动内核
要启动内核,我们得先知道内核在哪?内核很大,通常存储于NandFlash上(较NorFlash:容量大、块小、 擦写快、便宜)。这里我们可以初步 确定我们的工作:
工作1:从NandFlash上把内核读入内存
1、能读NandFlash
2、初始化内存
工作2:启动内核
3、设置参数(如:告诉内核我的内存有多大;去哪挂接根文件系统……)
4、跳转执行
工作3:其他
5、关看门狗;
6、提高运行速度,初始化时钟(系统上电12M);
7、清零bss段(存放未初始化的全局变量和初始化为零的全局变量)
8、……
到这里,我们所要做的工作就是完成上述基本工作,那bootloader第一阶段就基本完成了。下面有三个代码实现文件start.s、init.c、boot.lds。start.s是执行的第一个文件;init.c是一些函数C语言的实现和一些初始化函数,start.s中大部分跳转函数在这里边实现;boot.lds是链接脚本。
二、实现代码
1、start.s
- #define S3C2440_MPLL_200MHZ ((0x5c<<12)|(0x01<<4)|(0x02))
- #define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01))
- #define MEM_CTL_BASE 0x48000000
- .text //代码段
- .global _start //全局变量
- _start:
- /* 1. 关看门狗 */
- ldr r0, =0x53000000
- mov r1, #0
- str r1, [r0]
- /* 2. 设置时钟 */
- ldr r0, =0x4c000014
- // mov r1, #0x03; // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1
- mov r1, #0x05; // FCLK:HCLK:PCLK=1:4:8
- str r1, [r0]
- //当FCLK != HCLK时,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode
- /* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */
- mrc p15, 0, r1, c1, c0, 0 /* 读出控制寄存器 */
- orr r1, r1, #0xc0000000 /* 设置为“asynchronous bus mode” */
- mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */
- /* MPLLCON = S3C2440_MPLL_200MHZ */
- ldr r0, =0x4c000004
- ldr r1, =S3C2440_MPLL_400MHZ
- str r1, [r0]
- /* 启动ICACHE */
- mrc p15, 0, r0, c1, c0, 0 @ read control reg
- orr r0, r0, #(1<<12)
- mcr p15, 0, r0, c1, c0, 0 @ write it back
- /* 3. 初始化SDRAM */
- ldr r0, =MEM_CTL_BASE
- adr r1, sdram_config /* 得到sdram_config标号的当前地址 */
- add r3, r0, #(13*4) //r3 = r0 + 52
- 1:
- ldr r2, [r1], #4 //从r1所指的地方取一个值,存到r2,然后r1加4
- str r2, [r0], #4 //把r2的值存到r0的地址上,然后r0加4
- cmp r0, r3
- bne 1b //b表示back:表示前面的1标号;可以有多个1标号(1f:forward)
- /* 4. 重定位 : 把bootloader本身的代码从flash复制到它的链接地址去 */
- ldr sp, =0x34000000 //我们内存是64M,基地址是0x30000000,到0x34000000是64M大小,指向最高内存(栈是向下增长的)
- /* 不管是NandFlash启动还是NorFlash启动,内核都存在NandFlash上,所以都得先初始化NandFlash */
- bl nand_init
- /*汇编向C函数传递参数(参数1:r0、参数2:r1、参数3:r2)*/
- mov r0, #0 //源地址
- ldr r1, =_start //目的地址(即需要拷贝到内存中的链接地址)
- ldr r2, =__bss_start
- sub r2, r2, r1 //长度r2 = r2 - r1
- bl copy_code_to_sdram //接受r0、r1、r2三个参数给函数
- /*拷贝完后,将未初始化的全局和初始化为零的全局内存空间清零*/
- bl clear_bss
- /* 5. 执行main */
- ldr lr, =halt
- ldr pc, =main
- halt:
- b halt
- sdram_config:
- .long 0x22011110 //BWSCON
- .long 0x00000700 //BANKCON0
- .long 0x00000700 //BANKCON1
- .long 0x00000700 //BANKCON2
- .long 0x00000700 //BANKCON3
- .long 0x00000700 //BANKCON4
- .long 0x00000700 //BANKCON5
- .long 0x00018005 //BANKCON6
- .long 0x00018005 //BANKCON7
- .long 0x008C04F4 // REFRESH
- .long 0x000000B1 //BANKSIZE
- .long 0x00000030 //MRSRB6
- .long 0x00000030 //MRSRB7
2、init.c
- /* NAND FLASH控制器 */
- #define NFCONF (*((volatile unsigned long *)0x4E000000))
- #define NFCONT (*((volatile unsigned long *)0x4E000004))
- #define NFCMMD (*((volatile unsigned char *)0x4E000008))
- #define NFADDR (*((volatile unsigned char *)0x4E00000C))
- #define NFDATA (*((volatile unsigned char *)0x4E000010))
- #define NFSTAT (*((volatile unsigned char *)0x4E000020))
- /* GPIO */
- #define GPHCON (*(volatile unsigned long *)0x56000070)
- #define GPHUP (*(volatile unsigned long *)0x56000078)
- /* UART registers*/
- #define ULCON0 (*(volatile unsigned long *)0x50000000)
- #define UCON0 (*(volatile unsigned long *)0x50000004)
- #define UFCON0 (*(volatile unsigned long *)0x50000008)
- #define UMCON0 (*(volatile unsigned long *)0x5000000c)
- #define UTRSTAT0 (*(volatile unsigned long *)0x50000010)
- #define UTXH0 (*(volatile unsigned char *)0x50000020)
- #define URXH0 (*(volatile unsigned char *)0x50000024)
- #define UBRDIV0 (*(volatile unsigned long *)0x50000028)
- #define TXD0READY (1<<2)
- void nand_read(unsigned int addr, unsigned charchar *buf, unsigned int len);
- /*
- * 判断方法:像零地址写数据
- * NorFlash启动时从NorFlash零地址开始执行,向零地址写数据就是往NorFlash上写数据,会失败(Norflash可以像内存一样读,但不能像内存一样写)
- * NandFlash启动时,零地址为片内内存,向内存写数据,读出后会写成功。
- */
- int isBootFromNorFlash(void)
- {
- volatile intint *p = (volatile intint *)0;
- int val;
- val = *p;
- *p = 0x12345678;
- if (*p == 0x12345678)
- {
- /* 写成功, 是nand启动 */
- *p = val;
- return 0;
- }
- else
- {
- /* NOR不能像内存一样写 */
- return 1;
- }
- }
- void copy_code_to_sdram(unsigned charchar *src, unsigned charchar *dest, unsigned int len)
- {
- int i = 0;
- /* 如果是NOR启动 */
- if (isBootFromNorFlash())
- {
- while (i < len)
- {
- dest[i] = src[i];
- i++;
- }
- }
- else
- {
- //nand_init();
- nand_read((unsigned int)src, dest, len);
- }
- }
- /* C语言如何引用__bss_start等标号的典型实例
- ********************************************
- * 汇编中引用:
- * ldr r1, =_start //目的地址(即需要拷贝到内存中的链接地址)
- * ldr r2, =__bss_start
- * sub r2, r2, r1 //长度r2 = r2 - r1
- */
- void clear_bss(void)
- {
- extern int __bss_start, __bss_end;
- intint *p = &__bss_start;
- for (; p < &__bss_end; p++)
- *p = 0;
- }
- void nand_init(void)
- {
- #define TACLS 0
- #define TWRPH0 1
- #define TWRPH1 0
- /* 设置时序 */
- NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
- /* 使能NAND Flash控制器, 初始化ECC, 禁止片选 */
- NFCONT = (1<<4)|(1<<1)|(1<<0);
- }
- void nand_select(void)
- {
- NFCONT &= ~(1<<1);
- }
- void nand_deselect(void)
- {
- NFCONT |= (1<<1);
- }
- void nand_cmd(unsigned char cmd)
- {
- volatile int i;
- NFCMMD = cmd;
- for (i = 0; i < 10; i++);
- }
- void nand_addr(unsigned int addr)
- {
- unsigned int col = addr % 2048;
- unsigned int page = addr / 2048;
- volatile int i;
- NFADDR = col & 0xff;
- for (i = 0; i < 10; i++);
- NFADDR = (col >> 8) & 0xff;
- for (i = 0; i < 10; i++);
- NFADDR = page & 0xff;
- for (i = 0; i < 10; i++);
- NFADDR = (page >> 8) & 0xff;
- for (i = 0; i < 10; i++);
- NFADDR = (page >> 16) & 0xff;
- for (i = 0; i < 10; i++);
- }
- void nand_wait_ready(void)
- {
- while (!(NFSTAT & 1));
- }
- unsigned char nand_data(void)
- {
- return NFDATA;
- }
- void nand_read(unsigned int addr, unsigned charchar *buf, unsigned int len)
- {
- int col = addr % 2048;
- int i = 0;
- /* 1. 选中 */
- nand_select();
- while (i < len)
- {
- /* 2. 发出读命令00h */
- nand_cmd(0x00);
- /* 3. 发出地址(分5步发出) */
- nand_addr(addr);
- /* 4. 发出读命令30h */
- nand_cmd(0x30);
- /* 5. 判断状态(等待就绪),读操作需要一定时间,我们要等待读完后再进行其他操作 */
- nand_wait_ready();
- /* 6. 读数据 */
- for (; (col < 2048) && (i < len); col++)
- {
- buf[i] = nand_data();
- i++;
- addr++;
- }
- col = 0;
- }
- /* 7. 取消选中 */
- nand_deselect();
- }
- #define PCLK 50000000 // init.c中的clock_init函数设置PCLK为50MHz
- #define UART_CLK PCLK // UART0的时钟源设为PCLK
- #define UART_BAUD_RATE 115200 // 波特率
- #define UART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1)
- /*
- * 初始化UART0
- * 115200,8N1,无流控
- */
- void uart0_init(void)
- {
- GPHCON |= 0xa0; // GPH2,GPH3用作TXD0,RXD0
- GPHUP = 0x0c; // GPH2,GPH3内部上拉
- ULCON0 = 0x03; // 8N1(8个数据位,无较验,1个停止位)
- UCON0 = 0x05; // 查询方式,UART时钟源为PCLK
- UFCON0 = 0x00; // 不使用FIFO
- UMCON0 = 0x00; // 不使用流控
- UBRDIV0 = UART_BRD; // 波特率为115200
- }
- /*
- * 发送一个字符
- */
- void putc(unsigned char c)
- {
- /* 等待,直到发送缓冲区中的数据已经全部发送出去 */
- while (!(UTRSTAT0 & TXD0READY));
- /* 向UTXH0寄存器中写入数据,UART即自动将它发送出去 */
- UTXH0 = c;
- }
- void puts(charchar *str)
- {
- int i = 0;
- while (str[i])
- {
- putc(str[i]);
- i++;
- }
- }
- void puthex(unsigned int val)
- {
- /* 0x1234abcd */
- int i;
- int j;
- puts("0x");
- for (i = 0; i < 8; i++)
- {
- j = (val >> ((7-i)*4)) & 0xf;
- if ((j >= 0) && (j <= 9))
- putc('0' + j);
- else
- putc('A' + j - 0xa);
- }
- }
3、boot.lds
- SECTIONS {
- . = 0x33f80000; //跟最大地址刚好相差512K,我们的程序肯定不会超过512K,所以选该地址
- .text : { *(.text) }
- . = ALIGN(4);
- .rodata : {*(.rodata*)}
- . = ALIGN(4);
- .data : { *(.data) }
- /*bss段存放没有初始化的全局变量或则初始化为0的全局变量*/
- //我们编译出来的二进制文件不会含有这些初始化为0的变量
- //因为bss不是文件里的东西,那可知代码段大小(文件大小) = (__bss_start - _start(0x33f80000))
- . = ALIGN(4);
- __bss_start = .;
- .bss : { *(.bss) *(COMMON) }
- __bss_end = .;
- }