/* * MTD utility functions * * Copyright (C) 2010, Broadcom Corporation. All Rights Reserved. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * $Id: mtd.c,v 1.32 2009/12/02 20:01:24 Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef LINUX26 #include #else /* LINUX26 */ #include #endif /* LINUX26 */ #include #include #include /* * Open an MTD device * @param mtd path to or partition name of MTD device * @param flags open() flags * @return return value of open() */ 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)) { #ifdef LINUX26 snprintf(dev, sizeof(dev), "/dev/mtd%d", i); #else snprintf(dev, sizeof(dev), "/dev/mtd/%d", i); #endif fclose(fp); return open(dev, flags); } } fclose(fp); } return open(mtd, flags); } /* * Erase an MTD device * @param mtd path to or partition name of MTD device * @return 0 on success and errno on failure */ int mtd_erase(const char *mtd) { int mtd_fd; mtd_info_t mtd_info; erase_info_t erase_info; /* Open MTD device */ if ((mtd_fd = mtd_open(mtd, O_RDWR)) < 0) { perror(mtd); return errno; } /* Get sector size */ if (ioctl(mtd_fd, MEMGETINFO, &mtd_info) != 0) { perror(mtd); close(mtd_fd); return errno; } erase_info.length = mtd_info.erasesize; for (erase_info.start = 0; erase_info.start < mtd_info.size; erase_info.start += mtd_info.erasesize) { (void) ioctl(mtd_fd, MEMUNLOCK, &erase_info); if (ioctl(mtd_fd, MEMERASE, &erase_info) != 0) { perror(mtd); close(mtd_fd); return errno; } } close(mtd_fd); return 0; } extern int http_get(const char *server, char *buf, size_t count, off_t offset); /* * Write a file to an MTD device * @param path file to write or a URL * @param mtd path to or partition name of MTD device * @return 0 on success and errno on failure */ int mtd_write(const char *path, const char *mtd) { int mtd_fd = -1; mtd_info_t mtd_info; erase_info_t erase_info; struct sysinfo info; struct trx_header trx; unsigned long crc; FILE *fp; char *buf = NULL; long count, len, off; int ret = -1; /* Examine TRX header */ if ((fp = fopen(path, "r"))) count = safe_fread(&trx, 1, sizeof(struct trx_header), fp); else count = http_get(path, (char *) &trx, sizeof(struct trx_header), 0); if (count < sizeof(struct trx_header)) { fprintf(stderr, "%s: File is too small (%ld bytes)\n", path, count); goto fail; } /* Open MTD device and get sector size */ if ((mtd_fd = mtd_open(mtd, O_RDWR)) < 0 || ioctl(mtd_fd, MEMGETINFO, &mtd_info) != 0 || mtd_info.erasesize < sizeof(struct trx_header)) { perror(mtd); goto fail; } if (trx.magic != TRX_MAGIC || trx.len > mtd_info.size || trx.len < sizeof(struct trx_header)) { fprintf(stderr, "%s: Bad trx header\n", path); goto fail; } /* Allocate temporary buffer */ /* See if we have enough memory to store the whole file */ sysinfo(&info); if (info.freeram >= trx.len) { erase_info.length = ROUNDUP(trx.len, mtd_info.erasesize); if (!(buf = malloc(erase_info.length))) erase_info.length = mtd_info.erasesize; } /* fallback to smaller buffer */ else { erase_info.length = mtd_info.erasesize; buf = NULL; } if (!buf && (!(buf = malloc(erase_info.length)))) { perror("malloc"); goto fail; } /* Calculate CRC over header */ crc = hndcrc32((uint8 *) &trx.flag_version, sizeof(struct trx_header) - OFFSETOF(struct trx_header, flag_version), CRC32_INIT_VALUE); if (trx.flag_version & TRX_NO_HEADER) trx.len -= sizeof(struct trx_header); /* Write file or URL to MTD device */ for (erase_info.start = 0; erase_info.start < trx.len; erase_info.start += count) { len = MIN(erase_info.length, trx.len - erase_info.start); if ((trx.flag_version & TRX_NO_HEADER) || erase_info.start) count = off = 0; else { count = off = sizeof(struct trx_header); memcpy(buf, &trx, sizeof(struct trx_header)); } if (fp) count += safe_fread(&buf[off], 1, len - off, fp); else count += http_get(path, &buf[off], len - off, erase_info.start + off); if (count < len) { fprintf(stderr, "%s: Truncated file (actual %ld expect %ld)\n", path, count - off, len - off); goto fail; } /* Update CRC */ crc = hndcrc32((uint8 *)&buf[off], count - off, crc); /* Check CRC before writing if possible */ if (count == trx.len) { if (crc != trx.crc32) { fprintf(stderr, "%s: Bad CRC\n", path); goto fail; } } /* Do it */ (void) ioctl(mtd_fd, MEMUNLOCK, &erase_info); if (ioctl(mtd_fd, MEMERASE, &erase_info) != 0 || write(mtd_fd, buf, count) != count) { perror(mtd); goto fail; } } printf("%s: CRC OK\n", mtd); ret = 0; fail: if (buf) { /* Dummy read to ensure chip(s) are out of lock/suspend state */ (void) read(mtd_fd, buf, 2); free(buf); } if (mtd_fd >= 0) close(mtd_fd); if (fp) fclose(fp); return ret; }