U-boot移植之自己写一个简单的bootloader(一)

时间:2021-03-13 16:52:00

问题1、链接的时候出现引用错误,编译的时候对”//”注释方式报错
把start.s改为start.S即可
原因对于gcc编译器来说,遇到.S 的文件的时候会进行预处理(包含头文件,解析宏定义,条件编译等等),而遇到.s文件的时候就只进行编译而不会预先进行预处理

问题2、nand flash读函数不正常
实现四个字节四个字节的读取,而不是一个字节一个字节的读取,要求地址每次读完之后值加4,要求对输入的读数据长度进行除以4的操作来完成四字节的对齐读取,判断一页是否读完也要求判断是否小于2048 / 4。

1、tag

tag的类型

/* tag 列表以一个 ATAG_NONE 节点标记为结束. */
#define ATAG_NONE 0x00000000

/* tag 列表以一个 ATAG_CORE 节点标记为开始 */
#define ATAG_CORE 0x54410001

/* 允许有多个 ATAG_MEM 节点,常用于有多块不连续内存的内存设备 */
#define ATAG_MEM 0x54410002

/* VGA 文本类型显示 */
#define ATAG_VIDEOTEXT 0x54410003

/* 定义内核如何使用 ramdisk */
#define ATAG_RAMDISK 0x54410004

/* 描述 ramdisk 镜像压缩文件所在位置 (虚拟地址) */
/*
* this one accidentally used virtual addresses - as such,
* its depreciated.
*/

#define ATAG_INITRD 0x54410005

/* 描述 ramdisk 镜像压缩文件所在位置 (物理地址) */
#define ATAG_INITRD2 0x54420005

/* 板子串口数目. "64 bits should be enough for everybody" */
#define ATAG_SERIAL 0x54410006

/* board revision */
#define ATAG_REVISION 0x54410007

/* initial values for vesafb-type framebuffers. see struct screen_info
* in include/linux/tty.h
*/

#define ATAG_VIDEOLFB 0x54410008

/* command line: \0 terminated string */
#define ATAG_CMDLINE 0x54410009

/* acorn RiscPC specific information */
#define ATAG_ACORN 0x41000101

/* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */
#define ATAG_MEMCLK 0x41000402

tag结构体

struct tag {
struct tag_header hdr; //tag 头部

/* 共用体,里面标记tag的类型 */
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;
};

常用的tag有关的宏定义

#define __tag __attribute__((unused, __section__(".taglist")))
#define __tagtable(tag, fn) \
static struct tagtable __tagtable_##fn __tag = { tag, fn }

/* 当前 tag 的成员所在位置 */
#define tag_member_present(tag,member) \
((unsigned long)(&((struct tag *)0L)->member + 1) \
<= (tag)->hdr.size * 4)

/* 下一个 tag */
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
/* 求 tag 大小 */
#define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
/* 遍历 tag 列表中的所有 tag */
#define for_each_tag(t,base) \
for (t = base; t->hdr.size; t = tag_next(t))

2、本bootloader的tag设置

设置tag的开始

void setup_start_tag(void)
{
params = (struct tag *)0x30000100; //内核约定tag地址为0x30000100

params->hdr.tag = ATAG_CORE; //tag的开始必须是ATAG_CORE标记,上面的tag类型中有此要求
params->hdr.size = tag_size (tag_core); //该tag的大小,以四字节为单位

params->u.core.flags = 0; //tag_core的flags。此处说明这三个参数没有用到,所以设置为0
params->u.core.pagesize = 0; //tag_core的页大小
params->u.core.rootdev = 0; //tag_core的rootdev

params = tag_next (params); //指向下一个tag结构体
}

tag_core结构体原型

struct tag_core {
u32 flags; /* bit 0 = read-only */
u32 pagesize;
u32 rootdev;
};

设置内存tag

void setup_memory_tags(void)
{
params->hdr.tag = ATAG_MEM; //tag类型
params->hdr.size = tag_size (tag_mem32); //tag大小,四字节为单位

params->u.mem.start = 0x30000000; //内存的起始地址,该板子上的SDRAM起始地址
params->u.mem.size = 64*1024*1024; //内存的大小

params = tag_next (params); //指向下一个tag
}

tag_mem32原型,允许设置多个该tag,用于有多块内存设备的板子

struct tag_mem32 {
u32 size; //内存的大小
u32 start; //内存的物理起始地址
};

设置命令行tag

void setup_commandline_tag(char *cmdline)
{
int len = strlen(cmdline) + 1; //tag类型

params->hdr.tag = ATAG_CMDLINE; //tag大小,四字节为单位
params->hdr.size = (sizeof (struct tag_header) + len + 3) >> 2; //命令行的长度,进行4字节对齐

strcpy (params->u.cmdline.cmdline, cmdline);

params = tag_next (params); //指向下一个tag
}

tag_cmdline原型,字符要以’\0’结束

struct tag_cmdline {
char cmdline[1]; //最小的长度
};

字符串拷贝函数

void strcpy(char *dest, char *src)
{
while ((*dest++ = *src++) != '\0');
}

这里解释下为什么strcpy (params->u.cmdline.cmdline, cmdline);不会报错,我们都知道形如char cmdline[1];的cmdline并不是一个指针,如果把cmdline当做指针一样的进行加减操作或者取值操作就会报错,比如不可这样:cmdline++; 但是为什么上面的一个函数调用却没有出错,明明里面也进行了像指针一样的加以及取值操作。这个是因为在传入参数的时候等于说把params->u.cmdline.cmdline的值传了进去,其相当于一个偏移值,而这个偏移值就是其内部数组的地址,我们只是对传进去的值当做char类型的指针进行加以及取值操作,所以不会报错,如果使用类似下面的语句进行操作就不会报错了,效果跟上面的函数调用是类似的

char *tmp;

tmp = (char *)(params->u.cmdline.cmdline);
*tmp++ = 'h';
*tmp++ = 'e';

设置tag的结束,如上面的要求,必须用ATAG_NONE标记来说明tag参数的结束

void setup_end_tag(void)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}

3、跳转执行内核启动

theKernel = (void (*)(int, int, unsigned int))0x30008000;   //内核的起始地址
theKernel(0, 362, 0x30000100); //0:固定为0 362:芯片类型编号 0x30000100:tag参数起始地址

4、内核启动步骤

  • bootloader 对内存设备,时钟等等进行初始化
  • bootloader 拷贝内核到指定的地址去
  • bootloader 设置 tag 启动参数
  • 跳转去内核起始地址根据 bootloader 设置的参数启动内核