| /* |
| * 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" |
| #include "ubiutils-common.h" |
| |
| static const char doc[] = PROGRAM_NAME " version " VERSION |
| " - a tool to generate UBI images. An UBI image may contain one or more UBI " |
| "volumes which have to be defined in the input configuration ini-file. The " |
| "ini file defines all the UBI volumes - their characteristics and the " |
| "contents, but it does not define the characteristics of the flash the UBI " |
| "image is generated for. Instead, the flash characteristics are defined via " |
| "the command-line options. Note, if not sure about some of the command-line " |
| "parameters, do not specify them and let the utility use default values."; |
| |
| 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"; |
| |
| static const char usage[] = |
| "Usage: " PROGRAM_NAME " [-o filename] [-p <bytes>] [-m <bytes>] [-s <bytes>] [-O <num>] [-e <num>]\n" |
| "\t\t[-x <num>] [-Q <num>] [-v] [-h] [-V] [--output=<filename>] [--peb-size=<bytes>]\n" |
| "\t\t[--min-io-size=<bytes>] [--sub-page-size=<bytes>] [--vid-hdr-offset=<num>]\n" |
| "\t\t[--erase-counter=<num>] [--ubi-ver=<num>] [--image-seq=<num>] [--verbose] [--help]\n" |
| "\t\t[--version] ini-file\n" |
| "Example: " PROGRAM_NAME " -o ubi.img -p 16KiB -m 512 -s 256 cfg.ini - create UBI image\n" |
| " 'ubi.img' as described by configuration file 'cfg.ini'"; |
| |
| static const char ini_doc[] = "INI-file format.\n" |
| "The input configuration ini-file describes all the volumes which have to\n" |
| "be included to the output UBI image. Each volume is described in its own\n" |
| "section which may be named arbitrarily. The section consists on\n" |
| "\"key=value\" pairs, for example:\n\n" |
| "[jffs2-volume]\n" |
| "mode=ubi\n" |
| "image=../jffs2.img\n" |
| "vol_id=1\n" |
| "vol_size=30MiB\n" |
| "vol_type=dynamic\n" |
| "vol_name=jffs2_volume\n" |
| "vol_flags=autoresize\n" |
| "vol_alignment=1\n\n" |
| "This example configuration file tells the utility to create an UBI image\n" |
| "with one volume with ID 1, volume size 30MiB, the volume is dynamic, has\n" |
| "name \"jffs2_volume\", \"autoresize\" volume flag, and alignment 1. The\n" |
| "\"image=../jffs2.img\" line tells the utility to take the contents of the\n" |
| "volume from the \"../jffs2.img\" file. The size of the image file has to be\n" |
| "less or equivalent to the volume size (30MiB). The \"mode=ubi\" line is\n" |
| "mandatory and just tells that the section describes an UBI volume - other\n" |
| "section modes may be added in the future.\n" |
| "Notes:\n" |
| " * size in vol_size might be specified kilobytes (KiB), megabytes (MiB),\n" |
| " gigabytes (GiB) or bytes (no modifier);\n" |
| " * if \"vol_size\" key is absent, the volume size is assumed to be\n" |
| " equivalent to the size of the image file (defined by \"image\" key);\n" |
| " * if the \"image\" is absent, the volume is assumed to be empty;\n" |
| " * volume alignment must not be greater than the logical eraseblock size;\n" |
| " * one ini file may contain arbitrary number of sections, the utility will\n" |
| " put all the volumes which are described by these section to the output\n" |
| " UBI image file."; |
| |
| 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[]) |
| { |
| ubiutils_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 = ubiutils_get_bytes(optarg); |
| if (args.peb_size <= 0) |
| return errmsg("bad physical eraseblock size: \"%s\"", optarg); |
| break; |
| |
| case 'm': |
| args.min_io_size = ubiutils_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 = ubiutils_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': |
| ubiutils_print_text(stdout, doc, 80); |
| printf("\n%s\n\n", ini_doc); |
| printf("%s\n\n", usage); |
| printf("%s\n", optionsstr); |
| exit(EXIT_SUCCESS); |
| |
| case 'V': |
| common_print_version(); |
| exit(EXIT_SUCCESS); |
| |
| default: |
| fprintf(stderr, "Use -h for help\n"); |
| 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 = ubiutils_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); |
| ubiutils_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 alignement %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) { |
| if (!strcmp(p, "autoresize")) { |
| verbose(args.verbose, "autoresize flags found"); |
| vi->flags |= UBI_VTBL_AUTORESIZE_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) |
| goto out; |
| |
| args.dict = iniparser_load(args.f_in); |
| if (!args.dict) { |
| 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) { |
| errmsg("ini-file parsing error (iniparser_getnsec)"); |
| goto out_dict; |
| } |
| |
| verbose(args.verbose, "count of sections: %d", sects); |
| if (sects == 0) { |
| errmsg("no sections found the ini-file \"%s\"", args.f_in); |
| goto out_dict; |
| } |
| |
| if (sects > ui.max_volumes) { |
| 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) { |
| 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) { |
| 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) { |
| 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) { |
| 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)) { |
| 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_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) { |
| 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; |
| } |