版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接:https://blog.csdn.net/jackyzjk/article/details/50363740
前言:前陣子公司使用的基於at91sam9x5ek的板子出現了好幾塊系統無法啓動的問題,用串口打印顯示要不就是文件系統損壞,要不就是內核損壞了,排除了人爲誤操作的原因幾乎就可以斷定應該是存放內核或是文件系統的nand flash出現了壞塊所致。鑑於此,想重新規劃nand flash分區,給內核和文件系統都規劃一個備份分區,使得在內核或是文件系統損壞時可以從備份分區中修復。
概述:衆所周知,at91一般是由bootstrap先開始引導(當然其實前面還有一點固化在rom中的引導代碼由於這塊不能更改這裏就不提及了),bootstrap初始化一部分硬件之後會把下一級的引導鏡像拷貝在SDRAM中的一個固定地址上(對於at91sam9x5ek這個地址是0x22000000),這個鏡像可以是uboot鏡像也可以是kernel鏡像,也就是說bootstrap可以跳過uboot直接引導內核,公司之前的板子就是這樣,直接用bootstrap引導內核,雖然這樣引導速度略有提升,然而卻不好維護,因爲沒有uboot引導這一項,bootstrap功能太過單一,所以板子一出問題只能更換或是用samba重新燒寫,很是麻煩。若想實現備份自動恢復的功能,那麼就必須要藉助uboot的強大功能。
先說一下實現原理吧,首先規劃一下nand flash分區,如下所示:
<span style="font-size:18px;color:#ff9966;"><strong>mtd name offset size blocks
mtd0 bootstrap: 0x0 256k 2
mtd1 uboot 0x40000 512k 4
mtd2 env 0xc0000 256k 2
mtd3 judge 0x100000 1M 8
mtd4 user 0x200000 1M 8
mtd5 bak_kernel 0x300000 5M 40
mtd6 kernel 0x800000 5M 40
mtd7 rootfs 0xd00000 143M 1144
mtd8 data 0x9c00000 40M 320
mtd9 bak_rootfs 0xc400000 60M 480</strong></span>
各分區的作用如name所示,裏面最核心的一個分區就是judge分區,自動恢復的實現全依賴於它裏面存放的數據,其實它存放的數據很簡單,只有兩行數據"kernel_boot_succ=yes(or no)\nrootfs_boot_succ=yes(or no)",uboot啓動後會去讀取judge分區裏面的這兩個值,其中kernel_boot_succ存放是上一次內核是否啓動成功,而rootfs_boot_succ則存放的是上一次文件系統是否啓動成功。如果檢查到kernel_boot_succ=no則使用nand read命令從bak_kernel分區中拷貝出內核鏡像再次nand write寫入到kernel分區(寫入之前當然要用nand erase擦除一下了),同理rootfs_boot_succ是針對文件系統的備份恢復,uboot在啓動內核之前會把這兩個變量設置爲"kernel_boot_succ=no\nrootfs_boot_succ=yes\n\n"隨後啓動內核,內核若是啓動成功便會再把這兩個值改爲"kernel_boot_succ=yes\nrootfs_boot_succ=no\n\n",顯而易見文件系統啓動成功後把這兩個值全部設置爲yes。若是內核或是文件系統沒有啓動成功,那麼他們對應的標誌就會是no,由於任一啓動失敗最終都會導致watchdog重啓機制,所以下次重啓後uboot便可以通過判斷這兩個變量來決定是否需要進行備份恢復了,當然了,前提是要你在bootstrap和uboot階段把watchdog打開才行。
原理大致如此,下面說說具體的實現吧。
首先第一步去at官網下載bootstrap、uboot、kernel、還有buildroot的源碼包,網址如下:http://www.at91.com/linux4sam/bin/view/Linux4SAM,at91的官網有詳細介紹下載源碼的路徑以及每一項的編譯方法,這裏就不再贅述。
第二步修改源碼
1.修改bootstrap(本文使用的bootstrap爲at91bootstrap-at91bootstrap-3.5.x)
前面說過需要使用watchdog重啓機制,然而默認的bootstrap配置是沒有使能watchdog的,因此需要做一修改,有兩種方式,第一種通過make menuconfig配置去掉disable watchdog的選項,第二種直接修改源碼,由於之前的同事直接在源碼中進行更改的,所以我也沒有再使用make menuconfig進行配置,直接參照他的了,在driver/at91_wdt.c中增加兩個函數,具體如下:
void at91_enable_wdt(void)
{
writel(AT91C_WDTC_WDV | AT91C_WDTC_WDD | AT91C_WDTC_WDRSTEN , AT91C_BASE_WDT + WDTC_MR);
}
void at91_reset_wdt(void)
{
writel(AT91C_WDTC_WDRSTT | AT91C_WDTC_KEY, AT91C_BASE_WDT + WDTC_CR);
}
顯然一個是使能watchdog的函數,一個是喂狗函數,然後在board/at91sam9x5ek/at91sam9x5ek.c的hw_init()函數中註釋掉"at91_disable_wdt();"這一行,然後添加"at91_enable_wdt();"也就是在硬件初始化時打開watchdog開關,至於喂狗函數幾乎用不上,除非你想在bootstrap中做一些其他比較耗時的操作,那麼這時你便需要在適當的位置添加"at91_reset_wdt();"然後執行:
make at91sam9x5eknf_uboot_defconfig
其實就是把board/at91sam9x5ek/at91sam9x5eknf_uboot_defconfig這個文件作爲.config而已,這是at91給出的默認bootstrap引導uboot的配置,然後執行:
<span style="font-size:18px;">make CROSS_COMPILE=arm-none-linux-gnueabi-</span>
既可以在binaries目錄下生成at91sam9x5ek-nandflashboot-uboot-3.5.4.bin(也就是bootstrap的鏡像文件),這個文件可以直接用samba工具燒寫到板子上作爲引導文件。
由於at91sam9x5ek對nand flash增加了PMECC的校驗,如果想使用uboot進行燒寫,在at91官網中你可以看到在使用uboot燒寫bootstrap之前需要增加以下PMECC校驗的頭以及長度等等,如果不想麻煩在uboot敲那麼多命令,可以直接編寫一個程序給上述的bin文件增加上這個頭部校驗信息,我的代碼實現如下:
#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<span style="white-space:pre"> </span>/*使能watchdog的兩個配置選項*/
#define CONFIG_AT91SAM9_WATCHDOG
#define CONFIG_ENV_OVERWRITE<span style="white-space:pre"> </span>/*環境變量可以重新修改,不配此項有些環境變量無法進行修改*/
#define CONFIG_LIB_RAND<span style="white-space:pre"> </span>/*使用隨機數的庫,獲取隨機的mac地址會用到*/
#if 0<span style="white-space:pre"> </span>/*以下是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<span style="white-space:pre"> </span>/*配置網絡相關的uboot命令,實際上如果配置了下面幾項之後默認會配置此項的*/
#define CONFIG_IPADDR 172.7.18.99<span style="white-space:pre"> </span>/*默認的ip地址*/
#define CONFIG_NETMASK 255.255.255.0<span style="white-space:pre"> </span>/*默認的子網掩碼*/
#define CONFIG_GATEWAYIP 172.7.18.1<span style="white-space:pre"> </span>/*默認網關*/
#define CONFIG_SERVERIP 172.7.18.200<span style="white-space:pre"> </span>/*默認的遠程tftp或是nfs服務器的ip*/
#define CONFIG_EXTRA_ENV_SETTINGS \<span style="white-space:pre"> </span>/*額外的環境變量設置,這裏主要用來啓動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 */<span style="white-space:pre"> </span>/*nand flash PMECC相關的配置*/
#define CONFIG_ATMEL_NAND_HWECC<span style="white-space:pre"> </span>1
#define CONFIG_ATMEL_NAND_HW_PMECC<span style="white-space:pre"> </span>1
#define CONFIG_PMECC_CAP<span style="white-space:pre"> </span>4<span style="white-space:pre"> </span>/*changed by Jicky, the origin is 2*/
#define CONFIG_PMECC_SECTOR_SIZE<span style="white-space:pre"> </span>512
#define CONFIG_PREBOOT "mtdparts default"<span style="white-space:pre"> </span>/*uboot啓動預先執行的命令*/
/*默認分區的相關配置*/
#define MTDPARTS_DEFAULT "mtdparts=atmel_nand:256k(bootstrap)ro,512k(uboot)ro,"<span style="white-space:pre"> </span>\
<span style="white-space:pre"> </span>"256k(env)ro,1M(judge),1M(user),5M(bak_kernel)ro,"<span style="white-space:pre"> </span>\
<span style="white-space:pre"> </span>"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<span style="white-space:pre"> </span>0xc0000
#define CONFIG_ENV_SIZE<span style="white-space:pre"> </span>0x20000<span style="white-space:pre"> </span>/* 1 sector = 128 kB */
#define CONFIG_ENV_JUDGE_OFFSET<span style="white-space:pre"> </span>0x100000
#define CONFIG_ENV_JUDGE_SIZE<span style="white-space:pre"> </span>0x100000
#define CONFIG_ENV_USER_OFFSET<span style="white-space:pre"> </span>0x200000
#define CONFIG_ENV_USER_SIZE<span style="white-space:pre"> </span>0x100000
#define CONFIG_RECOVER_KERNEL<span style="white-space:pre"> </span>/*恢復內核*/
#define CONFIG_RECOVER_ROOTFS<span style="white-space:pre"> </span>/*恢復文件系統*/<span style="white-space:pre"> </span>
/*uboot引導內核相關的兩個參數*/
#define CONFIG_BOOTCOMMAND<span style="white-space:pre"> </span>"nand read " \
<span style="white-space:pre"> </span>"0x22000000 kernel 0x300000; " \
<span style="white-space:pre"> </span>"bootm 0x22000000"
#define CONFIG_BOOTARGS<span style="white-space:pre"> </span>\
<span style="white-space:pre"> </span>"console=ttyS0,115200 earlyprintk "<span style="white-space:pre"> </span>\
<span style="white-space:pre"> </span>"mtdparts=atmel_nand:256k(bootstrap)ro,512k(uboot)ro,"<span style="white-space:pre"> </span>\
<span style="white-space:pre"> </span>"256k(env)ro,1M(judge),1M(user),5M(bak_kernel)ro,"<span style="white-space:pre"> </span>\
<span style="white-space:pre"> </span>"5M(kernel)ro,143M(rootfs),40M(data),-(bak_rootfs)ro "<span style="white-space:pre"> </span>\
<span style="white-space:pre"> </span>"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);
<pre name="code" class="csharp" style="font-size: 18px;"> 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文件中添加如下代碼:
<pre name="code" class="cpp">/* 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調用的後面添加
<span style="white-space:pre"> </span>env_relocate_spec();
<span style="white-space:pre"> </span>/* added by Jicky */
<span style="white-space:pre"> </span>env_relocate_spec_judge();
<span style="white-space:pre"> </span>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 : "<UNDEFINED>");
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)
{
<span style="white-space:pre"> </span>............
(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:第一次寫博客,花了一下午的時間,如有描述不對之處,歡迎各位大神來拍磚,也希望可以對後續同行有所幫助!