Maxime Ripard | 594b4cc | 2017-02-27 18:22:02 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Allwinner NAND randomizer and image builder implementation: |
| 3 | * |
| 4 | * Copyright © 2016 NextThing Co. |
| 5 | * Copyright © 2016 Free Electrons |
| 6 | * |
| 7 | * Author: Boris Brezillon <boris.brezillon@free-electrons.com> |
| 8 | * |
| 9 | */ |
| 10 | |
| 11 | #include <linux/bch.h> |
| 12 | |
| 13 | #include <getopt.h> |
| 14 | #include <version.h> |
| 15 | |
| 16 | #define BCH_PRIMITIVE_POLY 0x5803 |
| 17 | |
| 18 | #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) |
| 19 | #define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d)) |
| 20 | |
| 21 | struct image_info { |
| 22 | int ecc_strength; |
| 23 | int ecc_step_size; |
| 24 | int page_size; |
| 25 | int oob_size; |
| 26 | int usable_page_size; |
| 27 | int eraseblock_size; |
| 28 | int scramble; |
| 29 | int boot0; |
| 30 | off_t offset; |
| 31 | const char *source; |
| 32 | const char *dest; |
| 33 | }; |
| 34 | |
| 35 | static void swap_bits(uint8_t *buf, int len) |
| 36 | { |
| 37 | int i, j; |
| 38 | |
| 39 | for (j = 0; j < len; j++) { |
| 40 | uint8_t byte = buf[j]; |
| 41 | |
| 42 | buf[j] = 0; |
| 43 | for (i = 0; i < 8; i++) { |
| 44 | if (byte & (1 << i)) |
| 45 | buf[j] |= (1 << (7 - i)); |
| 46 | } |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | static uint16_t lfsr_step(uint16_t state, int count) |
| 51 | { |
| 52 | state &= 0x7fff; |
| 53 | while (count--) |
| 54 | state = ((state >> 1) | |
| 55 | ((((state >> 0) ^ (state >> 1)) & 1) << 14)) & 0x7fff; |
| 56 | |
| 57 | return state; |
| 58 | } |
| 59 | |
| 60 | static uint16_t default_scrambler_seeds[] = { |
| 61 | 0x2b75, 0x0bd0, 0x5ca3, 0x62d1, 0x1c93, 0x07e9, 0x2162, 0x3a72, |
| 62 | 0x0d67, 0x67f9, 0x1be7, 0x077d, 0x032f, 0x0dac, 0x2716, 0x2436, |
| 63 | 0x7922, 0x1510, 0x3860, 0x5287, 0x480f, 0x4252, 0x1789, 0x5a2d, |
| 64 | 0x2a49, 0x5e10, 0x437f, 0x4b4e, 0x2f45, 0x216e, 0x5cb7, 0x7130, |
| 65 | 0x2a3f, 0x60e4, 0x4dc9, 0x0ef0, 0x0f52, 0x1bb9, 0x6211, 0x7a56, |
| 66 | 0x226d, 0x4ea7, 0x6f36, 0x3692, 0x38bf, 0x0c62, 0x05eb, 0x4c55, |
| 67 | 0x60f4, 0x728c, 0x3b6f, 0x2037, 0x7f69, 0x0936, 0x651a, 0x4ceb, |
| 68 | 0x6218, 0x79f3, 0x383f, 0x18d9, 0x4f05, 0x5c82, 0x2912, 0x6f17, |
| 69 | 0x6856, 0x5938, 0x1007, 0x61ab, 0x3e7f, 0x57c2, 0x542f, 0x4f62, |
| 70 | 0x7454, 0x2eac, 0x7739, 0x42d4, 0x2f90, 0x435a, 0x2e52, 0x2064, |
| 71 | 0x637c, 0x66ad, 0x2c90, 0x0bad, 0x759c, 0x0029, 0x0986, 0x7126, |
| 72 | 0x1ca7, 0x1605, 0x386a, 0x27f5, 0x1380, 0x6d75, 0x24c3, 0x0f8e, |
| 73 | 0x2b7a, 0x1418, 0x1fd1, 0x7dc1, 0x2d8e, 0x43af, 0x2267, 0x7da3, |
| 74 | 0x4e3d, 0x1338, 0x50db, 0x454d, 0x764d, 0x40a3, 0x42e6, 0x262b, |
| 75 | 0x2d2e, 0x1aea, 0x2e17, 0x173d, 0x3a6e, 0x71bf, 0x25f9, 0x0a5d, |
| 76 | 0x7c57, 0x0fbe, 0x46ce, 0x4939, 0x6b17, 0x37bb, 0x3e91, 0x76db, |
| 77 | }; |
| 78 | |
| 79 | static uint16_t brom_scrambler_seeds[] = { 0x4a80 }; |
| 80 | |
| 81 | static void scramble(const struct image_info *info, |
| 82 | int page, uint8_t *data, int datalen) |
| 83 | { |
| 84 | uint16_t state; |
| 85 | int i; |
| 86 | |
| 87 | /* Boot0 is always scrambled no matter the command line option. */ |
| 88 | if (info->boot0) { |
| 89 | state = brom_scrambler_seeds[0]; |
| 90 | } else { |
| 91 | unsigned seedmod = info->eraseblock_size / info->page_size; |
| 92 | |
| 93 | /* Bail out earlier if the user didn't ask for scrambling. */ |
| 94 | if (!info->scramble) |
| 95 | return; |
| 96 | |
| 97 | if (seedmod > ARRAY_SIZE(default_scrambler_seeds)) |
| 98 | seedmod = ARRAY_SIZE(default_scrambler_seeds); |
| 99 | |
| 100 | state = default_scrambler_seeds[page % seedmod]; |
| 101 | } |
| 102 | |
| 103 | /* Prepare the initial state... */ |
| 104 | state = lfsr_step(state, 15); |
| 105 | |
| 106 | /* and start scrambling data. */ |
| 107 | for (i = 0; i < datalen; i++) { |
| 108 | data[i] ^= state; |
| 109 | state = lfsr_step(state, 8); |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | static int write_page(const struct image_info *info, uint8_t *buffer, |
| 114 | FILE *src, FILE *rnd, FILE *dst, |
| 115 | struct bch_control *bch, int page) |
| 116 | { |
| 117 | int steps = info->usable_page_size / info->ecc_step_size; |
| 118 | int eccbytes = DIV_ROUND_UP(info->ecc_strength * 14, 8); |
| 119 | off_t pos = ftell(dst); |
| 120 | size_t pad, cnt; |
| 121 | int i; |
| 122 | |
| 123 | if (eccbytes % 2) |
| 124 | eccbytes++; |
| 125 | |
| 126 | memset(buffer, 0xff, info->page_size + info->oob_size); |
| 127 | cnt = fread(buffer, 1, info->usable_page_size, src); |
| 128 | if (!cnt) { |
| 129 | if (!feof(src)) { |
| 130 | fprintf(stderr, |
| 131 | "Failed to read data from the source\n"); |
| 132 | return -1; |
| 133 | } else { |
| 134 | return 0; |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | fwrite(buffer, info->page_size + info->oob_size, 1, dst); |
| 139 | |
| 140 | for (i = 0; i < info->usable_page_size; i++) { |
| 141 | if (buffer[i] != 0xff) |
| 142 | break; |
| 143 | } |
| 144 | |
| 145 | /* We leave empty pages at 0xff. */ |
| 146 | if (i == info->usable_page_size) |
| 147 | return 0; |
| 148 | |
| 149 | /* Restore the source pointer to read it again. */ |
| 150 | fseek(src, -cnt, SEEK_CUR); |
| 151 | |
| 152 | /* Randomize unused space if scrambling is required. */ |
| 153 | if (info->scramble) { |
| 154 | int offs; |
| 155 | |
| 156 | if (info->boot0) { |
| 157 | size_t ret; |
| 158 | |
| 159 | offs = steps * (info->ecc_step_size + eccbytes + 4); |
| 160 | cnt = info->page_size + info->oob_size - offs; |
| 161 | ret = fread(buffer + offs, 1, cnt, rnd); |
| 162 | if (!ret && !feof(rnd)) { |
| 163 | fprintf(stderr, |
| 164 | "Failed to read random data\n"); |
| 165 | return -1; |
| 166 | } |
| 167 | } else { |
| 168 | offs = info->page_size + (steps * (eccbytes + 4)); |
| 169 | cnt = info->page_size + info->oob_size - offs; |
| 170 | memset(buffer + offs, 0xff, cnt); |
| 171 | scramble(info, page, buffer + offs, cnt); |
| 172 | } |
| 173 | fseek(dst, pos + offs, SEEK_SET); |
| 174 | fwrite(buffer + offs, cnt, 1, dst); |
| 175 | } |
| 176 | |
| 177 | for (i = 0; i < steps; i++) { |
| 178 | int ecc_offs, data_offs; |
| 179 | uint8_t *ecc; |
| 180 | |
| 181 | memset(buffer, 0xff, info->ecc_step_size + eccbytes + 4); |
| 182 | ecc = buffer + info->ecc_step_size + 4; |
| 183 | if (info->boot0) { |
| 184 | data_offs = i * (info->ecc_step_size + eccbytes + 4); |
| 185 | ecc_offs = data_offs + info->ecc_step_size + 4; |
| 186 | } else { |
| 187 | data_offs = i * info->ecc_step_size; |
| 188 | ecc_offs = info->page_size + 4 + (i * (eccbytes + 4)); |
| 189 | } |
| 190 | |
| 191 | cnt = fread(buffer, 1, info->ecc_step_size, src); |
| 192 | if (!cnt && !feof(src)) { |
| 193 | fprintf(stderr, |
| 194 | "Failed to read data from the source\n"); |
| 195 | return -1; |
| 196 | } |
| 197 | |
| 198 | pad = info->ecc_step_size - cnt; |
| 199 | if (pad) { |
| 200 | if (info->scramble && info->boot0) { |
| 201 | size_t ret; |
| 202 | |
| 203 | ret = fread(buffer + cnt, 1, pad, rnd); |
| 204 | if (!ret && !feof(rnd)) { |
| 205 | fprintf(stderr, |
| 206 | "Failed to read random data\n"); |
| 207 | return -1; |
| 208 | } |
| 209 | } else { |
| 210 | memset(buffer + cnt, 0xff, pad); |
| 211 | } |
| 212 | } |
| 213 | |
| 214 | memset(ecc, 0, eccbytes); |
| 215 | swap_bits(buffer, info->ecc_step_size + 4); |
| 216 | encode_bch(bch, buffer, info->ecc_step_size + 4, ecc); |
| 217 | swap_bits(buffer, info->ecc_step_size + 4); |
| 218 | swap_bits(ecc, eccbytes); |
| 219 | scramble(info, page, buffer, info->ecc_step_size + 4 + eccbytes); |
| 220 | |
| 221 | fseek(dst, pos + data_offs, SEEK_SET); |
| 222 | fwrite(buffer, info->ecc_step_size, 1, dst); |
| 223 | fseek(dst, pos + ecc_offs - 4, SEEK_SET); |
| 224 | fwrite(ecc - 4, eccbytes + 4, 1, dst); |
| 225 | } |
| 226 | |
| 227 | /* Fix BBM. */ |
| 228 | fseek(dst, pos + info->page_size, SEEK_SET); |
| 229 | memset(buffer, 0xff, 2); |
| 230 | fwrite(buffer, 2, 1, dst); |
| 231 | |
| 232 | /* Make dst pointer point to the next page. */ |
| 233 | fseek(dst, pos + info->page_size + info->oob_size, SEEK_SET); |
| 234 | |
| 235 | return 0; |
| 236 | } |
| 237 | |
| 238 | static int create_image(const struct image_info *info) |
| 239 | { |
| 240 | off_t page = info->offset / info->page_size; |
| 241 | struct bch_control *bch; |
| 242 | FILE *src, *dst, *rnd; |
| 243 | uint8_t *buffer; |
| 244 | |
| 245 | bch = init_bch(14, info->ecc_strength, BCH_PRIMITIVE_POLY); |
| 246 | if (!bch) { |
| 247 | fprintf(stderr, "Failed to init the BCH engine\n"); |
| 248 | return -1; |
| 249 | } |
| 250 | |
| 251 | buffer = malloc(info->page_size + info->oob_size); |
| 252 | if (!buffer) { |
| 253 | fprintf(stderr, "Failed to allocate the NAND page buffer\n"); |
| 254 | return -1; |
| 255 | } |
| 256 | |
| 257 | memset(buffer, 0xff, info->page_size + info->oob_size); |
| 258 | |
| 259 | src = fopen(info->source, "r"); |
| 260 | if (!src) { |
| 261 | fprintf(stderr, "Failed to open source file (%s)\n", |
| 262 | info->source); |
| 263 | return -1; |
| 264 | } |
| 265 | |
| 266 | dst = fopen(info->dest, "w"); |
| 267 | if (!dst) { |
| 268 | fprintf(stderr, "Failed to open dest file (%s)\n", info->dest); |
| 269 | return -1; |
| 270 | } |
| 271 | |
| 272 | rnd = fopen("/dev/urandom", "r"); |
| 273 | if (!rnd) { |
| 274 | fprintf(stderr, "Failed to open /dev/urandom\n"); |
| 275 | return -1; |
| 276 | } |
| 277 | |
| 278 | while (!feof(src)) { |
| 279 | int ret; |
| 280 | |
| 281 | ret = write_page(info, buffer, src, rnd, dst, bch, page++); |
| 282 | if (ret) |
| 283 | return ret; |
| 284 | } |
| 285 | |
| 286 | return 0; |
| 287 | } |
| 288 | |
| 289 | static void display_help(int status) |
| 290 | { |
| 291 | fprintf(status == EXIT_SUCCESS ? stdout : stderr, |
| 292 | "sunxi-nand-image-builder %s\n" |
| 293 | "\n" |
| 294 | "Usage: sunxi-nand-image-builder [OPTIONS] source-image output-image\n" |
| 295 | "\n" |
| 296 | "Creates a raw NAND image that can be read by the sunxi NAND controller.\n" |
| 297 | "\n" |
| 298 | "-h --help Display this help and exit\n" |
| 299 | "-c <str>/<step> --ecc=<str>/<step> ECC config (strength/step-size)\n" |
| 300 | "-p <size> --page=<size> Page size\n" |
| 301 | "-o <size> --oob=<size> OOB size\n" |
| 302 | "-u <size> --usable=<size> Usable page size\n" |
| 303 | "-e <size> --eraseblock=<size> Erase block size\n" |
| 304 | "-b --boot0 Build a boot0 image.\n" |
| 305 | "-s --scramble Scramble data\n" |
| 306 | "-a <offset> --address=<offset> Where the image will be programmed.\n" |
| 307 | "\n" |
| 308 | "Notes:\n" |
| 309 | "All the information you need to pass to this tool should be part of\n" |
| 310 | "the NAND datasheet.\n" |
| 311 | "\n" |
| 312 | "The NAND controller only supports the following ECC configs\n" |
| 313 | " Valid ECC strengths: 16, 24, 28, 32, 40, 48, 56, 60 and 64\n" |
| 314 | " Valid ECC step size: 512 and 1024\n" |
| 315 | "\n" |
| 316 | "If you are building a boot0 image, you'll have specify extra options.\n" |
| 317 | "These options should be chosen based on the layouts described here:\n" |
| 318 | " http://linux-sunxi.org/NAND#More_information_on_BROM_NAND\n" |
| 319 | "\n" |
| 320 | " --usable should be assigned the 'Hardware page' value\n" |
| 321 | " --ecc should be assigned the 'ECC capacity'/'ECC page' values\n" |
| 322 | " --usable should be smaller than --page\n" |
| 323 | "\n" |
| 324 | "The --address option is only required for non-boot0 images that are \n" |
| 325 | "meant to be programmed at a non eraseblock aligned offset.\n" |
| 326 | "\n" |
| 327 | "Examples:\n" |
| 328 | " The H27UCG8T2BTR-BC NAND exposes\n" |
| 329 | " * 16k pages\n" |
| 330 | " * 1280 OOB bytes per page\n" |
| 331 | " * 4M eraseblocks\n" |
| 332 | " * requires data scrambling\n" |
| 333 | " * expects a minimum ECC of 40bits/1024bytes\n" |
| 334 | "\n" |
| 335 | " A normal image can be generated with\n" |
| 336 | " sunxi-nand-image-builder -p 16384 -o 1280 -e 0x400000 -s -c 40/1024\n" |
| 337 | " A boot0 image can be generated with\n" |
| 338 | " sunxi-nand-image-builder -p 16384 -o 1280 -e 0x400000 -s -b -u 4096 -c 64/1024\n", |
| 339 | PLAIN_VERSION); |
| 340 | exit(status); |
| 341 | } |
| 342 | |
| 343 | static int check_image_info(struct image_info *info) |
| 344 | { |
| 345 | static int valid_ecc_strengths[] = { 16, 24, 28, 32, 40, 48, 56, 60, 64 }; |
| 346 | int eccbytes, eccsteps; |
| 347 | unsigned i; |
| 348 | |
| 349 | if (!info->page_size) { |
| 350 | fprintf(stderr, "--page is missing\n"); |
| 351 | return -EINVAL; |
| 352 | } |
| 353 | |
| 354 | if (!info->page_size) { |
| 355 | fprintf(stderr, "--oob is missing\n"); |
| 356 | return -EINVAL; |
| 357 | } |
| 358 | |
| 359 | if (!info->eraseblock_size) { |
| 360 | fprintf(stderr, "--eraseblock is missing\n"); |
| 361 | return -EINVAL; |
| 362 | } |
| 363 | |
| 364 | if (info->ecc_step_size != 512 && info->ecc_step_size != 1024) { |
| 365 | fprintf(stderr, "Invalid ECC step argument: %d\n", |
| 366 | info->ecc_step_size); |
| 367 | return -EINVAL; |
| 368 | } |
| 369 | |
| 370 | for (i = 0; i < ARRAY_SIZE(valid_ecc_strengths); i++) { |
| 371 | if (valid_ecc_strengths[i] == info->ecc_strength) |
| 372 | break; |
| 373 | } |
| 374 | |
| 375 | if (i == ARRAY_SIZE(valid_ecc_strengths)) { |
| 376 | fprintf(stderr, "Invalid ECC strength argument: %d\n", |
| 377 | info->ecc_strength); |
| 378 | return -EINVAL; |
| 379 | } |
| 380 | |
| 381 | eccbytes = DIV_ROUND_UP(info->ecc_strength * 14, 8); |
| 382 | if (eccbytes % 2) |
| 383 | eccbytes++; |
| 384 | eccbytes += 4; |
| 385 | |
| 386 | eccsteps = info->usable_page_size / info->ecc_step_size; |
| 387 | |
| 388 | if (info->page_size + info->oob_size < |
| 389 | info->usable_page_size + (eccsteps * eccbytes)) { |
| 390 | fprintf(stderr, |
| 391 | "ECC bytes do not fit in the NAND page, choose a weaker ECC\n"); |
| 392 | return -EINVAL; |
| 393 | } |
| 394 | |
| 395 | return 0; |
| 396 | } |
| 397 | |
| 398 | int main(int argc, char **argv) |
| 399 | { |
| 400 | struct image_info info; |
| 401 | |
| 402 | memset(&info, 0, sizeof(info)); |
| 403 | /* |
| 404 | * Process user arguments |
| 405 | */ |
| 406 | for (;;) { |
| 407 | int option_index = 0; |
| 408 | char *endptr = NULL; |
| 409 | static const struct option long_options[] = { |
| 410 | {"help", no_argument, 0, 'h'}, |
| 411 | {"ecc", required_argument, 0, 'c'}, |
| 412 | {"page", required_argument, 0, 'p'}, |
| 413 | {"oob", required_argument, 0, 'o'}, |
| 414 | {"usable", required_argument, 0, 'u'}, |
| 415 | {"eraseblock", required_argument, 0, 'e'}, |
| 416 | {"boot0", no_argument, 0, 'b'}, |
| 417 | {"scramble", no_argument, 0, 's'}, |
| 418 | {"address", required_argument, 0, 'a'}, |
| 419 | {0, 0, 0, 0}, |
| 420 | }; |
| 421 | |
| 422 | int c = getopt_long(argc, argv, "c:p:o:u:e:ba:sh", |
| 423 | long_options, &option_index); |
| 424 | if (c == EOF) |
| 425 | break; |
| 426 | |
| 427 | switch (c) { |
| 428 | case 'h': |
| 429 | display_help(0); |
| 430 | break; |
| 431 | case 's': |
| 432 | info.scramble = 1; |
| 433 | break; |
| 434 | case 'c': |
| 435 | info.ecc_strength = strtol(optarg, &endptr, 0); |
| 436 | if (endptr || *endptr == '/') |
| 437 | info.ecc_step_size = strtol(endptr + 1, NULL, 0); |
| 438 | break; |
| 439 | case 'p': |
| 440 | info.page_size = strtol(optarg, NULL, 0); |
| 441 | break; |
| 442 | case 'o': |
| 443 | info.oob_size = strtol(optarg, NULL, 0); |
| 444 | break; |
| 445 | case 'u': |
| 446 | info.usable_page_size = strtol(optarg, NULL, 0); |
| 447 | break; |
| 448 | case 'e': |
| 449 | info.eraseblock_size = strtol(optarg, NULL, 0); |
| 450 | break; |
| 451 | case 'b': |
| 452 | info.boot0 = 1; |
| 453 | break; |
| 454 | case 'a': |
| 455 | info.offset = strtoull(optarg, NULL, 0); |
| 456 | break; |
| 457 | case '?': |
| 458 | display_help(-1); |
| 459 | break; |
| 460 | } |
| 461 | } |
| 462 | |
| 463 | if ((argc - optind) != 2) |
| 464 | display_help(-1); |
| 465 | |
| 466 | info.source = argv[optind]; |
| 467 | info.dest = argv[optind + 1]; |
| 468 | |
| 469 | if (!info.boot0) { |
| 470 | info.usable_page_size = info.page_size; |
| 471 | } else if (!info.usable_page_size) { |
| 472 | if (info.page_size > 8192) |
| 473 | info.usable_page_size = 8192; |
| 474 | else if (info.page_size > 4096) |
| 475 | info.usable_page_size = 4096; |
| 476 | else |
| 477 | info.usable_page_size = 1024; |
| 478 | } |
| 479 | |
| 480 | if (check_image_info(&info)) |
| 481 | display_help(-1); |
| 482 | |
| 483 | return create_image(&info); |
| 484 | } |