summaryrefslogtreecommitdiff
path: root/util/e6400-flash-unlock/e6400_flash_unlock.c
diff options
context:
space:
mode:
authorNicholas Chin <nic.c3.14@gmail.com>2023-10-09 22:36:33 -0600
committerNicholas Chin <nic.c3.14@gmail.com>2023-10-09 23:16:18 -0600
commit724cb39f867de2e1eacc470eb348c2f7bdf82c18 (patch)
treea9125b333dd635dae725c1ffcc242ce14a1bd1f0 /util/e6400-flash-unlock/e6400_flash_unlock.c
parent634aac0b69cbad5e25174c93f29f1ccb3d878194 (diff)
util/e6400-flash-unlock: Update to upstream version
This updates lbmk's copy of e6400-flash-unlock to commit c5567fece479 (README.md: Update with info about broader device support) in my upstream repo. Changes: - Theoretical support for any Dell system that implements that flash descriptor override command. This is done by reading base address registers at runtime instead of hard coding them for specific devices. Tested on the Latitude E6400 and Latitude E6430. - Support for OpenBSD. It compiles, runs, and behaves as expected, though I have not actually tested internally flashing with flashrom yet. It should work though, as the program checks if the descriptor override is set and the BIOS Write Enable is able to be set to 1, which is all that is needed to internal flash. - Integrated changes made in the lbmk copy - Moved operating system accessor implementations to their own file It should be fully functional, though minor formatting and cleanup changes are still planned. Signed-off-by: Nicholas Chin <nic.c3.14@gmail.com>
Diffstat (limited to 'util/e6400-flash-unlock/e6400_flash_unlock.c')
-rw-r--r--util/e6400-flash-unlock/e6400_flash_unlock.c182
1 files changed, 130 insertions, 52 deletions
diff --git a/util/e6400-flash-unlock/e6400_flash_unlock.c b/util/e6400-flash-unlock/e6400_flash_unlock.c
index d18970b..174a1c9 100644
--- a/util/e6400-flash-unlock/e6400_flash_unlock.c
+++ b/util/e6400-flash-unlock/e6400_flash_unlock.c
@@ -1,7 +1,6 @@
/* SPDX-License-Identifier: MIT */
/* SPDX-FileCopyrightText: 2023 Nicholas Chin */
-#include <sys/io.h>
#include <sys/mman.h>
#include <err.h>
@@ -10,67 +9,93 @@
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
+#include <unistd.h>
-enum
-EC_FDO_CMD {
- QUERY = 0,
- SET_OVERRIDE = 2,
- UNSET_OVERRIDE = 3
-};
+#include "accessors.h"
int get_fdo_status(void);
-void ec_fdo_command(enum EC_FDO_CMD arg);
+int check_lpc_decode(void);
+void ec_set_fdo();
void write_ec_reg(uint8_t index, uint8_t data);
void send_ec_cmd(uint8_t cmd);
-void wait_ec(void);
-int get_gbl_smi_en(void);
+int wait_ec(void);
+int check_bios_write_en(void);
int set_gbl_smi_en(int enable);
+int get_gbl_smi_en(void);
#define EC_INDEX 0x910
#define EC_DATA 0x911
-#define PMBASE 0x1000
-#define SMI_EN_REG (PMBASE + 0x30)
+#define EC_ENABLE_FDO 2
-/* Assume this is the same on all vendor BIOS versions */
-#define RCBA 0xfed18000
+#define LPC_DEV PCI_DEV(0, 0x1f, 0)
#define RCBA_MMIO_LEN 0x4000
+
+/* Register offsets */
#define SPIBAR 0x3800
#define HSFS_REG 0x04
+#define SMI_EN_REG 0x30
volatile uint8_t *rcba_mmio;
+uint16_t pmbase;
int
main(int argc, char *argv[])
{
- int devmemfd; (void)argc; (void)argv;
+ int devmemfd;
+ (void)argc;
+ (void)argv;
- if ((ioperm(EC_INDEX, 2, 1) == -1) || (ioperm(SMI_EN_REG, 4, 1) == -1))
+ if (sys_iopl(3) == -1)
err(errno, "Could not access IO ports");
if ((devmemfd = open("/dev/mem", O_RDONLY)) == -1)
err(errno, "/dev/mem");
+ /* Read RCBA and PMBASE from the LPC config registers */
+ long int rcba = pci_read_32(LPC_DEV, 0xf0) & 0xffffc000;
+ pmbase = pci_read_32(LPC_DEV, 0x40) & 0xff80;
+
/* FDO pin-strap status bit is in RCBA mmio space */
rcba_mmio = mmap(0, RCBA_MMIO_LEN, PROT_READ, MAP_SHARED, devmemfd,
- RCBA);
+ rcba);
if (rcba_mmio == MAP_FAILED)
err(errno, "Could not map RCBA");
- if (get_fdo_status() == 1) {
- ec_fdo_command(SET_OVERRIDE);
- printf("Flash Descriptor Override enabled. Shut down now. The "
- "EC will auto-boot the system and set the override.\n"
- "Upon boot, re-run this utility to unlock flash.\n");
- } else if (get_gbl_smi_en()){
- set_gbl_smi_en(0);
- printf("SMIs disabled. Internal flashing should work now.\n"
- "After flashing, re-run this utility to enable SMIs.\n"
- "(shutdown is buggy when SMIs are disabled)\n");
- } else {
- set_gbl_smi_en(1);
- printf("SMIs enabled, you can now shutdown the system.\n");
+ if (get_fdo_status() == 1) { /* Descriptor not overridden */
+ if (check_lpc_decode() == -1)
+ err(errno = ECANCELED, "Can't forward I/O to LPC");
+
+ printf("Sending FDO override command to EC:\n");
+ ec_set_fdo();
+ printf("Flash Descriptor Override enabled.\n"
+ "Shut down (don't reboot) now.\n\n"
+ "The EC may auto-boot on some systems; if not then "
+ "manually power on.\n When the system boots rerun "
+ "this utility to finish unlocking.\n");
+ } else if (check_bios_write_en() == 0) {
+ /* SMI locks in place, try disabling SMIs to bypass them */
+ if (set_gbl_smi_en(0)) {
+ printf("SMIs disabled. Internal flashing should work "
+ "now.\n After flashing, re-run this utility "
+ "to enable SMIs.\n (shutdown is buggy when "
+ "SMIs are disabled)\n");
+ } else {
+ err(errno = ECANCELED, "Could not disable SMIs!");
+ }
+ } else { /* SMI locks not in place or bypassed */
+ if (get_gbl_smi_en()) {
+ /* SMIs are still enabled, assume this is an Exx10
+ * or newer which don't need the SMM bypass */
+ printf("Flash is unlocked.\n"
+ "Internal flashing should work.\n");
+ } else {
+ /* SMIs disabled, assume this is an Exx00 after
+ * unlocking and flashing */
+ set_gbl_smi_en(1);
+ printf("SMIs enabled.\n"
+ "You can now shutdown the system.\n");
+ }
}
-
return errno;
}
@@ -80,60 +105,113 @@ get_fdo_status(void)
return (*(uint16_t*)(rcba_mmio + SPIBAR + HSFS_REG) >> 13) & 1;
}
-/*
- * arg:
- * 0 = Query EC FDO status - TODO
- * 2 = Enable FDO for next boot
- * 3 = Disable FDO for next boot - TODO
- */
+int
+check_lpc_decode(void)
+{
+ /* Check that at a Generic Decode Range Register is set up to
+ * forward I/O ports 0x910 and 0x911 over LPC for the EC */
+ int i = 0;
+ int gen_dec_free = -1;
+ for (; i < 4; i++) {
+ uint32_t reg_val = pci_read_32(LPC_DEV, 0x84 + 4*i);
+ uint16_t base_addr = reg_val & 0xfffc;
+ uint16_t mask = ((reg_val >> 16) & 0xfffc) | 0x3;
+
+ /* Bit 0 is the enable for each decode range. If disabled, note
+ * this register as available to add our own range decode */
+ if ((reg_val & 1) == 0)
+ gen_dec_free = i;
+
+ /* Check if the current range register matches port 0x910.
+ * 0x911 doesn't need to be checked as the LPC bridge only
+ * decodes at the dword level, and thus a check is redundant */
+ if ((0x910 & ~mask) == base_addr) {
+ return 0;
+ }
+ }
+
+ /* No matching range found, try setting a range in a free register */
+ if (gen_dec_free != -1) {
+ /* Set up an I/O decode range from 0x910-0x913 */
+ pci_write_32(LPC_DEV, 0x84 + 4 * gen_dec_free, 0x911);
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
void
-ec_fdo_command(enum EC_FDO_CMD arg)
+ec_set_fdo()
{
- write_ec_reg(0x12, arg);
+ /* EC FDO command arguments for reference:
+ * 0 = Query EC FDO status
+ * 2 = Enable FDO for next boot
+ * 3 = Disable FDO for next boot */
+ write_ec_reg(0x12, EC_ENABLE_FDO);
send_ec_cmd(0xb8);
}
void
write_ec_reg(uint8_t index, uint8_t data)
{
- outb(index, EC_INDEX);
- outb(data, EC_DATA);
+ sys_outb(EC_INDEX, index);
+ sys_outb(EC_DATA, data);
}
void
send_ec_cmd(uint8_t cmd)
{
- outb(0, EC_INDEX);
- outb(cmd, EC_DATA);
- wait_ec();
+ sys_outb(EC_INDEX, 0);
+ sys_outb(EC_DATA, cmd);
+ if (wait_ec() == -1)
+ err(errno = ECANCELED, "Timeout while waiting for EC!");
}
-void
+int
wait_ec(void)
{
uint8_t busy;
+ int timeout = 1000;
do {
- outb(0, EC_INDEX);
- busy = inb(EC_DATA);
- } while (busy);
+ sys_outb(EC_INDEX, 0);
+ busy = sys_inb(EC_DATA);
+ timeout--;
+ usleep(1000);
+ } while (busy && timeout > 0);
+ return timeout > 0 ? 0 : -1;
}
int
-get_gbl_smi_en(void)
+check_bios_write_en(void)
{
- return inl(SMI_EN_REG) & 1;
+ uint8_t bios_cntl = pci_read_32(LPC_DEV, 0xdc) & 0xff;
+ /* Bit 5 = SMM BIOS Write Protect Disable (SMM_BWP)
+ * Bit 1 = BIOS Lock Enable (BLE)
+ * If both are 0, then there's no write protection */
+ if ((bios_cntl & 0x22) == 0)
+ return 1;
+
+ /* SMM protection is enabled, but try enabling writes
+ * anyway in case the vendor SMM code doesn't reset it */
+ pci_write_32(LPC_DEV, 0xdc, bios_cntl | 0x1);
+ return pci_read_32(LPC_DEV, 0xdc) & 0x1;
}
int
set_gbl_smi_en(int enable)
{
- uint32_t smi_en = inl(SMI_EN_REG);
+ uint32_t smi_en = sys_inl(pmbase + SMI_EN_REG);
if (enable) {
smi_en |= 1;
} else {
smi_en &= ~1;
}
- outl(smi_en, SMI_EN_REG);
+ sys_outl(pmbase + SMI_EN_REG, smi_en);
return (get_gbl_smi_en() == enable);
}
+int
+get_gbl_smi_en(void)
+{
+ return sys_inl(pmbase + SMI_EN_REG) & 1;
+}