summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikolai Artemiev <nartemiev@google.com>2022-02-13 22:32:59 +1100
committerEdward O'Callaghan <quasisec@chromium.org>2022-04-05 23:47:46 +0000
commit9b20174fda038ef633af2163c9b1570f4dbf9a37 (patch)
treeeb3b03cd25dc3e158c6c6de34f66bb7a79b70c99
parenta850fd0aa8054a1125a9231fa3317428f15900f4 (diff)
downloadflashrom-9b20174fda038ef633af2163c9b1570f4dbf9a37.tar.gz
flashrom-9b20174fda038ef633af2163c9b1570f4dbf9a37.tar.bz2
flashrom-9b20174fda038ef633af2163c9b1570f4dbf9a37.zip
libflashrom,linux_mtd: add linux_mtd writeprotect support
This commit adds a generic framework to allow opaque programmers to implement writeprotect operations and uses the framework to support writeprotect operations on linux MTD device files. The generic framework comprises three new functions in `struct opaque_master` that are called from libflashrom: - wp_write_cfg() - wp_read_cfg() - wp_get_ranges() For linux_mtd, only the read/write functions are implemented. Linux's MTD interface doesn't provide a way to get available ranges, so calling get_wp_ranges() on the linux_mtd master will return FLASHROM_WP_ERR_RANGE_LIST_UNAVAILABLE. BUG=b:182223106 BRANCH=none TEST=WP ops on hana DUT (MT8173) with W25Q32DW flash TEST=flashrom --wp-enable --wp-range <non-empty> succeeds TEST=flashrom --wp-enable --wp-range <empty> fails as expected TEST=flashrom --wp-disable --wp-range <empty> succeeds TEST=flashrom --wp-disable --wp-range <non-empty> fails as expected TEST=flashrom --wp-status succeeds Change-Id: I5c86e28cdec44bec49ba1d36f8ab62241b9b01da Signed-off-by: Nikolai Artemiev <nartemiev@google.com> Reviewed-on: https://review.coreboot.org/c/flashrom/+/61897 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Anastasia Klimchuk <aklm@chromium.org> Reviewed-by: Edward O'Callaghan <quasisec@chromium.org>
-rw-r--r--libflashrom.c25
-rw-r--r--linux_mtd.c120
-rw-r--r--programmer.h3
3 files changed, 132 insertions, 16 deletions
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;
};