util/e6400-flash-unlock: Rename to dell-flash-unlock

This more accurately describes the scope of the utility.

Signed-off-by: Nicholas Chin <nic.c3.14@gmail.com>
This commit is contained in:
Nicholas Chin
2023-10-12 17:57:06 -06:00
parent 42068f7ce1
commit 5d6946c42c
6 changed files with 4 additions and 4 deletions
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2023 Nicholas Chin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2023 Nicholas Chin
CC=cc
CFLAGS=-Wall -Wextra -Werror -O2 -pedantic
ifeq ($(shell uname), OpenBSD)
CFLAGS += -l$(shell uname -p)
endif
SRCS=dell_flash_unlock.c accessors.c
all: $(SRCS) accessors.h
$(CC) $(CFLAGS) $(SRCS) -o dell_flash_unlock
clean:
rm -f dell_flash_unlock
+102
View File
@@ -0,0 +1,102 @@
# Dell Laptop Internal Flashing
This utility allows you to use flashrom's internal programmer to program the
entire BIOS flash chip from software while still running the original Dell
BIOS, which normally restricts software writes to the flash chip. It seems like
this works on any Dell laptop that has an EC similar to the SMSC MEC5035 on the
E6400, which mainly seem to be the Latitude and Precision lines starting from
around 2008 (E6400 era).
## TL;DR
Run `make` to compile the utility, and then run `sudo ./dell_flash_unlock` and
follow the directions it outputs.
## Confirmed supported devices
- Latitude E6400
- Latitude E6410
- Latitude E4310
- Latitude E6430
- Precision M6800
It is likely that any other Latitude/Precision laptops from the same era as
devices specifically mentioned in the above list will work as Dell seems to use
the same ECs in one generation.
## Detailed device specific behavior
- On GM45 era laptops, the expected behavior is that you will run the utility
for the first time, which will tell the EC to set the descriptor override on
the next boot. Then you will need to shut down the system, after which the
system will automatically boot up. You should then re-run the utility to
disable SMM, after which you can run flashrom. Finally, you should run the
utility a third time to reenable SMM so that shutdown works properly
afterwards.
- On 1st Generation Intel Core systems such as the E6410 and newer, run the
utility and shutdown in the same way as the E6400. However, it seems like the
EC no longer automatically boots the system. In this case you should manually
power it on. It also seems that the firmware does not set the BIOS Lock bit
when the descriptor override is set, making the 2nd run after the reboot
technically unnecessary. There is no harm in rerunning it though, as the
utility can detect when the flash is unlocked and perform the correct steps
as necessary.
## How it works
There are several ways the firmware can protect itself from being overwritten.
One way is the Intel Flash Descriptor (IFD) permissions. On Intel systems, the
flash image is divided into several regions such as the IFD itself, Gigabit
Ethernet (GBE) non-volative memory, Management Engine (ME) firmware, Platform
Data (PD), and the BIOS. The IFD contains a section which specifies the
read/write permissions for each SPI controller (such as the host system) and
each region of the flash, which are enforced by the chipset.
On the Latitude E6400, the host has read-only access to the IFD, no access to
the ME region, and read-write access to the PD, GBE, and BIOS regions. In order
for flashrom to write to the entire flash internally, the host needs full
permissions to all of these regions. Since the IFD is read only, we cannot
change these permissions unless we directly access the chip using an external
programmer, which defeats the purpose of internal flashing.
However, Intel chipsets have a pin strap that allows the flash descriptor
permissions to be overridden depending on the value of the pin at power on,
granting RW permissions to all regions. On the ICH9M chipset on the E6400, this
pin is HDA\_DOCK\_EN/GPIO33, which will enable the override if it is sampled
low. This pin happens to be connected to a GPIO controlled by the Embedded
Controller (EC), a small microcontroller on the board which handles things like
the keyboard, touchpad, LEDs, and other system level tasks. Software can send a
certain command to the EC, which tells it to pull GPIO33 low on the next boot.
Although we now have full access according to the IFD permissions, we still
cannot flash the whole chip, due to another protection the firmware uses.
Before software can update the BIOS, it must change the BIOS Write Enable
(BIOSWE) bit in the chipset from 0 to 1. However, if the BIOS Lock Enable (BLE)
bit is also set to 1, then changing the BIOSWE bit triggers a System Management
Interrupt (SMI). This causes the processor to enter System Management Mode
(SMM), a highly privileged x86 execution state which operates transparently to
the operating system. The code that SMM runs is provided by the BIOS, which
checks the BIOSWE bit and sets it back to 0 before returning control to the OS.
This feature is intended to only allow SMM code to update the system firmware.
As the switch to SMM suspends the execution of the OS, it appears to the OS
that the BIOSWE bit was never set to 1. Unfortunately, the BLE bit cannot be
set back to 0 once it is set to 1, so this functionality cannot be disabled
after it is first enabled by the BIOS.
Older versions of the E6400 BIOS did not set the BLE bit, allowing flashrom to
flash the entire flash chip internally after only setting the descriptor
override. However, more recent versions do set it, so we may have hit a dead
end unless we force downgrade to an older version (though there is a more
convenient method, as we are about to see).
What if there was a way to sidestep the BIOS Lock entirely? As it turns out,
there is, and it's called the Global SMI Enable (GBL\_SMI\_EN) bit. If it's set
to 1, then the chipset will generate SMIs, such as when we change BIOSWE with
BLE set. If it's 0, then no SMI will be generated, even with the BLE bit set.
On the E6400, GBL\_SMI\_EN is set to 1, and it can be changed back to 0, unlike
the BLE bit. But there still might be one bit in the way, the SMI\_LOCK bit,
which prevents modifications to GBL\_SMI\_EN when SMI\_LOCK is 1. Like the BLE
bit, it cannot be changed back to 0 once it set to 1. But we are in luck, as
the vendor E6400 BIOS leaves SMI\_LOCK unset at 0, allowing us to clear
GBL\_SMI\_EN and disable SMIs, bypassing the BIOS Lock protections.
There are other possible protection mechanisms that the firmware can utilize,
such as Protected Range Register settings, which apply access permissions to
address ranges of the flash, similar to the IFD. However, the E6400 vendor
firmware does not utilize these, so they will not be discussed.
+91
View File
@@ -0,0 +1,91 @@
/* SPDX-License-Identifier: MIT */
/* SPDX-FileCopyrightText: 2023 Nicholas Chin */
#if defined(__linux__)
#include <sys/io.h>
#endif
#if defined(__OpenBSD__)
#include <machine/sysarch.h>
#include <sys/types.h>
#if defined(__amd64__)
#include <amd64/pio.h>
#elif defined(__i386__)
#include <i386/pio.h>
#endif /* __i386__ */
#endif /* __OpenBSD__ */
#include <errno.h>
#include "accessors.h"
uint32_t
pci_read_32(uint32_t dev, uint8_t reg)
{
sys_outl(PCI_CFG_ADDR, dev | reg);
return sys_inl(PCI_CFG_DATA);
}
void
pci_write_32(uint32_t dev, uint8_t reg, uint32_t value)
{
sys_outl(PCI_CFG_ADDR, dev | reg);
sys_outl(PCI_CFG_DATA, value);
}
void
sys_outb(unsigned int port, uint8_t data)
{
#if defined(__linux__)
outb(data, port);
#endif
#if defined(__OpenBSD__)
outb(port, data);
#endif
}
void
sys_outl(unsigned int port, uint32_t data)
{
#if defined(__linux__)
outl(data, port);
#endif
#if defined(__OpenBSD__)
outl(port, data);
#endif
}
uint8_t
sys_inb(unsigned int port)
{
#if defined(__linux__) || defined (__OpenBSD__)
return inb(port);
#endif
return 0;
}
uint32_t
sys_inl(unsigned int port)
{
#if defined(__linux__) || defined (__OpenBSD__)
return inl(port);
#endif
return 0;
}
int
sys_iopl(int level)
{
#if defined(__linux__)
return iopl(level);
#endif
#if defined(__OpenBSD__)
#if defined(__i386__)
return i386_iopl(level);
#elif defined(__amd64__)
return amd64_iopl(level);
#endif /* __amd64__ */
#endif /* __OpenBSD__ */
errno = ENOSYS;
return -1;
}
+17
View File
@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: MIT */
/* SPDX-FileCopyrightText: 2023 Nicholas Chin */
#include <stdint.h>
#define PCI_CFG_ADDR 0xcf8
#define PCI_CFG_DATA 0xcfc
#define PCI_DEV(bus, dev, func) (1u << 31 | bus << 16 | dev << 11 | func << 8)
uint32_t pci_read_32(uint32_t dev, uint8_t reg);
void pci_write_32(uint32_t dev, uint8_t reg, uint32_t value);
int sys_iopl(int level);
void sys_outb(unsigned int port, uint8_t data);
void sys_outl(unsigned int port, uint32_t data);
uint8_t sys_inb(unsigned int port);
uint32_t sys_inl(unsigned int port);
+217
View File
@@ -0,0 +1,217 @@
/* SPDX-License-Identifier: MIT */
/* SPDX-FileCopyrightText: 2023 Nicholas Chin */
#include <sys/mman.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "accessors.h"
int get_fdo_status(void);
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);
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 EC_ENABLE_FDO 2
#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;
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);
if (rcba_mmio == MAP_FAILED)
err(errno, "Could not map RCBA");
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;
}
int
get_fdo_status(void)
{
return (*(uint16_t*)(rcba_mmio + SPIBAR + HSFS_REG) >> 13) & 1;
}
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_set_fdo()
{
/* 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)
{
sys_outb(EC_INDEX, index);
sys_outb(EC_DATA, data);
}
void
send_ec_cmd(uint8_t cmd)
{
sys_outb(EC_INDEX, 0);
sys_outb(EC_DATA, cmd);
if (wait_ec() == -1)
err(errno = ECANCELED, "Timeout while waiting for EC!");
}
int
wait_ec(void)
{
uint8_t busy;
int timeout = 1000;
do {
sys_outb(EC_INDEX, 0);
busy = sys_inb(EC_DATA);
timeout--;
usleep(1000);
} while (busy && timeout > 0);
return timeout > 0 ? 0 : -1;
}
int
check_bios_write_en(void)
{
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 = sys_inl(pmbase + SMI_EN_REG);
if (enable) {
smi_en |= 1;
} else {
smi_en &= ~1;
}
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;
}