diff --git a/Makefile b/Makefile index e475cbdbd..8af70428f 100644 --- a/Makefile +++ b/Makefile @@ -638,7 +638,7 @@ endif CHIP_OBJS = jedec.o stm50.o w39.o w29ee011.o \ sst28sf040.o 82802ab.o \ sst49lfxxxc.o sst_fwhub.o edi.o flashchips.o spi.o spi25.o spi25_statusreg.o \ - spi95.o opaque.o sfdp.o en29lv640b.o at45db.o + spi95.o opaque.o sfdp.o en29lv640b.o at45db.o writeprotect.o ############################################################################### # Library code. diff --git a/cli_classic.c b/cli_classic.c index 967ff5003..ae7f6ef04 100644 --- a/cli_classic.c +++ b/cli_classic.c @@ -17,6 +17,7 @@ * GNU General Public License for more details. */ +#include #include #include #include @@ -27,6 +28,7 @@ #include "flashchips.h" #include "fmap.h" #include "programmer.h" +#include "writeprotect.h" #include "libflashrom.h" static void cli_classic_usage(const char *name) @@ -54,6 +56,11 @@ static void cli_classic_usage(const char *name) " -n | --noverify don't auto-verify\n" " -N | --noverify-all verify included regions only (cf. -i)\n" " -l | --layout read ROM layout from \n" + " --wp-disable disable write protection\n" + " --wp-enable enable write protection\n" + " --wp-list list write protect range\n" + " --wp-status show write protect status\n" + " --wp-range=, set write protect range\n" " --flash-name read out the detected flash name\n" " --flash-size read out the detected flash size\n" " --fmap read ROM layout from fmap embedded in ROM\n" @@ -103,6 +110,32 @@ static int check_filename(char *filename, const char *type) return 0; } +static int parse_wp_range(unsigned int *start, unsigned int *len) +{ + char *endptr = NULL, *token = NULL; + + if (!optarg) { + msg_gerr("Error: No wp-range values provided\n"); + return -1; + } + + token = strtok(optarg, ","); + if (!token) { + msg_gerr("Error: Invalid wp-range argument format\n"); + return -1; + } + *start = strtoul(token, &endptr, 0); + + token = strtok(NULL, ","); + if (!token) { + msg_gerr("Error: Invalid wp-range argument format\n"); + return -1; + } + *len = strtoul(token, &endptr, 0); + + return 0; +} + int main(int argc, char *argv[]) { const struct flashchip *chip = NULL; @@ -116,6 +149,8 @@ int main(int argc, char *argv[]) int list_supported_wiki = 0; #endif int flash_name = 0, flash_size = 0; + int set_wp_enable = 0, set_wp_disable = 0, wp_status = 0; + int set_wp_range = 0, set_wp_region = 0, wp_list = 0; int read_it = 0, write_it = 0, erase_it = 0, verify_it = 0; int dont_verify_it = 0, dont_verify_all = 0, list_supported = 0, operation_specified = 0; struct flashrom_layout *layout = NULL; @@ -127,8 +162,15 @@ int main(int argc, char *argv[]) OPTION_FLASH_CONTENTS, OPTION_FLASH_NAME, OPTION_FLASH_SIZE, + OPTION_WP_STATUS, + OPTION_WP_SET_RANGE, + OPTION_WP_SET_REGION, + OPTION_WP_ENABLE, + OPTION_WP_DISABLE, + OPTION_WP_LIST, }; int ret = 0; + unsigned int wp_start = 0, wp_len = 0; static const char optstring[] = "r:Rw:v:nNVEfc:l:i:p:Lzho:"; static const struct option long_options[] = { @@ -150,6 +192,12 @@ int main(int argc, char *argv[]) {"flash-name", 0, NULL, OPTION_FLASH_NAME}, {"flash-size", 0, NULL, OPTION_FLASH_SIZE}, {"get-size", 0, NULL, OPTION_FLASH_SIZE}, // (deprecated): back compatibility. + {"wp-status", 0, 0, OPTION_WP_STATUS}, + {"wp-range", required_argument, NULL, OPTION_WP_SET_RANGE}, + {"wp-region", 1, 0, OPTION_WP_SET_REGION}, + {"wp-enable", optional_argument, 0, OPTION_WP_ENABLE}, + {"wp-disable", 0, 0, OPTION_WP_DISABLE}, + {"wp-list", 0, 0, OPTION_WP_LIST}, {"list-supported", 0, NULL, 'L'}, {"list-supported-wiki", 0, NULL, 'z'}, {"programmer", 1, NULL, 'p'}, @@ -169,6 +217,7 @@ int main(int argc, char *argv[]) char *tempstr = NULL; char *pparam = NULL; struct layout_include_args *include_args = NULL; + char *wp_mode_opt = NULL; flashrom_set_log_callback((flashrom_log_callback *)&flashrom_print_cb); @@ -286,6 +335,26 @@ int main(int argc, char *argv[]) cli_classic_validate_singleop(&operation_specified); flash_size = 1; break; + case OPTION_WP_STATUS: + wp_status = 1; + break; + case OPTION_WP_LIST: + wp_list = 1; + break; + case OPTION_WP_SET_RANGE: + if (parse_wp_range(&wp_start, &wp_len) < 0) + cli_classic_abort_usage("Incorrect wp-range arguments provided.\n"); + + set_wp_range = 1; + break; + case OPTION_WP_ENABLE: + set_wp_enable = 1; + if (optarg) + wp_mode_opt = strdup(optarg); + break; + case OPTION_WP_DISABLE: + set_wp_disable = 1; + break; case 'L': cli_classic_validate_singleop(&operation_specified); list_supported = 1; @@ -565,11 +634,32 @@ int main(int argc, char *argv[]) goto out_shutdown; } - if (!(read_it | write_it | verify_it | erase_it | flash_name | flash_size)) { + if (!(read_it | write_it | verify_it | erase_it | flash_name | flash_size + | set_wp_range | set_wp_region | set_wp_enable | + set_wp_disable | wp_status | wp_list)) { msg_ginfo("No operations were specified.\n"); goto out_shutdown; } + if (set_wp_enable && set_wp_disable) { + msg_ginfo("Error: --wp-enable and --wp-disable are mutually exclusive\n"); + ret = 1; + goto out_shutdown; + } + if (set_wp_range && set_wp_region) { + msg_gerr("Error: Cannot use both --wp-range and --wp-region simultaneously.\n"); + ret = 1; + goto out_shutdown; + } + + if (set_wp_range || set_wp_region) { + if (!fill_flash->chip->wp || !fill_flash->chip->wp->set_range) { + msg_gerr("Error: write protect is not supported on this flash chip.\n"); + ret = 1; + goto out_shutdown; + } + } + if (flash_name) { if (fill_flash->chip->vendor && fill_flash->chip->name) { printf("vendor=\"%s\" name=\"%s\"\n", @@ -586,6 +676,66 @@ int main(int argc, char *argv[]) goto out_shutdown; } + if (wp_status) { + if (fill_flash->chip->wp && fill_flash->chip->wp->wp_status) { + ret |= fill_flash->chip->wp->wp_status(fill_flash); + } else { + msg_gerr("Error: write protect is not supported on this flash chip.\n"); + ret = 1; + } + goto out_shutdown; + } + + /* Note: set_wp_disable should be done before setting the range */ + if (set_wp_disable) { + if (fill_flash->chip->wp && fill_flash->chip->wp->disable) { + ret |= fill_flash->chip->wp->disable(fill_flash); + } else { + msg_gerr("Error: write protect is not supported on this flash chip.\n"); + ret = 1; + goto out_shutdown; + } + } + + if (!ret && set_wp_enable) { + enum wp_mode wp_mode; + + if (wp_mode_opt) + wp_mode = get_wp_mode(wp_mode_opt); + else + wp_mode = WP_MODE_HARDWARE; /* default */ + + if (wp_mode == WP_MODE_UNKNOWN) { + msg_gerr("Error: Invalid WP mode: \"%s\"\n", wp_mode_opt); + ret = 1; + goto out_shutdown; + } + + if (fill_flash->chip->wp && fill_flash->chip->wp->enable) { + ret |= fill_flash->chip->wp->enable(fill_flash, wp_mode); + } else { + msg_gerr("Error: write protect is not supported on this flash chip.\n"); + ret = 1; + goto out_shutdown; + } + } + + if (wp_list) { + msg_ginfo("Valid write protection ranges:\n"); + if (fill_flash->chip->wp && fill_flash->chip->wp->list_ranges) { + ret |= fill_flash->chip->wp->list_ranges(fill_flash); + } else { + msg_gerr("Error: write protect is not supported on this flash chip.\n"); + ret = 1; + } + goto out_shutdown; + } + + /* Note: set_wp_range must happen before set_wp_enable */ + if (set_wp_range) { + ret |= fill_flash->chip->wp->set_range(fill_flash, wp_start, wp_len); + } + if (layoutfile) { layout = get_global_layout(); } else if (ifd && (flashrom_layout_read_from_ifd(&layout, fill_flash, NULL, 0) || diff --git a/flash.h b/flash.h index 2f0143b24..fefca9d57 100644 --- a/flash.h +++ b/flash.h @@ -235,6 +235,8 @@ struct flashchip { int (*unlock) (struct flashctx *flash); int (*write) (struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len); int (*read) (struct flashctx *flash, uint8_t *buf, unsigned int start, unsigned int len); + uint8_t (*read_status) (const struct flashctx *flash); + int (*write_status) (const struct flashctx *flash, int status); struct voltage { uint16_t min; uint16_t max; @@ -243,6 +245,8 @@ struct flashchip { /* SPI specific options (TODO: Make it a union in case other bustypes get specific options.) */ uint8_t wrea_override; /**< override opcode for write extended address register */ + + struct wp *wp; }; struct flashrom_flashctx { diff --git a/meson.build b/meson.build index 9836194f3..8418d72cd 100644 --- a/meson.build +++ b/meson.build @@ -366,6 +366,7 @@ srcs += 'stm50.c' srcs += 'udelay.c' srcs += 'w29ee011.c' srcs += 'w39.c' +srcs += 'writeprotect.c' mapfile = 'libflashrom.map' vflag = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), mapfile) diff --git a/writeprotect.c b/writeprotect.c new file mode 100644 index 000000000..b26c121c5 --- /dev/null +++ b/writeprotect.c @@ -0,0 +1,394 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2010 Google Inc. + * + * 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. + * + */ + +#include +#include +#include + +#include "flash.h" +#include "flashchips.h" +#include "chipdrivers.h" +#include "spi.h" +#include "writeprotect.h" + +/* + * The following procedures rely on look-up tables to match the user-specified + * range with the chip's supported ranges. This turned out to be the most + * elegant approach since diferent flash chips use different levels of + * granularity and methods to determine protected ranges. In other words, + * be stupid and simple since clever arithmetic will not work for many chips. + */ + +struct wp_range { + unsigned int start; /* starting address */ + unsigned int len; /* len */ +}; + +enum bit_state { + OFF = 0, + ON = 1, + X = -1 /* don't care. Must be bigger than max # of bp. */ +}; + +/* + * Generic write-protection schema for 25-series SPI flash chips. This assumes + * there is a status register that contains one or more consecutive bits which + * determine which address range is protected. + */ + +struct status_register_layout { + int bp0_pos; /* position of BP0 */ + int bp_bits; /* number of block protect bits */ + int srp_pos; /* position of status register protect enable bit */ +}; + +/* + * The following ranges and functions are useful for representing the + * writeprotect schema in which there are typically 5 bits of + * relevant information stored in status register 1: + * m.sec: This bit indicates the units (sectors vs. blocks) + * m.tb: The top-bottom bit indicates if the affected range is at the top of + * the flash memory's address space or at the bottom. + * bp: Bitmask representing the number of affected sectors/blocks. + */ +struct wp_range_descriptor { + struct modifier_bits m; + unsigned int bp; /* block protect bitfield */ + struct wp_range range; +}; + +struct wp_context { + struct status_register_layout sr1; /* status register 1 */ + struct wp_range_descriptor *descrs; + + /* + * Some chips store modifier bits in one or more special control + * registers instead of the status register like many older SPI NOR + * flash chips did. get_modifier_bits() and set_modifier_bits() will do + * any chip-specific operations necessary to get/set these bit values. + */ + int (*get_modifier_bits)(const struct flashctx *flash, + struct modifier_bits *m); + int (*set_modifier_bits)(const struct flashctx *flash, + struct modifier_bits *m); +}; + +/* + * Mask to extract write-protect enable and range bits + * Status register 1: + * SRP0: bit 7 + * range(BP2-BP0): bit 4-2 + * range(BP3-BP0): bit 5-2 (large chips) + * Status register 2: + * SRP1: bit 1 + */ +#define MASK_WP_AREA (0x9C) +#define MASK_WP_AREA_LARGE (0x9C) +#define MASK_WP2_AREA (0x01) + +static uint8_t do_read_status(const struct flashctx *flash) +{ + if (flash->chip->read_status) + return flash->chip->read_status(flash); + else + return spi_read_status_register(flash); +} + +static int do_write_status(const struct flashctx *flash, int status) +{ + if (flash->chip->write_status) + return flash->chip->write_status(flash, status); + else + return spi_write_status_register(flash, status); +} + +enum wp_mode get_wp_mode(const char *mode_str) +{ + enum wp_mode wp_mode = WP_MODE_UNKNOWN; + + if (!strcasecmp(mode_str, "hardware")) + wp_mode = WP_MODE_HARDWARE; + else if (!strcasecmp(mode_str, "power_cycle")) + wp_mode = WP_MODE_POWER_CYCLE; + else if (!strcasecmp(mode_str, "permanent")) + wp_mode = WP_MODE_PERMANENT; + + return wp_mode; +} + +/* Given a flash chip, this function returns its writeprotect info. */ +static int generic_range_table(const struct flashctx *flash, + struct wp_context **wp, + int *num_entries) +{ + *wp = NULL; + *num_entries = 0; + + switch (flash->chip->manufacture_id) { + default: + msg_cerr("%s: flash vendor (0x%x) not found, aborting\n", + __func__, flash->chip->manufacture_id); + return -1; + } + + return 0; +} + +static uint8_t generic_get_bp_mask(struct wp_context *wp) +{ + return ((1 << (wp->sr1.bp0_pos + wp->sr1.bp_bits)) - 1) ^ \ + ((1 << wp->sr1.bp0_pos) - 1); +} + +static uint8_t generic_get_status_check_mask(struct wp_context *wp) +{ + return generic_get_bp_mask(wp) | 1 << wp->sr1.srp_pos; +} + +/* Given a [start, len], this function finds a block protect bit combination + * (if possible) and sets the corresponding bits in "status". Remaining bits + * are preserved. */ +static int generic_range_to_status(const struct flashctx *flash, + unsigned int start, unsigned int len, + uint8_t *status, uint8_t *check_mask) +{ + struct wp_context *wp; + struct wp_range_descriptor *r; + int i, range_found = 0, num_entries; + uint8_t bp_mask; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + bp_mask = generic_get_bp_mask(wp); + + for (i = 0, r = &wp->descrs[0]; i < num_entries; i++, r++) { + msg_cspew("comparing range 0x%x 0x%x / 0x%x 0x%x\n", + start, len, r->range.start, r->range.len); + if ((start == r->range.start) && (len == r->range.len)) { + *status &= ~(bp_mask); + *status |= r->bp << (wp->sr1.bp0_pos); + + if (wp->set_modifier_bits) { + if (wp->set_modifier_bits(flash, &r->m) < 0) { + msg_cerr("error setting modifier bits for range.\n"); + return -1; + } + } + + range_found = 1; + break; + } + } + + if (!range_found) { + msg_cerr("%s: matching range not found\n", __func__); + return -1; + } + + *check_mask = generic_get_status_check_mask(wp); + return 0; +} + +static int generic_status_to_range(const struct flashctx *flash, + const uint8_t sr1, unsigned int *start, unsigned int *len) +{ + struct wp_context *wp; + struct wp_range_descriptor *r; + int num_entries, i, status_found = 0; + uint8_t sr1_bp; + struct modifier_bits m; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + /* modifier bits may be compared more than once, so get them here */ + if (wp->get_modifier_bits && wp->get_modifier_bits(flash, &m) < 0) + return -1; + + sr1_bp = (sr1 >> wp->sr1.bp0_pos) & ((1 << wp->sr1.bp_bits) - 1); + + for (i = 0, r = &wp->descrs[0]; i < num_entries; i++, r++) { + if (wp->get_modifier_bits) { + if (memcmp(&m, &r->m, sizeof(m))) + continue; + } + msg_cspew("comparing 0x%02x 0x%02x\n", sr1_bp, r->bp); + if (sr1_bp == r->bp) { + *start = r->range.start; + *len = r->range.len; + status_found = 1; + break; + } + } + + if (!status_found) { + msg_cerr("matching status not found\n"); + return -1; + } + + return 0; +} + +/* Given a [start, len], this function calls generic_range_to_status() to + * convert it to flash-chip-specific range bits, then sets into status register. + */ +static int generic_set_range(const struct flashctx *flash, + unsigned int start, unsigned int len) +{ + uint8_t status, expected, check_mask; + + status = do_read_status(flash); + msg_cdbg("%s: old status: 0x%02x\n", __func__, status); + + expected = status; /* preserve non-bp bits */ + if (generic_range_to_status(flash, start, len, &expected, &check_mask)) + return -1; + + do_write_status(flash, expected); + + status = do_read_status(flash); + msg_cdbg("%s: new status: 0x%02x\n", __func__, status); + if ((status & check_mask) != (expected & check_mask)) { + msg_cerr("expected=0x%02x, but actual=0x%02x. check mask=0x%02x\n", + expected, status, check_mask); + return 1; + } + return 0; +} + +/* Set/clear the status regsiter write protect bit in SR1. */ +static int generic_set_srp0(const struct flashctx *flash, int enable) +{ + uint8_t status, expected, check_mask; + struct wp_context *wp; + int num_entries; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + expected = do_read_status(flash); + msg_cdbg("%s: old status: 0x%02x\n", __func__, expected); + + if (enable) + expected |= 1 << wp->sr1.srp_pos; + else + expected &= ~(1 << wp->sr1.srp_pos); + + do_write_status(flash, expected); + + status = do_read_status(flash); + msg_cdbg("%s: new status: 0x%02x\n", __func__, status); + + check_mask = generic_get_status_check_mask(wp); + msg_cdbg("%s: check mask: 0x%02x\n", __func__, check_mask); + if ((status & check_mask) != (expected & check_mask)) { + msg_cerr("expected=0x%02x, but actual=0x%02x. check mask=0x%02x\n", + expected, status, check_mask); + return -1; + } + + return 0; +} + +static int generic_enable_writeprotect(const struct flashctx *flash, + enum wp_mode wp_mode) +{ + int ret; + + if (wp_mode != WP_MODE_HARDWARE) { + msg_cerr("%s(): unsupported write-protect mode\n", __func__); + return 1; + } + + ret = generic_set_srp0(flash, 1); + if (ret) + msg_cerr("%s(): error=%d.\n", __func__, ret); + + return ret; +} + +static int generic_disable_writeprotect(const struct flashctx *flash) +{ + int ret; + + ret = generic_set_srp0(flash, 0); + if (ret) + msg_cerr("%s(): error=%d.\n", __func__, ret); + + return ret; +} + +static int generic_list_ranges(const struct flashctx *flash) +{ + struct wp_context *wp; + struct wp_range_descriptor *r; + int i, num_entries; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + r = &wp->descrs[0]; + for (i = 0; i < num_entries; i++) { + msg_cinfo("start: 0x%06x, length: 0x%06x\n", + r->range.start, r->range.len); + r++; + } + + return 0; +} + +static int wp_context_status(const struct flashctx *flash) +{ + uint8_t sr1; + unsigned int start, len; + int ret = 0; + struct wp_context *wp; + int num_entries, wp_en; + + if (generic_range_table(flash, &wp, &num_entries)) + return -1; + + sr1 = do_read_status(flash); + wp_en = (sr1 >> wp->sr1.srp_pos) & 1; + + msg_cinfo("WP: status: 0x%04x\n", sr1); + msg_cinfo("WP: status.srp0: %x\n", wp_en); + /* FIXME: SRP1 is not really generic, but we probably should print + * it anyway to have consistent output. #legacycruft */ + msg_cinfo("WP: status.srp1: %x\n", 0); + msg_cinfo("WP: write protect is %s.\n", + wp_en ? "enabled" : "disabled"); + + msg_cinfo("WP: write protect range: "); + if (generic_status_to_range(flash, sr1, &start, &len)) { + msg_cinfo("(cannot resolve the range)\n"); + ret = -1; + } else { + msg_cinfo("start=0x%08x, len=0x%08x\n", start, len); + } + + return ret; +} + +struct wp wp_generic = { + .list_ranges = generic_list_ranges, + .set_range = generic_set_range, + .enable = generic_enable_writeprotect, + .disable = generic_disable_writeprotect, + .wp_status = wp_context_status, +}; diff --git a/writeprotect.h b/writeprotect.h new file mode 100644 index 000000000..bded4c854 --- /dev/null +++ b/writeprotect.h @@ -0,0 +1,51 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2010 Google Inc. + * + * 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. + * + */ + +#ifndef __WRITEPROTECT_H__ +#define __WRITEPROTECT_H__ 1 + +enum wp_mode { + WP_MODE_UNKNOWN = -1, + WP_MODE_HARDWARE, /* hardware WP pin determines status */ + WP_MODE_POWER_CYCLE, /* WP active until power off/on cycle */ + WP_MODE_PERMANENT, /* status register permanently locked, + WP permanently enabled */ +}; + +struct wp { + int (*list_ranges)(const struct flashctx *flash); + int (*set_range)(const struct flashctx *flash, + unsigned int start, unsigned int len); + int (*enable)(const struct flashctx *flash, enum wp_mode mode); + int (*disable)(const struct flashctx *flash); + int (*wp_status)(const struct flashctx *flash); +}; + +extern struct wp wp_generic; + +enum wp_mode get_wp_mode(const char *mode_str); + +/* + * Generic write-protect stuff + */ + +struct modifier_bits { + int sec; /* if 1, bp bits describe sectors */ + int tb; /* value of top/bottom select bit */ +}; + +#endif /* !__WRITEPROTECT_H__ */