| // SPDX-License-Identifier: Apache2.0 |
| /* |
| * Copyright (C) 2024 Amlogic Inc. |
| */ |
| #define LOG_TAG "SystemControl" |
| |
| #include <assert.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <pthread.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <sys/syscall.h> |
| #ifdef MTD_OLD |
| #include <linux/mtd/mtd.h> |
| #else |
| #define __user /* nothing */ |
| #include <mtd/mtd-user.h> |
| #endif |
| |
| #include "ubootenv.h" |
| #define SYS_LOGI(x...) printf(x) |
| #define ERROR(x...) printf(x) |
| #define NOTICE(x...) printf(x) |
| #define INFO(x...) printf(x) |
| |
| #define MAX_UBOOT_RWRETRY 5 |
| |
| typedef struct env_image { |
| uint32_t crc; /* CRC32 over data bytes */ |
| char data[]; /* Environment data */ |
| } env_image_t; |
| |
| typedef struct environment { |
| void *image; |
| uint32_t *crc; |
| char *data; |
| } environment_t; |
| |
| typedef struct env_kv { |
| struct env_kv *next; |
| char key[256]; |
| char *value; |
| } env_kv; |
| |
| static char gs_partition_name[32] = {0}; |
| |
| static unsigned int gs_env_partition_size = 0; |
| static unsigned int gs_env_erase_size = 0; |
| static unsigned int gs_env_data_size = 0; |
| |
| static bool gs_init_done = false; |
| |
| static struct environment gs_env_data; |
| static struct env_kv gs_kv_header; |
| static pthread_mutex_t gs_kv_lock = PTHREAD_MUTEX_INITIALIZER; |
| |
| //#define ENV_IMG_SHM_NAME "/uenv_shm" |
| #define ENV_SHM_PATH "/dev/shm" |
| #define ENV_IMG_SHM_NAME ENV_SHM_PATH "/uenvShm" |
| |
| static struct uenv_img_shm_info { |
| char lock; |
| int32_t refcnt; |
| uint32_t revision; |
| char imgdata[0]; |
| } *gs_env_shm_info = NULL; |
| |
| static uint32_t gs_current_revision = 0; |
| // check the existence of /dev/shm, |
| // to prevent malfunctions when running in a ramdisk environment |
| static bool gs_shm_available = true; |
| |
| #define CRC32_POLYNOMIAL 0xEDB88320 |
| |
| static uint32_t crc32(const char *buffer, size_t size) { |
| uint32_t crc = 0xFFFFFFFF; |
| size_t i, j; |
| |
| for (i = 0; i < size; i++) { |
| crc ^= buffer[i]; |
| for (j = 0; j < 8; j++) { |
| if (crc & 1) { |
| crc = (crc >> 1) ^ CRC32_POLYNOMIAL; |
| } else { |
| crc >>= 1; |
| } |
| } |
| } |
| |
| return ~crc; |
| } |
| |
| // For gcc version is not new enough, e.g. gcc 7.3.1, the glibc has no gettid function. |
| // So implement a weak function here for fallback. |
| pid_t gettid(void) __attribute__((weak)); |
| pid_t gettid(void) |
| { |
| return syscall(__NR_gettid); |
| } |
| |
| static void envimg_buffer_lock() { |
| unsigned int max_retry_cnt = 0; |
| while (__sync_val_compare_and_swap(&gs_env_shm_info->lock, 0, 1)) { |
| if (max_retry_cnt++ >= 1000) { |
| ERROR("[ubootenv][%d] envimg_buffer_lock failed !!! lock=%d, retry..\n", |
| gettid(), gs_env_shm_info->lock); |
| max_retry_cnt = 0; |
| } |
| |
| usleep(1000); |
| } |
| } |
| |
| static void envimg_buffer_unlock() { |
| if (gs_env_shm_info->lock != 1) |
| ERROR("[ubootenv][%d] envimg_buffer_unlock failed! lock = %d\n", gettid(), |
| gs_env_shm_info->lock); |
| assert(__sync_val_compare_and_swap(&gs_env_shm_info->lock, 1, 0)); |
| } |
| |
| static char *acquire_envimg_buffer() { |
| if (gs_env_shm_info) { |
| return gs_env_shm_info->imgdata; |
| } |
| |
| uint32_t size = gs_env_partition_size + sizeof(struct uenv_img_shm_info); |
| |
| gs_shm_available = access(ENV_SHM_PATH, F_OK) != -1; |
| |
| if (!gs_shm_available) { |
| gs_env_shm_info = (struct uenv_img_shm_info *)malloc(size); |
| memset((void *)gs_env_shm_info, 0, size); |
| INFO("[ubootenv] clean gs_env_shm_info!\n"); |
| return gs_env_shm_info->imgdata; |
| } |
| |
| mode_t old_umask = umask(0); |
| int32_t fd = open(ENV_IMG_SHM_NAME, O_CREAT | O_RDWR, 0666); |
| umask(old_umask); |
| |
| if (fd == -1) { |
| fd = open(ENV_IMG_SHM_NAME, O_RDWR); |
| if (fd == -1) { |
| perror("open"); |
| return NULL; |
| } |
| } |
| |
| if (ftruncate(fd, size) == -1) { |
| perror("ftruncate"); |
| goto failure; |
| } |
| |
| gs_env_shm_info = (struct uenv_img_shm_info *)mmap( |
| NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); |
| if (gs_env_shm_info == MAP_FAILED) { |
| perror("mmap"); |
| goto failure; |
| } |
| close(fd); |
| return gs_env_shm_info->imgdata; |
| failure: |
| if (gs_env_shm_info) |
| munmap((char *)gs_env_shm_info, size); |
| if (fd) |
| close(fd); |
| return NULL; |
| } |
| |
| static void release_envimg_buffer() { |
| if (gs_env_shm_info == NULL) |
| return; |
| |
| if (!gs_shm_available) { |
| free(gs_env_shm_info); |
| gs_env_shm_info = NULL; |
| return; |
| } |
| |
| envimg_buffer_lock(); |
| bool unlink_finally = --gs_env_shm_info->refcnt <= 0; |
| envimg_buffer_unlock(); |
| if (munmap((char *)gs_env_shm_info, |
| gs_env_partition_size + sizeof(struct uenv_img_shm_info)) == -1) { |
| goto failure; |
| } |
| |
| if (unlink_finally && unlink(ENV_IMG_SHM_NAME) == -1) { |
| goto failure; |
| } |
| gs_env_shm_info = NULL; |
| failure: |
| return; |
| } |
| |
| /* Parse a session kv */ |
| static env_kv *env_parse_kv(void) { |
| char *ptr = gs_env_data.data; |
| env_kv *attr = &gs_kv_header; |
| |
| pthread_mutex_lock(&gs_kv_lock); |
| bool bproc_key = true; |
| bool bproc_next = false; |
| while (ptr < gs_env_data.data + gs_env_data_size) { |
| if (*ptr == '\0') |
| break; |
| |
| if (bproc_next) { |
| bproc_next = false; |
| attr->next = (env_kv *)malloc(sizeof(env_kv)); |
| if (attr->next == NULL) { |
| ERROR("[ubootenv] exit malloc error \n"); |
| break; |
| } |
| attr = attr->next; |
| attr->next = NULL; |
| attr->value = NULL; |
| attr->key[0] = '\0'; |
| } |
| |
| char *pend = strchr(ptr, bproc_key ? '=' : '\0'); |
| if (!pend) { |
| ERROR("[ubootenv] error '%s' not found, end parsing\n", |
| bproc_key ? "=" : "\\0"); |
| break; |
| } |
| size_t plen = pend - ptr + 1; |
| size_t maxlen = bproc_key ? sizeof(attr->key) : 4096; |
| if (plen > maxlen) { |
| ERROR("[ubootenv] warning '%s' too long, truncated\n", |
| bproc_key ? "key" : "value"); |
| plen = maxlen; |
| } |
| if (bproc_key) { |
| snprintf(attr->key, plen, "%s", ptr); |
| // value should be processed |
| bproc_key = false; |
| } else { |
| attr->value = (char *)malloc(plen); |
| snprintf(attr->value, plen, "%s", ptr); |
| bproc_key = true; |
| bproc_next = true; |
| } |
| ptr = pend + 1; |
| } |
| pthread_mutex_unlock(&gs_kv_lock); |
| |
| return &gs_kv_header; |
| } |
| |
| /* serialize the kv list into raw env data */ |
| static int env_serialize_data(void) { |
| int len; |
| env_kv *attr = &gs_kv_header; |
| char *data = gs_env_data.data; |
| memset(gs_env_data.data, 0, gs_env_data_size); |
| pthread_mutex_lock(&gs_kv_lock); |
| do { |
| len = sprintf(data, "%s=%s", attr->key, attr->value); |
| if (len < 3) { |
| ERROR("[ubootenv] Invalid data\n"); |
| } else |
| data += len + 1; |
| |
| attr = attr->next; |
| } while (attr); |
| pthread_mutex_unlock(&gs_kv_lock); |
| // update revision |
| gs_current_revision++; |
| gs_env_shm_info->revision = gs_current_revision; |
| return 0; |
| } |
| |
| static void env_release_kv() { |
| pthread_mutex_lock(&gs_kv_lock); |
| if (gs_kv_header.value) { |
| free(gs_kv_header.value); |
| gs_kv_header.value = NULL; |
| } |
| for (env_kv *pAttr = gs_kv_header.next, *pTmp; pAttr != NULL; pAttr = pTmp) { |
| pTmp = pAttr->next; |
| if (pAttr->value) { |
| free(pAttr->value); |
| } |
| free(pAttr); |
| } |
| gs_kv_header.next = NULL; |
| pthread_mutex_unlock(&gs_kv_lock); |
| } |
| |
| static void check_load_kv() { |
| if (gs_current_revision != gs_env_shm_info->revision) { |
| env_release_kv(); |
| env_parse_kv(); |
| gs_current_revision = gs_env_shm_info->revision; |
| } |
| return; |
| } |
| |
| static int load_bootenv() { |
| int ret = 0; |
| struct env_image *image; |
| char *addr; |
| |
| addr = acquire_envimg_buffer(); |
| if (addr == NULL) { |
| ERROR("[ubootenv] Not enough memory for environment (%u bytes)\n", |
| gs_env_partition_size); |
| return -1; |
| } |
| |
| envimg_buffer_lock(); |
| gs_env_data.image = addr; |
| image = (struct env_image *)addr; |
| gs_env_data.crc = &(image->crc); |
| gs_env_data.data = image->data; |
| |
| if (gs_env_shm_info->refcnt == 0) { |
| int fd; |
| if ((fd = open(gs_partition_name, O_RDONLY)) < 0) { |
| ERROR("[ubootenv] open devices error: %s\n", strerror(errno)); |
| ret = -1; |
| goto failure; |
| } |
| int32_t bytes = read(fd, gs_env_data.image, gs_env_partition_size); |
| close(fd); |
| if (bytes != (int32_t)gs_env_partition_size) { |
| NOTICE("[ubootenv] read error 0x%x \n", bytes); |
| ret = -2; |
| goto failure; |
| } |
| } |
| |
| gs_current_revision = gs_env_shm_info->revision; |
| uint32_t crc_calc = crc32(gs_env_data.data, gs_env_data_size); |
| if (crc_calc != *(gs_env_data.crc)) { |
| ERROR("[ubootenv] CRC Check ERROR save_crc=%08x,calc_crc = %08x \n", |
| *gs_env_data.crc, crc_calc); |
| ret = -3; |
| goto failure; |
| } |
| if (env_parse_kv() == NULL) { |
| ret = -4; |
| goto failure; |
| } |
| // bootenv_print(); |
| failure: |
| if (ret == 0) { |
| gs_env_shm_info->refcnt++; |
| } else { |
| // reset to zero if error happens for the next retries |
| gs_env_shm_info->refcnt = 0; |
| } |
| envimg_buffer_unlock(); |
| return ret; |
| } |
| |
| static const char *bootenv_get_value(const char *key) { |
| if (!gs_init_done) { |
| return NULL; |
| } |
| |
| const char *val = NULL; |
| pthread_mutex_lock(&gs_kv_lock); |
| env_kv *attr; |
| for (attr = &gs_kv_header; attr && strcmp(key, attr->key); attr = attr->next) |
| ; |
| val = attr ? attr->value : NULL; |
| pthread_mutex_unlock(&gs_kv_lock); |
| return val; |
| } |
| |
| /* |
| creat_args_flag : if true , if envvalue don't exists Creat it . |
| if false , if envvalue don't exists just exit . |
| */ |
| static int bootenv_set_value(const char *key, const char *value, |
| int creat_args_flag) { |
| int ret = 0; |
| env_kv *attr = &gs_kv_header; |
| env_kv *last = attr; |
| pthread_mutex_lock(&gs_kv_lock); |
| while (attr) { |
| if (!strcmp(key, attr->key)) { |
| if (attr->value != NULL) { |
| free(attr->value); |
| } |
| attr->value = (char *)malloc(strlen(value) + 1); |
| strcpy(attr->value, value); |
| ret = 2; |
| goto out; |
| } |
| last = attr; |
| attr = attr->next; |
| } |
| |
| if (creat_args_flag) { |
| NOTICE("[ubootenv] ubootenv.var.%s not found, create it.\n", key); |
| /*******Creat a New args*********************/ |
| attr = (env_kv *)malloc(sizeof(env_kv)); |
| attr->next = NULL; |
| last->next = attr; |
| snprintf(attr->key, sizeof(attr->key), "%s", key); |
| attr->value = (char *)malloc(strlen(value) + 1); |
| strcpy(attr->value, value); |
| ret = 1; |
| } |
| out: |
| pthread_mutex_unlock(&gs_kv_lock); |
| return ret; |
| } |
| |
| static int save_bootenv() { |
| int fd; |
| int err; |
| struct erase_info_user erase; |
| struct mtd_info_user info; |
| unsigned char *data = NULL; |
| env_serialize_data(); |
| *(gs_env_data.crc) = crc32(gs_env_data.data, gs_env_data_size); |
| |
| if ((fd = open(gs_partition_name, O_RDWR | O_SYNC)) < 0) { |
| ERROR("[ubootenv] open devices error\n"); |
| return -1; |
| } |
| |
| if (strstr(gs_partition_name, "mtd")) { |
| memset(&info, 0, sizeof(info)); |
| err = ioctl(fd, MEMGETINFO, &info); |
| if (err < 0) { |
| ERROR("[ubootenv] Get MTD info error\n"); |
| close(fd); |
| return -4; |
| } |
| |
| erase.start = 0; |
| if (info.erasesize > gs_env_partition_size) { |
| data = (unsigned char *)malloc(info.erasesize); |
| if (data == NULL) { |
| ERROR("[ubootenv] Out of memory!!!\n"); |
| close(fd); |
| return -5; |
| } |
| memset(data, 0, info.erasesize); |
| err = read(fd, (void *)data, info.erasesize); |
| if (err != (int)info.erasesize) { |
| ERROR("[ubootenv] Read access failed !!!\n"); |
| free(data); |
| close(fd); |
| return -6; |
| } |
| memcpy(data, gs_env_data.image, gs_env_partition_size); |
| erase.length = info.erasesize; |
| } else { |
| erase.length = gs_env_partition_size; |
| } |
| |
| err = ioctl(fd, MEMERASE, &erase); |
| if (err < 0) { |
| ERROR("[ubootenv] MEMERASE ERROR %d\n", err); |
| close(fd); |
| if (info.erasesize > gs_env_partition_size) { |
| free(data); |
| } |
| return -2; |
| } |
| |
| if (info.erasesize > gs_env_partition_size) { |
| lseek(fd, 0L, SEEK_SET); |
| err = write(fd, data, info.erasesize); |
| free(data); |
| } else { |
| err = write(fd, gs_env_data.image, gs_env_partition_size); |
| } |
| |
| } else { |
| // emmc and nand needn't erase |
| err = write(fd, gs_env_data.image, gs_env_partition_size); |
| } |
| if (err < 0) { |
| ERROR("[ubootenv] ERROR write, size %d \n", gs_env_partition_size); |
| close(fd); |
| return -3; |
| } |
| |
| close(fd); |
| return 0; |
| } |
| |
| #define MAX_MTD_PARTITIONS 20 |
| static struct { |
| char name[16]; |
| int number; |
| } mtd_part_map[MAX_MTD_PARTITIONS]; |
| |
| static int mtd_part_count = -1; |
| |
| static void find_mtd_partitions(void) |
| { |
| int fd; |
| char buf[1024]; |
| char *pmtdbufp; |
| ssize_t pmtdsize; |
| int r; |
| fd = open("/proc/mtd", O_RDONLY); |
| if (fd < 0) |
| return; |
| buf[sizeof(buf) - 1] = '\0'; |
| pmtdsize = read(fd, buf, sizeof(buf) - 1); |
| pmtdbufp = buf; |
| while (pmtdsize > 0) { |
| int mtdnum, mtdsize, mtderasesize; |
| char mtdname[16]; |
| mtdname[0] = '\0'; |
| mtdnum = -1; |
| r = sscanf(pmtdbufp, "mtd%d: %x %x %15s", |
| &mtdnum, &mtdsize, &mtderasesize, mtdname); |
| if ((r == 4) && (mtdname[0] == '"')) { |
| char *x = strchr(mtdname + 1, '"'); |
| if (x) { |
| *x = 0; |
| } |
| INFO("mtd partition %d, %s\n", mtdnum, mtdname + 1); |
| if (mtd_part_count < MAX_MTD_PARTITIONS) { |
| strncpy(mtd_part_map[mtd_part_count].name, mtdname + 1, |
| sizeof(mtd_part_map[mtd_part_count].name) - 1); |
| mtd_part_map[mtd_part_count].number = mtdnum; |
| mtd_part_count++; |
| } else { |
| ERROR("too many mtd partitions\n"); |
| } |
| } |
| while (pmtdsize > 0 && *pmtdbufp != '\n') { |
| pmtdbufp++; |
| pmtdsize--; |
| } |
| if (pmtdsize > 0) { |
| pmtdbufp++; |
| pmtdsize--; |
| } |
| } |
| close(fd); |
| } |
| int mtd_name_to_number(const char *name) |
| { |
| int n; |
| if (mtd_part_count < 0) { |
| mtd_part_count = 0; |
| find_mtd_partitions(); |
| } |
| for (n = 0; n < mtd_part_count; n++) { |
| if (!strcmp(name, mtd_part_map[n].name)) { |
| return mtd_part_map[n].number; |
| } |
| } |
| return -1; |
| } |
| |
| int bootenv_init(void) { |
| struct stat st; |
| struct mtd_info_user info; |
| int err; |
| int fd; |
| int ret = -1; |
| int i = 0; |
| if (gs_init_done) |
| return 0; |
| int id = mtd_name_to_number("ubootenv"); |
| |
| if (id < 0) { |
| id = mtd_name_to_number("env"); |
| } |
| if (id >= 0) { |
| sprintf(gs_partition_name, "/dev/mtd%d", id); |
| if ((fd = open (gs_partition_name, O_RDWR)) < 0) { |
| ERROR("open device(%s) error : %s \n",gs_partition_name, strerror(errno) ); |
| return -2; |
| } |
| |
| memset(&info, 0, sizeof(info)); |
| err = ioctl(fd, MEMGETINFO, &info); |
| if (err < 0) { |
| ERROR("get MTD info error\n"); |
| close(fd); |
| return -3; |
| } |
| |
| gs_env_erase_size = info.erasesize; |
| gs_env_partition_size = info.size; |
| gs_env_data_size = gs_env_partition_size - sizeof(long); |
| close(fd); |
| } else if (!stat("/dev/nand_env", &st)) { |
| sprintf(gs_partition_name, "/dev/nand_env"); |
| gs_env_partition_size = 0x10000; |
| #if defined(MESON8_ENVSIZE) || defined(GXBABY_ENVSIZE) || \ |
| defined(GXTVBB_ENVSIZE) || defined(GXL_ENVSIZE) |
| gs_env_partition_size = 0x10000; |
| #elif defined(A1_ENVSIZE) |
| gs_env_partition_size = 0x2000; |
| #endif |
| gs_env_data_size = gs_env_partition_size - sizeof(uint32_t); |
| INFO("[ubootenv] using /dev/nand_env with size(%d)(%d)\n", |
| gs_env_partition_size, gs_env_data_size); |
| } else if (!stat("/dev/env", &st)) { |
| INFO("[ubootenv] stat /dev/env OK\n"); |
| sprintf(gs_partition_name, "/dev/env"); |
| gs_env_partition_size = 0x10000; |
| #if defined(MESON8_ENVSIZE) || defined(GXBABY_ENVSIZE) || \ |
| defined(GXTVBB_ENVSIZE) || defined(GXL_ENVSIZE) |
| gs_env_partition_size = 0x10000; |
| #endif |
| gs_env_data_size = gs_env_partition_size - sizeof(uint32_t); |
| INFO("[ubootenv] using /dev/env with size(%d)(%d)\n", gs_env_partition_size, |
| gs_env_data_size); |
| } else if (!stat("/dev/block/env", &st)) { |
| INFO("[ubootenv] stat /dev/block/env OK\n"); |
| sprintf(gs_partition_name, "/dev/block/env"); |
| gs_env_partition_size = 0x10000; |
| #if defined(MESON8_ENVSIZE) || defined(GXBABY_ENVSIZE) || \ |
| defined(GXTVBB_ENVSIZE) || defined(GXL_ENVSIZE) |
| gs_env_partition_size = 0x10000; |
| #endif |
| gs_env_data_size = gs_env_partition_size - sizeof(uint32_t); |
| INFO("[ubootenv] using /dev/block/env with size(%d)(%d)\n", |
| gs_env_partition_size, gs_env_data_size); |
| } else if (!stat("/dev/block/ubootenv", &st)) { |
| sprintf(gs_partition_name, "/dev/block/ubootenv"); |
| if ((fd = open(gs_partition_name, O_RDWR)) < 0) { |
| ERROR("[ubootenv] open device(%s) error\n", gs_partition_name); |
| return -2; |
| } |
| |
| memset(&info, 0, sizeof(info)); |
| err = ioctl(fd, MEMGETINFO, &info); |
| if (err < 0) { |
| fprintf(stderr, "get MTD info error\n"); |
| close(fd); |
| return -3; |
| } |
| |
| gs_env_erase_size = info.erasesize; // 0x20000;//128K |
| gs_env_partition_size = info.size; // 0x8000; |
| gs_env_data_size = gs_env_partition_size - sizeof(long); |
| close(fd); |
| } |
| |
| while (i < MAX_UBOOT_RWRETRY && ret < 0) { |
| i++; |
| ret = load_bootenv(); |
| if (ret < 0) |
| ERROR("[ubootenv] Cannot read %s: %d.\n", gs_partition_name, ret); |
| if (ret < -2) |
| release_envimg_buffer(); |
| } |
| |
| if (i >= MAX_UBOOT_RWRETRY) { |
| ERROR("[ubootenv] read %s failed \n", gs_partition_name); |
| return -2; |
| } |
| |
| gs_init_done = true; |
| |
| return 0; |
| } |
| |
| int bootenv_update(const char *name, const char *value) { |
| if (!gs_init_done) { |
| ERROR("[ubootenv] not inited\n"); |
| return -1; |
| } |
| |
| envimg_buffer_lock(); |
| check_load_kv(); |
| |
| INFO("[ubootenv] update_bootenv_variable name [%s]: value [%s] \n", name, |
| value); |
| |
| const char *variable_value = bootenv_get_value(name); |
| if (!variable_value) |
| variable_value = ""; |
| |
| if (!strcmp(value, variable_value)) { |
| envimg_buffer_unlock(); |
| return 0; |
| } |
| |
| bootenv_set_value(name, value, 1); |
| |
| int i = 0; |
| int ret = -1; |
| while (i < MAX_UBOOT_RWRETRY && ret < 0) { |
| i++; |
| ret = save_bootenv(); |
| if (ret < 0) |
| ERROR("[ubootenv] Cannot write %s: %d.\n", gs_partition_name, ret); |
| } |
| |
| if (i < MAX_UBOOT_RWRETRY) { |
| INFO("[ubootenv] Save ubootenv to %s succeed!\n", gs_partition_name); |
| } |
| |
| envimg_buffer_unlock(); |
| return ret; |
| } |
| |
| const char *bootenv_get(const char *key) { |
| if (!gs_init_done) { |
| ERROR("[ubootenv] not inited\n"); |
| return NULL; |
| } |
| envimg_buffer_lock(); |
| check_load_kv(); |
| const char *v = bootenv_get_value(key); |
| envimg_buffer_unlock(); |
| return v; |
| } |
| |
| void bootenv_print(void) { |
| pthread_mutex_lock(&gs_kv_lock); |
| env_kv *attr = &gs_kv_header; |
| while (attr != NULL) { |
| SYS_LOGI("[ubootenv] key: [%s]\n", attr->key); |
| SYS_LOGI("[ubootenv] value: [%s]\n\n", attr->value); |
| attr = attr->next; |
| } |
| pthread_mutex_unlock(&gs_kv_lock); |
| } |
| |
| __attribute__((destructor)) void _deinit() { |
| if (!gs_init_done) |
| return; |
| release_envimg_buffer(); |
| gs_env_data.image = NULL; |
| gs_env_data.crc = NULL; |
| gs_env_data.data = NULL; |
| env_release_kv(); |
| } |
| |