Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect WP and skip the area on users request #6

Merged
merged 5 commits into from
Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions Documentation/chips.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
List of chips for extending WP support in flashrom as of 26 July 2022.

| Manufacturer | Model(s) | Upstream | [flashrom#185] | Notes
| ----------------- | ---------------- | -------- | -------------- | -----
| Atmel | `AT25SL128A` | + | |
| Eon | `EN25QH128` | + | |
| ^ | `EN25QH32` | | + |
| ^ | `EN25QH64` | | + |
| GigaDevice | `GD25LQ64` | + | |
| ^ | `GD25LQ64B` | + | |
| ^ | `GD25LQ128C` | + | |
| ^ | `GD25LQ128D` | + | |
| ^ | `GD25LQ128E` | + | |
| ^ | `GD25Q32` | + | |
| ^ | `GD25Q32B` | + | |
| ^ | `GD25Q64` | + | |
| ^ | `GD25Q64B` | + | |
| ^ | `GD25Q127C` | + | |
| ^ | `GD25Q128C` | + | |
| ^ | `GD25Q256D` | + | |
| Winbond | `W25Q32.V` | | + |
| ^ | `W25Q32.W` | | + |
| ^ | `W25Q64BV` | + | + |
| ^ | `W25Q64CV` | + | + |
| ^ | `W25Q64FV` | + | + | in KGPE-D16
| ^ | `W25Q64JV-.Q` | + | + |
| ^ | `W25Q64.W` | + | + | in Protectli FW4C
| ^ | `W25Q128.V` | + | | in KGPE-D16
| ^ | `W25Q128.V..M` | + | |
| ^ | `W25Q128.W` | + | |
| ^ | `W25Q128.JW.DTR` | + | |
| ^ | `W25Q256.V` | + | |
| ^ | `W25Q256JV_M` | + | |
| Macronix | `MX25L3206E` | | + | in Dell OptiPlex
| ^ | `MX25L3208E` | | + | same flash description as MX25L3206E
| ^ | `MX25L6405` | | + |
| ^ | `MX25L6405D` | | + |
| ^ | `MX25L6406E` | | + | in Dell OptiPlex
| ^ | `MX25L6408E` | | + | same flash description as MX25L6406E
| ^ | `MX25L6436E` | | + |
| ^ | `MX25L6445E` | | + |
| ^ | `MX25L6465E` | | + |
| ^ | `MX25L6473E` | | + |
| ^ | `MX25L6473F` | | + |
| ^ | `MX25L6495F` | | + | **no datasheet!**
| Micron/Numonyx/ST | `N25Q032..1E` | | + |
| ^ | `N25Q032..3E` | | + |
| ^ | `N25Q064..1E` | | + |
| ^ | `N25Q064..3E` | | + |
| ^ | `MT25QL512ABB8ESF-0SIT` | | | in Talos II
| XTX | `XT25F16` | | optional |

[flashrom#185]: https://github.com/flashrom/flashrom/issues/185
75 changes: 75 additions & 0 deletions Documentation/heads-and-wp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
## Updating Heads
krystian-hebel marked this conversation as resolved.
Show resolved Hide resolved

Whether Heads is to be updated in full or only in part makes no difference for
the choice of the flash image, it's always the same full flash image.

### Updating Heads after the bootblock has been locked

When the patched `flashrom` detects an attempt to flash a write protected chip,
it prints a warning informing the user about potential danger of proceeding and
suggests to use `--skip-wp-area` parameter to perform the operation.

When `--skip-wp-area` is passed in, the danger warning is still printed, but
the code goes further and creates a custom layout which covers the whole chip
with the exception of its write-protected part. Because there is no way to
combine a loaded layout with a custom one in a meaningful way, the code fails
if chip layout is in use (there are multiple parameters to load it, they are all
treated the same way).

#### Risks of partial updates

Partial updates can produce an unbootable image if old bootblock doesn't work
with a more recent version of `coreboot`. This can be manifested in various
ways ranging from old bootblock not being able to find new romstage to system
booting successfully, but data in `coreboot` tables being mangled or incomplete.

The incompatibilities might happen when switching version of firmware or when
using the same version with a slightly different configuration.

Another thing that can potentially cause trouble is CBFS layout. When bootblock
is part of CBFS, it doesn't necessarily have a fixed address, moreover it can
change location as well if it depends on file size (when bootblock's last byte
must be the last byte of the image). If newer bootblock is smaller such that an
old WP range now covers bootblock and some other file, this file won't be fully
updated due to write-protection, potentially resulting in a corrupt image.
Luckily, when bootblock is the last file it's normally preceded by a
significant amount of empty space, which won't let this situation to occur.

On top of that, last 4 bytes of the image contain offset to the master header of
CBFS. Depending on the coreboot version this offset might be crucial for the
loading of romstage, in which case moving CBFS within the image without updating
of the offset (when it's blocked by WP) can also break booting process.

#### Recovering from a broken state

Since broken flash won't let the system to boot, the way to fix it is to flash
the chip externally by connecting it to a different device. A possible
alternative could be to have a backup flash created beforehand and swapping it
for the broken one.

### Flashing whole Heads image

The function of the hardware protection mechanism (`W#` or `W/` pin of flash
chips) is to lock state of software protection preventing it from being
disabled. After the chip is physically unlocked by changing the state of the
pin, the state of the write protection doesn't change. However, in this state
the protection can be easily turned off programmatically, which is what
`flashrom` tries to do before performing an operation on a chip.

In other words, changing state of the WP pin might be enough to be able to
flash the chip in full. If `flashrom` errors or you don't want to rely on the
automatic behaviour, you can try to explicitly disable the protection by running
`flashrom` like this:

```
flashrom --wp-disable
```

If you need to pass extra parameters to flash your chip (e.g., programmer or
chip name), add them to the above command (order of such parameters shouldn't
matter).

Mind that in `flashrom` the code for disabling protection automatically is
different from the code for processing `--wp-disable`. This means that they can
sometimes produce different results, which is why you should be aware about both
code paths that affect WP.
8 changes: 8 additions & 0 deletions cli_classic.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ static void cli_classic_usage(const char *name)
" --wp-range=<start>,<len> set write protection range (use --wp-range=0,0\n"
" to unprotect the entire flash)\n"
" --wp-region <region> set write protection region\n"
" --skip-wp-area skip over write-protected area\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"
Expand Down Expand Up @@ -472,6 +473,7 @@ int main(int argc, char *argv[])
int flash_name = 0, flash_size = 0;
int enable_wp = 0, disable_wp = 0, print_wp_status = 0;
int set_wp_range = 0, set_wp_region = 0, print_wp_ranges = 0;
int skip_wp_area = 0;
uint32_t wp_start = 0, wp_len = 0;
int read_it = 0, extract_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;
Expand All @@ -491,6 +493,7 @@ int main(int argc, char *argv[])
OPTION_WP_ENABLE,
OPTION_WP_DISABLE,
OPTION_WP_LIST,
OPTION_SKIP_WP_AREA,
OPTION_PROGRESS,
};
int ret = 0;
Expand Down Expand Up @@ -522,6 +525,7 @@ int main(int argc, char *argv[])
{"wp-region", 1, NULL, OPTION_WP_SET_REGION},
{"wp-enable", 0, NULL, OPTION_WP_ENABLE},
{"wp-disable", 0, NULL, OPTION_WP_DISABLE},
{"skip-wp-area", 0, NULL, OPTION_SKIP_WP_AREA},
{"list-supported", 0, NULL, 'L'},
{"list-supported-wiki", 0, NULL, 'z'},
{"programmer", 1, NULL, 'p'},
Expand Down Expand Up @@ -691,6 +695,9 @@ int main(int argc, char *argv[])
case OPTION_WP_DISABLE:
disable_wp = 1;
break;
case OPTION_SKIP_WP_AREA:
skip_wp_area = 1;
break;
case 'L':
cli_classic_validate_singleop(&operation_specified);
list_supported = 1;
Expand Down Expand Up @@ -1073,6 +1080,7 @@ int main(int argc, char *argv[])
#endif
flashrom_flag_set(fill_flash, FLASHROM_FLAG_VERIFY_AFTER_WRITE, !dont_verify_it);
flashrom_flag_set(fill_flash, FLASHROM_FLAG_VERIFY_WHOLE_CHIP, !dont_verify_all);
flashrom_flag_set(fill_flash, FLASHROM_FLAG_SKIP_WP_AREA, skip_wp_area);

/* FIXME: We should issue an unconditional chip reset here. This can be
* done once we have a .reset function in struct flashchip.
Expand Down
4 changes: 4 additions & 0 deletions flashrom.8.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ flashrom \- detect, read, write, verify and erase flash chips
[\fB\-i\fR <image>[:<file>]]]
[\fB\-\-wp\-status\fR] [\fB\-\-wp\-list\fR] [\fB\-\-wp\-enable\fR|\fB\-\-wp\-disable\fR]
[\fB\-\-wp\-range\fR <start>,<length>|\fB\-\-wp\-region\fR <region>]
[\fB\-\-skip\-wp\-area\fR]
[\fB\-n\fR] [\fB\-N\fR] [\fB\-f\fR])]
[\fB\-V\fR[\fBV\fR[\fBV\fR]]] [\fB-o\fR <logfile>] [\fB\-\-progress\fR]

Expand Down Expand Up @@ -315,6 +316,9 @@ This option requires a image layout to be specified, see \fB\-\-layout\fR. The
region must be supported by the flash, see
\fB\-\-wp\-list\fR.
.TP
.B "\-\-skip\-wp-area"
Make flashrom automatically skip over write-protected area.
.TP
.B "\-\-flash\-name"
Prints out the detected flash chip's name.
.TP
Expand Down
90 changes: 88 additions & 2 deletions flashrom.c
Original file line number Diff line number Diff line change
Expand Up @@ -1819,6 +1819,90 @@ static int chip_safety_check(const struct flashctx *flash, int force,
return 0;
}

static int get_wp_range(struct flashctx *flash, size_t *wp_start, size_t *wp_len)
{
struct flashrom_wp_cfg *cfg = NULL;
if (flashrom_wp_cfg_new(&cfg) != FLASHROM_WP_OK) {
msg_cerr("Error: Failed to create WP configuration.\n");
return 1;
}

if (flashrom_wp_read_cfg(cfg, flash) != FLASHROM_WP_OK) {
flashrom_wp_cfg_release(cfg);
msg_cerr("Error: Failed to read WP configuration.\n");
return 1;
}

flashrom_wp_get_range(wp_start, wp_len, cfg);

flashrom_wp_cfg_release(cfg);
return 0;
}

static int add_and_include_region(struct flashrom_layout *layout, size_t start, size_t end, const char *name)
{
if (flashrom_layout_add_region(layout, start, end, name)) {
msg_cerr("Error: Failed to add \"%s\" region to a layout.\n", name);
return 1;
}
if (flashrom_layout_include_region(layout, name)) {
msg_cerr("Error: Failed to include \"%s\" region in a layout.\n", name);
return 1;
}
return 0;
}

static int handle_locked_wp(struct flashctx *flash)
{
size_t wp_start, wp_len;
if (get_wp_range(flash, &wp_start, &wp_len))
return 1;

msg_cwarn("\nWARNING!: Updating only part of the image might render your device unusable if old and\n"
" new parts are not compatible!\n\n");

if (!flashrom_flag_get(flash, FLASHROM_FLAG_SKIP_WP_AREA)) {
msg_cwarn(" If you think you know what you're doing, pass --skip-wp-area to exclude\n"
" protected part of the flash from the operation.\n");
return 1;
}

if (get_layout(flash) != get_default_layout(flash)) {
msg_cerr("Error: Chip layout is non-trivial, can't skip WP area in this case.\n");
return 1;
}

struct flashrom_layout *layout = NULL;
if (flashrom_layout_new(&layout)) {
msg_cerr("Error: Failed to create a new layout.\n");
return 1;
}

size_t wp_end = wp_len == 0 ? wp_start : wp_start + wp_len - 1;
size_t flash_size = flashrom_flash_getsize(flash);

if (wp_start != 0 && add_and_include_region(layout, 0, wp_start - 1, "before WP"))
goto error;

if (flashrom_layout_add_region(layout, wp_start, wp_end, "WP")) {
msg_cerr("Error: Failed to add a region to a layout.\n");
goto error;
}

if (wp_end != flash_size - 1 && add_and_include_region(layout, wp_end + 1, flash_size - 1, "after WP"))
goto error;

flash->wpless_layout = layout;
flashrom_layout_set(flash, layout);

return 0;

error:
if (layout != NULL)
flashrom_layout_release(layout);
return 1;
}

int prepare_flash_access(struct flashctx *const flash,
const bool read_it, const bool write_it,
const bool erase_it, const bool verify_it)
Expand All @@ -1841,8 +1925,10 @@ int prepare_flash_access(struct flashctx *const flash,

/* Given the existence of read locks, we want to unlock for read,
erase and write. */
if (flash->chip->unlock)
flash->chip->unlock(flash);
if (flash->chip->unlock && flash->chip->unlock(flash)) {
if (handle_locked_wp(flash))
return 1;
}

flash->address_high_byte = -1;
flash->in_4ba_mode = false;
Expand Down
2 changes: 2 additions & 0 deletions include/flash.h
Original file line number Diff line number Diff line change
Expand Up @@ -350,11 +350,13 @@ struct flashrom_flashctx {
struct registered_master *mst;
const struct flashrom_layout *layout;
struct flashrom_layout *default_layout;
struct flashrom_layout *wpless_layout;
struct {
bool force;
bool force_boardmismatch;
bool verify_after_write;
bool verify_whole_chip;
bool skip_wp_area;
} flags;
/* We cache the state of the extended address register (highest byte
* of a 4BA for 3BA instructions) and the state of the 4BA mode here.
Expand Down
1 change: 1 addition & 0 deletions include/libflashrom.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ enum flashrom_flag {
FLASHROM_FLAG_FORCE_BOARDMISMATCH,
FLASHROM_FLAG_VERIFY_AFTER_WRITE,
FLASHROM_FLAG_VERIFY_WHOLE_CHIP,
FLASHROM_FLAG_SKIP_WP_AREA,
};

/**
Expand Down
3 changes: 3 additions & 0 deletions libflashrom.c
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ void flashrom_flash_release(struct flashrom_flashctx *const flashctx)
return;

flashrom_layout_release(flashctx->default_layout);
flashrom_layout_release(flashctx->wpless_layout);
free(flashctx->chip);
free(flashctx);
}
Expand All @@ -271,6 +272,7 @@ void flashrom_flag_set(struct flashrom_flashctx *const flashctx,
case FLASHROM_FLAG_FORCE_BOARDMISMATCH: flashctx->flags.force_boardmismatch = value; break;
case FLASHROM_FLAG_VERIFY_AFTER_WRITE: flashctx->flags.verify_after_write = value; break;
case FLASHROM_FLAG_VERIFY_WHOLE_CHIP: flashctx->flags.verify_whole_chip = value; break;
case FLASHROM_FLAG_SKIP_WP_AREA: flashctx->flags.skip_wp_area = value; break;
}
}

Expand All @@ -281,6 +283,7 @@ bool flashrom_flag_get(const struct flashrom_flashctx *const flashctx, const enu
case FLASHROM_FLAG_FORCE_BOARDMISMATCH: return flashctx->flags.force_boardmismatch;
case FLASHROM_FLAG_VERIFY_AFTER_WRITE: return flashctx->flags.verify_after_write;
case FLASHROM_FLAG_VERIFY_WHOLE_CHIP: return flashctx->flags.verify_whole_chip;
case FLASHROM_FLAG_SKIP_WP_AREA: return flashctx->flags.skip_wp_area;
default: return false;
}
}
Expand Down