ec/system76: Support lockdown based on EC security state
Change-Id: I00701aa1397c24efe6f2d163822968b528f5b915
This commit is contained in:
@@ -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
|
||||
|
@@ -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
|
||||
|
61
src/ec/system76/ec/lockdown.c
Normal file
61
src/ec/system76/ec/lockdown.c
Normal 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, ®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
|
@@ -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));
|
||||
}
|
||||
|
16
src/ec/system76/ec/system76_ec.h
Normal file
16
src/ec/system76/ec/system76_ec.h
Normal 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 */
|
Reference in New Issue
Block a user