// SPDX-License-Identifier: GPL-2.0 #include #include #include #ifdef CONFIG_AW_MTD_SPINAND #include #endif #ifdef CONFIG_AW_MTD_RAWNAND #include #endif #include #include #include #include #include #include /* * undef crc32 -> crc32_le in linux/crc32.h * crc32 for sunxi_mbr */ #undef crc32 #include #ifdef CONFIG_AW_MTD_SPINAND #include "spinand/sunxi-spinand.h" #endif #define GAP_BUF_MAX_SECTS (128) #define TYPE_STATIC_VOLUME (1 << 0) struct ubi_nand_vol { char name[PART_NAME_MAX_SIZE]; unsigned int addr; unsigned int sects; unsigned int type; }; struct ubi_mbr { unsigned int part_cnt; struct ubi_nand_vol vols[NAND_MAX_PART_CNT]; }; struct ubi_vol_status { #define INIT_UBI_VOL 0 #define CREATE_UBI_VOL 1 #define WRITE_UBI_VOL 2 int state; #define DYNAMIC_VOL 0 #define STATIC_VOL 1 int type; unsigned int plan_wr_sects; unsigned int written_sects; struct ubi_nand_vol *vol; }; struct ubi_status { int last_partno; struct ubi_vol_status vols[NAND_MAX_PART_CNT]; }; struct ubi_info { /* * saving mbr with follow feature: * 1. copy from sunxi_mbr_t * 2. add mbr volume from sunxi_mbr_t * 3. volume addr relative to user space address. * it means the first volume mbr's addr is 0x00 * 4. volume sects do not align */ struct ubi_mbr nand_mbr; /* * saving mbr with follow feature: * 1. copy from nand_mbr as above. * 2. volume addr relative to the whole flash address. * it means the first volume mbr's addr is NOT 0x00. * the first volume always be the start of mtd device. * 3. volume sects do not align */ struct ubi_mbr ubi_mbr; struct ubi_status ubi_status; struct ubi_mtd_info mtd_info; char last_name[PART_NAME_MAX_SIZE]; int last_partno; unsigned int last_offset; char mtdids[20]; char mtdparts[512]; unsigned int ubootblks; unsigned int last_vol_sects; }; static struct ubi_info g_ubi_info; extern int sunxi_mbr_convert_to_gpt(void *sunxi_mbr_buf, char *gpt_buf, int storage_type); static unsigned int to_align(unsigned int size, unsigned int limit) { if (limit) return (size + limit - 1) / limit * limit; return size; } static inline struct ubi_info *get_ubi_info(void) { return &g_ubi_info; } #define ubi_to_nand_mbr(ubi_info) (&(ubi_info)->nand_mbr) #define ubi_to_ubi_mbr(ubi_info) (&(ubi_info)->ubi_mbr) #define ubi_to_ubi_status(ubi_info) (&(ubi_info)->ubi_status) #define ubi_to_mtd(ubi_info) (&(ubi_info)->mtd_info) #if 0 static void randomize(unsigned int *buf, unsigned int len, int seed) { int i; srand(seed); for (i = 0; i < len; i++) *buf++ = rand(); } #endif static void print_ubi_mbr(struct ubi_mbr *mbr) { int i; pr_info("%-8s %-10s %-10s %-10s %s\n", "partno", "addr", "sects", "type", "name"); for (i = 0; i < mbr->part_cnt; i++) { struct ubi_nand_vol *vol = &mbr->vols[i]; pr_info("%-8d 0x%08x 0x%08x 0x%08x %s\n", i, vol->addr, vol->sects, vol->type, vol->name); } } static int get_partno_by_addr(unsigned int offset, unsigned int sec_cnt) { int i; unsigned int addr, sects; struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mbr *nand_mbr = ubi_to_nand_mbr(ubinfo); if (!nand_mbr->part_cnt) { pr_err("not found nand_mbr\n"); return -ENODEV; } for (i = 0; i < nand_mbr->part_cnt; i++) { addr = nand_mbr->vols[i].addr; sects = nand_mbr->vols[i].sects; if (offset >= addr + sects) continue; if (offset + sec_cnt > addr + sects) { pr_err("offset 0x%x with sects 0x%x over volume %s [0x%x 0x%x)\n", offset, sec_cnt, nand_mbr->vols[i].name, addr, addr + sects); break; } return i; } pr_err("get partno from nand_mbr failed: offset 0x%x sects %u\n", offset, sec_cnt); print_ubi_mbr(nand_mbr); return -EINVAL; } static int get_volnum_by_nand_mbr_name(char *name) { int i; struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mbr *nand_mbr = ubi_to_nand_mbr(ubinfo);; for (i = 0; i < nand_mbr->part_cnt; i++) { if (name == NULL) continue; if (!strcmp((char *)nand_mbr->vols[i].name, name)) return i; } return -ENODEV; } static int get_volnum_by_name(char *name) { int i; struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mbr *ubi_mbr = ubi_to_ubi_mbr(ubinfo);; for (i = 0; i < ubi_mbr->part_cnt; i++) { if (name == NULL) continue; if (!strcmp((char *)ubi_mbr->vols[i].name, name)) return i; } return -ENODEV; } static int get_mtd_num_by_name(char *name) { int i; struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mtd_info *mtd_info = ubi_to_mtd(ubinfo); if (name == NULL) return -EINVAL; for (i = 0; i < mtd_info->part_cnt; i++) if (!strcmp(mtd_info->part[i].name, name)) return i; return -EINVAL; } static int get_mtd_num_to_attach(void) { int num; num = sunxi_get_mtd_num_parts(); num = num ? num - 1 : -1; pr_info("ubi attach the last part of mtd device: NO.%d\n", num); return num; } static inline unsigned int offset_on_part(int partno, unsigned int addr) { struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mbr *nand_mbr = ubi_to_nand_mbr(ubinfo); return addr - nand_mbr->vols[partno].addr; } static int check_sunxi_mbr(sunxi_mbr_t *sunxi_mbr) { if (strncmp((const char *)sunxi_mbr->magic, SUNXI_MBR_MAGIC, 8)) { pr_err("mbr magic error: %.8s wanted %s\n", (char *)sunxi_mbr->magic, SUNXI_MBR_MAGIC); return -EINVAL; } if (WORK_MODE_BOOT == get_boot_work_mode()) { unsigned int crc = 0; crc = crc32(0, (const unsigned char *)&sunxi_mbr->version, SUNXI_MBR_SIZE - 4); if (crc != sunxi_mbr->crc32) { pr_err("mbr crc verify failed: wanted 0x%x get 0x%x\n", sunxi_mbr->crc32, crc); return -EINVAL; } } if (!sunxi_mbr->PartCount) { pr_err("partition count is zero, something error\n"); return -EINVAL; } return 0; } static void adjust_sunxi_mbr(sunxi_mbr_t *org_mbr, unsigned int last_vol_sects) { int i = 0; sunxi_mbr_t *tmp_mbr = org_mbr; unsigned int partcount = org_mbr->PartCount; if (last_vol_sects == org_mbr->array[partcount - 1].lenlo) return; org_mbr->array[partcount - 1].lenlo = last_vol_sects; org_mbr->crc32 = crc32(0, (const unsigned char *)&org_mbr->version, SUNXI_MBR_SIZE - 4); for (i = 1; i < SUNXI_MBR_COPY_NUM; i++) { tmp_mbr++; tmp_mbr->array[partcount - 1].lenlo = org_mbr->array[partcount - 1].lenlo; tmp_mbr->crc32 = org_mbr->crc32; } } static int init_nand_mbr(sunxi_mbr_t *sunxi_mbr, struct ubi_mbr *nand_mbr) { int i, ret; if (!sunxi_mbr) { pr_err("invalid sunxi_mbr\n"); return -EINVAL; } ret = check_sunxi_mbr(sunxi_mbr); if (ret) return ret; memset(nand_mbr, 0x00, sizeof(*nand_mbr)); if (!sunxi_mbr->PartCount) { pr_err("no partition on sunxi_mbr\n"); return -EINVAL; } /* the first part is MBR */ nand_mbr->part_cnt = 1; nand_mbr->vols[0].addr = 0x00; nand_mbr->vols[0].sects = sunxi_mbr->array[0].addrlo; nand_mbr->vols[0].type |= TYPE_STATIC_VOLUME; strcpy((char *)nand_mbr->vols[0].name, "mbr"); nand_mbr->part_cnt += sunxi_mbr->PartCount; for (i = 0; i < sunxi_mbr->PartCount; i++) { nand_mbr->vols[i + 1].addr = sunxi_mbr->array[i].addrlo; nand_mbr->vols[i + 1].sects = sunxi_mbr->array[i].lenlo; strncpy((char *)nand_mbr->vols[i + 1].name, (const char *)sunxi_mbr->array[i].name, PART_NAME_MAX_SIZE); nand_mbr->vols[i + 1].type = sunxi_mbr->array[i].user_type; } pr_info("MBR info (unalign):\n"); print_ubi_mbr(nand_mbr); return 0; } static int init_mtd_info(struct ubi_mtd_info *mtd_info) { int ret = -EINVAL; int i = 0; struct ubi_mtd_part *mtd_part; #ifdef CONFIG_AW_MTD_SPINAND if (support_spinand()) ret = spinand_init_mtd_info(mtd_info); #endif #ifdef CONFIG_AW_MTD_RAWNAND if (support_rawnand()) ret = rawnand_init_mtd_info(mtd_info); #endif if (ret) { pr_err("%s-%d _init_mtd_info fail\n", __func__, __LINE__); return ret; } pr_info("MTD info (%d)\n", mtd_info->part_cnt); pr_info("pagesize: 0x%x\n", mtd_info->pagesize); pr_info("blksize: 0x%x\n", mtd_info->blksize); pr_info("%-4s %-10s %-10s %s\n", "num", "offset", "bytes", "name"); for (i = 0; i < mtd_info->part_cnt; i++) { mtd_part = &mtd_info->part[i]; pr_info("%-4d 0x%08x 0x%08x %s\n", mtd_part->partno, mtd_info->part[i].offset, mtd_part->bytes, mtd_part->name); } return 0; } static void init_ubi_mbr(struct ubi_info *ubinfo) { int ret; int i, k; int mtdnum; int32_t n_cur_part_len; int32_t leb_sects; int32_t ubi_min_vol_sects; struct ubi_mbr *ubi_mbr = ubi_to_ubi_mbr(ubinfo); struct ubi_mbr *nand_mbr = ubi_to_nand_mbr(ubinfo); struct ubi_status *ubi_status = ubi_to_ubi_status(ubinfo); struct ubi_mtd_info *mtd_info = ubi_to_mtd(ubinfo); memset(ubi_mbr, 0x00, sizeof(struct ubi_mbr)); memset(ubi_status, 0x00, sizeof(struct ubi_status)); ubi_status->last_partno = -1; mtdnum = get_mtd_num_to_attach(); /* * multiplane with subpage, make it need only on super page to save * EC header and VID header */ leb_sects = to_sects(mtd_info->blksize - 1 * mtd_info->pagesize); ubi_min_vol_sects = leb_sects; for (i = 0, k = 0; i < nand_mbr->part_cnt; i++) { ret = get_mtd_num_by_name((char *)nand_mbr->vols[i].name); if (ret >= 0) continue; memcpy(ubi_mbr->vols[k].name, nand_mbr->vols[i].name, sizeof(nand_mbr->vols[i].name)); ubi_mbr->vols[k].type = nand_mbr->vols[i].type; if (k) ubi_mbr->vols[k].addr = ubi_mbr->vols[k - 1].addr + ubi_mbr->vols[k - 1].sects; else ubi_mbr->vols[k].addr = mtdnum >= 0 ? (unsigned int)to_sects(mtd_info->part[mtdnum].offset) : -1; /* align leb_size */ n_cur_part_len = to_align(nand_mbr->vols[i].sects, leb_sects); if (n_cur_part_len) ubi_mbr->vols[k].sects = max_t(unsigned int, n_cur_part_len, ubi_min_vol_sects); else ubi_mbr->vols[k].sects = 0; k++; ubi_mbr->part_cnt++; } pr_info("MBR info (align):\n"); print_ubi_mbr(ubi_mbr); } static int create_mtdparts_do(void) { int ret = 0; char cmd[2][20]; char *argv[2]; argv[0] = cmd[0]; argv[1] = cmd[1]; memset(cmd, 0x00, sizeof(cmd)); sprintf(argv[0], "mtdparts"); sprintf(argv[1], "default"); ret = sunxi_do_mtdparts(0, 2, argv); if (!ret) { debug("mtdparts info:\n"); sunxi_do_mtdparts(0, 1, argv); } return ret; } static int ubi_attach_mtd_do(int mtdnum) { int ret; char cmd[6][20]; char *argv[6]; char *mtd_name; int i; mtd_name = sunxi_get_mtdparts_name(mtdnum); if (mtd_name == NULL) { pr_err("mtd_name is NULL !!!\n"); return -EINVAL; } for (i = 0; i < 6; i++) argv[i] = cmd[i]; memset(cmd, 0x00, sizeof(cmd)); sprintf(&cmd[0][0], "ubi"); sprintf(&cmd[1][0], "part"); sprintf(&cmd[2][0], "%s", mtd_name); /* ubi part [part] [offset] */ ret = sunxi_do_ubi(0, 3, argv); if (ret) pr_err("ubi part %s err !\n", mtd_name); return ret; } static int check_volume_existed_do(char *name) { char cmd[6][20]; char *argv[6]; int i; if (get_volnum_by_name(name) < 0) { pr_err("not found volume %s in mbr !!!\n", name); return -ENODEV; } for (i = 0; i < 6; i++) argv[i] = cmd[i]; memset(cmd, 0x00, sizeof(cmd)); sprintf(&cmd[0][0], "ubi"); sprintf(&cmd[1][0], "check"); sprintf(&cmd[2][0], "%s", name); return sunxi_do_ubi(0, 3, argv); } static void check_and_adjust_ubi_mbr(struct ubi_nand_vol *vol, struct ubi_mbr *ubi_mbr, int partno) { int volumesize; int i = 0; char cmd[3][20]; char *argv[3]; for (i = 0; i < 3; i++) argv[i] = cmd[i]; memset(cmd, 0x00, sizeof(cmd)); sprintf(argv[0], "ubi"); sprintf(argv[1], "get"); sprintf(argv[2], "%s", vol->name); /* ubi create vloume size type */ volumesize = sunxi_do_ubi(0, 3, argv); if (volumesize != -1) { if (to_sects(volumesize) != ubi_mbr->vols[partno].sects) { pr_info("existed volume %s size is %d(sects) , inside ubi part size is %d(sects)," "existed volume size to update inside ubi part size\n", vol->name, to_sects(volumesize), ubi_mbr->vols[partno].sects); ubi_mbr->vols[partno].sects = to_sects(volumesize); } } } static int create_ubi_volume_do(struct ubi_nand_vol *vol) { int i, ret; char cmd[5][20]; char *argv[5]; for (i = 0; i < 5; i++) argv[i] = cmd[i]; ret = check_volume_existed_do((char *)vol->name); if (!ret) { pr_info("volume %s existed, not create again\n", vol->name); return ret; } memset(cmd, 0x00, sizeof(cmd)); sprintf(argv[0], "ubi"); sprintf(argv[1], "create"); sprintf(argv[2], "%s", vol->name); sprintf(argv[3], "0x%016x", to_bytes(vol->sects)); if (vol->type & TYPE_STATIC_VOLUME) sprintf(&cmd[4][0], "s"); else sprintf(&cmd[4][0], "d"); /* ubi create vloume size type */ ret = sunxi_do_ubi(0, 5, argv); if (ret) { pr_err("'ubi create %s %s %s' failed, return %d\n", argv[2], argv[3], argv[4], ret); return ret; } return 0; } static int create_ubi_volume(struct ubi_info *ubinfo) { int ret = -EINVAL; int i; char *mtd_name; int mtd_num; unsigned int mtd_bytes; unsigned int vol_total_bytes = 0; struct ubi_mbr *ubi_mbr = ubi_to_ubi_mbr(ubinfo); struct ubi_mbr *nand_mbr = ubi_to_nand_mbr(ubinfo); struct ubi_status *ubi_status = ubi_to_ubi_status(ubinfo); if (ubi_mbr == NULL) { pr_err("UBI mbr is NULL !!!\n"); return -EINVAL; } if (!ubi_mbr->part_cnt) return 0; mtd_num = get_mtd_num_to_attach(); if (mtd_num < 0) { pr_err("invalid mtd num %d\n", mtd_num); return -EINVAL; } mtd_name = sunxi_get_mtdparts_name(mtd_num); mtd_bytes = sunxi_get_mtdpart_size(mtd_num); if (mtd_name == NULL) { pr_err("mtd_name is NULL !!!\n"); return -EINVAL; } for (i = 0; i < ubi_mbr->part_cnt; i++) vol_total_bytes += to_bytes(ubi_mbr->vols[i].sects); if (vol_total_bytes > mtd_bytes) { pr_err("ubi volume total size is larger than mtd size.\n" "ubi_vol_total_bytes : 0x%x, mtd_bytes: 0x%x\n", vol_total_bytes, mtd_bytes); return -EINVAL; } pr_info("ubi attatch mtd, name: %s\n\n", mtd_name); ret = ubi_attach_mtd_do(mtd_num); if (ret) return ret; for (i = 0; i < ubi_mbr->part_cnt; i++) { ret = create_ubi_volume_do(&ubi_mbr->vols[i]); if (ret) return ret; ubi_status->vols[i].state = CREATE_UBI_VOL; } /* * reset the last volume size * The last volume always be 0 for aw sys_partition. * It means that the last volume should resize to the max size. * However, the max size is defined by ubi, so we insert a function * @check_add_adjust_ubi_mbr to check and get the last volume size, */ check_and_adjust_ubi_mbr(&ubi_mbr->vols[i - 1], nand_mbr, i - 1); check_and_adjust_ubi_mbr(&ubi_mbr->vols[i -1], ubi_mbr, i -1); if (ubinfo->last_vol_sects == 0) ubinfo->last_vol_sects = nand_mbr->vols[i - 1].sects; return ret; } static int read_ubi_volume_do(char *vol_name, unsigned int sectors, void *buf) { size_t bytes; char cmd[6][20]; char *argv[6]; int i; bytes = to_bytes(sectors); for (i = 0; i < 6; i++) argv[i] = cmd[i]; memset(cmd, 0x00, sizeof(cmd)); sprintf(&cmd[0][0], "ubi"); sprintf(&cmd[1][0], "read"); sprintf(&cmd[2][0], "0x%016x", (unsigned int)(ulong)buf); sprintf(&cmd[3][0], "%s", vol_name); sprintf(&cmd[4][0], "0x%016x", (unsigned int)(ulong)bytes); return sunxi_do_ubi(0, 5, argv); } static int write_ubi_volume_do(char *vol_name, void *buf, unsigned int bytes, unsigned int full_bytes) { char cmd[6][20]; char *argv[6]; int i; memset(cmd, 0x00, sizeof(cmd)); for (i = 0; i < 6; i++) argv[i] = cmd[i]; /* ubi write.part address volume size [fullsize] */ sprintf(&cmd[0][0], "ubi"); sprintf(&cmd[1][0], "write.part"); sprintf(&cmd[2][0], "0x%016x", (unsigned int)(ulong)buf); sprintf(&cmd[3][0], "%s", vol_name); sprintf(&cmd[4][0], "0x%016x", bytes); sprintf(&cmd[5][0], "0x%016x", full_bytes); if (full_bytes) return sunxi_do_ubi(0, 6, argv); else return sunxi_do_ubi(0, 5, argv); } static int write_ubi_volume(struct ubi_info *ubinfo, char *name, unsigned int sectors, void *buf) { int ret; size_t full_bytes, bytes; unsigned int plan_wr_sects, written_sects; int num; struct ubi_status *ubi_status = ubi_to_ubi_status(ubinfo); char *last_name = ubinfo->last_name; num = get_volnum_by_nand_mbr_name(name); if (num < 0) { pr_err("not found volume named %s\n", name); return -EINVAL; } plan_wr_sects = ubi_status->vols[num].plan_wr_sects; written_sects = ubi_status->vols[num].written_sects; bytes = to_bytes(sectors); if ((written_sects + sectors) > plan_wr_sects) { pr_err("write %u sectors over limit %u sectors, resize it\n", sectors, plan_wr_sects - written_sects); bytes = to_bytes(plan_wr_sects - written_sects); } full_bytes = 0; if (strcmp(last_name, name)) { if (ubi_status->vols[num].state != CREATE_UBI_VOL) { int vol_num; struct ubi_mbr *ubi_mbr = ubi_to_ubi_mbr(ubinfo); struct ubi_nand_vol *vol; vol_num = get_volnum_by_name(name); if (vol_num < 0) { pr_err("not found volume named %s\n", name); return -EINVAL; } vol = &ubi_mbr->vols[vol_num]; if (check_volume_existed_do(name)) { ret = create_ubi_volume_do(vol); if (ret) return ret; } } strcpy(last_name, name); full_bytes = to_bytes(plan_wr_sects); } ret = write_ubi_volume_do(name, buf, bytes, full_bytes); if (ret) pr_err("write volume %s with bytes %u full_bytes %u failed\n", name, bytes, full_bytes); ubi_status->last_partno = num; ubi_status->vols[num].written_sects += sectors; ubi_status->vols[num].state = WRITE_UBI_VOL; return ret; } static int read_mtd_do(loff_t from, size_t len, size_t *retlen, u_char *buf) { struct mtd_info *mtd = NULL; #ifdef CONFIG_AW_MTD_SPINAND if (support_spinand()) { struct aw_spinand *spinand = get_spinand(); mtd = spinand_to_mtd(spinand); } #endif #ifdef CONFIG_AW_MTD_RAWNAND if (support_rawnand()) { struct aw_nand_chip *chip = get_rawnand(); mtd = awnand_chip_to_mtd(chip); } #endif return mtd ? mtd_read(mtd, from, len, retlen, buf) : -EINVAL; } static int write_mtd_do(loff_t to, size_t len, size_t *retlen, u_char *buf) { struct mtd_info *mtd = NULL; #ifdef CONFIG_AW_MTD_SPINAND if (support_spinand()) { struct aw_spinand *spinand = get_spinand(); mtd = spinand_to_mtd(spinand); } #endif #ifdef CONFIG_AW_MTD_RAWNAND if (support_rawnand()) { struct aw_nand_chip *chip = get_rawnand(); mtd = awnand_chip_to_mtd(chip); } #endif return mtd ? mtd_write(mtd, to, len, retlen, buf) : -EINVAL; } static int flush_mtd_dev(struct ubi_mtd_info *mtd_info) { u_char *upd_buf; int num; unsigned int col; size_t retlen; unsigned int to, len, pgsize, offset; unsigned int plan_wr_bytes, written_bytes, upd_received; int ret; num = mtd_info->last_partno; if (num < 0) return 0; offset = mtd_info->part[num].offset; pgsize = mtd_info->pagesize; upd_buf = (u_char *)mtd_info->wbuf; upd_received = to_bytes(mtd_info->part[num].written_sects); written_bytes = upd_received / pgsize * pgsize; plan_wr_bytes = to_bytes(mtd_info->part[num].plan_wr_sects); plan_wr_bytes = to_align(plan_wr_bytes, pgsize); if (written_bytes >= plan_wr_bytes) return 0; len = plan_wr_bytes - written_bytes; col = upd_received % pgsize; if (col) memset(upd_buf + col, 0xff, pgsize - col); else memset(upd_buf, 0xff, pgsize); while (len) { to = offset + written_bytes; ret = write_mtd_do(to, pgsize, &retlen, upd_buf); if (ret) { pr_err("mtd write failed with size:%x, ret:%u\n", pgsize, retlen); return ret; } memset(upd_buf, 0xff, pgsize); written_bytes += pgsize; len -= pgsize; } mtd_info->part[num].written_sects = to_sects(written_bytes); pr_info("volume %s total written :0x%x\n", mtd_info->part[num].name, to_sects(written_bytes)); return 0; } static int read_mtd_part(struct ubi_mtd_info *mtd_info, int num, unsigned int start, unsigned int sectors, void *buffer) { size_t len, retlen; unsigned int offset; unsigned int partsize; unsigned int from; #ifdef CONFIG_AW_MTD_SPINAND struct aw_spinand *spinand = get_spinand(); struct mtd_info *mtd = spinand_to_mtd(spinand); #else struct aw_nand_chip *chip = get_rawnand(); struct mtd_info *mtd = awnand_chip_to_mtd(chip); #endif unsigned int block = 0; unsigned int left_len; unsigned int read, read_start; loff_t ofs = 0; static int last_read_mtd = -1; static int skip_size; int ret; if (last_read_mtd != num) { last_read_mtd = num; skip_size = 0; } partsize = mtd_info->part[num].bytes; offset = mtd_info->part[num].offset; len = to_bytes(sectors); from = offset + to_bytes(start) + skip_size; if (to_bytes(start + sectors) > partsize) { pr_err("%s,out of mtdsize(0x%x).\n", __func__, partsize); pr_err("start:%0x, sectors:%0x.\n", start, sectors); return -EINVAL; } flush_mtd_dev(mtd_info); left_len = len; read_start = from; block = read_start / mtd->erasesize; ofs = block * mtd->erasesize; if (len > mtd->erasesize - (from & mtd->erasesize_mask)) { check0: if (mtd->_block_isbad(mtd, ofs)) { pr_info("check 0: read %d (in block %d) is bad, skip," "its data store in next good block\n", from, (unsigned int)ofs / mtd->erasesize); skip_size += mtd->erasesize; read_start += mtd->erasesize; ofs += mtd->erasesize; goto check0; } read = from & mtd->erasesize_mask; ret = read_mtd_do(read_start, read, &retlen, buffer); if (ret) { pr_err("read from %llu len %llu err\n", from, len); return ret; } left_len -= read; buffer += read; read_start += read; } while (left_len) { check1: block = read_start / mtd->erasesize; ofs = block * mtd->erasesize; if (mtd->_block_isbad(mtd, ofs)) { pr_info("check1: read %d (in block %d) is bad, skip," "its data store in next good block\n", read_start, block); read_start += mtd->erasesize; skip_size += mtd->erasesize; goto check1; } if (left_len <= mtd->erasesize) read = left_len; else read = mtd->erasesize; ret = read_mtd_do(read_start, read, &retlen, buffer); if (ret) { pr_err("read from %llu len %llu err\n", from, len); return ret; } left_len -= read; read_start += read; buffer += read; from += read; } return 0; } static int write_mtd_part(struct ubi_mtd_info *mtd_info, int num, unsigned int sectors, void *buffer) { static int last_num = -1; size_t len, retlen; unsigned int col, rest_pgsize; unsigned int to, offset, pgsize, upd_received; u_char *src_buf, *upd_buf; #ifdef CONFIG_AW_MTD_SPINAND struct aw_spinand *spinand = get_spinand(); struct mtd_info *mtd = spinand_to_mtd(spinand); #else struct aw_nand_chip *chip = get_rawnand(); struct mtd_info *mtd = awnand_chip_to_mtd(chip); #endif unsigned int block = 0; unsigned int skip_size = 0; if (num != last_num) { pr_err("First mtd write. part_name : %s, wr_sectors: 0x%0x\n", mtd_info->part[num].name, sectors); pr_err("%s, cur_num : 0x%x. last_num : 0x%x\n", __func__, num, last_num); /* the first write mtdx, then flush the last_num mtdx */ flush_mtd_dev(mtd_info); last_num = num; mtd_info->part[num].written_sects = 0; mtd_info->last_partno = num; mtd_info->part[num].skip_sects = 0; mtd_info->part[num].last_skip_block = -1; } offset = mtd_info->part[num].offset; skip_size = to_bytes(mtd_info->part[num].skip_sects); upd_received = to_bytes(mtd_info->part[num].written_sects); src_buf = (u_char *)buffer; upd_buf = (u_char *)mtd_info->wbuf; pgsize = mtd_info->pagesize; if (pgsize > MTD_W_BUF_SIZE) { pr_err("Err: pgsize(0x%x) is larger than bufsize(64KB).\n", pgsize); return -EINVAL; } check: block = (upd_received + skip_size + offset) / mtd->erasesize; loff_t ofs = block * mtd->erasesize; if (mtd->_block_isbad(mtd, ofs) && block != mtd_info->part[num].last_skip_block) { pr_info("write %d (in block %d) is bad, skip\n", upd_received, (unsigned int)ofs / mtd->erasesize); mtd_info->part[num].last_skip_block = block; mtd_info->part[num].skip_sects += to_sects(mtd->erasesize); skip_size = to_bytes(mtd_info->part[num].skip_sects); pr_info("skip %d , write in block %d\n", skip_size, (offset + upd_received + skip_size) / mtd->erasesize); goto check; } len = to_bytes(sectors); col = upd_received % pgsize; rest_pgsize = pgsize - col; if (col) { if (len >= rest_pgsize) { pr_debug("col:0x%x, rest_pgsize:%x, len:%x\n", col, rest_pgsize, len); pr_debug("mtd writing :0x%x\n", rest_pgsize >> 9); memcpy(upd_buf + col, src_buf, rest_pgsize); to = (offset + upd_received + skip_size) / pgsize * pgsize; if (write_mtd_do(to, pgsize, &retlen, upd_buf)) { pr_err("%d, %s, Err:wr_size:%x, written:%0x\n", __LINE__, __func__, pgsize, retlen); return -EINVAL; } src_buf += rest_pgsize; len -= rest_pgsize; upd_received += rest_pgsize; } else { memcpy(upd_buf + col, src_buf, len); len = 0; upd_received += len; } } while (len) { if (len >= pgsize) { pr_debug("mtd writing :0x%x\n", to_bytes(pgsize)); memcpy(upd_buf, src_buf, pgsize); to = offset + upd_received + skip_size; if (to % mtd->erasesize == 0) { struct erase_info erase = { .addr = to, .len = mtd->erasesize, }; pr_debug("erase %u to %u (block %d)\n", to, to + mtd->erasesize, to / mtd->erasesize); if (mtd->_erase(mtd, &erase)) { pr_err("erase %u to %u fail\n", to, to + mtd->erasesize); return -EINVAL; } } if (write_mtd_do(to, pgsize, &retlen, upd_buf)) { pr_err("%d, %s,Err: wr_size:%x, written:%0x\n", __LINE__, __func__, pgsize, retlen); return -EINVAL; } src_buf += pgsize; len -= pgsize; upd_received += pgsize; } else { debug("mtd copy to buf: 0x%x\n", (unsigned int)(len >> 9)); memcpy(upd_buf, src_buf, len); upd_received += len; len = 0; } } mtd_info->part[num].written_sects = to_sects(upd_received); pr_debug("%s total written :0x%x\n", mtd_info->part[num].name, to_sects(upd_received)); return 0; } static int fill_gap_do(struct ubi_info *ubinfo, char *vol_name, int64_t gap_size, char fill_data) { char *gap_buf; int i, ret = 0; struct ubi_device *ubi = ubi_devices[0]; struct ubi_status *ubi_status = ubi_to_ubi_status(ubinfo); int last_volnum = get_volnum_by_name(vol_name); if (last_volnum < 0) return -EINVAL; int leb_sects = to_sects(ubi->leb_size); int64_t remain = 0; fill_data = 0x55; if (!gap_size) return 0; pr_info("fill gap start: volume %s sects 0x%x\n", vol_name, (unsigned int )gap_size); gap_buf = (void *)malloc(to_bytes(GAP_BUF_MAX_SECTS)); if (!gap_buf) { pr_err("Err: malloc memory fail, line: %d, %s\n", __LINE__, __func__); return -ENOMEM; } for (i = 0; i < to_bytes(GAP_BUF_MAX_SECTS); i++) { if (i % 2 == 0) gap_buf[i] = 0x55; else if (i % 2 == 1) gap_buf[i] = 0xaa; } if (ubi_status->vols[last_volnum].written_sects % leb_sects) { remain = (leb_sects - (ubi_status->vols[last_volnum].written_sects % leb_sects)); /*to fill the last used peb of the volume*/ for (i = remain; i > 0 ; i -= GAP_BUF_MAX_SECTS) { ret = write_ubi_volume(ubinfo, vol_name, min_t(unsigned int, i, GAP_BUF_MAX_SECTS), gap_buf); if (ret) { pr_err("fill gap fail with %d back!\n", ret); free(gap_buf); return ret; } } } /*data pattern 0xff don't really write to flash*/ memset(gap_buf, 0xff, GAP_BUF_MAX_SECTS << 9); for (i = gap_size - remain; i > 0; i -= GAP_BUF_MAX_SECTS) { ret = write_ubi_volume(ubinfo, vol_name, min_t(unsigned int, i, GAP_BUF_MAX_SECTS), gap_buf); if (ret) { pr_err("fill gap fail with %d back!\n", ret); free(gap_buf); return ret; } } pr_info("fill gap end: volume %s\n", vol_name); free(gap_buf); return 0; } static int fill_gap(struct ubi_info *ubinfo, int partno) { int ret = 0; int64_t gap = 0; char *name; struct ubi_status *ubi_status = ubi_to_ubi_status(ubinfo); struct ubi_mbr *nand_mbr = ubi_to_nand_mbr(ubinfo); unsigned last_volnum; if (partno < 0) return 0; name = (char *)nand_mbr->vols[partno].name; last_volnum = get_volnum_by_nand_mbr_name(name); if (last_volnum < 0) return -EINVAL; gap = ubi_status->vols[last_volnum].plan_wr_sects - ubi_status->vols[last_volnum].written_sects; if (gap) ret = fill_gap_do(ubinfo, name, gap, 0x00); return ret; } static int read_ubi_volume(struct ubi_info *ubinfo, char *name, unsigned int offs, unsigned int sectors, void *buf) { size_t full_size, size; int num, volnum; loff_t offp; struct ubi_mbr *ubi_mbr = ubi_to_ubi_mbr(ubinfo); volnum = get_volnum_by_name(name); if (volnum < 0) { pr_err("volume %s is not in ubi mbr !!!\n", name); return -ENODEV; } offp = to_bytes((loff_t)offs); full_size = to_bytes((size_t)ubi_mbr->vols[volnum].sects); size = (size_t)sectors << 9; if (size > full_size) { pr_warn("Warning: par overflow."); pr_warn("size(byte):%0x, full_size(byte):%0x\n", size, full_size); size = full_size; } num = get_volnum_by_nand_mbr_name(name); if (num < 0) { pr_err("volume %s is not in ubi mbr !!!\n", name); return -ENODEV; } /* * aw burning tools will check the written data if finish downloading * image, we should fill full to volume. */ if (num == ubinfo->last_partno) { int ret; ret = fill_gap(ubinfo, num); if (ret) { pr_err("ubi flush fail!!!\n"); return ret; } } return sunxi_ubi_volume_read(name, offp, (char *)buf, size); } static int init_ubi_info(struct ubi_info *ubinfo, sunxi_mbr_t *org_mbr) { int ret; struct ubi_mtd_info *mtd_info = ubi_to_mtd(ubinfo); struct ubi_mbr *nand_mbr = ubi_to_nand_mbr(ubinfo); ret = init_mtd_info(mtd_info); if (ret) return ret; ret = init_nand_mbr(org_mbr, nand_mbr); if (ret) return ret; init_ubi_mbr(ubinfo); ubinfo->last_offset = ubinfo->last_partno = -1; return 0; } static int init_ubi(struct ubi_info *ubinfo, sunxi_mbr_t *org_mbr) { int ret; ret = create_mtdparts_do(); if (ret) { pr_err("create mtd partitions failed\n"); return ret; } ret = init_ubi_info(ubinfo, org_mbr); if (ret) return ret; ret = create_ubi_volume(ubinfo); if (ret) return ret; return 0; } #define PART_ENV_BUF_SIZE 1024 static unsigned int get_gpt_from_mbr_vol(unsigned int start, unsigned int sectors, void *buffer) { int mtd_num; int ret = 0; char *mbr_buf = NULL, *gpt_buf = NULL; struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mbr *nand_mbr = ubi_to_nand_mbr(ubinfo); struct ubi_mtd_info *mtd_info = ubi_to_mtd(ubinfo); gpt_buf = malloc(8 * 1024); if (!gpt_buf) { pr_err("malloc 8k gpt_buf fail\n"); goto err; } memset(gpt_buf, 0, 8 * 1024); mtd_num = get_mtd_num_by_name("mbr"); if (mtd_num >= 0) { ret = read_mtd_part(mtd_info, mtd_num, start, sectors, buffer); } else { mbr_buf = (char *)malloc(to_bytes(MBR_SECTORS)); if (!mbr_buf) { pr_err("allocate for mbr buf failed\n"); goto free_gpt_buf; } ret = read_ubi_volume_do("mbr", MBR_SECTORS, mbr_buf); if (ret) { pr_err("read volume mbr failed return %d\n", ret); goto free_mbr_buf; } ret = check_sunxi_mbr((sunxi_mbr_t *)mbr_buf); if (ret) goto free_mbr_buf; sunxi_mbr_convert_to_gpt(mbr_buf, (char *)gpt_buf, STORAGE_SPI_NAND); memcpy(buffer, gpt_buf + to_bytes(start), to_bytes(sectors)); } if (!ret && !start && !nand_mbr->part_cnt) { ret = init_ubi_info(ubinfo, (sunxi_mbr_t *)mbr_buf); if (ret) goto free_mbr_buf; } ret = sectors; free_mbr_buf: if (mbr_buf) free(mbr_buf); free_gpt_buf: if (gpt_buf) free(gpt_buf); err: return ret; } /* * The last volume always be 0 for aw sys_partition. * It means that the last volume should resize to the max size. * However, the max size is defined by ubi, so we insert a function * @mtd_set_last_vol_sects to get the last volume size, */ int mtd_set_last_vol_sects(unsigned int sects) { struct ubi_info *ubinfo = get_ubi_info(); ubinfo->last_vol_sects = sects; pr_info("reset last volume size to 0x%x\n", sects); return 0; } #ifdef CONFIG_AW_MTD_SPINAND uint64_t spinand_sys_part_offset(void) { struct aw_spinand *spinand = get_spinand(); struct aw_spinand_chip *chip = spinand_to_chip(spinand); struct aw_spinand_info *info = chip->info; unsigned int phy_blk_bytes = info->phy_block_size(chip); unsigned int uboot_end; uint64_t offset = 0; spinand_uboot_blknum(NULL, &uboot_end); offset += uboot_end * phy_blk_bytes; #if IS_ENABLED(CONFIG_AW_MTD_SPINAND_FASTBOOT) unsigned int bootsize = CONFIG_BIMAGE_SIZE * 1024 * 1024; // boot.img default define 20MiB /* boot partition */ offset += bootsize; /* boot_backup partition */ offset += bootsize; #endif offset += AW_SPINAND_RESERVED_PHY_BLK_FOR_SECURE_STORAGE * phy_blk_bytes; #if IS_ENABLED(CONFIG_AW_SPINAND_PSTORE_MTD_PART) offset += AW_SPINAND_RESERVED_FOR_PSTORE_KB * SZ_1K;; #endif #if IS_ENABLED(CONFIG_RAW_KERNEL) unsigned int blk_bytes = info->block_size(chip); /* kernel */ if (CONFIG_KERNEL_SIZE_BYTE) { offset += ALIGN(CONFIG_KERNEL_SIZE_BYTE, blk_bytes); } #endif return offset; } static void set_env_mtdparts_spinand(void) { struct ubi_info *ubinfo = get_ubi_info(); struct aw_spinand *spinand = get_spinand(); struct aw_spinand_chip *chip = spinand_to_chip(spinand); struct aw_spinand_info *info = chip->info; unsigned int phy_blk_bytes = info->phy_block_size(chip); int wlen; struct ubi_mtd_part mtd_part; unsigned int uboot_start, uboot_end, offset = 0; spinand_uboot_blknum(&uboot_start, &uboot_end); ubinfo->ubootblks = uboot_end - uboot_start; wlen = snprintf(ubinfo->mtdparts, 512, "mtdparts=nand:"); /* boot0 */ snprintf((char *)&mtd_part.name, PART_NAME_MAX_SIZE, "boot0"); mtd_part.offset = 0; mtd_part.bytes = uboot_start * phy_blk_bytes; offset += mtd_part.bytes; wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "%uk@%u(%s)ro,", mtd_part.bytes / SZ_1K, mtd_part.offset, mtd_part.name); /* uboot */ snprintf((char *)&mtd_part.name, PART_NAME_MAX_SIZE, "uboot"); mtd_part.offset = offset; mtd_part.bytes = uboot_end * phy_blk_bytes - mtd_part.offset; offset += mtd_part.bytes; wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "%uk@%u(%s)ro,", mtd_part.bytes / SZ_1K, mtd_part.offset, mtd_part.name); /*boot mtd part append to uboot mtd part tail, boot0 is easy to handle*/ #if IS_ENABLED(CONFIG_AW_MTD_SPINAND_FASTBOOT) unsigned int bootsize = CONFIG_BIMAGE_SIZE * 1024 * 1024; // boot.img default define 20MiB snprintf((char *)&mtd_part.name, PART_NAME_MAX_SIZE, "boot"); mtd_part.offset = offset; mtd_part.bytes = bootsize; offset += mtd_part.bytes; wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "%uk@%u(%s)ro,", mtd_part.bytes / SZ_1K, mtd_part.offset, mtd_part.name); snprintf((char *)&mtd_part.name, PART_NAME_MAX_SIZE, "boot_backup"); mtd_part.offset = offset; mtd_part.bytes = bootsize; offset += mtd_part.bytes; wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "%uk@%u(%s)ro,", mtd_part.bytes / SZ_1K, mtd_part.offset, mtd_part.name); #endif spinand->sec_sto.chip = chip; spinand->sec_sto.startblk = offset / phy_blk_bytes; spinand->sec_sto.endblk = spinand->sec_sto.startblk + AW_SPINAND_RESERVED_PHY_BLK_FOR_SECURE_STORAGE; /* secure storage */ snprintf((char *)&mtd_part.name, PART_NAME_MAX_SIZE, "secure_storage"); mtd_part.offset = offset; mtd_part.bytes = AW_SPINAND_RESERVED_PHY_BLK_FOR_SECURE_STORAGE; mtd_part.bytes *= phy_blk_bytes; offset += mtd_part.bytes; wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "%uk@%u(%s)ro,", mtd_part.bytes / SZ_1K, mtd_part.offset, mtd_part.name); #if IS_ENABLED(CONFIG_AW_SPINAND_PSTORE_MTD_PART) /* pstore */ snprintf((char *)&mtd_part.name, PART_NAME_MAX_SIZE, "pstore"); mtd_part.offset = offset; mtd_part.bytes = AW_SPINAND_RESERVED_FOR_PSTORE_KB * SZ_1K; offset += mtd_part.bytes; wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "%uk@%u(%s)ro,", mtd_part.bytes / SZ_1K, mtd_part.offset, mtd_part.name); #endif #if IS_ENABLED(CONFIG_RAW_KERNEL) unsigned int blk_bytes = info->block_size(chip); /* kernel */ snprintf((char *)&mtd_part.name, PART_NAME_MAX_SIZE, "kernel"); mtd_part.offset = offset; if (CONFIG_KERNEL_SIZE_BYTE) mtd_part.bytes = ALIGN(CONFIG_KERNEL_SIZE_BYTE, blk_bytes); else mtd_part.bytes = blk_bytes; offset += mtd_part.bytes; wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "%uk@%u(%s)ro,", mtd_part.bytes / SZ_1K, mtd_part.offset, mtd_part.name); #endif /* user data */ wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "-(sys)"); pr_info("mtdparts: %s\n", ubinfo->mtdparts); snprintf(ubinfo->mtdids, 20, "nand0=nand"); sunxi_set_defualt_mtdpart(ubinfo->mtdids, ubinfo->mtdparts); } int spinand_mtd_flush_last_volume(void) { struct ubi_info *ubinfo = get_ubi_info(); fill_gap(ubinfo, ubinfo->last_partno); ubinfo->last_partno = -1; *ubinfo->last_name = 0; return 0; } unsigned int spinand_mtd_write_ubi(unsigned int start, unsigned int sectors, void *buffer) { char *name = NULL; int ret; int num, mtd_num; struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mbr *nand_mbr = ubi_to_nand_mbr(ubinfo); struct ubi_mtd_info *mtd_info = ubi_to_mtd(ubinfo); struct ubi_status *ubi_status = ubi_to_ubi_status(ubinfo); unsigned int offset; /* * The start sectors [0-80) is used for mbr. Write to this area means * spinand ubi haven't initialized, we do it now. */ if (!start && (sectors <= MBR_SECTORS)) { if (sectors != MBR_SECTORS) { pr_err("the buffer size for mbr is wrongly: %d sectors\n", sectors); goto err; } ret = init_ubi(ubinfo, buffer); if (ret) { pr_err("initialize sunxi spinand ubi failed\n"); goto err; } /* * After creating all volumes, we got the actucal size of last * partition(UDISK), it`s time to correct the corresponding * partition entry info in sunxi_mbr */ if (WORK_MODE_BOOT != get_boot_work_mode()) adjust_sunxi_mbr(buffer, ubinfo->last_vol_sects); } num = get_partno_by_addr(start, sectors); if (!sectors || num < 0) goto err; /* * new start for a volume, it means this volume has data to download. * we should set ubi_status */ ret = offset_on_part(num, start); if (!ret) { /* * the last volume has 0 size default for aw platform with * sys_partition. But some customers need to download image to * the last volume, which need the real size of the last volume. */ if (num + 1 == nand_mbr->part_cnt) ubi_status->vols[num].plan_wr_sects = ubinfo->last_vol_sects; else ubi_status->vols[num].plan_wr_sects = nand_mbr->vols[num].sects; ubi_status->vols[num].written_sects = 0; } name = (char *)nand_mbr->vols[num].name; mtd_num = get_mtd_num_by_name(name); if (mtd_num >= 0) { ret = write_mtd_part(mtd_info, mtd_num, sectors, buffer); return ret ? 0 : sectors; } ret = check_volume_existed_do(name); if (ret) { pr_err("Warning: ubi volume does not exist!\n"); goto err; } offset = offset_on_part(num, start); if (num == ubinfo->last_partno) { if (offset != ubinfo->last_offset) { pr_err("offset 0x%x smaller than last offset 0x%x\n", ubinfo->last_offset, offset); goto err; } } else { /* write to new volume */ if (offset) { pr_err("offset should be 0 but 0x%x when write to new volume %s\n", offset, (char *)nand_mbr->vols[num].name); return -EINVAL; } ret = fill_gap(ubinfo, ubinfo->last_partno); if (ret != 0) pr_warn("fill gap fail, part:%s, start:%0x, sectors:%0x\n", name, start, sectors); } ubinfo->last_offset = offset + sectors; ubinfo->last_partno = num; pr_debug("write to volume %s: offset 0x%x sects 0x%x\n", name, start, sectors); ret = write_ubi_volume(ubinfo, name, sectors, buffer); return ret ? 0 : sectors; err: return 0; } unsigned int spinand_mtd_read_ubi(unsigned int start, unsigned int sectors, void *buffer) { char *name = NULL; unsigned int offset; int ret = 0; int num, mtd_num; struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mbr *nand_mbr = ubi_to_nand_mbr(ubinfo); struct ubi_mtd_info *mtd_info = ubi_to_mtd(ubinfo); if ((start < MBR_SECTORS) && ((start + sectors) <= MBR_SECTORS)) { return get_gpt_from_mbr_vol(start, sectors, buffer); } num = get_partno_by_addr(start, sectors); if (!sectors || num < 0) return 0; name = (char *)nand_mbr->vols[num].name; offset = offset_on_part(num, start); mtd_num = get_mtd_num_by_name(name); if (mtd_num >= 0) { ret = read_mtd_part(mtd_info, mtd_num, offset, sectors, buffer); return ret < 0 ? 0 : sectors; } ret = check_volume_existed_do(name); if (ret) { debug("Warning: ubi volume does not exist!\n"); return 0; } ret = read_ubi_volume(ubinfo, name, offset, sectors, buffer); return ret ? 0 : sectors; } int spinand_mtd_update_ubi_env(void) { struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mbr *ubi_mbr = ubi_to_ubi_mbr(ubinfo); char parts_set[PART_ENV_BUF_SIZE], *ppset; char buf[PART_ENV_BUF_SIZE]; char *nand_root = NULL; char *rootname; int i; #define BLOCK_DEVICE "/dev/ubiblock" #define BLOCK_DEVICE_NUM "0_x" rootname = env_get("root_partition"); if (rootname) pr_info("root_partition is %s\n", rootname); nand_root = env_get("nand_root"); if (rootname) pr_info("nand_root is %s\n", nand_root); memset(parts_set, 0, PART_ENV_BUF_SIZE); ppset = parts_set; for (i = 0; i < ubi_mbr->part_cnt; i++) { memset(buf, 0, PART_ENV_BUF_SIZE); /* set parts_set */ snprintf(buf, PART_ENV_BUF_SIZE, "ubi0_%d", i); ppset += snprintf(ppset, PART_ENV_BUF_SIZE - (ppset - parts_set), "%s@%s:", ubi_mbr->vols[i].name, buf); /* set root */ if (rootname && !strcmp(rootname, ubi_mbr->vols[i].name)) { if (!strncmp(nand_root, BLOCK_DEVICE, strlen(BLOCK_DEVICE))) { /*rootfs mount as ext4、squashfs ...*/ snprintf(buf, PART_ENV_BUF_SIZE, "/dev/ubiblock0_%d", i); #ifdef CONFIG_SUNXI_DM_VERITY pr_info("set verity_dev to %s\n", buf); env_set("verity_dev", buf); pr_info("set root to /dev/dm-0\n"); env_set("nand_root", "/dev/dm-0"); #else pr_info("set root to %s\n", buf); env_set("nand_root", buf); #endif } else { /*rootfs mount as ubifs*/ snprintf(buf, PART_ENV_BUF_SIZE, "ubi0_%d", i); pr_info("set root to %s\n", buf); env_set("nand_root", buf); } } } env_set("partitions", parts_set); snprintf(buf, PART_ENV_BUF_SIZE, "%u", ubinfo->ubootblks); pr_info("set aw-ubi-spinand.ubootblks to %u\n", ubinfo->ubootblks); env_set("aw-ubi-spinand.ubootblks", buf); snprintf(buf, PART_ENV_BUF_SIZE, "%d", get_mtd_num_to_attach()); pr_info("set ubi_attach_mtdnum to %d\n", get_mtd_num_to_attach()); env_set("ubi_attach_mtdnum", buf); pr_info("update ubi part info ok\n"); return 0; } /* * ubi->good_peb_count = ubi->peb_count - ubi->bad_peb_count; @ubi_attach * @ubi_read_volume_table * ubi->avail_pebs = ubi->good_peb_count - ubi->corr_peb_count * ubi->avail_pebs -= all vol->reserved_pebs; @init_volumes * ubi->avail_pebs -= UBI_LAYOUT_VOLUME_EBS; @init_volumes, 2 * ubi->avail_pebs -= WL_RESERVED_PEBS; @ubi_wl_init, 1 * ubi->avail_pebs -= EBA_RESERVED_PEBS; @ubi_eba_init, 1 * ubi->avail_pebs -= ubi->beb_rsvd_pebs; @ubi_eba_init */ int spinand_ubi_user_volumes_size(void) { int pebs = 0; int leb_size = 0; int vol_pebs = 0; int i = 0; struct ubi_device *ubi = ubi_devices[0]; if (ubi) { leb_size = ubi->leb_size; pebs = ubi->rsvd_pebs - ubi->beb_rsvd_pebs - 2 - 1 - 1; /*UBI_INT_VOL_COUNT: 1*/ for (i = 0; i < (ubi->vol_count - 1); i++) { vol_pebs += ubi->volumes[i]->reserved_pebs; } if (vol_pebs != pebs) pr_info("warning vol_pebs: %d, %d\n", vol_pebs, pebs); } pr_info("logical area info: %d %d last_lba: %d\n", pebs, leb_size, pebs * (leb_size >> 9) - 1); return leb_size * pebs; } int spinand_mtd_attach_mtd(void) { int num; struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mtd_info *mtd_info = ubi_to_mtd(ubinfo); /* support to configure size of mtd part 0 (boot_parts) */ set_env_mtdparts_spinand(); if (create_mtdparts_do()) return -EINVAL; init_mtd_info(mtd_info); num = get_mtd_num_to_attach(); if (num < 0) return -EINVAL; if (ubi_attach_mtd_do(num)) return -EINVAL; return 0; } #endif /*CONFIG_AW_MTD_SPINAND*/ #ifdef CONFIG_AW_MTD_RAWNAND static void set_env_mtdparts_rawnand(void) { struct ubi_info *ubinfo = get_ubi_info(); struct aw_nand_chip *chip = get_rawnand(); struct aw_nand_sec_sto *rawnand_sec_sto = get_rawnand_sec_sto(); unsigned int phy_blk_bytes = chip->erasesize; int wlen; struct ubi_mtd_part mtd_part; unsigned int uboot_start, uboot_end, offset = 0; rawnand_uboot_blknum(&uboot_start, &uboot_end); ubinfo->ubootblks = uboot_end - uboot_start; wlen = snprintf(ubinfo->mtdparts, 512, "mtdparts=nand:"); rawnand_sec_sto->startblk = uboot_end; rawnand_sec_sto->endblk = uboot_end + AW_RAWNAND_RESERVED_PHY_BLK_FOR_SECURE_STORAGE; /* boot0 */ snprintf((char *)&mtd_part.name, PART_NAME_MAX_SIZE, "boot0"); mtd_part.offset = 0; mtd_part.bytes = uboot_start * phy_blk_bytes; offset += mtd_part.bytes; wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "%uk@%u(%s)ro,", mtd_part.bytes / SZ_1K, mtd_part.offset, mtd_part.name); /* uboot */ snprintf((char *)&mtd_part.name, PART_NAME_MAX_SIZE, "uboot"); mtd_part.offset = offset; mtd_part.bytes = uboot_end * phy_blk_bytes - mtd_part.offset; offset += mtd_part.bytes; wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "%uk@%u(%s)ro,", mtd_part.bytes / SZ_1K, mtd_part.offset, mtd_part.name); /* secure storage */ snprintf((char *)&mtd_part.name, PART_NAME_MAX_SIZE, "secure_storage"); mtd_part.offset = offset; mtd_part.bytes = AW_RAWNAND_RESERVED_PHY_BLK_FOR_SECURE_STORAGE; mtd_part.bytes *= phy_blk_bytes; offset += mtd_part.bytes; wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "%uk@%u(%s)ro,", mtd_part.bytes / SZ_1K, mtd_part.offset, mtd_part.name); #if IS_ENABLED(CONFIG_AW_RAWNAND_PSTORE_MTD_PART) /* pstore */ snprintf((char *)&mtd_part.name, PART_NAME_MAX_SIZE, "pstore"); mtd_part.offset = offset; mtd_part.bytes = AW_SPINAND_RESERVED_FOR_PSTORE_KB * SZ_1K; offset += mtd_part.bytes; wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "%uk@%u(%s)ro,", mtd_part.bytes / SZ_1K, mtd_part.offset, mtd_part.name); #endif #if IS_ENABLED(CONFIG_RAW_KERNEL) unsigned int blk_bytes = phy_blk_bytes * 2; /* kernel */ snprintf((char *)&mtd_part.name, PART_NAME_MAX_SIZE, "kernel"); mtd_part.offset = offset; if (CONFIG_KERNEL_SIZE_BYTE) mtd_part.bytes = ALIGN(CONFIG_KERNEL_SIZE_BYTE, blk_bytes); else mtd_part.bytes = blk_bytes; offset += mtd_part.bytes; wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "%uk@%u(%s)ro,", mtd_part.bytes / SZ_1K, mtd_part.offset, mtd_part.name); #endif /* user data */ wlen += snprintf(ubinfo->mtdparts + wlen, 512 - wlen, "-(sys)"); pr_info("mtdparts: %s\n", ubinfo->mtdparts); snprintf(ubinfo->mtdids, 20, "nand0=nand"); sunxi_set_defualt_mtdpart(ubinfo->mtdids, ubinfo->mtdparts); } int rawnand_mtd_flush_last_volume(void) { struct ubi_info *ubinfo = get_ubi_info(); fill_gap(ubinfo, ubinfo->last_partno); ubinfo->last_partno = -1; *ubinfo->last_name = 0; return 0; } unsigned int rawnand_mtd_write_ubi(unsigned int start, unsigned int sectors, void *buffer) { char *name = NULL; int ret; int num, mtd_num; struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mbr *nand_mbr = ubi_to_nand_mbr(ubinfo); struct ubi_mtd_info *mtd_info = ubi_to_mtd(ubinfo); struct ubi_status *ubi_status = ubi_to_ubi_status(ubinfo); unsigned int offset; /* * The start sectors [0-80) is used for mbr. Write to this area means * rawnand ubi haven't initialized, we do it now. */ if (!start && (sectors <= MBR_SECTORS)) { if (sectors != MBR_SECTORS) { pr_err("the buffer size for mbr is wrongly: %d sectors\n", sectors); goto err; } ret = init_ubi(ubinfo, buffer); if (ret) { pr_err("initialize sunxi rawnand ubi failed\n"); goto err; } /* * After creating all volumes, we got the actucal size of last * partition(UDISK), it`s time to correct the corresponding * partition entry info in sunxi_mbr */ printf("WORK_MODE_BOOT@%d get_boot_work_mode@%d\n", WORK_MODE_BOOT, get_boot_work_mode()); if (WORK_MODE_BOOT != get_boot_work_mode()) adjust_sunxi_mbr(buffer, ubinfo->last_vol_sects); } num = get_partno_by_addr(start, sectors); if (!sectors || num < 0) goto err; /* * new start for a volume, it means this volume has data to download. * we should set ubi_status */ ret = offset_on_part(num, start); if (!ret) { /* * the last volume has 0 size default for aw platform with * sys_partition. But some customers need to download image to * the last volume, which need the real size of the last volume. */ if (num + 1 == nand_mbr->part_cnt) ubi_status->vols[num].plan_wr_sects = ubinfo->last_vol_sects; else ubi_status->vols[num].plan_wr_sects = nand_mbr->vols[num].sects; ubi_status->vols[num].written_sects = 0; } name = (char *)nand_mbr->vols[num].name; mtd_num = get_mtd_num_by_name(name); if (mtd_num >= 0) { ret = write_mtd_part(mtd_info, mtd_num, sectors, buffer); return ret ? 0 : sectors; } ret = check_volume_existed_do(name); if (ret) { pr_err("Warning: ubi volume does not exist!\n"); goto err; } offset = offset_on_part(num, start); if (num == ubinfo->last_partno) { if (offset != ubinfo->last_offset) { pr_err("offset 0x%x smaller than last offset 0x%x\n", ubinfo->last_offset, offset); goto err; } } else { /* write to new volume */ if (offset) { pr_err("offset should be 0 but 0x%x when write to new volume %s\n", offset, (char *)nand_mbr->vols[num].name); return -EINVAL; } ret = fill_gap(ubinfo, ubinfo->last_partno); if (ret != 0) pr_warn("fill gap fail, part:%s, start:%0x, sectors:%0x\n", name, start, sectors); /* *pr_warn("don't fill gap, part:%s, start:%0x, sectors:%0x\n", * name, start, sectors); */ } ubinfo->last_offset = offset + sectors; ubinfo->last_partno = num; pr_debug("write to volume %s: offset 0x%x sects 0x%x\n", name, start, sectors); ret = write_ubi_volume(ubinfo, name, sectors, buffer); return ret ? 0 : sectors; err: return 0; } unsigned int rawnand_mtd_read_ubi(unsigned int start, unsigned int sectors, void *buffer) { char *name = NULL; unsigned int offset; int ret = 0; int num, mtd_num; struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mbr *nand_mbr = ubi_to_nand_mbr(ubinfo); struct ubi_mtd_info *mtd_info = ubi_to_mtd(ubinfo); if ((start < MBR_SECTORS) && ((start + sectors) <= MBR_SECTORS)) { return get_gpt_from_mbr_vol(start, sectors, buffer); } num = get_partno_by_addr(start, sectors); if (!sectors || num < 0) return 0; name = (char *)nand_mbr->vols[num].name; offset = offset_on_part(num, start); mtd_num = get_mtd_num_by_name(name); if (mtd_num >= 0) { ret = read_mtd_part(mtd_info, mtd_num, offset, sectors, buffer); return ret < 0 ? 0 : sectors; } ret = check_volume_existed_do(name); if (ret) { debug("Warning: ubi volume does not exist!\n"); return 0; } ret = read_ubi_volume(ubinfo, name, offset, sectors, buffer); return ret ? 0 : sectors; } int rawnand_mtd_update_ubi_env(void) { struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mbr *ubi_mbr = ubi_to_ubi_mbr(ubinfo); char parts_set[PART_ENV_BUF_SIZE], *ppset; char buf[PART_ENV_BUF_SIZE]; char *nand_root = NULL; char *rootname; int i; #define BLOCK_DEVICE "/dev/ubiblock" #define BLOCK_DEVICE_NUM "0_x" rootname = env_get("root_partition"); if (rootname) pr_info("root_partition is %s\n", rootname); nand_root = env_get("nand_root"); if (rootname) pr_info("nand_root is %s\n", nand_root); memset(parts_set, 0, PART_ENV_BUF_SIZE); ppset = parts_set; for (i = 0; i < ubi_mbr->part_cnt; i++) { memset(buf, 0, PART_ENV_BUF_SIZE); /* set parts_set */ snprintf(buf, PART_ENV_BUF_SIZE, "ubi0_%d", i); ppset += snprintf(ppset, PART_ENV_BUF_SIZE - (ppset - parts_set), "%s@%s:", ubi_mbr->vols[i].name, buf); /* set root */ if (rootname && !strcmp(rootname, ubi_mbr->vols[i].name)) { if (!strncmp(nand_root, BLOCK_DEVICE, strlen(BLOCK_DEVICE))) { /*rootfs mount as ext4、squashfs ...*/ snprintf(buf, PART_ENV_BUF_SIZE, "/dev/ubiblock0_%d", i); #ifdef CONFIG_SUNXI_DM_VERITY pr_info("set verity_dev to %s\n", buf); env_set("verity_dev", buf); pr_info("set root to /dev/dm-0\n"); env_set("nand_root", "/dev/dm-0"); #else pr_info("set root to %s\n", buf); env_set("nand_root", buf); #endif } else { /*rootfs mount as ubifs*/ snprintf(buf, PART_ENV_BUF_SIZE, "ubi0_%d", i); pr_info("set root to %s\n", buf); env_set("nand_root", buf); } } } env_set("partitions", parts_set); snprintf(buf, PART_ENV_BUF_SIZE, "%u", ubinfo->ubootblks); pr_info("set aw-ubi-rawnand.ubootblks to %u\n", ubinfo->ubootblks); env_set("aw-ubi-rawnand.ubootblks", buf); snprintf(buf, PART_ENV_BUF_SIZE, "%d", get_mtd_num_to_attach()); pr_info("set ubi_attach_mtdnum to %d\n", get_mtd_num_to_attach()); env_set("ubi_attach_mtdnum", buf); pr_info("update ubi part info ok\n"); return 0; } /* * ubi->good_peb_count = ubi->peb_count - ubi->bad_peb_count; @ubi_attach * @ubi_read_volume_table * ubi->avail_pebs = ubi->good_peb_count - ubi->corr_peb_count * ubi->avail_pebs -= all vol->reserved_pebs; @init_volumes * ubi->avail_pebs -= UBI_LAYOUT_VOLUME_EBS; @init_volumes, 2 * ubi->avail_pebs -= WL_RESERVED_PEBS; @ubi_wl_init, 1 * ubi->avail_pebs -= EBA_RESERVED_PEBS; @ubi_eba_init, 1 * ubi->avail_pebs -= ubi->beb_rsvd_pebs; @ubi_eba_init */ int rawnand_ubi_user_volumes_size(void) { int pebs = 0; int leb_size = 0; int vol_pebs = 0; int i = 0; struct ubi_device *ubi = ubi_devices[0]; if (ubi) { leb_size = ubi->leb_size; pebs = ubi->rsvd_pebs - ubi->beb_rsvd_pebs - 2 - 1 - 1; /*UBI_INT_VOL_COUNT: 1*/ for (i = 0; i < (ubi->vol_count - 1); i++) { vol_pebs += ubi->volumes[i]->reserved_pebs; } if (vol_pebs != pebs) pr_info("warning vol_pebs: %d, %d\n", vol_pebs, pebs); } pr_info("logical area info: %d %d last_lba: %d\n", pebs, leb_size, pebs * (leb_size >> 9) - 1); return leb_size * pebs; } int rawnand_mtd_attach_mtd(void) { int num; struct ubi_info *ubinfo = get_ubi_info(); struct ubi_mtd_info *mtd_info = ubi_to_mtd(ubinfo); /* support to configure size of mtd part 0 (boot_parts) */ set_env_mtdparts_rawnand(); if (create_mtdparts_do()) return -EINVAL; init_mtd_info(mtd_info); num = get_mtd_num_to_attach(); if (num < 0) return -EINVAL; if (ubi_attach_mtd_do(num)) return -EINVAL; return 0; } #endif /*CONFIG_AW_MTD_RAWNAND*/