/* * Copyright (C) 2008 Nokia Corporation * Copyright (c) International Business Machines Corp., 2006 * * 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; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * Generate UBI images. * * Authors: Artem Bityutskiy * Oliver Lohmann */ #define PROGRAM_NAME "ubinize" #include <sys/stat.h> #include <stdlib.h> #include <getopt.h> #include <string.h> #include <fcntl.h> #include <mtd/ubi-media.h> #include <libubigen.h> #include <libiniparser.h> #include <libubi.h> #include "common.h" static const char optionsstr[] = "-o, --output=<file name> output file name\n" "-p, --peb-size=<bytes> size of the physical eraseblock of the flash\n" " this UBI image is created for in bytes,\n" " kilobytes (KiB), or megabytes (MiB)\n" " (mandatory parameter)\n" "-m, --min-io-size=<bytes> minimum input/output unit size of the flash\n" " in bytes\n" "-s, --sub-page-size=<bytes> minimum input/output unit used for UBI\n" " headers, e.g. sub-page size in case of NAND\n" " flash (equivalent to the minimum input/output\n" " unit size by default)\n" "-O, --vid-hdr-offset=<num> offset if the VID header from start of the\n" " physical eraseblock (default is the next\n" " minimum I/O unit or sub-page after the EC\n" " header)\n" "-e, --erase-counter=<num> the erase counter value to put to EC headers\n" " (default is 0)\n" "-x, --ubi-ver=<num> UBI version number to put to EC headers\n" " (default is 1)\n" "-Q, --image-seq=<num> 32-bit UBI image sequence number to use\n" " (by default a random number is picked)\n" "-v, --verbose be verbose\n" "-h, --help print help message\n" "-V, --version print program version\n\n"; static const char usage[] = "Usage: " PROGRAM_NAME " [options] <ini-file>\n\n" "Generate UBI images. An UBI image may contain one or more UBI volumes which\n" "have to be defined in the input configuration ini-file. The flash\n" "characteristics are defined via the command-line options.\n\n"; static const struct option long_options[] = { { .name = "output", .has_arg = 1, .flag = NULL, .val = 'o' }, { .name = "peb-size", .has_arg = 1, .flag = NULL, .val = 'p' }, { .name = "min-io-size", .has_arg = 1, .flag = NULL, .val = 'm' }, { .name = "sub-page-size", .has_arg = 1, .flag = NULL, .val = 's' }, { .name = "vid-hdr-offset", .has_arg = 1, .flag = NULL, .val = 'O' }, { .name = "erase-counter", .has_arg = 1, .flag = NULL, .val = 'e' }, { .name = "ubi-ver", .has_arg = 1, .flag = NULL, .val = 'x' }, { .name = "image-seq", .has_arg = 1, .flag = NULL, .val = 'Q' }, { .name = "verbose", .has_arg = 0, .flag = NULL, .val = 'v' }, { .name = "help", .has_arg = 0, .flag = NULL, .val = 'h' }, { .name = "version", .has_arg = 0, .flag = NULL, .val = 'V' }, { NULL, 0, NULL, 0} }; struct args { const char *f_in; const char *f_out; int out_fd; int peb_size; int min_io_size; int subpage_size; int vid_hdr_offs; int ec; int ubi_ver; uint32_t image_seq; int verbose; dictionary *dict; }; static struct args args = { .peb_size = -1, .min_io_size = -1, .subpage_size = -1, .ubi_ver = 1, }; static int parse_opt(int argc, char * const argv[]) { util_srand(); args.image_seq = rand(); while (1) { int key, error = 0; unsigned long int image_seq; key = getopt_long(argc, argv, "o:p:m:s:O:e:x:Q:vhV", long_options, NULL); if (key == -1) break; switch (key) { case 'o': args.out_fd = open(optarg, O_CREAT | O_TRUNC | O_WRONLY, S_IWUSR | S_IRUSR | S_IRGRP | S_IWGRP | S_IROTH); if (args.out_fd == -1) return sys_errmsg("cannot open file \"%s\"", optarg); args.f_out = optarg; break; case 'p': args.peb_size = util_get_bytes(optarg); if (args.peb_size <= 0) return errmsg("bad physical eraseblock size: \"%s\"", optarg); break; case 'm': args.min_io_size = util_get_bytes(optarg); if (args.min_io_size <= 0) return errmsg("bad min. I/O unit size: \"%s\"", optarg); if (!is_power_of_2(args.min_io_size)) return errmsg("min. I/O unit size should be power of 2"); break; case 's': args.subpage_size = util_get_bytes(optarg); if (args.subpage_size <= 0) return errmsg("bad sub-page size: \"%s\"", optarg); if (!is_power_of_2(args.subpage_size)) return errmsg("sub-page size should be power of 2"); break; case 'O': args.vid_hdr_offs = simple_strtoul(optarg, &error); if (error || args.vid_hdr_offs < 0) return errmsg("bad VID header offset: \"%s\"", optarg); break; case 'e': args.ec = simple_strtoul(optarg, &error); if (error || args.ec < 0) return errmsg("bad erase counter value: \"%s\"", optarg); break; case 'x': args.ubi_ver = simple_strtoul(optarg, &error); if (error || args.ubi_ver < 0) return errmsg("bad UBI version: \"%s\"", optarg); break; case 'Q': image_seq = simple_strtoul(optarg, &error); if (error || image_seq > 0xFFFFFFFF) return errmsg("bad UBI image sequence number: \"%s\"", optarg); args.image_seq = image_seq; break; case 'v': args.verbose = 1; break; case 'h': fputs(usage, stdout); fputs(optionsstr, stdout); printf("For more information see `man 8 %s`\n\n", PROGRAM_NAME); exit(EXIT_SUCCESS); case 'V': common_print_version(); exit(EXIT_SUCCESS); default: fputs(usage, stderr); fputs("Use -h for help\n\n", stderr); return -1; } } if (optind == argc) return errmsg("input configuration file was not specified (use -h for help)"); if (optind != argc - 1) return errmsg("more then one configuration file was specified (use -h for help)"); args.f_in = argv[optind]; if (args.peb_size < 0) return errmsg("physical eraseblock size was not specified (use -h for help)"); if (args.peb_size > UBI_MAX_PEB_SZ) return errmsg("too high physical eraseblock size %d", args.peb_size); if (args.min_io_size < 0) return errmsg("min. I/O unit size was not specified (use -h for help)"); if (args.subpage_size < 0) args.subpage_size = args.min_io_size; if (args.subpage_size > args.min_io_size) return errmsg("sub-page cannot be larger then min. I/O unit"); if (args.peb_size % args.min_io_size) return errmsg("physical eraseblock should be multiple of min. I/O units"); if (args.min_io_size % args.subpage_size) return errmsg("min. I/O unit size should be multiple of sub-page size"); if (!args.f_out) return errmsg("output file was not specified (use -h for help)"); if (args.vid_hdr_offs) { if (args.vid_hdr_offs + (int)UBI_VID_HDR_SIZE >= args.peb_size) return errmsg("bad VID header position"); if (args.vid_hdr_offs % 8) return errmsg("VID header offset has to be multiple of min. I/O unit size"); } return 0; } static int read_section(const struct ubigen_info *ui, const char *sname, struct ubigen_vol_info *vi, const char **img, struct stat *st) { char buf[256]; const char *p; *img = NULL; if (strlen(sname) > 128) return errmsg("too long section name \"%s\"", sname); /* Make sure mode is UBI, otherwise ignore this section */ sprintf(buf, "%s:mode", sname); p = iniparser_getstring(args.dict, buf, NULL); if (!p) { errmsg("\"mode\" key not found in section \"%s\"", sname); errmsg("the \"mode\" key is mandatory and has to be " "\"mode=ubi\" if the section describes an UBI volume"); return -1; } /* If mode is not UBI, skip this section */ if (strcmp(p, "ubi")) { verbose(args.verbose, "skip non-ubi section \"%s\"", sname); return 1; } verbose(args.verbose, "mode=ubi, keep parsing"); /* Fetch volume type */ sprintf(buf, "%s:vol_type", sname); p = iniparser_getstring(args.dict, buf, NULL); if (!p) { normsg("volume type was not specified in " "section \"%s\", assume \"dynamic\"\n", sname); vi->type = UBI_VID_DYNAMIC; } else { if (!strcmp(p, "static")) vi->type = UBI_VID_STATIC; else if (!strcmp(p, "dynamic")) vi->type = UBI_VID_DYNAMIC; else return errmsg("invalid volume type \"%s\" in section \"%s\"", p, sname); } verbose(args.verbose, "volume type: %s", vi->type == UBI_VID_DYNAMIC ? "dynamic" : "static"); /* Fetch the name of the volume image file */ sprintf(buf, "%s:image", sname); p = iniparser_getstring(args.dict, buf, NULL); if (p) { *img = p; if (stat(p, st)) return sys_errmsg("cannot stat \"%s\" referred from section \"%s\"", p, sname); if (st->st_size == 0) return errmsg("empty file \"%s\" referred from section \"%s\"", p, sname); } else if (vi->type == UBI_VID_STATIC) return errmsg("image is not specified for static volume in section \"%s\"", sname); /* Fetch volume id */ sprintf(buf, "%s:vol_id", sname); vi->id = iniparser_getint(args.dict, buf, -1); if (vi->id == -1) return errmsg("\"vol_id\" key not found in section \"%s\"", sname); if (vi->id < 0) return errmsg("negative volume ID %d in section \"%s\"", vi->id, sname); if (vi->id >= ui->max_volumes) return errmsg("too high volume ID %d in section \"%s\", max. is %d", vi->id, sname, ui->max_volumes); verbose(args.verbose, "volume ID: %d", vi->id); /* Fetch volume size */ sprintf(buf, "%s:vol_size", sname); p = iniparser_getstring(args.dict, buf, NULL); if (p) { vi->bytes = util_get_bytes(p); if (vi->bytes <= 0) return errmsg("bad \"vol_size\" key value \"%s\" (section \"%s\")", p, sname); /* Make sure the image size is not larger than volume size */ if (*img && st->st_size > vi->bytes) return errmsg("error in section \"%s\": size of the image file " "\"%s\" is %lld, which is larger than volume size %lld", sname, *img, (long long)st->st_size, vi->bytes); verbose(args.verbose, "volume size: %lld bytes", vi->bytes); } else { struct stat st; if (!*img) return errmsg("neither image file (\"image=\") nor volume size " "(\"vol_size=\") specified in section \"%s\"", sname); if (stat(*img, &st)) return sys_errmsg("cannot stat \"%s\"", *img); vi->bytes = st.st_size; if (vi->bytes == 0) return errmsg("file \"%s\" referred from section \"%s\" is empty", *img, sname); normsg_cont("volume size was not specified in section \"%s\", assume" " minimum to fit image \"%s\"", sname, *img); util_print_bytes(vi->bytes, 1); printf("\n"); } /* Fetch volume name */ sprintf(buf, "%s:vol_name", sname); p = iniparser_getstring(args.dict, buf, NULL); if (!p) return errmsg("\"vol_name\" key not found in section \"%s\"", sname); vi->name = p; vi->name_len = strlen(p); if (vi->name_len > UBI_VOL_NAME_MAX) return errmsg("too long volume name in section \"%s\", max. is %d characters", vi->name, UBI_VOL_NAME_MAX); verbose(args.verbose, "volume name: %s", p); /* Fetch volume alignment */ sprintf(buf, "%s:vol_alignment", sname); vi->alignment = iniparser_getint(args.dict, buf, -1); if (vi->alignment == -1) vi->alignment = 1; else if (vi->id < 0) return errmsg("negative volume alignment %d in section \"%s\"", vi->alignment, sname); verbose(args.verbose, "volume alignment: %d", vi->alignment); /* Fetch volume flags */ sprintf(buf, "%s:vol_flags", sname); p = iniparser_getstring(args.dict, buf, NULL); if (p) { /* * For now, the flag can be either autoresize or skip-check, as * skip-check is reserved for static volumes and autoresize for * such a volume makes no sense. * Once we add another flag that isn't incompatible with each * and every existing flag, we'll have to implement a solution * that allows multiple flags to be set at the same time in * vol_flags setting of the section. */ if (!strcmp(p, "autoresize")) { verbose(args.verbose, "autoresize flags found"); vi->flags |= UBI_VTBL_AUTORESIZE_FLG; } else if (!strcmp(p, "skip-check")) { verbose(args.verbose, "skip-check flag found"); vi->flags |= UBI_VTBL_SKIP_CRC_CHECK_FLG; } else { return errmsg("unknown flags \"%s\" in section \"%s\"", p, sname); } } /* Initialize the rest of the volume information */ vi->data_pad = ui->leb_size % vi->alignment; vi->usable_leb_size = ui->leb_size - vi->data_pad; if (vi->type == UBI_VID_DYNAMIC) vi->used_ebs = (vi->bytes + vi->usable_leb_size - 1) / vi->usable_leb_size; else vi->used_ebs = (st->st_size + vi->usable_leb_size - 1) / vi->usable_leb_size; vi->compat = 0; return 0; } int main(int argc, char * const argv[]) { int err = -1, sects, i, autoresize_was_already = 0; struct ubigen_info ui; struct ubi_vtbl_record *vtbl; struct ubigen_vol_info *vi; off_t seek; err = parse_opt(argc, argv); if (err) return -1; ubigen_info_init(&ui, args.peb_size, args.min_io_size, args.subpage_size, args.vid_hdr_offs, args.ubi_ver, args.image_seq); verbose(args.verbose, "LEB size: %d", ui.leb_size); verbose(args.verbose, "PEB size: %d", ui.peb_size); verbose(args.verbose, "min. I/O size: %d", ui.min_io_size); verbose(args.verbose, "sub-page size: %d", args.subpage_size); verbose(args.verbose, "VID offset: %d", ui.vid_hdr_offs); verbose(args.verbose, "data offset: %d", ui.data_offs); verbose(args.verbose, "UBI image sequence number: %u", ui.image_seq); vtbl = ubigen_create_empty_vtbl(&ui); if (!vtbl) { err = -1; goto out; } args.dict = iniparser_load(args.f_in); if (!args.dict) { err = -1; errmsg("cannot load the input ini file \"%s\"", args.f_in); goto out_vtbl; } verbose(args.verbose, "loaded the ini-file \"%s\"", args.f_in); /* Each section describes one volume */ sects = iniparser_getnsec(args.dict); if (sects == -1) { err = -1; errmsg("ini-file parsing error (iniparser_getnsec)"); goto out_dict; } verbose(args.verbose, "count of sections: %d", sects); if (sects == 0) { err = -1; errmsg("no sections found the ini-file \"%s\"", args.f_in); goto out_dict; } if (sects > ui.max_volumes) { err = -1; errmsg("too many sections (%d) in the ini-file \"%s\"", sects, args.f_in); normsg("each section corresponds to an UBI volume, maximum " "count of volumes is %d", ui.max_volumes); goto out_dict; } vi = calloc(sizeof(struct ubigen_vol_info), sects); if (!vi) { err = -1; errmsg("cannot allocate memory"); goto out_dict; } /* * Skip 2 PEBs at the beginning of the file for the volume table which * will be written later. */ seek = ui.peb_size * 2; if (lseek(args.out_fd, seek, SEEK_SET) != seek) { err = -1; sys_errmsg("cannot seek file \"%s\"", args.f_out); goto out_free; } for (i = 0; i < sects; i++) { const char *sname = iniparser_getsecname(args.dict, i); const char *img = NULL; struct stat st; int fd, j; if (!sname) { err = -1; errmsg("ini-file parsing error (iniparser_getsecname)"); goto out_free; } if (args.verbose) printf("\n"); verbose(args.verbose, "parsing section \"%s\"", sname); err = read_section(&ui, sname, &vi[i], &img, &st); if (err == -1) goto out_free; verbose(args.verbose, "adding volume %d", vi[i].id); /* * Make sure that volume ID and name is unique and that only * one volume has auto-resize flag */ for (j = 0; j < i; j++) { if (vi[i].id == vi[j].id) { err = -1; errmsg("volume IDs must be unique, but ID %d " "in section \"%s\" is not", vi[i].id, sname); goto out_free; } if (!strcmp(vi[i].name, vi[j].name)) { err = -1; errmsg("volume name must be unique, but name " "\"%s\" in section \"%s\" is not", vi[i].name, sname); goto out_free; } } if (vi[i].flags & UBI_VTBL_SKIP_CRC_CHECK_FLG && vi[i].type != UBI_VID_STATIC) return errmsg("skip-check is only valid for static volumes"); if (vi[i].flags & UBI_VTBL_AUTORESIZE_FLG) { if (autoresize_was_already) return errmsg("only one volume is allowed " "to have auto-resize flag"); autoresize_was_already = 1; } err = ubigen_add_volume(&ui, &vi[i], vtbl); if (err) { errmsg("cannot add volume for section \"%s\"", sname); goto out_free; } if (img) { fd = open(img, O_RDONLY); if (fd == -1) { err = fd; sys_errmsg("cannot open \"%s\"", img); goto out_free; } verbose(args.verbose, "writing volume %d", vi[i].id); verbose(args.verbose, "image file: %s", img); err = ubigen_write_volume(&ui, &vi[i], args.ec, st.st_size, fd, args.out_fd); close(fd); if (err) { errmsg("cannot write volume for section \"%s\"", sname); goto out_free; } } if (args.verbose) printf("\n"); } verbose(args.verbose, "writing layout volume"); err = ubigen_write_layout_vol(&ui, 0, 1, args.ec, args.ec, vtbl, args.out_fd); if (err) { errmsg("cannot write layout volume"); goto out_free; } verbose(args.verbose, "done"); free(vi); iniparser_freedict(args.dict); free(vtbl); close(args.out_fd); return 0; out_free: free(vi); out_dict: iniparser_freedict(args.dict); out_vtbl: free(vtbl); out: close(args.out_fd); remove(args.f_out); return err; }