diff --git a/libflashrom.c b/libflashrom.c index 39b0c67b6..658d8122b 100644 --- a/libflashrom.c +++ b/libflashrom.c @@ -714,14 +714,12 @@ void flashrom_wp_get_range(size_t *start, size_t *len, const struct flashrom_wp_ */ enum flashrom_wp_result flashrom_wp_write_cfg(struct flashctx *flash, const struct flashrom_wp_cfg *cfg) { - /* - * TODO: Call custom implementation if the programmer is opaque, as - * direct WP operations require SPI access. In particular, linux_mtd - * has its own WP operations we should use instead. - */ if (flash->mst->buses_supported & BUS_SPI) return wp_write_cfg(flash, cfg); + if (flash->mst->buses_supported & BUS_PROG && flash->mst->opaque.wp_write_cfg) + return flash->mst->opaque.wp_write_cfg(flash, cfg); + return FLASHROM_WP_ERR_OTHER; } @@ -736,14 +734,12 @@ enum flashrom_wp_result flashrom_wp_write_cfg(struct flashctx *flash, const stru */ enum flashrom_wp_result flashrom_wp_read_cfg(struct flashrom_wp_cfg *cfg, struct flashctx *flash) { - /* - * TODO: Call custom implementation if the programmer is opaque, as - * direct WP operations require SPI access. In particular, linux_mtd - * has its own WP operations we should use instead. - */ if (flash->mst->buses_supported & BUS_SPI) return wp_read_cfg(cfg, flash); + if (flash->mst->buses_supported & BUS_PROG && flash->mst->opaque.wp_read_cfg) + return flash->mst->opaque.wp_read_cfg(cfg, flash); + return FLASHROM_WP_ERR_OTHER; } @@ -761,15 +757,12 @@ enum flashrom_wp_result flashrom_wp_read_cfg(struct flashrom_wp_cfg *cfg, struct */ enum flashrom_wp_result flashrom_wp_get_available_ranges(struct flashrom_wp_ranges **list, struct flashrom_flashctx *flash) { - /* - * TODO: Call custom implementation if the programmer is opaque, as - * direct WP operations require SPI access. We actually can't implement - * this in linux_mtd right now, but we should adopt a proper generic - * architechure to match the read and write functions anyway. - */ if (flash->mst->buses_supported & BUS_SPI) return wp_get_available_ranges(list, flash); + if (flash->mst->buses_supported & BUS_PROG && flash->mst->opaque.wp_get_ranges) + return flash->mst->opaque.wp_get_ranges(list, flash); + return FLASHROM_WP_ERR_OTHER; } diff --git a/linux_mtd.c b/linux_mtd.c index 9d80a51a0..445a90359 100644 --- a/linux_mtd.c +++ b/linux_mtd.c @@ -308,6 +308,123 @@ static int linux_mtd_shutdown(void *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, @@ -317,6 +434,9 @@ static const struct opaque_master linux_mtd_opaque_master = { .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 */ diff --git a/programmer.h b/programmer.h index 0657bb1cf..47d2dc31b 100644 --- a/programmer.h +++ b/programmer.h @@ -403,6 +403,9 @@ struct opaque_master { int (*read) (struct flashctx *flash, uint8_t *buf, unsigned int start, unsigned int len); int (*write) (struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len); int (*erase) (struct flashctx *flash, unsigned int blockaddr, unsigned int blocklen); + enum flashrom_wp_result (*wp_write_cfg)(struct flashctx *, const struct flashrom_wp_cfg *); + enum flashrom_wp_result (*wp_read_cfg)(struct flashrom_wp_cfg *, struct flashctx *); + enum flashrom_wp_result (*wp_get_ranges)(struct flashrom_wp_ranges **, struct flashctx *); int (*shutdown)(void *data); void *data; };