diff options
Diffstat (limited to 'package/mtd/src/mtd.c')
-rw-r--r-- | package/mtd/src/mtd.c | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/package/mtd/src/mtd.c b/package/mtd/src/mtd.c new file mode 100644 index 0000000..23e9916 --- /dev/null +++ b/package/mtd/src/mtd.c @@ -0,0 +1,475 @@ +/* + * mtd - simple memory technology device manipulation tool + * + * Copyright (C) 2005 Waldemar Brodkorb <wbx@dass-it.de>, + * Felix Fietkau <nbd@openwrt.org> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * $Id$ + * + * The code is based on the linux-mtd examples. + */ + +#include <limits.h> +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdint.h> +#include <fcntl.h> +#include <errno.h> +#include <error.h> +#include <time.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/param.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/reboot.h> +#include <string.h> + +#include <linux/mtd/mtd.h> + +#define TRX_MAGIC 0x30524448 /* "HDR0" */ +#define BUFSIZE (16 * 1024) +#define MAX_ARGS 8 + +#define DEBUG + +#define SYSTYPE_UNKNOWN 0 +#define SYSTYPE_BROADCOM 1 +/* to be continued */ + +struct trx_header { + uint32_t magic; /* "HDR0" */ + uint32_t len; /* Length of file including header */ + uint32_t crc32; /* 32-bit CRC from flag_version to end of file */ + uint32_t flag_version; /* 0:15 flags, 16:31 version */ + uint32_t offsets[3]; /* Offsets of partitions from start of header */ +}; + +char buf[BUFSIZE]; +int buflen; + +int +image_check_bcom(int imagefd, const char *mtd) +{ + struct trx_header *trx = (struct trx_header *) buf; + struct mtd_info_user mtdInfo; + int fd; + + buflen = read(imagefd, buf, 32); + if (buflen < 32) { + fprintf(stdout, "Could not get image header, file too small (%ld bytes)\n", buflen); + return 0; + } + + switch(trx->magic) { + case 0x47343557: /* W54G */ + case 0x53343557: /* W54S */ + case 0x73343557: /* W54s */ + case 0x46343557: /* W54F */ + case 0x55343557: /* W54U */ + /* ignore the first 32 bytes */ + buflen = read(imagefd, buf, sizeof(struct trx_header)); + break; + } + + if (trx->magic != TRX_MAGIC || trx->len < sizeof(struct trx_header)) { + fprintf(stderr, "Bad trx header\n"); + fprintf(stderr, "If this is a firmware in bin format, like some of the\n" + "original firmware files are, use following command to convert to trx:\n" + "dd if=firmware.bin of=firmware.trx bs=32 skip=1\n"); + return 0; + } + + /* check if image fits to mtd device */ + fd = mtd_open(mtd, O_RDWR | O_SYNC); + if(fd < 0) { + fprintf(stderr, "Could not open mtd device: %s\n", mtd); + exit(1); + } + + if(ioctl(fd, MEMGETINFO, &mtdInfo)) { + fprintf(stderr, "Could not get MTD device info from %s\n", mtd); + exit(1); + } + + if(mtdInfo.size < trx->len) { + fprintf(stderr, "Image too big for partition: %s\n", mtd); + close(fd); + return 0; + } + + close(fd); + return 1; +} + +int +image_check(int imagefd, const char *mtd) +{ + int fd, systype; + size_t count; + char *c; + FILE *f; + + systype = SYSTYPE_UNKNOWN; + f = fopen("/proc/cpuinfo", "r"); + while (!feof(f) && (fgets(buf, BUFSIZE - 1, f) != NULL)) { + if ((strncmp(buf, "system type", 11) == 0) && (c = strchr(buf, ':'))) { + c += 2; + if (strncmp(c, "Broadcom BCM947XX", 17) == 0) + systype = SYSTYPE_BROADCOM; + } + } + fclose(f); + + switch(systype) { + case SYSTYPE_BROADCOM: + return image_check_bcom(imagefd, mtd); + default: + return 1; + } +} + +int mtd_check(char *mtd) +{ + struct mtd_info_user mtdInfo; + int fd; + + fd = mtd_open(mtd, O_RDWR | O_SYNC); + if(fd < 0) { + fprintf(stderr, "Could not open mtd device: %s\n", mtd); + return 0; + } + + if(ioctl(fd, MEMGETINFO, &mtdInfo)) { + fprintf(stderr, "Could not get MTD device info from %s\n", mtd); + close(fd); + return 0; + } + + close(fd); + return 1; +} + +int +mtd_unlock(const char *mtd) +{ + int fd; + struct mtd_info_user mtdInfo; + struct erase_info_user mtdLockInfo; + + fd = mtd_open(mtd, O_RDWR | O_SYNC); + if(fd < 0) { + fprintf(stderr, "Could not open mtd device: %s\n", mtd); + exit(1); + } + + if(ioctl(fd, MEMGETINFO, &mtdInfo)) { + fprintf(stderr, "Could not get MTD device info from %s\n", mtd); + close(fd); + exit(1); + } + + mtdLockInfo.start = 0; + mtdLockInfo.length = mtdInfo.size; + if(ioctl(fd, MEMUNLOCK, &mtdLockInfo)) { + close(fd); + return 0; + } + + close(fd); + return 0; +} + +int +mtd_open(const char *mtd, int flags) +{ + FILE *fp; + char dev[PATH_MAX]; + int i; + + if ((fp = fopen("/proc/mtd", "r"))) { + while (fgets(dev, sizeof(dev), fp)) { + if (sscanf(dev, "mtd%d:", &i) && strstr(dev, mtd)) { + snprintf(dev, sizeof(dev), "/dev/mtd/%d", i); + fclose(fp); + return open(dev, flags); + } + } + fclose(fp); + } + + return open(mtd, flags); +} + +int +mtd_erase(const char *mtd) +{ + int fd; + struct mtd_info_user mtdInfo; + struct erase_info_user mtdEraseInfo; + + fd = mtd_open(mtd, O_RDWR | O_SYNC); + if(fd < 0) { + fprintf(stderr, "Could not open mtd device: %s\n", mtd); + exit(1); + } + + if(ioctl(fd, MEMGETINFO, &mtdInfo)) { + fprintf(stderr, "Could not get MTD device info from %s\n", mtd); + close(fd); + exit(1); + } + + mtdEraseInfo.length = mtdInfo.erasesize; + + for (mtdEraseInfo.start = 0; + mtdEraseInfo.start < mtdInfo.size; + mtdEraseInfo.start += mtdInfo.erasesize) { + + ioctl(fd, MEMUNLOCK, &mtdEraseInfo); + if(ioctl(fd, MEMERASE, &mtdEraseInfo)) { + fprintf(stderr, "Could not erase MTD device: %s\n", mtd); + close(fd); + exit(1); + } + } + + close(fd); + return 0; + +} + +int +mtd_write(int imagefd, const char *mtd, int quiet) +{ + int fd, i, result; + size_t r, w, e; + struct mtd_info_user mtdInfo; + struct erase_info_user mtdEraseInfo; + int ret = 0; + + fd = mtd_open(mtd, O_RDWR | O_SYNC); + if(fd < 0) { + fprintf(stderr, "Could not open mtd device: %s\n", mtd); + exit(1); + } + + if(ioctl(fd, MEMGETINFO, &mtdInfo)) { + fprintf(stderr, "Could not get MTD device info from %s\n", mtd); + close(fd); + exit(1); + } + + r = w = e = 0; + if (!quiet) + fprintf(stderr, " [ ]"); + + for (;;) { + /* buffer may contain data already (from trx check) */ + r = buflen; + r += read(imagefd, buf + buflen, BUFSIZE - buflen); + w += r; + + /* EOF */ + if (r <= 0) break; + + /* need to erase the next block before writing data to it */ + while (w > e) { + mtdEraseInfo.start = e; + mtdEraseInfo.length = mtdInfo.erasesize; + + if (!quiet) + fprintf(stderr, "\b\b\b[e]"); + /* erase the chunk */ + if (ioctl (fd,MEMERASE,&mtdEraseInfo) < 0) { + fprintf(stderr, "Erasing mtd failed: %s\n", mtd); + exit(1); + } + e += mtdInfo.erasesize; + } + + if (!quiet) + fprintf(stderr, "\b\b\b[w]"); + + if ((result = write(fd, buf, r)) < r) { + if (result < 0) { + fprintf(stderr, "Error writing image.\n"); + exit(1); + } else { + fprintf(stderr, "Insufficient space.\n"); + exit(1); + } + } + + buflen = 0; + } + if (!quiet) + fprintf(stderr, "\b\b\b\b"); + + close(fd); + return 0; +} + +void usage(void) +{ + fprintf(stderr, "Usage: mtd [<options> ...] <command> [<arguments> ...] <device>\n\n" + "The device is in the format of mtdX (eg: mtd4) or its label.\n" + "mtd recognizes these commands:\n" + " unlock unlock the device\n" + " erase erase all data on device\n" + " write <imagefile>|- write <imagefile> (use - for stdin) to device\n" + "Following options are available:\n" + " -q quiet mode (once: no [w] on writing,\n" + " twice: no status messages)\n" + " -r reboot after successful command\n" + " -f force write without trx checks\n" + " -e <device> erase <device> before executing the command\n\n" + "Example: To write linux.trx to mtd4 labeled as linux and reboot afterwards\n" + " mtd -r write linux.trx linux\n\n"); + exit(1); +} + +int main (int argc, char **argv) +{ + int ch, i, boot, unlock, imagefd, force, quiet, unlocked; + char *erase[MAX_ARGS], *device, *imagefile; + enum { + CMD_ERASE, + CMD_WRITE, + CMD_UNLOCK + } cmd; + + erase[0] = NULL; + boot = 0; + force = 0; + buflen = 0; + quiet = 0; + + while ((ch = getopt(argc, argv, "frqe:")) != -1) + switch (ch) { + case 'f': + force = 1; + break; + case 'r': + boot = 1; + break; + case 'q': + quiet++; + break; + case 'e': + i = 0; + while ((erase[i] != NULL) && ((i + 1) < MAX_ARGS)) + i++; + + erase[i++] = optarg; + erase[i] = NULL; + break; + + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if (argc < 2) + usage(); + + if ((strcmp(argv[0], "unlock") == 0) && (argc == 2)) { + cmd = CMD_UNLOCK; + device = argv[1]; + } else if ((strcmp(argv[0], "erase") == 0) && (argc == 2)) { + cmd = CMD_ERASE; + device = argv[1]; + } else if ((strcmp(argv[0], "write") == 0) && (argc == 3)) { + cmd = CMD_WRITE; + device = argv[2]; + + if (strcmp(argv[1], "-") == 0) { + imagefile = "<stdin>"; + imagefd = 0; + } else { + imagefile = argv[1]; + if ((imagefd = open(argv[1], O_RDONLY)) < 0) { + fprintf(stderr, "Couldn't open image file: %s!\n", imagefile); + exit(1); + } + } + + /* check trx file before erasing or writing anything */ + if (!image_check(imagefd, device)) { + if ((quiet < 2) || !force) + fprintf(stderr, "TRX check failed!\n"); + if (!force) + exit(1); + } else { + if (!mtd_check(device)) { + fprintf(stderr, "Can't open device for writing!\n"); + exit(1); + } + } + } else { + usage(); + } + + sync(); + + i = 0; + unlocked = 0; + while (erase[i] != NULL) { + if (quiet < 2) + fprintf(stderr, "Unlocking %s ...\n", erase[i]); + mtd_unlock(erase[i]); + if (quiet < 2) + fprintf(stderr, "Erasing %s ...\n", erase[i]); + mtd_erase(erase[i]); + if (strcmp(erase[i], device) == 0) + unlocked = 1; + i++; + } + + if (!unlocked) { + if (quiet < 2) + fprintf(stderr, "Unlocking %s ...\n", device); + mtd_unlock(device); + } + + switch (cmd) { + case CMD_UNLOCK: + break; + case CMD_ERASE: + if (quiet < 2) + fprintf(stderr, "Erasing %s ...\n", device); + mtd_erase(device); + break; + case CMD_WRITE: + if (quiet < 2) + fprintf(stderr, "Writing from %s to %s ... ", imagefile, device); + mtd_write(imagefd, device, quiet); + if (quiet < 2) + fprintf(stderr, "\n"); + break; + } + + sync(); + + if (boot) + kill(1, 15); // send SIGTERM to init for reboot + + return 0; +} |