*******设计可靠的env存储区(WS_ENV)
*** 使用环境
在嵌入式系统中,bootloader与linux kernel都需要互相传递变量,例如在bootloader中设
置IP地址,在kernel中读取IP地址。如果让boot loader直接写linux的文件系统,bootloader
将会变得非常大,一般都使用环境变量实现变量的传递。
*** 算法设计
通过研究发现,env使用的是字符串,而且flash的特点是由1变0容易,由0变1需要重新擦除。
可以顺序的按照tag value把env存储于flash中,例如:
⊙代表结束符”/0”.
原始存储区orig:tag1⊙val1⊙tag2⊙val2⊙tag3⊙val3⊙剩余位置
a. 增加操作ws_setenv:
从orig增加一个tag4时,在结尾处增加,如:
tag1⊙val1⊙tag2⊙val2⊙tag3⊙val3⊙tag4⊙val4⊙剩余位置
从orig增加tag4的值为空字符串时,如下:
tag1⊙val1⊙tag2⊙val2⊙tag3⊙val3⊙tag4⊙⊙剩余位置
当剩余位置到达了env所在的块的结尾位置,则需要整理(defrag):
将整块擦除,然后再从内存中的控制结构中写入所有的tag,下面会详述控制结构。
为了保证擦除整块env后,突然断电,不会造成数据丢失,需要有一个备份env块。
擦除前将env拷贝到备份env后,再擦除env。
b. 删除操作ws_unsetenv:
从orig删除一个tag1时,将对应的tag1与val全部变为⊙(因为1变0容易)。如:
⊙⊙⊙⊙⊙⊙⊙⊙⊙⊙tag2⊙val2⊙tag3⊙val3⊙剩余位置
c. 修改操作ws_setenv:
从orig修改tag1, 让其值变为changed,将实行删除后,再增加操作.如:
⊙⊙⊙⊙⊙⊙⊙⊙⊙⊙tag2⊙val2⊙tag3⊙val3⊙tag1⊙changed⊙剩余位置
通过上述算法,可以设计出env的排列结构如下:
若env块的前4个字节为“good”表示env存储区未损坏,不需要从备份区拷贝数据。否
则需要擦除env,然后从备份区拷贝(64k-4)的数据。
相应的内存数据结构设计如下所示。
d. recovery env algorithm:
1. 如果backup env的magic有效,从backup env中拷贝数据到env。数据拷贝完后,
擦除backup env block。
2. 当前env的数据存储满后,先把当前env的所有数据存储到backup env中,然后
擦除env,再把内存中的数据写入env区。写完后,写入env magic, 然后擦除
backup env block.
分析:这里需要考虑极端情况:假设当前系统env已满,但defrag后剩余的空间
只有一点点, 那么,例如设置一个相同tag的值时,首先找到env,然后设置为⊙,
然后defrag,仅回收刚才设置为⊙的空间,这样会照成每设几个env,就要defrag,
实在不划算。因此建议判断是否需要defrag的条件改为:
当前系统有效空间+env值小于env size-4k。如果不满足,则直接返回空间已满。
***Hash设计
为了提高查找效率,需要设计好的hash算法,对于env来说,字符串,让每个字母相加,
得到的值再对hash表的大小取余,得到的位置就是env要插入的链表位置。
*** 数据结构设计
/* 每个env变量的结构 */
struct env_entry
{
struct env_entry *next; /* pointer to next entry */
unsigned int tag_offset; /* Offset in the env block */
char *tag_name;
char *tag_val;
};
/* 整个env的控制区 */
struct env_info
{
unsigned int base_addr; /* env base address, must start from a new block */
unsigned int env_size; /* env size, include backup env sector */
unsigned int block_size; /* memory block size, in byte */
unsigned int env_valid_size;/* valid env entrys size */
unsigned int free_offs; /* Offset from base_addr */
unsigned int numEntrys; /* number of env entrys */
struct env_entry **hash_bucket;
void *priv; /* private data, use it whateven you like */
struct semaphore env_lock; /* big lock */
int (*ws_hash)(char *name); /* hash function */
int (*load_env)(struct env_info *info); /* load env from memory */
int (*erase)(struct env_info *info, unsigned addr, unsigned len); /* erash method */
int (*read)(struct env_info *info, unsigned from, unsigned len, char *buf); /* read method */
int (*write)(struct env_info *info, unsigned to, unsigned len, char *buf); /* write method */
};
*** 代码实现
ws_env.h
void wsenv_lock_init(struct env_info *info);
void wsenv_lock(struct env_info *info);
void wsenv_unlock(struct env_info *info);
int wsenv_hash(char *name);
struct env_entry *ws_create(struct env_info *info, char *name, char *val, unsigned int offset);
struct env_entry *ws_lookup(struct env_info *info, char *name);
int ws_delete(struct env_info *info, char *name);
int __ws_setenv(char *name, char *val);
int __ws_unsetenv(char *name);
int ws_setenv(char *name, char *val);
int ws_getenv(char *name, char *val, unsigned val_len);
int ws_unsetenv(char *name);
int wsenv_init(void);
void wsenv_release(void);
#endif
ws_env.c
/* Mark tag value if exist */
if ( strlen(entry->tag_val) > 0 )
{
for ( i=0; i<strlen(entry->tag_val); i++ )
{
if ( 1 != ws_env_info->read(ws_env_info, offset, 1, &data) )
{
ALOHA_ASSERT("read data failed, offset[%u]!", offset);
return -1;
}
if ( data == 0 )
{
ALOHA_ASSERT("read env[%s] is already zero, offset=%u", name, offset);
return -1;
}
ws_env_info->write(ws_env_info, offset, 1, zero);
offset++;
}
}
ws_env_info->env_valid_size -= (strlen(entry->tag_name)+strlen(entry->tag_val)+2);
ws_delete(ws_env_info, name);
return 0;
}
/* env operation interface */
int ws_setenv(char *name, char *val)
{
struct env_entry *entry;
int ret = 0;
wsenv_lock(ws_env_info);
ALOHA_PROMPT("ws_setenv: name:%s, val=%s", name, val);
/* Find if the entry exist already */
entry = ws_lookup(ws_env_info, name);
if ( entry )
{
/* delete it first */
ret = __ws_unsetenv(name);
}
/* Create the new env */
ret |= __ws_setenv(name, val);
wsenv_unlock(ws_env_info);
return ret;
}
int ws_getenv(char *name, char *val, unsigned val_len)
{
struct env_entry *entry;
wsenv_lock(ws_env_info);
ALOHA_PROMPT("get env[%s]", name);
entry = ws_lookup(ws_env_info, name);
if ( !entry )
{
wsenv_unlock(ws_env_info);
return -1;
}
if ( strlen(entry->tag_val) >= val_len )
{
wsenv_unlock(ws_env_info);
ALOHA_ASSERT("buf_len[%u] is too small!", val_len);
return -1;
}
strcpy(val, entry->tag_val);
ALOHA_PROMPT("ws_getenv return: %s=%s", name, val);
wsenv_unlock(ws_env_info);
return 0;
}
int ws_unsetenv(char *name)
{
int ret;
wsenv_lock(ws_env_info);
ALOHA_PROMPT("ws_unsetenv: name:%s", name);
ret = __ws_unsetenv(name);
wsenv_unlock(ws_env_info);
return ret;
}
void wsenv_release(void)
{
struct env_entry *entry, *next;
int i;
ALOHA_PROMPT("wsenv_release() enter.");
for ( i=0; i<WS_ENV_BUCKET_SIZE; i++ )
{
entry = ws_env_info->hash_bucket[i];
while ( entry )
{
if ( entry->tag_name )
kfree(entry->tag_name);
if ( entry->tag_val )
kfree(entry->tag_val);
next = entry->next;
kfree(entry);
entry = next;
}
}
kfree(ws_env_info->hash_bucket);
kfree(ws_env_info);
return;
}
ws_env_linux.
int wsenv_recoverenv(struct env_info *info)
{
unsigned env_size = info->env_size / 2;
unsigned offset;
int read_len;
int i;
char *buf;
/* Erase env, then copy data from backup env,
* finally, erase backup env
*/
if ( info->erase(info, 0, env_size) )
{
ALOHA_ASSERT("erase mtd error!");
return -1;
}
offset = 0;
buf = kmalloc(1024, GFP_KERNEL);
for ( i=0; i<env_size/1024; i++ )
{
offset = env_size + i*1024;
read_len = info->read(info, offset, 1024, buf);
if ( read_len != 1024 )
{
kfree(buf);
ALOHA_ASSERT("mtd read error!");
return -1;
}
info->write(info, i*1024, 1024, buf);
}
kfree(buf);
if ( info->erase(info, env_size, env_size) )
{
ALOHA_ASSERT("erase mtd error!");
return -1;
}
return 0;
}
int wsenv_load_mem(struct env_info *info)
{
/* load env from file */
struct mtd_info *env_mtd = (struct mtd_info *)info->priv;
unsigned env_size = info->env_size / 2;
char *tmp_name, *tmp_val;
unsigned off;
unsigned char data;
size_t retlen;
int ret;
unsigned char null_byte[4] = {0xFF, 0XFF, 0XFF, 0XFF};
char header[5], backup_header[5];
/* Read head and backup header */
memset(header, 0, sizeof(header));
retlen = 0;
env_mtd->read(env_mtd, info->base_addr, 4, &retlen, header);
if ( retlen != 4 )
{
ALOHA_ASSERT("mtd read error!");
return -1;
}
memset(backup_header, 0, sizeof(backup_header));
retlen = 0;
env_mtd->read(env_mtd, info->base_addr+env_size, 4, &retlen, backup_header);
if ( retlen != 4 )
{
ALOHA_ASSERT("mtd read error!");
return -1;
}
/* If Backup head magic exist, recover env.
* If head and backup header is no used, write magic.
* If header is different from magic, erase all env.
*/
if ( 0 == strcmp(backup_header, WS_ENV_MAGIC) )
{
/* recovery env */
wsenv_recoverenv(info);
}
if ( 0 == memcmp(header, null_byte, sizeof(null_byte)) &&
0 == memcmp(backup_header, null_byte, sizeof(null_byte)) )
{
/* env is clear, write magic to env */
env_mtd->write(env_mtd, info->base_addr, strlen(WS_ENV_MAGIC), &retlen, WS_ENV_MAGIC);
}
else if ( 0 != strcmp(header, WS_ENV_MAGIC) )
{
/* the env is corrupt, erase all */
info->erase(info, 0, info->env_size);
env_mtd->write(env_mtd, info->base_addr, strlen(WS_ENV_MAGIC), &retlen, WS_ENV_MAGIC);
}
else
{
}
/* write wsenv magic to memory */
off = 4;
for ( ; ; )
{
int read_error = 0;
int name_offset;
int tag_offset;
if ( off >= env_size )
{
ALOHA_PROMPT("Read to the end, and env is full");
info->free_offs = env_size;
break;
}
/* bypass all zero data */
ret = env_mtd->read(env_mtd, off+info->base_addr, 1, &retlen, &data);
if ( ret || 1!=retlen )
{
/* END */
ALOHA_PROMPT("Read to the end, ret[%d], retlen[%d]", ret, retlen);
break;
}
if ( data == 0xFF )
{
/* Read non-used data, rest memory is free */
info->free_offs = off;
break;
}
off++;
if ( data == 0 )
continue;
/* Read tag name */
tag_offset = 0;
name_offset = off;
wsenv_name[tag_offset++] = data;
while ( 0 == env_mtd->read(env_mtd, off+info->base_addr, 1, &retlen, &data) )
{
off++;
if ( retlen != 1 )
{
ALOHA_ASSERT("read error!");
read_error = 1;
break;
}
if ( data == 0 )
{
/* read to string end */
break;
}
else
{
wsenv_name[tag_offset++] = data;
}
/* truncate if the buffer overflow */
if ( tag_offset >= sizeof(wsenv_name) )
break;
}
if ( read_error )
break;
wsenv_name[tag_offset++] = 0;
/* Read tag val */
tag_offset = 0;
while ( 0 == env_mtd->read(env_mtd, off+info->base_addr, 1, &retlen, &data) )
{
off++;
if ( retlen != 1 )
{
ALOHA_ASSERT("read error!");
read_error = 1;
break;
}
if ( data == 0 )
{
/* read to string end */
break;
}
else
{
wsenv_buf[tag_offset++] = data;
}
/* truncate if the buffer overflow */
if ( tag_offset >= sizeof(wsenv_buf) )
break;
}
if ( read_error )
break;
wsenv_buf[tag_offset++] = 0;
/* create an new entry */
tmp_name = (char *)kmalloc(strlen(wsenv_name)+1, GFP_KERNEL);
strcpy(tmp_name, wsenv_name);
tmp_val = (char *)kmalloc(strlen(wsenv_buf)+1, GFP_KERNEL);
strcpy(tmp_val, wsenv_buf);
ALOHA_PROMPT("create a entry: %s=%s", tmp_name, tmp_val);
ws_create(info, tmp_name, tmp_val, name_offset);
info->env_valid_size += strlen(tmp_name)+strlen(tmp_val)+2;
}
ALOHA_PROMPT("load memory success! total[%u], free_ofs[%u]", info->numEntrys, info->free_offs);
return 0;
}
int wsenv_mem_erase(struct env_info *info, unsigned addr, unsigned len)
{
struct mtd_info *env_mtd = (struct mtd_info *)info->priv;
struct erase_info ei;
unsigned off;
int ret;
ALOHA_PROMPT("wsenv_mem_erase: addr[0x%08x], len[0x%08x]!", addr, len);
if ( (addr%info->block_size != 0) || len%info->block_size!= 0 )
{
ALOHA_ASSERT("BUG: addr or len is no block aligned!!");
return -1;
}
off = addr + info->base_addr;
ei.mtd = env_mtd;
ei.addr = off;
ei.len = len;
ei.time = 1000;
ei.retries = 2;
ei.callback = NULL;
ei.priv = (unsigned long)info;
ret = env_mtd->erase(env_mtd, &ei);
return ret;
}
int wsenv_mem_read(struct env_info *info, unsigned from, unsigned len, char *buf)
{
struct mtd_info *env_mtd = (struct mtd_info *)info->priv;
unsigned off;
int ret;
size_t retlen;
off = from + info->base_addr;
if ( from+len > info->env_size )
{
ALOHA_ASSERT("BUG: overflow!!");
return -1;
}
ret = env_mtd->read(env_mtd, off, len, &retlen, buf);
if ( ret )
return -1;
return retlen;
}
int wsevn_mem_write(struct env_info *info, unsigned to, unsigned len, char *buf)
{
struct mtd_info *env_mtd = (struct mtd_info *)info->priv;
unsigned off;
int ret;
size_t retlen;
if ( to+len > info->env_size )
{
ALOHA_ASSERT("BUG: overflow!!");
return -1;
}
off = to + info->base_addr;
ret = env_mtd->write(env_mtd, off, len, &retlen, buf);
if ( ret )
return -1;
return retlen;
}
static ssize_t wsenv_read(struct file *file, char __user *buf, size_t count,loff_t *ppos)
{
char *kname_buf, *kval_buf;
if ( !count || count>WS_ENV_ENTRY_VAL_LEN )
return -EPERM;
kname_buf = kmalloc(count, GFP_KERNEL);
kval_buf = kmalloc(count, GFP_KERNEL);
if ( !kname_buf || !kval_buf )
{
return -ENOMEM;
}
if ( copy_from_user(kname_buf, buf, count) )
{
kfree(kname_buf);
kfree(kval_buf);
return -EFAULT;
}
if ( ws_getenv(kname_buf, kval_buf, count) )
{
kfree(kname_buf);
kfree(kval_buf);
return -ENXIO;
}
if ( strlen(kval_buf) == 0 )
{
kfree(kname_buf);
kfree(kval_buf);
return 0;
}
if ( copy_to_user(buf, kval_buf, strlen(kval_buf)) )
{
kfree(kname_buf);
kfree(kval_buf);
return -EFAULT;
}
return strlen(kval_buf);
}
static ssize_t wsenv_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos)
{
char *kbuf, *kname, *kval;
int ret;
if ( !count || count>(WS_ENV_ENTRY_VAL_LEN+WS_ENV_ENTRY_NAME_LEN) )
return -EPERM;
kbuf = kmalloc(count, GFP_KERNEL);
if ( !kbuf )
{
return -ENOMEM;
}
if ( copy_from_user(kbuf, buf, count) )
{
kfree(kbuf);
return -EFAULT;
}
kname = kbuf;
kval = strchr(kbuf, '=');
if ( !kval )
{
/* unsetenv */
ret = ws_unsetenv(kname);
if ( ret )
{
kfree(kbuf);
return -EPERM;
}
return count;
}
*kval = 0;
kval++;
ret = ws_setenv(kname, kval);
kfree(kbuf);
if ( ret )
return -EPERM;
return count;
}
static int wsenv_ioctl(struct inode *inode, struct file *file,
u_int cmd, u_long arg)
{
int ret = 0;
struct env_entry *entry, *next;
int i;
ALOHA_PROMPT("wsenv_ioctl() enter.");
switch ( cmd )
{
case WSENV_ERASE_ALL:
wsenv_lock(ws_env_info);
ret = ws_env_info->erase(ws_env_info, 0, ws_env_info->env_size);
ws_env_info->write(ws_env_info, 0, strlen(WS_ENV_MAGIC), WS_ENV_MAGIC);
if ( ret != 0 )
{
ALOHA_ASSERT("Erase all error!");
wsenv_unlock(ws_env_info);
ret = -EPERM;
return ret;
}
for ( i=0; i<WS_ENV_BUCKET_SIZE; i++ )
{
entry = ws_env_info->hash_bucket[i];
while ( entry )
{
next = entry->next;
if ( entry->tag_name )
kfree(entry->tag_name);
if ( entry->tag_val )
kfree(entry->tag_val);
kfree(entry);
entry = next;
}
}
ws_env_info->env_valid_size = 0;
ws_env_info->free_offs = strlen(WS_ENV_MAGIC);
ws_env_info->numEntrys = 0;
wsenv_unlock(ws_env_info);
break;
default:
ret = -ENXIO;
break;
}
return ret;
}
static struct file_operations wsenv_fops = {
.owner = THIS_MODULE,
.read = wsenv_read,
.write = wsenv_write,
.ioctl = wsenv_ioctl,
};
int __init wsenv_init(void)
{
int i;
ALOHA_PROMPT("wsenv_init() enter.");
ws_env_info = kmalloc(sizeof(struct env_info), GFP_KERNEL);
if ( !ws_env_info )
{
ALOHA_ASSERT("Null!");
return -1;
}
memset(ws_env_info, 0, sizeof(*ws_env_info));
/* setup infomation */
ws_env_info->base_addr = 0x001D0000;
ws_env_info->block_size = WS_ENV_BLOCK_SIZE;
ws_env_info->env_size = ws_env_info->block_size*2;
ws_env_info->free_offs = strlen(WS_ENV_MAGIC);
wsenv_lock_init(ws_env_info);
ws_env_info->ws_hash = wsenv_hash;
ws_env_info->load_env =wsenv_load_mem;
ws_env_info->erase = wsenv_mem_erase;
ws_env_info->read = wsenv_mem_read;
ws_env_info->write = wsevn_mem_write;
/* We use mtd api for IO ops */
ws_env_info->priv = aloha_mtd;
ws_env_info->hash_bucket = kmalloc(sizeof(struct env_entry *)*WS_ENV_BUCKET_SIZE, GFP_KERNEL);
if ( !ws_env_info->hash_bucket )
{
ALOHA_ASSERT("Null!");
return -1;
}
for ( i=0; i<WS_ENV_BUCKET_SIZE; i++ )
{
ws_env_info->hash_bucket[i] = NULL;
}
/* load env from memory */
ws_env_info->load_env(ws_env_info);
/* create chardev for user app access */
if (register_chrdev(WSENV_CHAR_MAJOR, "wsenv", &wsenv_fops)) {
ALOHA_ASSERT("Can't allocate major number %d for Memory Technology Devices./n",
WSENV_CHAR_MAJOR);
return -EAGAIN;
}
return 0;
}
static void __exit wsenv_exit(void)
{
wsenv_release();
unregister_chrdev(WSENV_CHAR_MAJOR, "wsenv");
}
module_init(wsenv_init);
module_exit(wsenv_exit);
MODULE_LICENSE("GPL");