1.适用平台
ARMV8架构芯片,小端存储,uboot版本为2019.07-Ver1.7.4。为了省事,代码里默认是小端程序,如果是大端存储,需要修改部分代码。操作系统为某国产系统,不是Linux。但是基本解析代码是与平台无关的。
2.uboot环境变量存储格式解析
读取uboot代码,发现使用uboot版本有两种格式的uboot env。下面贴出格式代码:
struct env_image_single {
uint32_t crc; /* CRC32 over data bytes */
char data[];
};
struct env_image_redundant {
uint32_t crc; /* CRC32 over data bytes */
unsigned char flags; /* active or obsolete */
char data[];
};
两种格式是由uboot中宏定义 CONFIG_SYS_REDUNDAND_ENVIRONMENT 决定的,我的uboot使用的是上面那种。首先是4个字节的CRC校验码,后面紧跟着的就是所有的环境变量。
下面那种格式中的lags不太清楚是什么意思,似乎是标志着当前uboot使用哪个环境变量。粗略地看了一下,似乎定义了 CONFIG_SYS_REDUNDAND_ENVIRONMENT ,就会有个冗余的uboot环境变量,那么flags很可能指示了当前环境变量是否是被使用的,还是备份的。
3.其他一些相关宏定义
我的uboot环境变量是放在spi flash中的,相关的环境变量如下:
#define ENV_HEADER_SIZE 4
#define CONFIG_ENV_OFFSET 0x70000
#define CONFIG_ENV_SIZE 4096
#define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE)
ENV_HEADER_SIZE:其实是CRC的字节数,如果加上flags,那应该就是5字节。
CONFIG_ENV_SIZE:环境变量占用空间的字节数,最大就这么多。
CONFIG_ENV_OFFSET:环境变量的偏移地址,也就是说放在FLASH的地址。
ENV_SIZE:环境变量数据部分占用的字节数,不包括CRC、flags等数据。
4.CRC相关代码
uboot加载环境变量时,会计算环境变量的CRC,与放在最前面的四个字节的CRC做比较,如果不同的话,就会从代码中加载默认的环境变量。因此每次修改环境变量都需要更新CRC的值并写入FLASH中。此部分代码是从uboot中copy出来的,不一定适用于其他版本的uboot。
crc32.c
#include <stdio.h>
#include "crc32.h"
#include "crc32table.h"
#define DO_CRC(x) crc = tab[ (crc ^ (x)) & 255 ] ^ (crc>>8)
u32 crc32_le(u32 crc, unsigned char const *p, size_t len)
{
const u32 *b =(u32 *)p;
const u32 *tab = crc32table;
/* printf("Crc32_le crc=%x\n",crc); */
/* Align it */
if((((long)b)&3 && len)){
do {
u8 *p = (u8 *)b;
DO_CRC(*p++);
b = (void *)p;
} while ((--len) && ((long)b)&3 );
}
if((len >= 4)){
/* load data 32 bits wide, xor data 32 bits wide. */
size_t save_len = len & 3;
len = len >> 2;
--b; /* use pre increment below(*++b) for speed */
do {
crc ^= *++b;
DO_CRC(0);
DO_CRC(0);
DO_CRC(0);
DO_CRC(0);
} while (--len);
b++; /* point to next byte(s) */
len = save_len;
}
/* And the last few bytes */
if(len){
do {
u8 *p = (u8 *)b;
DO_CRC(*p++);
b = (void *)p;
} while (--len);
}
return (crc);
}
/* No ones complement version. JFFS2 (and other things ?)
* don't use ones compliment in their CRC calculations.
*/
u32 crc32_no_comp(u32 crc, const u8 *buf, u32 len)
{
const u32 *tab = crc32table;
const u32 *b =(const u32 *)buf;
size_t rem_len;
#ifdef CONFIG_DYNAMIC_CRC_TABLE
if (crc_table_empty)
make_crc_table();
#endif
/* Align it */
if (((long)b) & 3 && len) {
u8 *p = (u8 *)b;
do {
DO_CRC(*p++);
} while ((--len) && ((long)p)&3);
b = (u32 *)p;
}
rem_len = len & 3;
len = len >> 2;
for (--b; len; --len) {
/* load data 32 bits wide, xor data 32 bits wide. */
crc ^= *++b; /* use pre increment for speed */
DO_CRC(0);
DO_CRC(0);
DO_CRC(0);
DO_CRC(0);
}
len = rem_len;
/* And the last few bytes */
if (len) {
u8 *p = (u8 *)(b + 1) - 1;
do {
DO_CRC(*++p); /* use pre increment for speed */
} while (--len);
}
return crc;
}
u32 crc32(u32 crc, const u8 *p, u32 len)
{
return crc32_no_comp(crc ^ 0xffffffffL, p, len) ^ 0xffffffffL;
}
crc32.h
#ifndef _CRC_32_H_
#define _CRC_32_H_
#define CRC_LE_BITS 8
#define __LITTLE_ENDIAN 1
//extern u32 crc32_le(u32 crc, unsigned char const *p, size_t len);
//#define crc32(seed, data, length) crc32_le(seed, (unsigned char const *)data, length)
extern u32 crc32(u32 crc, const u8 *p, u32 len);
#endif
crc32table.h
/* this file is generated - do not edit */
static const u32 crc32table[] = {
(0x00000000L), (0x77073096L), (0xee0e612cL), (0x990951baL),
(0x076dc419L), (0x706af48fL), (0xe963a535L), (0x9e6495a3L),
(0x0edb8832L), (0x79dcb8a4L), (0xe0d5e91eL), (0x97d2d988L),
(0x09b64c2bL), (0x7eb17cbdL), (0xe7b82d07L), (0x90bf1d91L),
(0x1db71064L), (0x6ab020f2L), (0xf3b97148L), (0x84be41deL),
(0x1adad47dL), (0x6ddde4ebL), (0xf4d4b551L), (0x83d385c7L),
(0x136c9856L), (0x646ba8c0L), (0xfd62f97aL), (0x8a65c9ecL),
(0x14015c4fL), (0x63066cd9L), (0xfa0f3d63L), (0x8d080df5L),
(0x3b6e20c8L), (0x4c69105eL), (0xd56041e4L), (0xa2677172L),
(0x3c03e4d1L), (0x4b04d447L), (0xd20d85fdL), (0xa50ab56bL),
(0x35b5a8faL), (0x42b2986cL), (0xdbbbc9d6L), (0xacbcf940L),
(0x32d86ce3L), (0x45df5c75L), (0xdcd60dcfL), (0xabd13d59L),
(0x26d930acL), (0x51de003aL), (0xc8d75180L), (0xbfd06116L),
(0x21b4f4b5L), (0x56b3c423L), (0xcfba9599L), (0xb8bda50fL),
(0x2802b89eL), (0x5f058808L), (0xc60cd9b2L), (0xb10be924L),
(0x2f6f7c87L), (0x58684c11L), (0xc1611dabL), (0xb6662d3dL),
(0x76dc4190L), (0x01db7106L), (0x98d220bcL), (0xefd5102aL),
(0x71b18589L), (0x06b6b51fL), (0x9fbfe4a5L), (0xe8b8d433L),
(0x7807c9a2L), (0x0f00f934L), (0x9609a88eL), (0xe10e9818L),
(0x7f6a0dbbL), (0x086d3d2dL), (0x91646c97L), (0xe6635c01L),
(0x6b6b51f4L), (0x1c6c6162L), (0x856530d8L), (0xf262004eL),
(0x6c0695edL), (0x1b01a57bL), (0x8208f4c1L), (0xf50fc457L),
(0x65b0d9c6L), (0x12b7e950L), (0x8bbeb8eaL), (0xfcb9887cL),
(0x62dd1ddfL), (0x15da2d49L), (0x8cd37cf3L), (0xfbd44c65L),
(0x4db26158L), (0x3ab551ceL), (0xa3bc0074L), (0xd4bb30e2L),
(0x4adfa541L), (0x3dd895d7L), (0xa4d1c46dL), (0xd3d6f4fbL),
(0x4369e96aL), (0x346ed9fcL), (0xad678846L), (0xda60b8d0L),
(0x44042d73L), (0x33031de5L), (0xaa0a4c5fL), (0xdd0d7cc9L),
(0x5005713cL), (0x270241aaL), (0xbe0b1010L), (0xc90c2086L),
(0x5768b525L), (0x206f85b3L), (0xb966d409L), (0xce61e49fL),
(0x5edef90eL), (0x29d9c998L), (0xb0d09822L), (0xc7d7a8b4L),
(0x59b33d17L), (0x2eb40d81L), (0xb7bd5c3bL), (0xc0ba6cadL),
(0xedb88320L), (0x9abfb3b6L), (0x03b6e20cL), (0x74b1d29aL),
(0xead54739L), (0x9dd277afL), (0x04db2615L), (0x73dc1683L),
(0xe3630b12L), (0x94643b84L), (0x0d6d6a3eL), (0x7a6a5aa8L),
(0xe40ecf0bL), (0x9309ff9dL), (0x0a00ae27L), (0x7d079eb1L),
(0xf00f9344L), (0x8708a3d2L), (0x1e01f268L), (0x6906c2feL),
(0xf762575dL), (0x806567cbL), (0x196c3671L), (0x6e6b06e7L),
(0xfed41b76L), (0x89d32be0L), (0x10da7a5aL), (0x67dd4accL),
(0xf9b9df6fL), (0x8ebeeff9L), (0x17b7be43L), (0x60b08ed5L),
(0xd6d6a3e8L), (0xa1d1937eL), (0x38d8c2c4L), (0x4fdff252L),
(0xd1bb67f1L), (0xa6bc5767L), (0x3fb506ddL), (0x48b2364bL),
(0xd80d2bdaL), (0xaf0a1b4cL), (0x36034af6L), (0x41047a60L),
(0xdf60efc3L), (0xa867df55L), (0x316e8eefL), (0x4669be79L),
(0xcb61b38cL), (0xbc66831aL), (0x256fd2a0L), (0x5268e236L),
(0xcc0c7795L), (0xbb0b4703L), (0x220216b9L), (0x5505262fL),
(0xc5ba3bbeL), (0xb2bd0b28L), (0x2bb45a92L), (0x5cb36a04L),
(0xc2d7ffa7L), (0xb5d0cf31L), (0x2cd99e8bL), (0x5bdeae1dL),
(0x9b64c2b0L), (0xec63f226L), (0x756aa39cL), (0x026d930aL),
(0x9c0906a9L), (0xeb0e363fL), (0x72076785L), (0x05005713L),
(0x95bf4a82L), (0xe2b87a14L), (0x7bb12baeL), (0x0cb61b38L),
(0x92d28e9bL), (0xe5d5be0dL), (0x7cdcefb7L), (0x0bdbdf21L),
(0x86d3d2d4L), (0xf1d4e242L), (0x68ddb3f8L), (0x1fda836eL),
(0x81be16cdL), (0xf6b9265bL), (0x6fb077e1L), (0x18b74777L),
(0x88085ae6L), (0xff0f6a70L), (0x66063bcaL), (0x11010b5cL),
(0x8f659effL), (0xf862ae69L), (0x616bffd3L), (0x166ccf45L),
(0xa00ae278L), (0xd70dd2eeL), (0x4e048354L), (0x3903b3c2L),
(0xa7672661L), (0xd06016f7L), (0x4969474dL), (0x3e6e77dbL),
(0xaed16a4aL), (0xd9d65adcL), (0x40df0b66L), (0x37d83bf0L),
(0xa9bcae53L), (0xdebb9ec5L), (0x47b2cf7fL), (0x30b5ffe9L),
(0xbdbdf21cL), (0xcabac28aL), (0x53b39330L), (0x24b4a3a6L),
(0xbad03605L), (0xcdd70693L), (0x54de5729L), (0x23d967bfL),
(0xb3667a2eL), (0xc4614ab8L), (0x5d681b02L), (0x2a6f2b94L),
(0xb40bbe37L), (0xc30c8ea1L), (0x5a05df1bL), (0x2d02ef8dL)
};
5.env.c与env.h
这两个文件主要实现了读取、解析、修改、保存环境变量的功能。
env.c
#include <stdio.h>
#include <string.h>
#include <memory.h>
#include "env.h"
#include "crc32/crc32.h"
unsigned char uboot_env[ENV_SIZE];
static env_t environment;
extern int bootNorErase(u32 ulStart, u32 uLength);
extern int bootNorWrite(u32 ulStart, u32 uLength, void *pBuffer);
extern int bootNorRead(u32 ulStart, u16 uLength, void *pBuffer);
/*
* Check if CRC is valid and (if yes) import the environment.
* Note that "buf" may or may not be aligned.
*/
static int import_env(const char *buf, int check)
{
memcpy((void*)(&environment), (void*)buf, sizeof(env_t));
if (check) {
uint32_t crc;
crc = environment.crc;
if (crc32(0, (u8*)environment.data, ENV_SIZE) != crc) {
printk("Calculate CRC = 0x%x, read CRC = 0x%x.\n", crc32(0, (u8*)environment.data, ENV_SIZE), crc);
printk("bad CRC.\n");
return -1; /* needed for env_load() */
}
return 0;
}
printk("Cannot import environment.\n");
printk("import env failed\n");
return -1;
}
// read uboot env from flash to buff
int load_env(void)
{
int ret;
char buf_env[CONFIG_ENV_SIZE];
memset(&environment, 0, sizeof(env_t));
bootNorRead(CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, &buf_env);
ret = import_env(buf_env, 1);
if (!ret)
{
printk("valid env\n");
return ENV_VALID;
}
else
{
printk("Invalid env.\n");
return ret;
}
}
// write buff env to flash
int save_env(void)
{
int ret = 1;
env_t env_new;
// update crc
environment.crc = crc32(0, (u8 *) environment.data, ENV_SIZE);
bootNorErase(CONFIG_ENV_OFFSET, BLOCK_SIZE);
bootNorWrite(CONFIG_ENV_OFFSET, CONFIG_ENV_SIZE, (void*)&environment);
printf("lhl:done\n");
return ret;
}
/*
* s1 is either a simple 'name', or a 'name=value' pair.
* s2 is a 'name=value' pair.
* If the names match, return the value of s2, else NULL.
*/
static u8 *envmatch(u8 *s1, u8 *s2)
{
if (s1 == NULL || s2 == NULL)
return NULL;
while (*s1 == *s2++)
if (*s1++ == '=')
return s2;
if (*s1 == '\0' && *(s2 - 1) == '=')
return s2;
return NULL;
}
/**
* Search the environment for a variable.
* Return the value, if found, or NULL, if not found.
*/
u8 * get_env(u8 *name)
{
u8 *env, *nxt;
for (env = environment.data; *env; env = nxt + 1) {
char *val;
for (nxt = env; *nxt; ++nxt) {
if (nxt >= &environment.data[ENV_SIZE]) {
fprintf(stderr, "## Error: "
"environment not terminated\n");
return NULL;
}
}
val = envmatch(name, env);
if (!val)
continue;
return val;
}
return NULL;
}
/*
* Set/Clear a single variable in the environment.
* This is called in sequence to update the environment
* in RAM without updating the copy in flash after each set
*/
int write_env(u8 *name, u8 *value)
{
int len;
u8 *env, *nxt;
u8 *oldval = NULL;
int deleting, creating, overwriting;
/*
* search if variable with this name already exists
*/
for (nxt = env = environment.data; *env; env = nxt + 1) {
for (nxt = env; *nxt; ++nxt) {
if (nxt >= &environment.data[ENV_SIZE]) {
printk("## Error: environment not terminated\n");
// errno = EINVAL;
return -1;
}
}
oldval = envmatch(name, env);
if (oldval)
break;
}
deleting = (oldval && !(value && strlen(value)));
creating = (!oldval && (value && strlen(value)));
overwriting = (oldval && (value && strlen(value)));
if (deleting || overwriting) {
if (*++nxt == '\0') {
*env = '\0';
} else {
for (;;) {
*env = *nxt++;
if ((*env == '\0') && (*nxt == '\0'))
break;
++env;
}
}
*++env = '\0';
}
/* Delete only ? */
if (!value || !strlen(value))
return 0;
/*
* Append new definition at the end
*/
for (env = environment.data; *env || *(env + 1); ++env)
;
if (env > environment.data)
++env;
/*
* Overflow when:
* "name" + "=" + "val" +"\0\0" > CUR_ENVSIZE - (env-environment)
*/
len = strlen(name) + 2;
/* add '=' for first arg, ' ' for all others */
len += strlen(value) + 1;
// if (len > (&environment.data[ENV_SIZE] - env)) {
// printk("Error: environment overflow, \"%s\" deleted\n", name);
// return -1;
// }
while ((*env = *name++) != '\0')
env++;
*env = '=';
while ((*++env = *value++) != '\0')
;
/* end is marked with double '\0' */
*++env = '\0';
return 0;
}
void uboot_printenv(void)
{
unsigned char *env, *nxt;
// load_env();
for (env = environment.data; *env; env = nxt + 1) {
for (nxt = env; *nxt; ++nxt) {
if (nxt >= &environment.data[ENV_SIZE]) {
fprintf(stderr, "## Error: "
"environment not terminated\n");
return;
}
}
printf("%s\n", env);
}
}
解析:
5.1. flash读写函数
下面三个函数是我自己实现flash读写的函数,不同平台不同芯片不一样。
extern int bootNorErase(u32 ulStart, u32 uLength);
extern int bootNorWrite(u32 ulStart, u32 uLength, void *pBuffer);
extern int bootNorRead(u32 ulStart, u16 uLength, void *pBuffer);
5.2. 读取uboot环境变量
load_env 函数会调用import_env 函数从flash中读取环境变量到内存中,并且校验CRC码。如果打印校验失败,说明环境变量读取有问题。
int load_env(void);
static int import_env(const char *buf, int check);
5.3. 保存uboot环境变量
save_env 函数会重新计算内存中环境变量的 CRC 值,并更新。之后调用flash读写函数,来将内存中数据写入flash。
int save_env(void);
5.4. 获取相应的环境变量
get_env 调用 envmatch 来查找 内存中的环境变量的相应值。
一条环境变量的结束符号是'\0',格式是: name=val 。
static u8 *envmatch(u8 *s1, u8 *s2);
u8 * get_env(u8 *name);
5.5. 修改环境变量
write_env 用来创建、修改、删除内存中的环境变量的相应值。修改我测试过,创建删除没试过,但是应该可以的。因为他们原理一样,都是先将原来的值清除,然后写入新的值。
注意:此函数修改的是内存中的环境变量,要真正生效还需要调用函数写入存储芯片中。
int write_env(u8 *name, u8 *value);
5.6. 打印环境变量
此函数仅用来测试读取到的uboot环境变量是否正确,调用前需要先load_env,然后再调用。
void uboot_printenv(void);
env.h
#ifndef _ENV_H_
#define _ENV_H_
//#define ENV_HEADER_SIZE (sizeof(unsigned int))
#define ENV_HEADER_SIZE 4
#define CONFIG_ENV_OFFSET 0x70000
#define CONFIG_ENV_SIZE 4096
#define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE)
#define BLOCK_SIZE 0x10000
#define readl(addr) (*(volatile unsigned int *) (addr))
#define writel(data,addr) (*(volatile unsigned int *) (addr ) = (data))
#define readb(addr) (*(volatile unsigned char *) (addr))
#define writeb(data,addr) (*(volatile unsigned char *) (addr ) = (data))
/* Value for environment validity */
enum env_valid {
ENV_INVALID, /* No valid environment */
ENV_VALID, /* First or only environment is valid */
ENV_REDUND, /* Redundant environment is valid */
};
typedef struct environment_s {
uint32_t crc; /* CRC32 over data bytes */
unsigned char data[ENV_SIZE]; /* Environment data */
} env_t;
#endif
6. 保存默认环境变量
此处代码可以写入一个默认的环境变量到存储芯片中,适合移植到不同的系统中,而不用修改编译uboot。
下面为环境变量的宏定义
#define CONFIG_DEFAULT_ENV_SETTINGS \
"arch=arm\0" \
"cpu=armv8\0" \
"baudrate=115200\0" \
"board=ft2004\0" \
"board_name=ft2004\0" \
"bootargs=console=ttyAMA0,115200 earlycon=pl011,0x28001000 root=/dev/sda2 rw\0" \
"eth1addr=98:0e:24:33:25:8b\0" \
"ethact=ethernet@2820c000\0" \
"ethaddr=98:0e:24:33:25:8a\0" \
"ipaddr=192.168.3.136\0" \
"gatewayip=192.168.3.1\0" \
"netmask=255.255.255.0\0" \
"serverip=192.168.3.111\0" \
"load_kernel=ext4load scsi 0:1 0x90100000 uImage\0" \
"load_fdt=ext4load scsi 0:1 0x95000000 dtb/ft2004-clock_64.dtb\0" \
"boot_fdt=bootm 0x90100000 -:- 0x95000000\0" \
"distro_bootcmd=run load_kernel; run load_fdt; run boot_fdt\0" \
"bootOS=cp.b 0x00600000 0xfa000000 0x600000;bootelf 0xfa000000\0" \
"bak_boot_OS=cp.b 0x01000000 0xfa000000 0x600000;bootelf 0xfa000000\0" \
"bootcmd=run bootOS\0" \
"bootdelay=0\0" \
"stderr=uart@28001000\0" \
"stdin=uart@28001000\0" \
"stdout=uart@28001000\0" \
"watchdog=off\0" \
"watchdogcnt=40000"
下面为写入存储的函数,部分声明在本文前面章节有描述。
// write default uboot envirment to flash
void saveDefaultEnv()
{
unsigned char data[ENV_SIZE] = CONFIG_DEFAULT_ENV_SETTINGS, *env, *nxt;
int num = 0, offset = 0;
memset(&environment, 0, sizeof(env_t));
for (env = data; *env; env = nxt + 1) {
num = 0;
for (nxt = env; *nxt; ++nxt) {
num++;
if (nxt >= &data[ENV_SIZE]) {
printf("## Error: environment not terminated\n");
return;
}
}
memcpy(&environment.data[offset], env, num+1);
offset += num+1;
}
save_env();
}
7. 哈希链表
uboot环境变量中似乎用到了哈希链表等数据结构。但是经过简略分析,似乎哈希链表只是影响了对uboot环境变量的修改删除等的权限。只要谨慎地读写环境变量,这部分代码不用关心移植。