diff options
Diffstat (limited to 'linux_mtd.c')
-rw-r--r-- | linux_mtd.c | 293 |
1 files changed, 220 insertions, 73 deletions
diff --git a/linux_mtd.c b/linux_mtd.c index 22702e904..495db9a74 100644 --- a/linux_mtd.c +++ b/linux_mtd.c @@ -17,6 +17,7 @@ #include <ctype.h> #include <errno.h> #include <fcntl.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <mtd/mtd-user.h> @@ -31,16 +32,16 @@ #define LINUX_DEV_ROOT "/dev" #define LINUX_MTD_SYSFS_ROOT "/sys/class/mtd" -static FILE *dev_fp = NULL; - -static int mtd_device_is_writeable; - -static int mtd_no_erase; - -/* Size info is presented in bytes in sysfs. */ -static unsigned long int mtd_total_size; -static unsigned long int mtd_numeraseregions; -static unsigned long int mtd_erasesize; /* only valid if numeraseregions is 0 */ +struct linux_mtd_data { + FILE *dev_fp; + bool device_is_writeable; + bool no_erase; + /* Size info is presented in bytes in sysfs. */ + unsigned long int total_size; + unsigned long int numeraseregions; + /* only valid if numeraseregions is 0 */ + unsigned long int erasesize; +}; /* read a string from a sysfs file and sanitize it */ static int read_sysfs_string(const char *sysfs_path, const char *filename, char *buf, int len) @@ -120,76 +121,80 @@ static int popcnt(unsigned int u) } /* returns 0 to indicate success, non-zero to indicate error */ -static int get_mtd_info(const char *sysfs_path) +static int get_mtd_info(const char *sysfs_path, struct linux_mtd_data *data) { unsigned long int tmp; - char mtd_device_name[32]; + char device_name[32]; /* Flags */ if (read_sysfs_int(sysfs_path, "flags", &tmp)) return 1; if (tmp & MTD_WRITEABLE) { /* cache for later use by write function */ - mtd_device_is_writeable = 1; + data->device_is_writeable = true; } if (tmp & MTD_NO_ERASE) { - mtd_no_erase = 1; + data->no_erase = true; } /* Device name */ - if (read_sysfs_string(sysfs_path, "name", mtd_device_name, sizeof(mtd_device_name))) + if (read_sysfs_string(sysfs_path, "name", device_name, sizeof(device_name))) return 1; /* Total size */ - if (read_sysfs_int(sysfs_path, "size", &mtd_total_size)) + if (read_sysfs_int(sysfs_path, "size", &data->total_size)) return 1; - if (popcnt(mtd_total_size) != 1) { + if (popcnt(data->total_size) != 1) { msg_perr("MTD size is not a power of 2\n"); return 1; } /* Erase size */ - if (read_sysfs_int(sysfs_path, "erasesize", &mtd_erasesize)) + if (read_sysfs_int(sysfs_path, "erasesize", &data->erasesize)) return 1; - if (popcnt(mtd_erasesize) != 1) { + if (popcnt(data->erasesize) != 1) { msg_perr("MTD erase size is not a power of 2\n"); return 1; } /* Erase regions */ - if (read_sysfs_int(sysfs_path, "numeraseregions", &mtd_numeraseregions)) + if (read_sysfs_int(sysfs_path, "numeraseregions", &data->numeraseregions)) return 1; - if (mtd_numeraseregions != 0) { + if (data->numeraseregions != 0) { msg_perr("Non-uniform eraseblock size is unsupported.\n"); return 1; } msg_pdbg("%s: device_name: \"%s\", is_writeable: %d, " "numeraseregions: %lu, total_size: %lu, erasesize: %lu\n", - __func__, mtd_device_name, mtd_device_is_writeable, - mtd_numeraseregions, mtd_total_size, mtd_erasesize); + __func__, device_name, data->device_is_writeable, + data->numeraseregions, data->total_size, data->erasesize); return 0; } static int linux_mtd_probe(struct flashctx *flash) { - if (mtd_no_erase) + struct linux_mtd_data *data = flash->mst->opaque.data; + + if (data->no_erase) flash->chip->feature_bits |= FEATURE_NO_ERASE; - flash->chip->tested = TEST_OK_PREW; - flash->chip->total_size = mtd_total_size / 1024; /* bytes -> kB */ - flash->chip->block_erasers[0].eraseblocks[0].size = mtd_erasesize; - flash->chip->block_erasers[0].eraseblocks[0].count = mtd_total_size / mtd_erasesize; + flash->chip->tested = TEST_OK_PREWB; + flash->chip->total_size = data->total_size / 1024; /* bytes -> kB */ + flash->chip->block_erasers[0].eraseblocks[0].size = data->erasesize; + flash->chip->block_erasers[0].eraseblocks[0].count = + data->total_size / data->erasesize; return 1; } static int linux_mtd_read(struct flashctx *flash, uint8_t *buf, unsigned int start, unsigned int len) { + struct linux_mtd_data *data = flash->mst->opaque.data; unsigned int eb_size = flash->chip->block_erasers[0].eraseblocks[0].size; unsigned int i; - if (fseek(dev_fp, start, SEEK_SET) != 0) { + if (fseek(data->dev_fp, start, SEEK_SET) != 0) { msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno)); return 1; } @@ -203,13 +208,14 @@ static int linux_mtd_read(struct flashctx *flash, uint8_t *buf, unsigned int step = eb_size - ((start + i) % eb_size); step = min(step, len - i); - if (fread(buf + i, step, 1, dev_fp) != 1) { + if (fread(buf + i, step, 1, data->dev_fp) != 1) { msg_perr("Cannot read 0x%06x bytes at 0x%06x: %s\n", step, start + i, strerror(errno)); return 1; } i += step; + update_progress(flash, FLASHROM_PROGRESS_READ, i, len); } return 0; @@ -219,13 +225,14 @@ static int linux_mtd_read(struct flashctx *flash, uint8_t *buf, static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len) { + struct linux_mtd_data *data = flash->mst->opaque.data; unsigned int chunksize = flash->chip->block_erasers[0].eraseblocks[0].size; unsigned int i; - if (!mtd_device_is_writeable) + if (!data->device_is_writeable) return 1; - if (fseek(dev_fp, start, SEEK_SET) != 0) { + if (fseek(data->dev_fp, start, SEEK_SET) != 0) { msg_perr("Cannot seek to 0x%06x: %s\n", start, strerror(errno)); return 1; } @@ -240,17 +247,18 @@ static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf, unsigned int step = chunksize - ((start + i) % chunksize); step = min(step, len - i); - if (fwrite(buf + i, step, 1, dev_fp) != 1) { + if (fwrite(buf + i, step, 1, data->dev_fp) != 1) { msg_perr("Cannot write 0x%06x bytes at 0x%06x\n", step, start + i); return 1; } - if (fflush(dev_fp) == EOF) { + if (fflush(data->dev_fp) == EOF) { msg_perr("Failed to flush buffer: %s\n", strerror(errno)); return 1; } i += step; + update_progress(flash, FLASHROM_PROGRESS_WRITE, i, len); } return 0; @@ -259,37 +267,169 @@ static int linux_mtd_write(struct flashctx *flash, const uint8_t *buf, static int linux_mtd_erase(struct flashctx *flash, unsigned int start, unsigned int len) { + struct linux_mtd_data *data = flash->mst->opaque.data; uint32_t u; - if (mtd_no_erase) { + if (data->no_erase) { msg_perr("%s: device does not support erasing. Please file a " "bug report at flashrom@flashrom.org\n", __func__); return 1; } - if (mtd_numeraseregions != 0) { + if (data->numeraseregions != 0) { /* TODO: Support non-uniform eraseblock size using use MEMGETREGIONCOUNT/MEMGETREGIONINFO ioctls */ - msg_perr("%s: mtd_numeraseregions must be 0\n", __func__); + msg_perr("%s: numeraseregions must be 0\n", __func__); return 1; } - for (u = 0; u < len; u += mtd_erasesize) { + for (u = 0; u < len; u += data->erasesize) { struct erase_info_user erase_info = { .start = start + u, - .length = mtd_erasesize, + .length = data->erasesize, }; - if (ioctl(fileno(dev_fp), MEMERASE, &erase_info) == -1) { - msg_perr("%s: ioctl: %s\n", __func__, strerror(errno)); - return 1; + int ret = ioctl(fileno(data->dev_fp), MEMERASE, &erase_info); + if (ret < 0) { + msg_perr("%s: MEMERASE ioctl call returned %d, error: %s\n", + __func__, ret, strerror(errno)); + return 1; } + update_progress(flash, FLASHROM_PROGRESS_ERASE, u + data->erasesize, len); } return 0; } -static struct opaque_master programmer_linux_mtd = { +static int linux_mtd_shutdown(void *data) +{ + struct linux_mtd_data *mtd_data = data; + if (mtd_data->dev_fp != NULL) { + fclose(mtd_data->dev_fp); + } + free(data); + + return 0; +} + +static enum flashrom_wp_result linux_mtd_wp_read_cfg(struct flashrom_wp_cfg *cfg, struct flashctx *flash) +{ + struct linux_mtd_data *data = flash->mst->opaque.data; + bool start_found = false; + bool end_found = false; + + cfg->mode = FLASHROM_WP_MODE_DISABLED; + cfg->range.start = 0; + cfg->range.len = 0; + + /* Check protection status of each block */ + for (size_t u = 0; u < data->total_size; u += data->erasesize) { + struct erase_info_user erase_info = { + .start = u, + .length = data->erasesize, + }; + + int ret = ioctl(fileno(data->dev_fp), MEMISLOCKED, &erase_info); + if (ret == 0) { + /* Block is unprotected. */ + + if (start_found) { + end_found = true; + } + } else if (ret == 1) { + /* Block is protected. */ + + if (end_found) { + /* + * We already found the end of another + * protection range, so this is the start of a + * new one. + */ + return FLASHROM_WP_ERR_OTHER; + } + if (!start_found) { + cfg->range.start = erase_info.start; + cfg->mode = FLASHROM_WP_MODE_HARDWARE; + start_found = true; + } + cfg->range.len += data->erasesize; + } else { + msg_perr("%s: ioctl: %s\n", __func__, strerror(errno)); + return FLASHROM_WP_ERR_READ_FAILED; + } + + } + + return FLASHROM_WP_OK; +} + +static enum flashrom_wp_result linux_mtd_wp_write_cfg(struct flashctx *flash, const struct flashrom_wp_cfg *cfg) +{ + const struct linux_mtd_data *data = flash->mst->opaque.data; + + const struct erase_info_user entire_chip = { + .start = 0, + .length = data->total_size, + }; + const struct erase_info_user desired_range = { + .start = cfg->range.start, + .length = cfg->range.len, + }; + + /* + * MTD ioctls will enable hardware status register protection if and + * only if the protected region is non-empty. Return an error if the + * cfg cannot be activated using the MTD interface. + */ + if ((cfg->range.len == 0) != (cfg->mode == FLASHROM_WP_MODE_DISABLED)) { + return FLASHROM_WP_ERR_OTHER; + } + + /* + * MTD handles write-protection additively, so whatever new range is + * specified is added to the range which is currently protected. To + * just protect the requsted range, we need to disable the current + * write protection and then enable it for the desired range. + */ + int ret = ioctl(fileno(data->dev_fp), MEMUNLOCK, &entire_chip); + if (ret < 0) { + msg_perr("%s: Failed to disable write-protection, MEMUNLOCK ioctl " + "retuned %d, error: %s\n", __func__, ret, strerror(errno)); + return FLASHROM_WP_ERR_WRITE_FAILED; + } + + if (cfg->range.len > 0) { + ret = ioctl(fileno(data->dev_fp), MEMLOCK, &desired_range); + if (ret < 0) { + msg_perr("%s: Failed to enable write-protection, " + "MEMLOCK ioctl retuned %d, error: %s\n", + __func__, ret, strerror(errno)); + return FLASHROM_WP_ERR_WRITE_FAILED; + } + } + + /* Verify */ + struct flashrom_wp_cfg readback_cfg; + enum flashrom_wp_result read_ret = linux_mtd_wp_read_cfg(&readback_cfg, flash); + if (read_ret != FLASHROM_WP_OK) + return read_ret; + + if (readback_cfg.mode != cfg->mode || + readback_cfg.range.start != cfg->range.start || + readback_cfg.range.len != cfg->range.len) { + return FLASHROM_WP_ERR_VERIFY_FAILED; + } + + return FLASHROM_WP_OK; +} + +static enum flashrom_wp_result linux_mtd_wp_get_available_ranges(struct flashrom_wp_ranges **list, struct flashctx *flash) +{ + /* Not supported by MTD interface. */ + return FLASHROM_WP_ERR_RANGE_LIST_UNAVAILABLE; +} + +static const struct opaque_master linux_mtd_opaque_master = { /* max_data_{read,write} don't have any effect for this programmer */ .max_data_read = MAX_DATA_UNSPECIFIED, .max_data_write = MAX_DATA_UNSPECIFIED, @@ -297,10 +437,14 @@ static struct opaque_master programmer_linux_mtd = { .read = linux_mtd_read, .write = linux_mtd_write, .erase = linux_mtd_erase, + .shutdown = linux_mtd_shutdown, + .wp_read_cfg = linux_mtd_wp_read_cfg, + .wp_write_cfg = linux_mtd_wp_write_cfg, + .wp_get_ranges = linux_mtd_wp_get_available_ranges, }; /* Returns 0 if setup is successful, non-zero to indicate error */ -static int linux_mtd_setup(int dev_num) +static int linux_mtd_setup(int dev_num, struct linux_mtd_data *data) { char sysfs_path[32]; int ret = 1; @@ -309,8 +453,7 @@ static int linux_mtd_setup(int dev_num) if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0) goto linux_mtd_setup_exit; - char buf[4]; - memset(buf, 0, sizeof(buf)); + char buf[4] = { 0 }; if (read_sysfs_string(sysfs_path, "type", buf, sizeof(buf))) return 1; @@ -332,15 +475,15 @@ static int linux_mtd_setup(int dev_num) /* so far so good, get more info from other files in this dir */ if (snprintf(sysfs_path, sizeof(sysfs_path), "%s/mtd%d/", LINUX_MTD_SYSFS_ROOT, dev_num) < 0) goto linux_mtd_setup_exit; - if (get_mtd_info(sysfs_path)) + if (get_mtd_info(sysfs_path, data)) goto linux_mtd_setup_exit; /* open file stream and go! */ - if ((dev_fp = fopen(dev_path, "r+")) == NULL) { + if ((data->dev_fp = fopen(dev_path, "r+")) == NULL) { msg_perr("Cannot open file stream for %s\n", dev_path); goto linux_mtd_setup_exit; } - ret = setvbuf(dev_fp, NULL, _IONBF, 0); + ret = setvbuf(data->dev_fp, NULL, _IONBF, 0); if (ret) msg_pwarn("Failed to set MTD device to unbuffered: %d\n", ret); @@ -351,31 +494,22 @@ linux_mtd_setup_exit: return ret; } -static int linux_mtd_shutdown(void *data) -{ - if (dev_fp != NULL) { - fclose(dev_fp); - dev_fp = NULL; - } - - return 0; -} - -int linux_mtd_init(void) +static int linux_mtd_init(const struct programmer_cfg *cfg) { - char *param; + char *param_str; int dev_num = 0; int ret = 1; + struct linux_mtd_data *data = NULL; - param = extract_programmer_param("dev"); - if (param) { + param_str = extract_programmer_param_str(cfg, "dev"); + if (param_str) { char *endptr; - dev_num = strtol(param, &endptr, 0); + dev_num = strtol(param_str, &endptr, 0); if ((*endptr != '\0') || (dev_num < 0)) { msg_perr("Invalid device number %s. Use flashrom -p " "linux_mtd:dev=N where N is a valid MTD\n" - "device number.\n", param); + "device number.\n", param_str); goto linux_mtd_init_exit; } } @@ -391,23 +525,36 @@ int linux_mtd_init(void) struct stat s; if (stat(sysfs_path, &s) < 0) { - if (param) + if (param_str) msg_perr("%s does not exist\n", sysfs_path); else msg_pdbg("%s does not exist\n", sysfs_path); goto linux_mtd_init_exit; } + free(param_str); - if (linux_mtd_setup(dev_num)) - goto linux_mtd_init_exit; + data = calloc(1, sizeof(*data)); + if (!data) { + msg_perr("Unable to allocate memory for linux_mtd_data\n"); + return 1; + } - if (register_shutdown(linux_mtd_shutdown, NULL)) - goto linux_mtd_init_exit; + /* Get MTD info and store it in `data` */ + if (linux_mtd_setup(dev_num, data)) { + free(data); + return 1; + } - register_opaque_master(&programmer_linux_mtd); + return register_opaque_master(&linux_mtd_opaque_master, data); - ret = 0; linux_mtd_init_exit: - free(param); + free(param_str); return ret; } + +const struct programmer_entry programmer_linux_mtd = { + .name = "linux_mtd", + .type = OTHER, + .devs.note = "Device files /dev/mtd*\n", + .init = linux_mtd_init, +}; |