sdk-hwV1.3/lichee/brandy-2.0/u-boot-2018/cmd/ubi_simu.c

638 lines
19 KiB
C

/*
* (C) Copyright 2007-2017
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
* tuchengmao <tuchengmao@allwinnertech.com>
*
* See file CREDITS for list of people who contributed to this
* project.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.
*/
#include <common.h>
#include <command.h>
#include <exports.h>
#include <memalign.h>
#include <mtd.h>
#include <nand.h>
#include <linux/mtd/mtd.h>
#include <linux/mtd/partitions.h>
#include <linux/err.h>
#include <ubi_uboot.h>
#include <linux/errno.h>
#include <mtd/ubi-user.h>
#include <linux/crc32.h>
#include "../drivers/mtd/ubi/ubi-media.h"
#include "../drivers/mtd/ubi/ubi.h"
#include "ubi_simu.h"
#define INIT_EC_CNT (1)
#define VOL_ALIGN_SIZE (1)
#if 0
#define sim_dbg(fmt, ...) printf(fmt "\n", ##__VA_ARGS__)
#else
#define sim_dbg(fmt, ...)
#endif
static struct ubi_device sim_ubi;
static struct ubi_device *p_ubi = &sim_ubi;
struct ubi_volume *w_vol = NULL;
static unsigned char g_bbt[2048] = { 0 }; //bad block table
static int vol_idx = 0;
static int cur_pnum_idx = 0;
static unsigned long long seq_num = 0;
static struct ubi_volume *ubi_simu_find_volume(char *volume)
{
struct ubi_volume *vol = NULL;
int i = 0;
for (i = 0; i < p_ubi->vtbl_slots; i++) {
vol = p_ubi->volumes[i];
if (vol && !strcmp(vol->name, volume))
return vol;
}
printf("Volume %s not found!\n", volume);
return NULL;
}
static int get_pnum(void)
{
while ((cur_pnum_idx < p_ubi->peb_count) && g_bbt[cur_pnum_idx])
cur_pnum_idx++;
if (cur_pnum_idx >= p_ubi->peb_count) {
sim_dbg("no available peb");
return -1;
}
sim_dbg("pnum: %d", cur_pnum_idx);
return cur_pnum_idx++;
}
static unsigned long long next_seq_num(void)
{
return seq_num++;
}
static int erase_peb(int pnum)
{
struct erase_info ei;
memset(&ei, 0, sizeof(ei));
ei.mtd = p_ubi->mtd;
ei.addr = (loff_t)pnum * p_ubi->peb_size;
ei.len = p_ubi->peb_size;
if (mtd_erase(p_ubi->mtd, &ei)) {
sim_dbg("erase %d fail", pnum);
return -1;
}
return 0;
}
static void fill_ec_hdr(void)
{
unsigned int crc = 0;
struct ubi_ec_hdr *ech = (struct ubi_ec_hdr *)p_ubi->peb_buf;
memset(ech, 0, p_ubi->ec_hdr_alsize);
ech->ec = cpu_to_be64(INIT_EC_CNT);
ech->magic = cpu_to_be32(UBI_EC_HDR_MAGIC);
ech->version = UBI_VERSION;
ech->vid_hdr_offset = cpu_to_be32(p_ubi->vid_hdr_offset);
ech->data_offset = cpu_to_be32(p_ubi->leb_start);
ech->image_seq = cpu_to_be32(p_ubi->image_seq);
crc = crc32(UBI_CRC32_INIT, ech, UBI_EC_HDR_SIZE_CRC);
ech->hdr_crc = cpu_to_be32(crc);
}
static void fill_vid_hdr(int lnum, int vol_type, int vol_id)
{
unsigned int crc = 0;
struct ubi_vid_hdr *vch = (struct ubi_vid_hdr *)(p_ubi->peb_buf + p_ubi->vid_hdr_offset);
memset(vch, 0, p_ubi->vid_hdr_alsize);
vch->vol_type = vol_type;
vch->vol_id = cpu_to_be32(vol_id);
vch->compat = (vol_id == UBI_LAYOUT_VOLUME_ID) ? UBI_LAYOUT_VOLUME_COMPAT : 0;
vch->data_size = vch->used_ebs = vch->data_pad = cpu_to_be32(0);
vch->lnum = cpu_to_be32(lnum);
vch->sqnum = cpu_to_be64(next_seq_num());
vch->magic = cpu_to_be32(UBI_VID_HDR_MAGIC);
vch->version = UBI_VERSION;
crc = crc32(UBI_CRC32_INIT, vch, UBI_VID_HDR_SIZE_CRC);
vch->hdr_crc = cpu_to_be32(crc);
}
static void show_mtd_info(struct mtd_info *mtd)
{
pr_err("mtd size: %lld\n", mtd->size);
pr_err("erasesize: %d\n", mtd->erasesize);
pr_err("writesize: %d\n", mtd->writesize);
pr_err("writebufsize: %d\n", mtd->writebufsize);
pr_err("subpage_sft: %d\n", mtd->subpage_sft);
pr_err("mtd: %p, %s\n", mtd, mtd->name);
}
static int wr_vol_table(void)
{
int i = 0;
int pnum = -1;
struct ubi_vtbl_record *layout_vol = (struct ubi_vtbl_record *)(p_ubi->peb_buf + p_ubi->leb_start);
/* filling empty entries */
sim_dbg("vol_idx: %d", vol_idx);
for (i = vol_idx; i < p_ubi->vtbl_slots; i++)
layout_vol[i].crc = cpu_to_be32(0xf116c36b);
/* write internal layout volume peb */
for (i = 0; i < UBI_LAYOUT_VOLUME_EBS; i++) {
pnum = get_pnum();
erase_peb(pnum);
fill_ec_hdr();
fill_vid_hdr(i, UBI_LAYOUT_VOLUME_TYPE, UBI_LAYOUT_VOLUME_ID);
/* Write the layout volume contents */
if (ubi_io_write(p_ubi, p_ubi->peb_buf, pnum, 0,
p_ubi->leb_start + p_ubi->vtbl_size))
sim_dbg("wr layout volume %d@%d fail", i,
UBI_LAYOUT_VOLUME_ID);
}
#if 0
/* read and check */
for (i=0;i<UBI_LAYOUT_VOLUME_EBS;i++) {
unsigned char g_vid_hdr[p_ubi->vid_hdr_alsize] = {0};
int j = 0;
if (ubi_io_read_vid_hdr(p_ubi, i, g_vid_hdr, 1))
sim_dbg("rd %d@%d fail", g_vid_hdr->lnum, g_vid_hdr->vol_id);
//ubi_dump_vid_hdr(g_vid_hdr);
/* read the layout volume contents */
memset(layout_vol, 0, p_ubi->vtbl_size);
if (ubi_io_read_data(p_ubi, layout_vol, pnum, 0, p_ubi->vtbl_size))
sim_dbg("rd layout volume %d fail", g_vid_hdr->lnum);
for(j=0;j<p_ubi->vtbl_slots;j++) {
if (0xf116c36b == be32_to_cpu(layout_vol[j].crc))
continue;
else
; //ubi_dump_vtbl_record(layout_vol + j, j);
}
}
#endif
return 0;
}
int ubi_simu_create_vol(char *volume, int64_t size, int dynamic, int vol_id)
{
int i = 0;
unsigned int crc = 0;
struct ubi_volume *vol = NULL;
struct ubi_vtbl_record *layout_vol =
(struct ubi_vtbl_record *)(p_ubi->peb_buf + p_ubi->leb_start);
sim_dbg("%s %s %lld %d %d, vol_idx: %d\n", __func__, volume, size, dynamic, vol_id, vol_idx);
vol = malloc(sizeof(struct ubi_volume));
if (!vol) {
sim_dbg("malloc %d fail", vol_idx);
goto fail_exit;
}
memset(vol, 0, sizeof(*vol));
/* Calculate how many eraseblocks are requested */
vol->usable_leb_size = p_ubi->leb_size - p_ubi->leb_size % VOL_ALIGN_SIZE;
vol->reserved_pebs = div_u64(size + vol->usable_leb_size - 1, vol->usable_leb_size);
/* Reserve physical eraseblocks */
if (vol->reserved_pebs > p_ubi->avail_pebs) {
sim_dbg("not enough PEBs, only %d available", p_ubi->avail_pebs);
goto fail_exit;
}
p_ubi->avail_pebs -= vol->reserved_pebs;
p_ubi->rsvd_pebs += vol->reserved_pebs;
vol->vol_id = vol_idx;
vol->alignment = VOL_ALIGN_SIZE;
vol->data_pad = p_ubi->leb_size % vol->alignment;
vol->vol_type = dynamic ? UBI_DYNAMIC_VOLUME : UBI_STATIC_VOLUME;
vol->name_len = strlen(volume);
memcpy(vol->name, volume, vol->name_len);
vol->ubi = p_ubi;
vol->eba_tbl = malloc(vol->reserved_pebs * sizeof(int));
if (!vol->eba_tbl) {
sim_dbg("alloc eba_tbl fail");
goto fail_exit;
}
for (i = 0; i < vol->reserved_pebs; i++)
vol->eba_tbl[i] = UBI_LEB_UNMAPPED;
/* filling ubi_vtbl_record for each volume */
memset(&layout_vol[vol_idx], 0, UBI_VTBL_RECORD_SIZE);
layout_vol[vol_idx].reserved_pebs = cpu_to_be32(vol->reserved_pebs);
layout_vol[vol_idx].alignment = cpu_to_be32(vol->alignment);
layout_vol[vol_idx].data_pad = cpu_to_be32(vol->data_pad);
layout_vol[vol_idx].name_len = cpu_to_be16(vol->name_len);
layout_vol[vol_idx].vol_type = dynamic ? UBI_VID_DYNAMIC : UBI_VID_STATIC;
memcpy(layout_vol[vol_idx].name, vol->name, vol->name_len);
/*
* add UBI_VTBL_AUTORESIZE_FLG for the last UDISK volume
* in case that we calculate size by mistake.
*/
if (0 == strncmp(volume, "UDISK", 5))
layout_vol[vol_idx].flags = UBI_VTBL_AUTORESIZE_FLG;
crc = crc32(UBI_CRC32_INIT, layout_vol + vol_idx, UBI_VTBL_RECORD_SIZE_CRC);
layout_vol[vol_idx].crc = cpu_to_be32(crc);
p_ubi->volumes[vol_idx] = vol;
p_ubi->vol_count++;
vol_idx++;
if (0 == strncmp(volume, "UDISK", 5)) {
sim_dbg("it is time to write layout volume table");
wr_vol_table();
}
return 0;
fail_exit:
if (vol->eba_tbl)
free(vol->eba_tbl);
if (vol)
free(vol);
return -1;
}
static int wr_peb(struct ubi_volume *vol, int lnum, void *buf, int len)
{
int pnum = 0;
if (len != vol->usable_leb_size)
pr_info("%s lnum: %d, len: %d\n", vol->name, lnum, len);
if (vol->vol_type == UBI_DYNAMIC_VOLUME) {
/* For dynamic volume, this function checks if the data contains 0xFF bytes
* at the end. If yes, the 0xFF bytes are cut and not written. So if the whole
* buffer contains only 0xFF bytes, the LEB is left unmapped.
* The reason why we skip the trailing 0xFF bytes in case of dynamic volume is
* that we want to make sure that more data may be appended to the logical
* eraseblock in future. Indeed, writing 0xFF bytes may have side effects and
* this PEB won't be writable anymore. So if one writes the file-system image
* to the UBI volume where 0xFFs mean free space - UBI makes sure this free
* space is writable after the update.
*/
int l = ALIGN(len, p_ubi->min_io_size);
memset(buf + len, 0xFF, l - len);
len = ubi_calc_data_len(p_ubi, buf, l);
if (len == 0) {
pr_warn("all %d bytes contain 0xFF - skip\n", len);
return 0;
}
} else {
/*
* When writing static volume, and this is the last logical
* eraseblock, the length (@len) does not have to be aligned to
* the minimal flash I/O unit. The 'ubi_eba_write_leb_st()'
* function accepts exact (unaligned) length and stores it in
* the VID header. And it takes care of proper alignment by
* padding the buffer. Here we just make sure the padding will
* contain zeros, not random trash.
*/
memset(buf + len, 0, vol->usable_leb_size - len);
}
pnum = get_pnum();
if (pnum == -1) {
pr_err("%s fail\n", __func__);
return -1;
}
vol->eba_tbl[lnum] = pnum;
erase_peb(pnum);
fill_ec_hdr();
fill_vid_hdr(lnum, (vol->vol_type == UBI_DYNAMIC_VOLUME) ?
UBI_VID_DYNAMIC : UBI_VID_STATIC, vol->vol_id);
if (vol->vol_type == UBI_STATIC_VOLUME) {
struct ubi_vid_hdr *vch = (struct ubi_vid_hdr *)(p_ubi->peb_buf + p_ubi->vid_hdr_offset);
vch->data_size = cpu_to_be32(len);
vch->used_ebs = cpu_to_be32(vol->upd_ebs);
vch->data_crc = cpu_to_be32(crc32(UBI_CRC32_INIT, buf, len));
/* re-calc vid hdr crc */
vch->hdr_crc = cpu_to_be32(crc32(UBI_CRC32_INIT, vch, UBI_VID_HDR_SIZE_CRC));
sim_dbg("size: %d, ebs: %d, data_crc: %x\n", len, vol->upd_ebs, crc32(UBI_CRC32_INIT, buf, len));
if (lnum == vol->upd_ebs - 1)
/* If this is the last LEB @len may be unaligned */
len = ALIGN(len, p_ubi->min_io_size);
else
ubi_assert(!(len & (p_ubi->min_io_size - 1)));
}
/* Write ec_hdr, vid_hdr and data */
if (ubi_io_write(p_ubi, p_ubi->peb_buf, pnum, 0, p_ubi->leb_start + len)) {
pr_err("wr %d@%d fail", lnum, vol->vol_id);
return -1;
}
return 0;
}
static int wr_vol(struct ubi_volume *vol, void *buf, size_t count)
{
int lnum, len;
u32 offs = 0;
//sim_dbg("write %d of %lld bytes, %lld already passed", count, vol->upd_bytes, vol->upd_received);
lnum = div_u64_rem(vol->upd_received, vol->usable_leb_size, &offs);
if (vol->upd_received + count > vol->upd_bytes)
count = vol->upd_bytes - vol->upd_received;
/*
* When updating volumes, we accumulate whole logical eraseblock of
* data and write it at once.
*/
if (offs != 0) {
/*
* This is a write to the middle of the logical eraseblock. We
* copy the data to our update buffer and wait for more data or
* flush it if the whole eraseblock is written or the update
* is finished.
*/
len = vol->usable_leb_size - offs;
if (len > count)
len = count;
memcpy(vol->upd_buf + offs, buf, len);
if (offs + len == vol->usable_leb_size ||
vol->upd_received + len == vol->upd_bytes)
wr_peb(vol, lnum, vol->upd_buf, offs + len);
vol->upd_received += len;
count -= len;
buf += len;
lnum += 1;
}
/*
* If we've got more to write, let's continue. At this point we know we
* are starting from the beginning of an eraseblock.
*/
while (count) {
if (count > vol->usable_leb_size)
len = vol->usable_leb_size;
else
len = count;
memcpy(vol->upd_buf, buf, len);
if (len == vol->usable_leb_size || vol->upd_received + len == vol->upd_bytes)
wr_peb(vol, lnum, vol->upd_buf, len);
vol->upd_received += len;
count -= len;
lnum += 1;
buf += len;
}
if (vol->upd_received == vol->upd_bytes)
sim_dbg("********wr over for %s**********", vol->name);
return 0;
}
int ubi_simu_volume_continue_write(char *volume, void *buf, size_t size)
{
struct ubi_volume *vol = ubi_simu_find_volume(volume);
sim_dbg("%s %s %p %d\n", __func__, volume, buf, size);
if (!vol)
return -1;
if (vol != w_vol) {
sim_dbg("warning diff %s %s", volume, w_vol->name);
while (1)
;
}
return wr_vol(vol, buf, size);
}
int ubi_simu_volume_begin_write(char *volume, void *buf, size_t size,
size_t full_size)
{
struct ubi_volume *vol = NULL;
sim_dbg("%s %s %p %d %d\n", __func__, volume, buf, size, full_size);
vol = ubi_simu_find_volume(volume);
if (!vol)
return -1;
if (vol != w_vol) {
sim_dbg("last wr status: %lld %lld", vol->upd_received, vol->upd_bytes);
/* flush last vol */
if (vol->upd_received != vol->upd_bytes)
sim_dbg("********************should flush first*****************\n");
}
w_vol = vol;
vol->upd_bytes = full_size;
vol->upd_ebs = div_u64(full_size + vol->usable_leb_size - 1, vol->usable_leb_size);
vol->used_bytes = vol->upd_ebs * vol->usable_leb_size;
vol->upd_received = 0;
vol->upd_buf = p_ubi->peb_buf + p_ubi->leb_start;
sim_dbg("%s upd_bytes:%lld, used_bytes:%lld", __func__, vol->upd_bytes, vol->used_bytes);
return wr_vol(vol, buf, size);
}
int ubi_simu_volume_write(char *volume, void *buf, size_t size)
{
sim_dbg("%s %s %p %d\n", __func__, volume, buf, size);
return ubi_simu_volume_begin_write(volume, buf, size, size);
}
/*
* copy from get_bad_peb_limit
*/
static int calc_bad_peb_limit(const struct ubi_device *ubi, int max_beb_per1024)
{
int limit, device_pebs;
uint64_t device_size;
if (!max_beb_per1024)
return 0;
/*
* Here we are using size of the entire flash chip and
* not just the MTD partition size because the maximum
* number of bad eraseblocks is a percentage of the
* whole device and bad eraseblocks are not fairly
* distributed over the flash chip. So the worst case
* is that all the bad eraseblocks of the chip are in
* the MTD partition we are attaching (ubi->mtd).
*/
device_size = mtd_get_device_size(ubi->mtd);
device_pebs = mtd_div_by_eb(device_size, ubi->mtd);
limit = mult_frac(device_pebs, max_beb_per1024, 1024);
/* Round it up */
if (mult_frac(limit, 1024, max_beb_per1024) < device_pebs)
limit += 1;
return limit;
}
struct ubi_device *ubi_simu_part(char *part_name, const char *vid_header_offset)
{
struct mtd_info *mtd = NULL;
int i = 0;
pr_err("part_name: %s, vid_hdr_offset: %s, sizeof(void): %d\n", part_name, vid_header_offset, sizeof(void));
mtd_probe_devices();
mtd = get_mtd_device_nm(part_name);
if (IS_ERR(mtd)) {
printf("Partition %s not found!\n", part_name);
return NULL;
}
put_mtd_device(mtd);
show_mtd_info(mtd);
memset(p_ubi, 0, sizeof(struct ubi_device));
p_ubi->ubi_num = 0;
strncpy(p_ubi->ubi_name, "ubi0", 4);
p_ubi->mtd = mtd;
/* duplicated from io_init */
p_ubi->peb_size = p_ubi->mtd->erasesize;
p_ubi->peb_count = mtd_div_by_eb(p_ubi->mtd->size, p_ubi->mtd);
p_ubi->flash_size = p_ubi->mtd->size;
p_ubi->bad_allowed = 1;
p_ubi->bad_peb_limit = calc_bad_peb_limit(p_ubi, CONFIG_MTD_UBI_BEB_LIMIT);
p_ubi->min_io_size = p_ubi->mtd->writesize;
p_ubi->hdrs_min_io_size = p_ubi->mtd->writesize >> p_ubi->mtd->subpage_sft;
p_ubi->max_write_size = p_ubi->mtd->writebufsize;
/* Calculate default aligned sizes of EC and VID headers */
p_ubi->ec_hdr_alsize = ALIGN(UBI_EC_HDR_SIZE, p_ubi->hdrs_min_io_size);
p_ubi->vid_hdr_alsize = ALIGN(UBI_VID_HDR_SIZE, p_ubi->hdrs_min_io_size);
p_ubi->vid_hdr_offset = p_ubi->vid_hdr_aloffset = p_ubi->ec_hdr_alsize;
p_ubi->leb_start = p_ubi->vid_hdr_offset + UBI_VID_HDR_SIZE;
p_ubi->leb_start = ALIGN(p_ubi->leb_start, p_ubi->min_io_size);
p_ubi->leb_size = p_ubi->peb_size - p_ubi->leb_start;
p_ubi->image_seq = 0;
p_ubi->bad_allowed = 1;
p_ubi->peb_buf = malloc(p_ubi->peb_size);
if (!p_ubi->peb_buf) {
sim_dbg("malloc peb_buf fail");
return NULL;
}
memset(p_ubi->peb_buf, 0, p_ubi->peb_size);
for (i = 0; i < p_ubi->peb_count; i++) {
if (ubi_io_is_bad(p_ubi, i)) {
g_bbt[i] = 1;
sim_dbg("peb %d is bad block", i);
p_ubi->bad_peb_count++;
continue;
}
}
p_ubi->avail_pebs = p_ubi->peb_count - p_ubi->bad_peb_count;
p_ubi->beb_rsvd_level = p_ubi->bad_peb_limit - p_ubi->bad_peb_count;
p_ubi->beb_rsvd_pebs = p_ubi->beb_rsvd_level;
pr_err("beb_pebs: %d, beb_level: %d, limit: %d\n",
p_ubi->beb_rsvd_pebs, p_ubi->beb_rsvd_level,
p_ubi->bad_peb_limit);
/*
* The number of supported volumes is limited by the eraseblock size
* and by the UBI_MAX_VOLUMES constant.
*/
p_ubi->vtbl_slots = p_ubi->leb_size / UBI_VTBL_RECORD_SIZE;
if (p_ubi->vtbl_slots > UBI_MAX_VOLUMES)
p_ubi->vtbl_slots = UBI_MAX_VOLUMES;
p_ubi->vtbl_size = p_ubi->vtbl_slots * UBI_VTBL_RECORD_SIZE;
pr_err("UBI_VTBL_RECORD_SIZE: %d, size: %d\n", UBI_VTBL_RECORD_SIZE,
p_ubi->vtbl_size);
p_ubi->vtbl_size = ALIGN(p_ubi->vtbl_size, p_ubi->min_io_size);
pr_err("UBI_VTBL_RECORD_SIZE: %d, size: %d\n", UBI_VTBL_RECORD_SIZE,
p_ubi->vtbl_size);
/* 4: reserved 2 layout volumes, 1 wl and 1 atomic change */
p_ubi->rsvd_pebs += p_ubi->beb_rsvd_pebs + 4;
p_ubi->avail_pebs -= p_ubi->rsvd_pebs;
ubi_devices[0] = p_ubi;
pr_err("cnt: %d, bad_cnt:%d, avail: %d, rsvd: %d", p_ubi->peb_count,
p_ubi->bad_peb_count, p_ubi->avail_pebs, p_ubi->rsvd_pebs);
return p_ubi;
}
int sunxi_ubi_simu_volume_read(char *volume, loff_t offp, char *buf,
size_t size)
{
int err = 0, lnum, off, len, tbuf_size;
void *tbuf;
unsigned long long tmp;
struct ubi_volume *vol;
sim_dbg("%s %s %lld %p %d\n", __func__, volume, offp, buf, size);
vol = ubi_simu_find_volume(volume);
if (vol == NULL)
return -ENODEV;
if (offp == vol->used_bytes)
return 0;
if (size == 0) {
printf("simu No size specified -> Using max size (%lld)\n", vol->used_bytes);
size = vol->used_bytes;
}
if (offp + size > vol->used_bytes)
size = vol->used_bytes - offp;
tbuf_size = vol->usable_leb_size;
if (size < tbuf_size)
tbuf_size = ALIGN(size, p_ubi->min_io_size);
tbuf = malloc(tbuf_size);
if (!tbuf) {
printf("NO MEM\n");
return -ENOMEM;
}
len = size > tbuf_size ? tbuf_size : size;
tmp = offp;
off = do_div(tmp, vol->usable_leb_size);
lnum = tmp;
do {
if (off + len >= vol->usable_leb_size)
len = vol->usable_leb_size - off;
sim_dbg("%d -> %d, off: %d, %d", lnum, vol->eba_tbl[lnum], off, len);
if (UBI_LEB_UNMAPPED == vol->eba_tbl[lnum])
memset(tbuf, 0xff, len);
else
err = ubi_io_read_data(p_ubi, tbuf, vol->eba_tbl[lnum], off, len);
off += len;
if (off == vol->usable_leb_size) {
lnum += 1;
off -= vol->usable_leb_size;
}
size -= len;
offp += len;
memcpy(buf, tbuf, len);
buf += len;
len = size > tbuf_size ? tbuf_size : size;
} while (size);
free(tbuf);
return err;
}