ec/system76: Support lockdown based on EC security state

Change-Id: I00701aa1397c24efe6f2d163822968b528f5b915
This commit is contained in:
Jeremy Soller
2023-03-06 15:43:16 -07:00
parent ea1d258dfe
commit 9632ad33b1
5 changed files with 172 additions and 3 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <bootstate.h>
#include <commonlib/region.h>
#include <fmap.h>
#include <spi_flash.h>
#include "system76_ec.h"
static int protect_region_by_name(const char *name)
{
int res;
struct region region;
res = fmap_locate_area(name, &region);
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(),
&region,
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

View File

@@ -3,6 +3,7 @@
#include <arch/io.h>
#include <console/system76_ec.h>
#include <timer.h>
#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));
}

View File

@@ -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 */