跟着韦东山老师学习JZ2440 u_boot的编写。
今天我们自己写一个简易的u_boot来启动kernel。
u_boot的启动流程我们默认分为两个阶段:
第一阶段 硬件驱动配置。
第二阶段 u_boot启动参数设置。
接下来我们先分析一下:
硬件驱动配置:
硬件启动流程
1、关看门狗。
2、设置CPU的工作时钟频率。(目前设置时钟工作频率是200MHz)
3、初始化SDRAM。
4、重定向代码。(当代码段大于4K的时候,需要把代码段从nand flash拷贝到sdram中运行)
这些工作主要在start.S中完成。关于nand flash的内容我们放到init.c中执行。
接下来上代码:
start.S
/**
******************************************************************************
* @file : start.S
* @version : none
* @date : 2019-12-16
* @brief : init JZ2440 Board peripheral
******************************************************************************
* @attention : JZ2440 EVAL BOARD
* @author : shao
* @personal : shao work stdio
******************************************************************************
*/
/**
* 注:init.S主要用来初始化第一阶段的硬件。
* 全部由汇编编写。
*/
#define S3C2440_MPLL_200MHZ ((0x5c<<12)|(0x01<<4)|(0x02))
#define MEM_CTL_BASE 0x48000000
.text
.global _start
_start:
/* 1. 关看门狗 */
ldr r0, =0x53000000
mov r1, #0x00
str r1, [r0]
/* 2. 设置时钟 */
ldr r0, =0x4C000014
mov r1, #0x03 // FCLK:HCLK:PCLK = 1:2:4
str r1, [r0]
/* 当FLCK != HCLK时, CPU必须由Fast mode改为 Asynchronous 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_200MHZ
str r1, [r0]
/* 3. 初始化SDRAM(运行内存,代码重定位用) */
ldr r0, =MEM_CTL_BASE
adr r1, sdram_config /* sdram_config函数的当前地址 */
add r3, r0, #(13*4) /* 求和: r3 = r0+13*4 */
/* 循环向 MEM_CTL_BASE到MEM_CTL_BASE + 13*4的寄存器写入数据*/
1:
ldr r2, [r1], #4 /* r2=r1, r1地址加4 */
str r2, [r0], #4 /* r0=r2, r0地址加4 */
cmp r0, r3 /* 比较r0, r3是否相等 */
bne 1b /* 若果r0, r3 不相等,那么继续回到1b */
/* 4. 重定位 */
ldr sp, =0x34000000
bl nand_init
mov r0, #0 //传参的起始地址
ldr r1, =_start //srcAddr代码起始地址
ldr r2, =__bss_start //代码结束地址
sub r2, r2, r1 //代码长度,r2=r2-r1
/* 汇编调用C的时候,可以用r0~r3来传参数
* param1:r0
* param2: r1
* param3: r2
*/
bl copy_code_to_sdram
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
然后是init.c。在这里我们需要init一下uart,方便调试和信息输出。
/**
******************************************************************************
* @file : init.c
* @version : none
* @date : 2019-12-16
* @brief : init JZ2440 Board peripheral
******************************************************************************
* @attention : JZ2440 EVAL BOARD
* @author : shao
* @personal : shao work stdio
******************************************************************************
*/
/* 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 char *buf, unsigned int len);
/**
* brief:write data to address 0, then read
* back this address data.
* if data equal to write data, then
* address 0 can write and read. So boot
* from nand falsh.
* else boot from nor flash.
*/
int isBootFromNorFlash(void)
{
volatile int *p = (volatile int *)0; //让p指向0地址
int val;
val = *p; //先保存当前0地址的数据
*p = 0x12345678; //向0地址写入数据
if(*p == 0x12345678)
{
/* 写成功,nand 启动 */
*p = val; //把值还原回去
return 0;
}
else
{
/* Nor Flash 启动 */
return 1;
}
}
/**
* brief: copy code from flash to SDRAM
* param: *src code start source address
* param: *dest code destination address
* param: code length
*/
void copy_code_to_sdram(unsigned char *src, unsigned char *dest, unsigned int len)
{
int i = 0;
/* 如果是Nor Flash 启动 */
if(isBootFromNorFlash())
{
while(i < len)
{
dest[i] = src[i];
i++;
}
}
else
{
//nand_init(); //放到start.S中去做了
nand_read((unsigned int)src, dest, len);
}
}
/**
* brief: clear bss
*/
void clear_bss(void)
{
extern int __bss_start, __bss_end; //获取lds文件中变量的固定格式
int *p = &__bss_start; //让p指向__bss_start地址
for(;p < &__bss_end; p++)
{
*p = 0;
}
}
/**
* brief: init nand flash
*/
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);
}
/**
* brief: select nand flash
*/
void nand_select(void)
{
NFCONT &= ~(1<<1); //拉低选中
}
/**
* brief: deselect nand flash
*/
void nand_deselect(void)
{
NFCONT |= (1<<1);//拉高释放
}
/**
* brief: send nand flash control command
* param: cmd command type
*/
void nand_cmd(unsigned char cmd)
{
volatile int i;
NFCMMD = cmd;
for(i= 0; i < 10; i++); //延时一小会,经验之作
}
/**
* brief: send r/w address
* param: addr
*/
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++); //延时一小会
}
/**
* brief: wait for nand flash ready
*/
void nand_wait_ready(void)
{
while(!(NFSTAT & 1));
}
/**
* brief: return current data
*/
unsigned char nand_data(void)
{
return NFDATA;
}
void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
{
int col = addr % 2048;
int i = 0;
/* 1. 选中 */
nand_select();
while(i < len)
{
/* 2. 发出读命令 00h*/
nand_cmd(0x00);
/* 3. 发出地址(分五步发出) */
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(char *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);
}
}
还需要设置一下lds连接脚本,来设置代码存放的段区。
boot.lds
SECTIONS{
. = 0x33f80000;
.text : { *(.text) }
. = ALIGN(4);
.rodata : { *(.rodata*) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(COMMON) }
__bss_end = .;
}
这里用的是4字节对齐。
到这里第一阶段基本完成,硬件驱动也写完了(其他功能可以再添加这里是最简化的版本)。
u_boot启动参数设置:
0、初始化串口
1、从nand flash 把内核读入内存
2、设置参数
3、跳转执行
boot.c
/**
******************************************************************************
* @file : boot.c
* @version : none
* @date : 2019-12-18
* @brief : init JZ2440 Board peripheral
******************************************************************************
* @attention : JZ2440 EVAL BOARD
* @author : shao
* @personal : shao work stdio
******************************************************************************
*/
#include "setup.h"
extern void uart0_init(void);
extern void putc(unsigned char c);
extern void puts(char *str);
extern void puthex(unsigned int val);
extern void nand_read(unsigned int addr, unsigned char *buf, unsigned int len);
static struct tag *params;
/**
* brief: set start tag
*/
void setup_start_tag()
{
params = (struct tag *)0x30000100;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size(tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next(params);
}
/**
* brief: set memory tag
*/
void setup_memory_tags()
{
params->hdr.tag =
ATAG_MEM;
params->hdr.size = tag_size(tag_mem32);
params->u.mem.start = 0x30000000;
params->u.mem.size = 64*1024*1024;
params = tag_next(params);
}
/**
* brief: caculate string length
* *str : string to be caculate
*/
int strlen(char *str)
{
int i = 0;
while
(str[i])
{
i++;
}
return i;
}
/**
* brief: copy string to another
* *dest: destinate string
* *src : source string
*/
void strcopy(char *dest, char *src)
{
while((*dest++ = *src++) != '\0');
}
/**
* brief: set commandline tag
*/
void setup_commandline_tag(char *cmdline)
{
int len = strlen(cmdline) + 1;
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size = (sizeof(struct tag_header) + len + 3) >> 2;
strcopy(params->u.cmdline.cmdline, cmdline);
params = tag_next(params);
}
/**
* brief: set end tag
*/
void setup_end_tag()
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
int main(void)
{
void (*theKernel)(int zero, int arch, unsigned int params);
volatile unsigned int *p = (volatile unsigned int *)0x30008000;
/* 0. 设置串口,打印初始化信息*/
uart0_init();
puts("shao .... Config bootloader ...\r\n");
/* 1. 从Nand Flash把内核读入内存 */
puts("Copy kernel from nand!\r\n");
/**
* 0x60000+64 : 内核起始地址+64byte
* 写入到SDRAM : 0x30008000地址
* 写入长度 : 0x200000 2MB
*/
nand_read(0x60000+64, (unsigned char *)0x30008000, 0x200000);
puthex(0x1234ABCD);
puts("\r\n");
puthex(*p);
puts("\r\n");
/* 2. 设置参数 */
puts("Set boot params.\r\n");
setup_start_tag();
setup_memory_tags();
setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");
setup_end_tag();
/* 3. 跳转执行 */
puts("Boot kernel... \r\n");
theKernel = (void (*)(int, int, unsigned int))0x30008000;
theKernel(0, 362, 0x30000100);
puts("Error!\r\n"); //执行到这里会打印错误信息,程序正常的情况下不会执行到这里
return -1; //执行到这里肯定就有错误了
}
我们借用了u_boot源码的,setup.h
/*
* linux/include/asm/setup.h
*
* Copyright (C) 1997-1999 Russell King
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Structure passed to kernel to tell it about the
* hardware it's running on. See linux/Documentation/arm/Setup
* for more info.
*
* NOTE:
* This file contains two ways to pass information from the boot
* loader to the kernel. The old struct param_struct is deprecated,
* but it will be kept in the kernel for 5 years from now
* (2001). This will allow boot loaders to convert to the new struct
* tag way.
*/
#ifndef __ASMARM_SETUP_H
#define __ASMARM_SETUP_H
#define u8 unsigned char
#define u16 unsigned short
#define u32 unsigned long
/*
* Usage:
* - do not go blindly adding fields, add them at the end
* - when adding fields, don't rely on the address until
* a patch from me has been released
* - unused fields should be zero (for future expansion)
* - this structure is relatively short-lived - only
* guaranteed to contain useful data in setup_arch()
*/
#define COMMAND_LINE_SIZE 1024
/* This is the old deprecated way to pass parameters to the kernel */
struct param_struct {
union {
struct {
unsigned long page_size; /* 0 */
unsigned long nr_pages; /* 4 */
unsigned long ramdisk_size; /* 8 */
unsigned long flags; /* 12 */
#define FLAG_READONLY 1
#define FLAG_RDLOAD 4
#define FLAG_RDPROMPT 8
unsigned long rootdev; /* 16 */
unsigned long video_num_cols; /* 20 */
unsigned long video_num_rows; /* 24 */
unsigned long video_x; /* 28 */
unsigned long video_y; /* 32 */
unsigned long memc_control_reg; /* 36 */
unsigned char sounddefault; /* 40 */
unsigned char adfsdrives; /* 41 */
unsigned char bytes_per_char_h; /* 42 */
unsigned char bytes_per_char_v; /* 43 */
unsigned long pages_in_bank[4]; /* 44 */
unsigned long pages_in_vram; /* 60 */
unsigned long initrd_start; /* 64 */
unsigned long initrd_size; /* 68 */
unsigned long rd_start; /* 72 */
unsigned long system_rev; /* 76 */
unsigned long system_serial_low; /* 80 */
unsigned long system_serial_high; /* 84 */
unsigned long mem_fclk_21285; /* 88 */
} s;
char unused[256];
} u1;
union {
char paths[8][128];
struct {
unsigned long magic;
char n[1024 - sizeof(unsigned long)];
} s;
} u2;
char commandline[COMMAND_LINE_SIZE];
};
/*
* The new way of passing information: a list of tagged entries
*/
/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE 0x00000000
struct tag_header {
u32 size;
u32 tag;
};
/* The list must start with an ATAG_CORE node */
#define ATAG_CORE 0x54410001
struct tag_core {
u32 flags; /* bit 0 = read-only */
u32 pagesize;
u32 rootdev;
};
/* it is allowed to have multiple ATAG_MEM nodes */
#define ATAG_MEM 0x54410002
struct tag_mem32 {
u32 size;
u32 start; /* physical start address */
};
/* VGA text type displays */
#define ATAG_VIDEOTEXT 0x54410003
struct tag_videotext {
u8 x;
u8 y;
u16 video_page;
u8 video_mode;
u8 video_cols;
u16 video_ega_bx;
u8 video_lines;
u8 video_isvga;
u16 video_points;
};
/* describes how the ramdisk will be used in kernel */
#define ATAG_RAMDISK 0x54410004
struct tag_ramdisk {
u32 flags; /* bit 0 = load, bit 1 = prompt */
u32 size; /* decompressed ramdisk size in _kilo_ bytes */
u32 start; /* starting block of floppy-based RAM disk image */
};
/* describes where the compressed ramdisk image lives (virtual address) */
/*
* this one accidentally used virtual addresses - as such,
* its depreciated.
*/
#define ATAG_INITRD 0x54410005
/* describes where the compressed ramdisk image lives (physical address) */
#define ATAG_INITRD2 0x54420005
struct tag_initrd {
u32 start; /* physical start address */
u32 size; /* size of compressed ramdisk image in bytes */
};
/* board serial number. "64 bits should be enough for everybody" */
#define ATAG_SERIAL 0x54410006
struct tag_serialnr {
u32 low;
u32 high;
};
/* board revision */
#define ATAG_REVISION 0x54410007
struct tag_revision {
u32 rev;
};
/* initial values for vesafb-type framebuffers. see struct screen_info
* in include/linux/tty.h
*/
#define ATAG_VIDEOLFB 0x54410008
struct tag_videolfb {
u16 lfb_width;
u16 lfb_height;
u16 lfb_depth;
u16 lfb_linelength;
u32 lfb_base;
u32 lfb_size;
u8 red_size;
u8 red_pos;
u8 green_size;
u8 green_pos;
u8 blue_size;
u8 blue_pos;
u8 rsvd_size;
u8 rsvd_pos;
};
/* command line: \0 terminated string */
#define ATAG_CMDLINE 0x54410009
struct tag_cmdline {
char cmdline[1]; /* this is the minimum size */
};
/* acorn RiscPC specific information */
#define ATAG_ACORN 0x41000101
struct tag_acorn {
u32 memc_control_reg;
u32 vram_pages;
u8 sounddefault;
u8 adfsdrives;
};
/* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */
#define ATAG_MEMCLK 0x41000402
struct tag_memclk {
u32 fmemclk;
};
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
/*
* Acorn specific
*/
struct tag_acorn acorn;
/*
* DC21285 specific
*/
struct tag_memclk memclk;
} u;
};
struct tagtable {
u32 tag;
int (*parse)(const struct tag *);
};
#define tag_member_present(tag,member) \
((unsigned long)(&((struct tag *)0L)->member + 1) \
<= (tag)->hdr.size * 4)
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
#define for_each_tag(t,base) \
for (t = base; t->hdr.size; t = tag_next(t))
/*
* Memory map description
*/
#define NR_BANKS 8
struct meminfo {
int nr_banks;
unsigned long end;
struct {
unsigned long start;
unsigned long size;
int node;
} bank[NR_BANKS];
};
extern struct meminfo meminfo;
#endif
最后执行一下Makefile
CC = arm-linux-gcc
LD = arm-linux-ld
AR = arm-linux-ar
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump
CFLAGS := -Wall -O2
CPPFLAGS := -nostdinc -nostdlib -fno-builtin
objs := start.o init.o boot.o
boot.bin : $(objs)
${LD} -Tboot.lds -o boot.elf $^
${OBJCOPY} -O binary -S boot.elf $@
${OBJDUMP} -D -m arm boot.elf > boot.dis
%.o : %.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
%.o : %.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<
clean:
rm -f *.o *.bin *.elf *.dis
放到Ubuntu16.04中进行编译。
得到boot.bin文件烧录到JZ2440中可以看到打印的初始化信息:
红色的部分是我们u_boot的初始化信息。
到这里我们的u_boot基本功能,启动内核的任务就完成了。