前言:前陣子公司使用的基于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:第一次寫部落格,花了一下午的時間,如有描述不對之處,歡迎各位大神來拍磚,也希望可以對後續同行有所幫助!