#include
#include
#include
#include
#include
#include
#include
#define PMECC_HEADER 0xc0902405 //correct bits: 4bits, PMECC sector size: 512
int main(int argc, char **argv)
{
int fd;
char buf[0x10000];
unsigned int *pmecc_header = (unsigned int *)buf;
struct stat stat;
int i;
fd = open((argc == 2) ? argv[1] : "binaries/at91sam9x5ek-nandflashboot-uboot-3.5.4.bin", O_RDONLY);
if (fd == -1)
{
perror("open binaries/at91sam9x5ek-nandflashboot-linux-3.5.4.bin fail");
return -1;
}
memset(buf, 0xff, sizeof(buf));
for (i = 0; i < 0x34; i++)
pmecc_header[i] = PMECC_HEADER;
memset(&stat, 0, sizeof(stat));
if (-1 == fstat(fd, &stat))
{
close(fd);
perror("fstat error");
return -1;
}
read(fd, &buf[0xd0], stat.st_size);
memcpy(&buf[0xe4], &stat.st_size, 4);
close(fd);
fd = creat("bootstrap", 0666);
if (fd == -1)
{
perror("create bootstrap fail");
return -1;
}
write(fd, buf, sizeof(buf));
fdatasync(fd);
close(fd);
return 0;
}
经此代码生成的bootstrap文件便可以直接通过uboot烧写到nand flash的0x0地址上
2.修改uboot(本文使用的uboot为u-boot-at91-u-boot-2015.01-at91)
首先需要配置uboot,at91sam9x5ek默认的配置大都位于include/configs/
at91sam9x5ek.h文件中,我需要添加一些其他的配置,例如:watchdog的使能,环境变量的重写还有一些其他额外的配置等等,具体如下所示:
#define CONFIG_HW_WATCHDOG /*使能watchdog的两个配置选项*/
#define CONFIG_AT91SAM9_WATCHDOG
#define CONFIG_ENV_OVERWRITE /*环境变量可以重新修改,不配此项有些环境变量无法进行修改*/
#define CONFIG_LIB_RAND /*使用随机数的库,获取随机的mac地址会用到*/
#if 0 /*以下是eth0和eth1配置默认mac地址,由于板子要量产所以不能把所有的板子全配置为一样的mac地址,调试的时候会用到,发布时注释掉使用随机mac*/
#define CONFIG_ETHADDR 0c:9b:2f:ce:01:d8
#define CONFIG_ETH1ADDR 6e:3c:2a:fe:c2:b1
#endif
#define CONFIG_CMD_NET /*配置网络相关的uboot命令,实际上如果配置了下面几项之后默认会配置此项的*/
#define CONFIG_IPADDR 172.7.18.99 /*默认的ip地址*/
#define CONFIG_NETMASK 255.255.255.0 /*默认的子网掩码*/
#define CONFIG_GATEWAYIP 172.7.18.1 /*默认网关*/
#define CONFIG_SERVERIP 172.7.18.200 /*默认的远程tftp或是nfs服务器的ip*/
#define CONFIG_EXTRA_ENV_SETTINGS \ /*额外的环境变量设置,这里主要用来启动nfs文件系统*/
"nfsbootargs=noinitrd init=/linuxrc root=/dev/nfs rw " \
"nfsroot=${serverip}:/ubifs,nolock " \
"ip=${ipaddr}:${serverip}:${gatewayip}:${netmask}::eth1:off " \
"console=ttyS0,115200 earlyprintk"
/* PMECC & PMERRLOC */ /*nand flash PMECC相关的配置*/
#define CONFIG_ATMEL_NAND_HWECC 1
#define CONFIG_ATMEL_NAND_HW_PMECC 1
#define CONFIG_PMECC_CAP 4 /*changed by Jicky, the origin is 2*/
#define CONFIG_PMECC_SECTOR_SIZE 512
#define CONFIG_PREBOOT "mtdparts default" /*uboot启动预先执行的命令*/
/*默认分区的相关配置*/
#define MTDPARTS_DEFAULT "mtdparts=atmel_nand:256k(bootstrap)ro,512k(uboot)ro," \
"256k(env)ro,1M(judge),1M(user),5M(bak_kernel)ro," \
"5M(kernel)ro,143M(rootfs),40M(data),-(bak_rootfs)ro"
#define MTD_ACTIVE_PART "nand0,0"
#define MTDIDS_DEFAULT "nand0=atmel_nand"
/*uboot环境变量的偏移地址大小以及judge的偏移地址大小*/
/* bootstrap + u-boot + env + linux in nandflash */
#define CONFIG_ENV_IS_IN_NAND
#define CONFIG_ENV_OFFSET 0xc0000
#define CONFIG_ENV_SIZE 0x20000 /* 1 sector = 128 kB */
#define CONFIG_ENV_JUDGE_OFFSET 0x100000
#define CONFIG_ENV_JUDGE_SIZE 0x100000
#define CONFIG_ENV_USER_OFFSET 0x200000
#define CONFIG_ENV_USER_SIZE 0x100000
#define CONFIG_RECOVER_KERNEL /*恢复内核*/
#define CONFIG_RECOVER_ROOTFS /*恢复文件系统*/
/*uboot引导内核相关的两个参数*/
#define CONFIG_BOOTCOMMAND "nand read " \
"0x22000000 kernel 0x300000; " \
"bootm 0x22000000"
#define CONFIG_BOOTARGS \
"console=ttyS0,115200 earlyprintk " \
"mtdparts=atmel_nand:256k(bootstrap)ro,512k(uboot)ro," \
"256k(env)ro,1M(judge),1M(user),5M(bak_kernel)ro," \
"5M(kernel)ro,143M(rootfs),40M(data),-(bak_rootfs)ro " \
"rootfstype=ubifs ubi.mtd=7 root=ubi0:rootfs rw"
以上只是列出了部分修改的配置,还有一些其他默认的配置请查看源文件。
(1)修改网络部分
也不知道是由于at91sam9x5ek本身的问题还是公司硬件组同事的设计问题,在at91sam9x5ek启动uboot后,eth0总是不能ping通远程的nfs服务器,然而把环境变量"ethact"设置为"macb1"再配置"eth1addr"就可以ping通了,也就是说使用eth1可以ping通网络,很奇怪,也懒得去深究了,总之网络能通就行。这块主要在使用uboot调试的时候会用到,等内核和文件系统启动后eth0和eth1仍然都是好用的。只是在调试的时候为了避免每次启动后还要重新去修改环境变量,因此我就直接更改了uboot对于网络初始化的一些代码。其实主要是设置"ethact"这个环境变量,这个变量默认是uboot初始化第一块网卡的name,另外要提出一点,即使你在at91sam9x5ek.h中默认配置了"#define CONFIG_ETHACT "macb1" "等网卡初始化后还是会改为"macb0"的,除非系统初始化的第一块网卡就是macb1(也就是eth1,uboot中称之为macb1),所以需要调整网卡初始化的顺序,在board/atmel/at91sam9x5ek/at91sam9x5ek.c的board_eth_init函数中可以看到如下所示:
int board_eth_init(bd_t *bis)
{
int rc = 0;
#ifdef CONFIG_MACB
if (has_emac0())
rc = macb_eth_initialize(0,
(void *)ATMEL_BASE_EMAC0, 0x00);
if (has_emac1())
rc = macb_eth_initialize(1,
(void *)ATMEL_BASE_EMAC1, 0x00);
#endif return rc; }
很明显系统先初始化eth0再初始化eth1,要想先初始化eth1,只需要把二者互换位置即可,这里就不再贴出代码了。(如果无需使用uboot网络调试那么这块可以不用修改的)。
另外一个对于网络的修改就是mac地址这块,也不知道咋回事,at91sam9x5ek这板子一重启后mac就丢失了,每次启动都只能随机生成一个,经常由于mac地址的变动导致系统启动后要过好久网络才能通(若想了解为何mac变动会导致网络好久才通请去了解一下arp协议你就懂了,这里不做解释)。因此为了有一个固定的mac地址,我打算在uboot中生成随机的mac地址,并把生成的mac地址保存在env分区的ethaddr或是eth1addr环境变量中,下次重启直接读取环境变量即可,免得每次都要随机生成。
修改很简单,把net/eth.c中的eth_getenv_enetaddr函数改为如下即可:
int eth_getenv_enetaddr(const char *name, uchar *enetaddr)
{
char *addr = getenv(name);
/* added by Jicky for set mac address, in case the kernel use random when boot every time */
if (addr == NULL)
{ /*set mac addr */
eth_random_addr(enetaddr);
eth_setenv_enetaddr(name, enetaddr);
saveenv();
}
else
/* add over */
eth_parse_enetaddr(addr, enetaddr);
return is_valid_ether_addr(enetaddr);
}
注意以上改动使用了随机数的lib,因此我在include/configs/
at91sam9x5ek.h中配置了
CONFIG_LIB_RAND这个选项,目的是使能随机数的库,不然编译会报错的。
(2)修改uboot环境变量
实现原理中曾提到要使用uboot去读取judge分区存放的kernel_boot_succ和rootfs_boot_succ变量值,因此需要在uboot中添加对于judge分区的读写。我这里把judge分区作为uboot环境变量的另一份存放区,因此只需仿照uboot对于env分区的读写以及一些命令行操作即可。这里我实际上用uboot又额外读取了两个分区的信息,一个是前面提到的judge分区,另外一个是user分区,user分区里面存放是内核、文件系统以及用户存放在data分区的相关大小。
judge分区存放的数据格式为:
kernel_boot_succ=yes
rootfs_boot_succ=yes
user分区存放的数据格式为:
datasize=2048
kernelsize=300000
rootfssize=2800000
其中datasize存放的用户存放入data分区的十进制文件大小,而kernelsize和rootfssize分区对应于内核和文件系统的十六进制镜像文件的大小,uboot在恢复内核和文件系统时会通过kernelsize和rootfssize从备份分区中读取相应大小的数据。
对于这两个分区大小都为1M(即8个块,每块128K),它们的读取方式与读取env一样,只不过在读取时我循环了8次,每次读取一个块(128K),若是读取成功就跳出循环,这样可以在有坏块的时候跳过坏块去读取下一块,当然写的时候亦是如此,避免了由于坏块出现而导致不能读取或是写入数据到nand flash中。
uboot对于环境变量的读取大致是先分配一个CONFIG_ENV_SIZE的buf(这里为128K),然后从配置的相关偏移地址中读取一块数据存放到buf中,最后把buf按照'\0'为分割点导入到一个env_htab中,这样env_htab中存放的便是环境变量的每个键值对,后续的查询和修改都是通过操作env_htab来完成,最后在保存环境变量时,把env_htab再按照'\0'为分割点导出到一个buf,然后把buf写入到对于的flash中即可。
因此,我仿照这些定义了一个env_htab_judge,然后把judge分区和user分区的数据分区读取之后分别导入到env_htab_judge中,最后再仿照getenv、setenv、printenv等函数的实现,额外实现了getenv_judge、setenv_judge、printenv_judge等函数,这样便可以在uboot中通过getenv_judge去获取judge和user分区的环境变量了。有一点需要注意,uboot对于env分区的环境变量是按照\0进行分割的,而我实现的judge和user分区很显然应当是按照换行符'\n'来进行分割,所以对于himport_r和hexport_r函数中的sep参数我填的都是'\n'。另外在导入user分区的环境变量到env_htab_judge时,使用himport_r函数的第5个参数flag需要设置为H_NOCLEAR,因为在导入user分区变量之前已经导入了judge分区的变量,如果不指定这个flag,那么hinport_r函数会摧毁之前的env_htab_judge然后再重新创建一个的,而我是要两个分区共用一个htab,所以这个flag必须指定,目的就是告诉himport函数不需要摧毁之前的htab,接着之前的htab继续创建一些entry,具体代码实现如下:
首先需要在common目录的env_nand.c文件中添加如下代码:
/* added by Jicky */
int saveenv_judge(void)
{
int ret = 1;
ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
int i, num = CONFIG_ENV_JUDGE_SIZE / CONFIG_ENV_SIZE;
ssize_t len;
memset(buf, 0, CONFIG_ENV_SIZE);
len = hexport_r(&env_htab_judge, '\n', 0, &buf, ENV_SIZE, 0, NULL);
if (len < 0) {
error("Cannot export environment: errno = %d\n", errno);
return 1;
}
for (i = 0; i < num; i++)
{
struct env_location location = {
.name = "JUDGE",
.erase_opts = {
.length = CONFIG_ENV_RANGE * (i + 1),
.offset = CONFIG_ENV_JUDGE_OFFSET,
},
};
ret = erase_and_write_env(&location, (u_char *)buf);
if (ret == 0)
break;
}
return ret;
}
void env_relocate_spec_user(void)
{
#if defined(CONFIG_ENV_USER_OFFSET) && defined(CONFIG_ENV_USER_SIZE)
int ret = 1;
ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
const char *default_environment_user = "kernelsize=300000\nrootfssize=1e00000\n\n";
int i, num = CONFIG_ENV_USER_SIZE / CONFIG_ENV_SIZE;
for (i = 0; i < num; i++)
{
ret = readenv(CONFIG_ENV_USER_OFFSET + i * CONFIG_ENV_SIZE, (u_char *)buf);
if (ret == 0)
break;
}
if (ret) {
himport_r(&env_htab_judge, (char *)default_environment_user,
sizeof(default_environment_user), '\n', H_NOCLEAR, 0,
0, NULL);
return;
}
if (himport_r(&env_htab_judge, (char *)buf, ENV_SIZE, '\n', H_NOCLEAR, 0,
0, NULL) == 0) {
himport_r(&env_htab_judge, (char *)default_environment_user,
sizeof(default_environment_user), '\n', H_NOCLEAR, 0,
0, NULL);
}
#endif
}
void env_relocate_spec_judge(void)
{
#if defined(CONFIG_ENV_JUDGE_OFFSET) && defined(CONFIG_ENV_JUDGE_SIZE)
int ret;
ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
int i, num = CONFIG_ENV_JUDGE_SIZE / CONFIG_ENV_SIZE;
for (i = 0; i < num; i++)
{
ret = readenv(CONFIG_ENV_JUDGE_OFFSET + i * CONFIG_ENV_SIZE, (u_char *)buf);
if (ret == 0)
break;
}
if (ret) {
set_default_env_judge("!readenv() in judge partition failed");
return;
}
if (himport_r(&env_htab_judge, (char *)buf, ENV_SIZE, '\n', 0, 0, 0, NULL) == 0
|| getenv_judge("kernel_boot_succ") == NULL
|| getenv_judge("rootfs_boot_succ") == NULL) {
set_default_env_judge("!import env in judge partition to env_htab_judge failed");
}
#endif
}
在common/env_common.c中添加:
/* added by Jicky */
struct hsearch_data env_htab_judge = {
.change_ok = env_flags_validate,
};
void set_default_env_judge(const char *s)
{
int flags = 0;
const char *default_environment_judge = "kernel_boot_succ=yes\nrootfs_boot_succ=yes\n\n";
if (s) {
if (*s == '!') {
printf("*** Warning - %s, "
"using default environment in judge\n\n",
s + 1);
} else {
flags = H_INTERACTIVE;
puts(s);
}
} else {
puts("Using default environment in judge\n\n");
}
if (himport_r(&env_htab_judge, (char *)default_environment_judge,
sizeof(default_environment_judge), '\n', flags, 0,
0, NULL) == 0)
error("Environment in judge partition import failed: errno = %d\n", errno);
}
并在env_relocate函数中对于env_relocate_spec调用的后面添加
env_relocate_spec();
/* added by Jicky */
env_relocate_spec_judge();
env_relocate_spec_user();
并在include/environment.h中添加相关的函数声明,如下:
extern struct hsearch_data env_htab_judge;
void set_default_env_judge(const char *s);
extern void env_relocate_spec_judge(void);
extern void env_relocate_spec_user(void);
最后在common目录下新建一个cmd_nvedit_judge.c添加如下代码:
/*
* (C) Copyright 2000-2013
* Wolfgang Denk, DENX Software Engineering, [email protected].
*
* (C) Copyright 2001 Sysgo Real-Time Solutions, GmbH
* Andreas Heppel
*
* Copyright 2011 Freescale Semiconductor, Inc.
*
* SPDX-License-Identifier: GPL-2.0+
*/
/*
* Support for persistent environment data
*
* The "environment" is stored on external storage as a list of '\0'
* terminated "name=value" strings. The end of the list is marked by
* a double '\0'. The environment is preceeded by a 32 bit CRC over
* the data part and, in case of redundant environment, a byte of
* flags.
*
* This linearized representation will also be used before
* relocation, i. e. as long as we don't have a full C runtime
* environment. After that, we use a hash table.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
DECLARE_GLOBAL_DATA_PTR;
#if !defined(CONFIG_ENV_IS_IN_EEPROM) && \
!defined(CONFIG_ENV_IS_IN_FLASH) && \
!defined(CONFIG_ENV_IS_IN_DATAFLASH) && \
!defined(CONFIG_ENV_IS_IN_MMC) && \
!defined(CONFIG_ENV_IS_IN_FAT) && \
!defined(CONFIG_ENV_IS_IN_NAND) && \
!defined(CONFIG_ENV_IS_IN_NVRAM) && \
!defined(CONFIG_ENV_IS_IN_ONENAND) && \
!defined(CONFIG_ENV_IS_IN_SPI_FLASH) && \
!defined(CONFIG_ENV_IS_IN_REMOTE) && \
!defined(CONFIG_ENV_IS_IN_UBI) && \
!defined(CONFIG_ENV_IS_NOWHERE)
# error Define one of CONFIG_ENV_IS_IN_{EEPROM|FLASH|DATAFLASH|ONENAND|\
SPI_FLASH|NVRAM|MMC|FAT|REMOTE|UBI} or CONFIG_ENV_IS_NOWHERE
#endif
#ifndef CONFIG_SPL_BUILD
/*
* Command interface: print one or all environment variables
*
* Returns 0 in case of error, or length of printed string
*/
static int env_print_judge(char *name, int flag)
{
char *res = NULL;
ssize_t len;
if (name) { /* print a single name */
ENTRY e, *ep;
e.key = name;
e.data = NULL;
hsearch_r(e, FIND, &ep, &env_htab_judge, flag);
if (ep == NULL)
return 0;
len = printf("%s=%s\n", ep->key, ep->data);
return len;
}
/* print whole list */
len = hexport_r(&env_htab_judge, '\n', flag, &res, 0, 0, NULL);
if (len > 0) {
puts(res);
free(res);
return len;
}
/* should never happen */
printf("## Error: cannot export environment\n");
return 0;
}
static int do_env_print_judge(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
int i;
int rcode = 0;
int env_flag = H_HIDE_DOT;
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'a') {
argc--;
argv++;
env_flag &= ~H_HIDE_DOT;
}
if (argc == 1) {
/* print all env vars */
rcode = env_print_judge(NULL, env_flag);
if (!rcode)
return 1;
printf("\nEnvironment size: %d/%ld bytes\n",
rcode, (ulong)ENV_SIZE);
return 0;
}
/* print selected env vars */
env_flag &= ~H_HIDE_DOT;
for (i = 1; i < argc; ++i) {
int rc = env_print_judge(argv[i], env_flag);
if (!rc) {
printf("## Error: \"%s\" not defined\n", argv[i]);
++rcode;
}
}
return rcode;
}
#endif /* CONFIG_SPL_BUILD */
/*
* Set a new environment variable,
* or replace or delete an existing one.
*/
static int _do_env_set_judge(int flag, int argc, char * const argv[])
{
int i, len;
char *name, *value, *s;
ENTRY e, *ep;
int env_flag = H_INTERACTIVE;
debug("Initial value for argc=%d\n", argc);
while (argc > 1 && **(argv + 1) == '-') {
char *arg = *++argv;
--argc;
while (*++arg) {
switch (*arg) {
case 'f': /* force */
env_flag |= H_FORCE;
break;
default:
return CMD_RET_USAGE;
}
}
}
debug("Final value for argc=%d\n", argc);
name = argv[1];
value = argv[2];
if (strchr(name, '=')) {
printf("## Error: illegal character '='"
"in variable name \"%s\"\n", name);
return 1;
}
/* Delete only ? */
if (argc < 3 || argv[2] == NULL) {
int rc = hdelete_r(name, &env_htab_judge, env_flag);
return !rc;
}
/*
* Insert / replace new value
*/
for (i = 2, len = 0; i < argc; ++i)
len += strlen(argv[i]) + 1;
value = malloc(len);
if (value == NULL) {
printf("## Can't malloc %d bytes\n", len);
return 1;
}
for (i = 2, s = value; i < argc; ++i) {
char *v = argv[i];
while ((*s++ = *v++) != '\0')
;
*(s - 1) = ' ';
}
if (s != value)
*--s = '\0';
e.key = name;
e.data = value;
hsearch_r(e, ENTER, &ep, &env_htab_judge, env_flag);
free(value);
if (!ep) {
printf("## Error inserting \"%s\" variable, errno=%d\n",
name, errno);
return 1;
}
return 0;
}
int setenv_judge(const char *varname, const char *varvalue)
{
const char * const argv[4] = { "setenv", varname, varvalue, NULL };
/* before import into hashtable */
if (!(gd->flags & GD_FLG_ENV_READY))
return 1;
if (varvalue == NULL || varvalue[0] == '\0')
return _do_env_set_judge(0, 2, (char * const *)argv);
else
return _do_env_set_judge(0, 3, (char * const *)argv);
}
#ifndef CONFIG_SPL_BUILD
static int do_env_set_judge(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
if (argc < 2)
return CMD_RET_USAGE;
return _do_env_set_judge(flag, argc, argv);
}
#endif /* CONFIG_SPL_BUILD */
/*
* Look up variable from environment,
* return address of storage for that variable,
* or NULL if not found
*/
char *getenv_judge(const char *name)
{
if (gd->flags & GD_FLG_ENV_READY) { /* after import into hashtable */
ENTRY e, *ep;
WATCHDOG_RESET();
e.key = name;
e.data = NULL;
hsearch_r(e, FIND, &ep, &env_htab_judge, 0);
return ep ? ep->data : NULL;
}
return NULL;
}
#ifndef CONFIG_SPL_BUILD
#if defined(CONFIG_CMD_SAVEENV) && !defined(CONFIG_ENV_IS_NOWHERE)
static int do_env_save_judge(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
printf("Saving Environment to judge partition\n");
return saveenv_judge() ? 1 : 0;
}
U_BOOT_CMD(
saveenv_judge, 1, 0, do_env_save_judge,
"save environment variables to judge partition",
""
);
#endif
U_BOOT_CMD_COMPLETE(
printenv_judge, CONFIG_SYS_MAXARGS, 1, do_env_print_judge,
"print environment variables of judge partition",
"[-a]\n - print [all] values of all environment variables\n"
"printenv name ...\n"
" - print value of environment variable 'name'",
var_complete
);
U_BOOT_CMD_COMPLETE(
setenv_judge, CONFIG_SYS_MAXARGS, 0, do_env_set_judge,
"set environment variables of judge partition",
"[-f] name value ...\n"
" - [forcibly] set environment variable 'name' to 'value ...'\n"
"setenv [-f] name\n"
" - [forcibly] delete environment variable 'name'",
var_complete
);
#endif /* CONFIG_SPL_BUILD */
并把此文件添加到makefile的编译中。
最后修改common/autoboot.c中的autoboot_command函数如下所示:
void autoboot_command(const char *s)
{
#if defined(CONFIG_RECOVER_KERNEL) && defined(CONFIG_RECOVER_ROOTFS) /* changed by Jicky */
char bootcmd[512] = {0};
int ret1 = strcmp(getenv_judge("kernel_boot_succ"), "yes");
int ret2 = strcmp(getenv_judge("rootfs_boot_succ"), "yes");
char *kernelsize = getenv_judge("kernelsize");
char *rootfssize = getenv_judge("rootfssize");
int len = 0;
if (ret1 != 0 && ret2 != 0) /*当内核和文件系统都损坏时把stored_bootdelay设置为1s使得可以进入uboot命令行*/
stored_bootdelay = 1;
#endif
debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "");
if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
int prev = disable_ctrlc(1); /* disable Control C checking */
#endif
#if defined(CONFIG_RECOVER_KERNEL) && defined(CONFIG_RECOVER_ROOTFS) /* changed by Jicky */
if (simple_strtoul(kernelsize, NULL, 16) < 0x200000) /*内核大小最小2M*/
kernelsize = "300000";
if (simple_strtoul(rootfssize, NULL, 16) < 0x1e00000) /*文件系统大小最小30M*/
rootfssize = "2800000";
if (ret1 != 0 && ret2 == 0) { /*说明内核损坏需要从备份中恢复*/
len = sprintf(bootcmd, "nand read 0x22000000 bak_kernel %s; "\
"nand erase.part kernel; nand write 0x22000000 kernel %s; ", kernelsize, kernelsize);
printf("^^^^^^^ the kernel is damaged, recover it! ^^^^^^^\n");
} else if (ret1 == 0 && ret2 != 0) { /*说明文件系统损坏需要从备份中恢复*/
len = sprintf(bootcmd, "nand read 0x22000000 bak_rootfs %s; "\
"nand erase.part rootfs; nand write.trimffs 0x22000000 rootfs %s; ", rootfssize, rootfssize);
printf("^^^^^^^ the rootfs is damaged, recover it! ^^^^^^^\n");
} else if (ret1 != 0 && ret2 != 0) { /*说明内核和文件系统全都损坏了需要在启动之前恢复备份*/
len = sprintf(bootcmd, "nand read 0x22000000 bak_rootfs %s; "\
"nand erase.part rootfs; nand write.trimffs 0x22000000 rootfs %s; "\
"nand read 0x22000000 bak_kernel %s; "\
"nand erase.part kernel; nand write 0x22000000 kernel %s; ",
rootfssize, rootfssize, kernelsize, kernelsize);
printf("^^^^^^^ both the kernel and rootfs are damaged, recover them! ^^^^^^^\n");
} else {
//printf("^^^^^^^ both the kernel and rootfs are good! ^^^^^^^\n");
}
if (ret1 != 0) /*说明内核需要恢复,由于之前已经把内核拷贝到0x22000000,所以此处直接使用bootm启动*/
sprintf(bootcmd + len, "bootm 0x22000000");
else
sprintf(bootcmd + len, "nand read 0x22000000 kernel %s; bootm 0x22000000", kernelsize);
setenv_judge("kernel_boot_succ", "no"); /* 内核启动成功会恢复为"yes" */
setenv_judge("rootfs_boot_succ", "yes"); /* 内核启动成功后会设置为"no",文件系统启动成功会恢复为"yes" */
/*删除kernelsize,rootfssize,datasize这几个环境变量,避免下次重启时环境变量重复,因为这些变量每次启动时都是从user分区导出来的*/
setenv_judge("kernelsize", NULL);
setenv_judge("rootfssize", NULL);
setenv_judge("datasize", NULL);
saveenv_judge();
run_command_list(bootcmd, -1, 0);
#else
run_command_list(s, -1, 0);
#endif
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
disable_ctrlc(prev); /* restore Control C checking */
#endif
}
#ifdef CONFIG_MENUKEY
if (menukey == CONFIG_MENUKEY) {
s = getenv("menucmd");
if (s)
run_command_list(s, -1, 0);
}
#endif /* CONFIG_MENUKEY */
}
执行:make at91sam9x5ek_nandflash_defconfig && make
即可编译出u-boot.bin
3.修改内核(本文内核使用的是linux-at91-linux-2.6.39-at91)
前面实现原理中曾提到内核启动成功后会把judge分区中的环境变量分别设置为"kernel_boot_succ=yes\nrootfs_boot_succ=no\n\n"表明内核启动成功,文件系统尚未启动成功,因此需要在内核引导文件系统之前添加对于judge分区这两个变量的设置,即对judge分区进行处理。
在driver/mtd/mtdcore.c中添加如下代码:
/* 内核启动成功后,在尚未加载文件系统之前,
* 把judge分区的内核启动成功(kernel_boot_succ)环境变量设置为"yes"
* judge分区存放的环境变量为:"kernel_boot_succ=no\nrootfs_boot_succ=no\n\n"由uboot设置
* added by Jicky */
void mtd_judge_part_deal(void)
{
struct mtd_info *mtd = get_mtd_device_nm("judge");
struct erase_info erase;
u_char *buf = NULL;
size_t len = 0;
int i;
if (IS_ERR(mtd)) {
printk(KERN_WARNING "the judge mtd partition isn't exist!\n");
return;
}
buf = kzalloc(mtd->writesize, GFP_KERNEL);
if (IS_ERR_OR_NULL(buf)) {
printk(KERN_ERR "kzalloc for judge partition to store env fail!\n");
put_mtd_device(mtd);
return;
}
strcpy(buf, "kernel_boot_succ=yes\nrootfs_boot_succ=no\n\n");
memset(&erase, 0, sizeof(erase));
erase.mtd = mtd;
erase.addr = 0;
erase.len = mtd->erasesize;
for (i = 0; i < 8; i++, erase.addr += mtd->erasesize) { /*judge分区总共8个块*/
if (mtd->block_isbad(mtd, erase.addr) == 0)
continue;
if (mtd->erase(mtd, &erase) == 0) {
mtd->block_markbad(mtd, erase.addr);
continue;
}
if (mtd->write(mtd, 0, mtd->writesize, &len, buf) != 0) {
printk(KERN_ERR "write judge partition env fail!\n");
mtd->block_markbad(mtd, erase.addr);
} else {
printk(KERN_INFO "write judge partition env succ!\n");
break;
}
}
kfree(buf);
put_mtd_device(mtd);
}
此函数先获取judge的mtd_info结构体,然后通过mtd_info擦除judge的一个块再写入
"kernel_boot_succ=yes\nrootfs_boot_succ=no\n\n"字符串,最后释放mtd_info给内核。
随后在init/main.c中的kernel_init函数中的sys_dup(0)调用之后添加mtd_judge_part_deal()函数的调用:
#if 1 /* added by Jicky */
extern void mtd_judge_part_deal(void);
#endif
static int __init kernel_init(void * unused)
{
............
(void) sys_dup(0);
(void) sys_dup(0);
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/
#if 1 /* added by Jicky for set env 'kernel_boot_succ=yes' */
mtd_judge_part_deal();
#endif
最后编译内核
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
mkimage -A arm -O linux -C none -T kernel -a 20008000 -e 20008000 -n linux-2.6 -d arch/arm/boot/zImage uImage-2.6.39-at91sam9x5ek.bin
#更新模块之间的依赖关系
depmod -b $PWD -e -F $PWD/System.map -v 2.6.39 -A -a
4.修改文件系统启动脚本(本文使用的文件系统是buildroot-2015.08.1)
在生成文件系统output/target/etc/init.d/目录下任意一个启动脚本中添加如下内容:
set_judge()
{
if grep -q 'mtd3: 00100000 00020000 "judge"' /proc/mtd; then
echo -ne "kernel_boot_succ=yes\nrootfs_boot_succ=yes\n\n" > /tmp/judge
flash_erase /dev/mtd3 0 1 && nandwrite -m -p /dev/mtd3 /tmp/judge
if [ $? -eq 0 ]; then
right "set judge partition successful!"
else
error "set judge partition fail!"
fi
rm /tmp/judge &> /dev/null
else
error "judge partition isn't exist!"
fi
}
并把set_judge函数加入到start函数中,这段脚本就是在文件系统启动后擦除judge分区,然后把两个启动成功的标志改为"yes"再写入到judge分区中。
注意:上述脚本函数中使用到了flash_erase、nandwrite等命令,这些命令需要在buildroot中编译mtd-tools才能得到。
至此,整个BSP的修改结束,剩下的便是烧写镜像了,另外在我的实现中,如果后续板子需要更换内核或是文件系统,只需要在系统中使用flash_erase和nandwrite等命令把新的内核或是文件系统写入到bak_kernel或是bak_rootfs分区中,然后把judge分区的相应标志设置为"no"即可在下次重启后自动更新。不过有一点需要注意,如果需要直接在系统中使用命令进行升级更新内核和文件系统,默认是不允许的,因为我规划的bak_kernel和bak_rootfs分区是只读的,这个在bootargs的启动参数里有定义。那么如果你真的想在系统中直接升级更新可以使用我额外编写的一个小驱动,它的目的就是用来设置相应的分区为可写,代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
static char *mtdname = "bootstrap/kernel";
module_param(mtdname, charp, 0644);
static uint offset = 0;
module_param(offset, uint, 0644);
static uint size = 0;
module_param(size, uint, 0644);
static int __init io_init(void)
{
struct mtd_info *mtd = get_mtd_device_nm(mtdname);
struct erase_info erase;
uint left = 0;
if (IS_ERR(mtd))
{
printk(KERN_WARNING "the judge mtd partition isn't exist!");
return - 1;
}
printk(KERN_INFO "get %s mtd device info succ!\n", mtdname);
mtd->flags |= MTD_WRITEABLE; //设置为可写
if (size >= 128 * 1024)
{
printk(KERN_INFO "erase %s mtd device, offset: %u, size: %u\n", mtdname, offset, size);
left = size % mtd->erasesize;
if (left)
size += mtd->erasesize - left;
memset(&erase, 0, sizeof(erase));
erase.mtd = mtd;
erase.addr = offset;
erase.len = size;
mtd->erase(mtd, &erase);
}
put_mtd_device(mtd);
return -1;
}
static void __exit io_exit(void)
{
}
module_init(io_init);
module_exit(io_exit);
MODULE_AUTHOR("Hikvision Corporation");
MODULE_DESCRIPTION("Signal machine main controller board erase flash driver");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_VERSION("1.0.0");
ifneq ($(KERNELRELEASE),)
obj-m := writeable.o
else
#KERNELDIR ?= /lib/modules/$(shell uname -r)/build
KERNELDIR ?= /usr/jay/kernel_code/linux-at91-linux-2.6.39-at91
PWD := $(shell pwd)
default: clean
make -C $(KERNELDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
rm -rf *.o .*.cmd .tmp_versions *.mod.c modules.order Module.symvers *.ko.unsigned
clean:
$(RM) -r *.ko *.o .*.cmd .tmp_versions *.mod.c modules.order Module.symvers *.ko.unsigned
endif
执行insmod writeable.ko mtdname=bak_kernel或是bak_rootfs便可以设置相应的分区为可写,即使驱动加载是失败的。
随后便可以使用flash_erase和nandwrite对此mtd分区进行擦除和写入了。
另外在我的设计中,data分区存放的是用户常用目录的生成的压缩包,datasize存放在user分区上文已经提到过,系统启动后如果在常用目录没有找到相应的可执行程序则会从data分区中进行恢复,例如文件系统损坏恢复后常用目录里面肯定都是空的,这时就需要从data分区中恢复用户数据了,而且在用户对系统的某个控制程序进行升级时也会备份一份到data分区,这样其实也实现了用户数据的备份机制。这些操作也需要添加了文件系统的启动脚本中,这里就不再展示具体内容实现了。
最后给出制作ubifs文件系统的脚本,因为我觉得文件系统通过buildroot编译好之后,还需要对新生成的文件系统做一些额外的调整或是裁剪,例如一些不用的命令要删除,修改一些脚本之内的,每次修改完还是再次通过buildroot来编译太过麻烦,因此我直接做了一个脚本方便生成ubifs镜像,脚本如下:
#!/bin/sh
dir=`pwd`
#逻辑擦除块大小
UBIFS_LEBSIZE=0x1f000
#最小的输入输出大小
UBIFS_MINIOSIZE=0x800
#最大逻辑擦除块数量
UBIFS_MAXLEBCNT=2048
# -x 的相关选项
UBIFS_RT_NONE=
UBIFS_RT_ZLIB=
UBIFS_RT_LZO=y
#物理擦除块大小
UBI_PEBSIZE=0x20000
#子页面大小
UBI_SUBSIZE=2048
#制作ubifs的其他配置选项
UBIFS_OPTS=
#使用ubinize制作rootfs.ubi的选项
UBI_OPTS=
MKFS_UBIFS_OPTS="-e $UBIFS_LEBSIZE -c $UBIFS_MAXLEBCNT -m $UBIFS_MINIOSIZE"
if [ -n "$UBIFS_RT_NONE" ]; then
MKFS_UBIFS_OPTS="$MKFS_UBIFS_OPTS -x none"
fi
if [ -n "$UBIFS_RT_ZLIB" ]; then
MKFS_UBIFS_OPTS="$MKFS_UBIFS_OPTS -x zlib"
fi
if [ -n "$UBIFS_RT_LZO" ]; then
MKFS_UBIFS_OPTS="$MKFS_UBIFS_OPTS -x lzo"
fi
MKFS_UBI_OPTS="-m $UBIFS_MINIOSIZE -p $UBI_PEBSIZE"
if [ -n "$UBI_OPTS" -a "$UBI_OPTS" != '""' ]; then
MKFS_UBI_OPTS="$MKFS_UBI_OPTS $UBI_OPTS"
fi
if [ -n "$UBI_SUBSIZE" ]; then
MKFS_UBI_OPTS="$MKFS_UBI_OPTS -s $UBI_SUBSIZE"
fi
MKFS_UBIFS_CMD=$dir/tools/mkfs.ubifs
UBINIZE_CMD=$dir/tools/ubinize
UBINIZE_CFG_PATH=$dir/tools/ubinize.cfg
TARGET_FILE_DIR=$dir/target
ROOTFS_UBIFS_PATH=$dir/images/rootfs.ubifs
ROOTFS_UBI_PATH=$dir/images/rootfs.ubi
rm $ROOTFS_UBIFS_PATH $ROOTFS_UBI_PATH &> /dev/null
#首先生成可供uboot中使用ubi write烧写的rootfs.ubifs
$MKFS_UBIFS_CMD -d $TARGET_FILE_DIR $MKFS_UBIFS_OPTS -o $ROOTFS_UBIFS_PATH
if [ $? -ne 0 ]; then
echo 'generate rootfs.ubifs fail'
exit 1
else
echo 'generate rootfs.ubifs succ'
fi
#接着使用ubinize生成可直接使用nand write烧写的rootfs.ubi
/bin/cp $UBINIZE_CFG_PATH . && echo "image=$ROOTFS_UBIFS_PATH" >> ubinize.cfg
$UBINIZE_CMD -o $ROOTFS_UBI_PATH $MKFS_UBI_OPTS ubinize.cfg
if [ $? -ne 0 ]; then
echo 'generate rootfs.ubi fail'
exit 1
else
echo 'generate rootfs.ubi succ'
fi
rm -f ubinize.cfg[ubifs]
mode=ubi
vol_id=0
vol_type=dynamic
vol_name=rootfs
vol_alignment=1
vol_flags=autoresize
PS:第一次写博客,花了一下午的时间,如有描述不对之处,欢迎各位大神来拍砖,也希望可以对后续同行有所帮助!