diff --git a/src/ec/system76/ec/Kconfig b/src/ec/system76/ec/Kconfig index c1e4caf530..9b05d798e8 100644 --- a/src/ec/system76/ec/Kconfig +++ b/src/ec/system76/ec/Kconfig @@ -18,6 +18,11 @@ config EC_SYSTEM76_EC_DGPU bool default n +config EC_SYSTEM76_EC_LOCKDOWN + depends on EC_SYSTEM76_EC + bool + default n + config EC_SYSTEM76_EC_OLED depends on EC_SYSTEM76_EC bool diff --git a/src/ec/system76/ec/Makefile.inc b/src/ec/system76/ec/Makefile.inc index 110b997e9a..31693bf134 100644 --- a/src/ec/system76/ec/Makefile.inc +++ b/src/ec/system76/ec/Makefile.inc @@ -2,6 +2,7 @@ ifeq ($(CONFIG_EC_SYSTEM76_EC),y) all-y += system76_ec.c +ramstage-$(CONFIG_EC_SYSTEM76_EC_LOCKDOWN) += lockdown.c smm-$(CONFIG_DEBUG_SMI) += system76_ec.c endif diff --git a/src/ec/system76/ec/lockdown.c b/src/ec/system76/ec/lockdown.c new file mode 100644 index 0000000000..d107a9db17 --- /dev/null +++ b/src/ec/system76/ec/lockdown.c @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#include +#include +#include +#include + +#include "system76_ec.h" + +static int protect_region_by_name(const char *name) +{ + int res; + struct region region; + + res = fmap_locate_area(name, ®ion); + if (res < 0) { + printk(BIOS_ERR, "fmap_locate_area '%s' failed: %d\n", name, res); + return res; + } + + res = spi_flash_ctrlr_protect_region( + boot_device_spi_flash(), + ®ion, + WRITE_PROTECT + ); + if (res < 0) { + printk(BIOS_ERR, "spi_flash_ctrlr_protect_region '%s' failed: %d\n", name, res); + return res; + } + + printk(BIOS_INFO, "protected '%s'\n", name); + return 0; +} + +static void lock(void *unused) +{ + uint8_t state = SYSTEM76_EC_SECURITY_STATE_UNLOCK; + if (system76_ec_security_get(&state) < 0) { + printk(BIOS_INFO, "failed to get security state, assuming unlocked\n"); + state = SYSTEM76_EC_SECURITY_STATE_UNLOCK; + } + + printk(BIOS_INFO, "security state: %d\n", state); + if (state != SYSTEM76_EC_SECURITY_STATE_UNLOCK) { + // Protect WP_RO region, which should contain FMAP and COREBOOT + protect_region_by_name("WP_RO"); + // Protect RW_MRC_CACHE region, this must be done after it is written + protect_region_by_name("RW_MRC_CACHE"); + //TODO: protect entire flash except when in SMM? + } +} + +/* + * Keep in sync with mrc_cache.c + */ + +#if CONFIG(MRC_WRITE_NV_LATE) +BOOT_STATE_INIT_ENTRY(BS_OS_RESUME_CHECK, BS_ON_EXIT, lock, NULL); +#else +BOOT_STATE_INIT_ENTRY(BS_DEV_RESOURCES, BS_ON_ENTRY, lock, NULL); +#endif diff --git a/src/ec/system76/ec/system76_ec.c b/src/ec/system76/ec/system76_ec.c index ddcb602d4c..095002ecf4 100644 --- a/src/ec/system76/ec/system76_ec.c +++ b/src/ec/system76/ec/system76_ec.c @@ -3,6 +3,7 @@ #include #include #include +#include "system76_ec.h" // This is the command region for System76 EC firmware. It must be // enabled for LPC in the mainboard. @@ -11,15 +12,22 @@ #define REG_CMD 0 #define REG_RESULT 1 +#define REG_DATA 2 // When command register is 0, command is complete #define CMD_FINISHED 0 // Print command. Registers are unique for each command #define CMD_PRINT 4 -#define CMD_PRINT_REG_FLAGS 2 -#define CMD_PRINT_REG_LEN 3 -#define CMD_PRINT_REG_DATA 4 +#define CMD_PRINT_REG_FLAGS REG_DATA +#define CMD_PRINT_REG_LEN (REG_DATA + 1) +#define CMD_PRINT_REG_DATA (REG_DATA + 2) + +// Get security state command +#define CMD_SECURITY_GET 20 + +// OK result, any other values are errors +#define RESULT_OK 0 static inline uint8_t system76_ec_read(uint8_t addr) { @@ -59,3 +67,81 @@ void system76_ec_print(uint8_t byte) if (byte == '\n' || len >= (SYSTEM76_EC_SIZE - CMD_PRINT_REG_DATA)) system76_ec_flush(); } + +// Issue a command not checking if the console needs to be flushed +// Do not print from this command to avoid EC protocol issues +static int system76_ec_unsafe(uint8_t cmd, uint8_t * data, int length) { + // Error if length is too long + if (length > (SYSTEM76_EC_SIZE - REG_DATA)) { + return -1; + } + + // Error if command is in progress + if (system76_ec_read(REG_CMD) != CMD_FINISHED) { + return -1; + } + + // Write command data + for (int i = 0; i < length; i++) { + system76_ec_write(REG_DATA + i, data[i]); + } + + // Start command + system76_ec_write(REG_CMD, cmd); + + // Wait for command completion, for up to 10 milliseconds, with a + // test period of 1 microsecond + wait_us(10000, system76_ec_read(REG_CMD) == CMD_FINISHED); + + // Error if command did not complete + if (system76_ec_read(REG_CMD) != CMD_FINISHED) { + return -1; + } + + // Read command data + for (int i = 0; i < length; i++) { + data[i] = system76_ec_read(REG_DATA + i); + } + + // Check result + if (system76_ec_read(REG_RESULT) != RESULT_OK) { + return -1; + } + + return 0; +} + +// Wrapper to allow issuing commands while console is being used +// Do not print from this command to avoid EC protocol issues +static int system76_ec_command(uint8_t cmd, uint8_t * data, int length) { + // Error if command is in progress + if (system76_ec_read(REG_CMD) != CMD_FINISHED) { + return -1; + } + + // Flush print buffer if it has data + // Checked for completion by system76_ec_unsafe + if (system76_ec_read(CMD_PRINT_REG_LEN) > 0) { + system76_ec_flush(); + } + + // Run command now that print buffer is flushed + int res = system76_ec_unsafe(cmd, data, length); + if (res < 0) { + return res; + } + + // Clear command data (for future prints) + // Length is checked by system76_ec_unsafe + for (int i = 0; i < length; i++) { + system76_ec_write(REG_DATA + i, 0); + } + + return 0; +} + +// Get security state +int system76_ec_security_get(uint8_t * state) { + *state = 0; + return system76_ec_command(CMD_SECURITY_GET, state, sizeof(uint8_t)); +} diff --git a/src/ec/system76/ec/system76_ec.h b/src/ec/system76/ec/system76_ec.h new file mode 100644 index 0000000000..ea376cf38c --- /dev/null +++ b/src/ec/system76/ec/system76_ec.h @@ -0,0 +1,16 @@ +#ifndef EC_SYSTEM76_EC_H +#define EC_SYSTEM76_EC_H + +// Default value, flashing is prevented, cannot be set with CMD_SECURITY_SET +#define SYSTEM76_EC_SECURITY_STATE_LOCK 0 +// Flashing is allowed, cannot be set with CMD_SECURITY_SET +#define SYSTEM76_EC_SECURITY_STATE_UNLOCK 1 +// Flashing will be prevented on the next reboot +#define SYSTEM76_EC_SECURITY_STATE_PREPARE_LOCK 2 +// Flashing will be allowed on the next reboot +#define SYSTEM76_EC_SECURITY_STATE_PREPARE_UNLOCK 3 + +// Get security state +int system76_ec_security_get(uint8_t * state); + +#endif /* EC_SYSTEM76_EC_H */