This patch adds the OTP memory and its controller as part of the Secure Boot Controller (SBC) device model. The OTP memory content is persisted to a file named 'otpmem', which is created if it does not already exist.
Signed-off-by: Kane-Chen-AS <kane_c...@aspeedtech.com> --- hw/misc/aspeed_sbc.c | 304 +++++++++++++++++++++++++++++++++++ include/hw/misc/aspeed_sbc.h | 14 ++ 2 files changed, 318 insertions(+) diff --git a/hw/misc/aspeed_sbc.c b/hw/misc/aspeed_sbc.c index e4a6bd1581..5d77fd45d7 100644 --- a/hw/misc/aspeed_sbc.c +++ b/hw/misc/aspeed_sbc.c @@ -15,9 +15,15 @@ #include "hw/misc/aspeed_sbc.h" #include "qapi/error.h" #include "migration/vmstate.h" +#include "system/block-backend.h" +#include "qobject/qdict.h" #define R_PROT (0x000 / 4) +#define R_CMD (0x004 / 4) +#define R_ADDR (0x010 / 4) #define R_STATUS (0x014 / 4) +#define R_CAMP1 (0x020 / 4) +#define R_CAMP2 (0x024 / 4) #define R_QSR (0x040 / 4) /* R_STATUS */ @@ -41,6 +47,18 @@ #define QSR_RSA_MASK (0x3 << 12) #define QSR_HASH_MASK (0x3 << 10) +#define OTP_FILE_PATH "otpmem" + +#define BLK_VALID(s) \ + do { \ + if (s->blk == NULL) { \ + qemu_log_mask(LOG_GUEST_ERROR, \ + "%s: blk is not initialized\n", \ + __func__); \ + return; \ + } \ + } while (0) + static uint64_t aspeed_sbc_read(void *opaque, hwaddr addr, unsigned int size) { AspeedSBCState *s = ASPEED_SBC(opaque); @@ -57,6 +75,196 @@ static uint64_t aspeed_sbc_read(void *opaque, hwaddr addr, unsigned int size) return s->regs[addr]; } +static void aspeed_sbc_otpmem_read(void *opaque) +{ + AspeedSBCState *s = ASPEED_SBC(opaque); + uint32_t otp_addr, data, otp_offset; + bool is_data = false; + + BLK_VALID(s); + otp_addr = s->regs[R_ADDR]; + if (otp_addr < OTP_DATA_DWORD_COUNT) { + is_data = true; + } else if (otp_addr >= OTP_TOTAL_DWORD_COUNT) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Invalid OTP addr 0x%x\n", + __func__, otp_addr); + return; + } + otp_offset = otp_addr << 2; + + if (blk_pread(s->blk, (int64_t)otp_offset, sizeof(data), &data, 0) < 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to read data 0x%x\n", + __func__, otp_offset); + return; + } + s->regs[R_CAMP1] = data; + + if (is_data) { + if (blk_pread(s->blk, (int64_t)otp_offset + 4, + sizeof(data), &data, 0) < 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to read data 0x%x\n", + __func__, otp_offset); + return; + } + s->regs[R_CAMP2] = data; + } +} + +static bool check_bit_conditions(uint32_t otp_addr, + uint32_t value, uint32_t prog_bit) +{ + uint32_t programed_bits, pass; + bool is_odd = otp_addr & 1; + + if (is_odd) { + programed_bits = ~value & prog_bit; + } else { + programed_bits = value & (~prog_bit); + } + + pass = value ^ (~prog_bit); + + if (programed_bits) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Found programed bits in addr %x\n", + __func__, otp_addr); + for (int i = 0; i < 32; ++i) { + if (programed_bits & (1U << i)) { + qemu_log_mask(LOG_GUEST_ERROR, + " Programed bit %d\n", + i); + } + } + } + + return pass != 0; +} + +static bool program_otp_data(void *opaque, uint32_t otp_addr, + uint32_t prog_bit, uint32_t *value) +{ + AspeedSBCState *s = ASPEED_SBC(opaque); + bool is_odd = otp_addr & 1; + uint32_t otp_offset = otp_addr << 2; + + if (blk_pread(s->blk, (int64_t)otp_offset, + sizeof(uint32_t), value, 0) < 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to read data 0x%x\n", + __func__, otp_offset); + return false; + } + + if (check_bit_conditions(otp_addr, *value, prog_bit) == false) { + return false; + } + + if (is_odd) { + *value &= ~prog_bit; + } else { + *value |= ~prog_bit; + } + + return true; +} + +static void mr_handler(uint32_t otp_addr, uint32_t data) +{ + switch (otp_addr) { + case MODE_REGISTER: + case MODE_REGISTER_A: + case MODE_REGISTER_B: + /* HW behavior, do nothing here */ + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Unsupported address 0x%x\n", + __func__, otp_addr); + return; + } +} + +static void aspeed_sbc_otpmem_write(void *opaque) +{ + AspeedSBCState *s = ASPEED_SBC(opaque); + uint32_t otp_addr, data; + + otp_addr = s->regs[R_ADDR]; + data = s->regs[R_CAMP1]; + + if (otp_addr == 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: ignore write program bit request\n", + __func__); + } else if (otp_addr >= MODE_REGISTER) { + mr_handler(otp_addr, data); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Unhandled OTP write address 0x%x\n", + __func__, otp_addr); + } +} + +static void aspeed_sbc_otpmem_prog(void *opaque) +{ + AspeedSBCState *s = ASPEED_SBC(opaque); + uint32_t otp_addr, value, otp_offset; + + BLK_VALID(s); + otp_addr = s->regs[R_ADDR]; + if (otp_addr >= OTP_TOTAL_DWORD_COUNT) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Invalid OTP addr 0x%x\n", + __func__, otp_addr); + return; + } + + otp_offset = otp_addr << 2; + if (program_otp_data(opaque, otp_addr, + s->regs[R_CAMP1], &value) == false) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to program data 0x%x to 0x%x\n", + __func__, s->regs[R_CAMP1], otp_offset); + return; + } + + if (blk_pwrite(s->blk, (int64_t)otp_offset, + sizeof(value), &value, 0) < 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to write data 0x%x to 0x%x\n", + __func__, value, otp_offset); + } +} + +static void aspeed_sbc_handle_command(void *opaque, uint32_t cmd) +{ + AspeedSBCState *s = ASPEED_SBC(opaque); + + s->regs[R_STATUS] &= ~(OTP_MEM_IDLE | OTP_IDLE); + + switch (cmd) { + case READ_CMD: + aspeed_sbc_otpmem_read(s); + break; + case WRITE_CMD: + aspeed_sbc_otpmem_write(s); + break; + case PROG_CMD: + aspeed_sbc_otpmem_prog(s); + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Unknown command 0x%x\n", + __func__, cmd); + break; + } + + s->regs[R_STATUS] |= (OTP_MEM_IDLE | OTP_IDLE); +} + static void aspeed_sbc_write(void *opaque, hwaddr addr, uint64_t data, unsigned int size) { @@ -78,6 +286,9 @@ static void aspeed_sbc_write(void *opaque, hwaddr addr, uint64_t data, "%s: write to read only register 0x%" HWADDR_PRIx "\n", __func__, addr << 2); return; + case R_CMD: + aspeed_sbc_handle_command(opaque, data); + return; default: break; } @@ -113,6 +324,92 @@ static void aspeed_sbc_reset(DeviceState *dev) } s->regs[R_QSR] = s->signing_settings; + + if (!s->blk) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: blk not initialized!\n", + __func__); + return; + } +} + +static BlockBackend *init_otpmem(int64_t size_bytes) +{ + Error *local_err = NULL; + BlockDriverState *bs = NULL; + BlockBackend *blk = NULL; + bool image_created = false; + QDict *options; + uint32_t i, odd_def = 0xffffffff, even_def = 0, *def; + + if (!g_file_test(OTP_FILE_PATH, G_FILE_TEST_EXISTS)) { + bdrv_img_create(OTP_FILE_PATH, "raw", NULL, NULL, + NULL, size_bytes, 0, true, &local_err); + if (local_err) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to create image %s: %s\n", + __func__, OTP_FILE_PATH, + error_get_pretty(local_err)); + error_free(local_err); + return NULL; + } + image_created = true; + } + + blk = blk_new(qemu_get_aio_context(), + BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE, + 0); + if (!blk) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to create BlockBackend\n", + __func__); + return NULL; + } + + options = qdict_new(); + qdict_put_str(options, "driver", "raw"); + bs = bdrv_open(OTP_FILE_PATH, NULL, options, BDRV_O_RDWR, &local_err); + if (local_err) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to create OTP memory, err = %s\n", + __func__, error_get_pretty(local_err)); + blk_unref(blk); + error_free(local_err); + return NULL; + } + + blk_insert_bs(blk, bs, &local_err); + if (local_err) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to insert OTP memory to SBC, err = %s\n", + __func__, error_get_pretty(local_err)); + bdrv_unref(bs); + blk_unref(blk); + error_free(local_err); + return NULL; + } + bdrv_unref(bs); + + if (image_created) { + /* init otp memory data */ + for (i = 0; i < OTP_TOTAL_DWORD_COUNT; i++) { + if (i & 1) { + def = &odd_def; + } else { + def = &even_def; + } + + if (blk_pwrite(blk, i << 2, sizeof(uint32_t), def, 0) < 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to init OTP memory file\n", + __func__); + blk_unref(blk); + return NULL; + } + } + } + + return blk; } static void aspeed_sbc_realize(DeviceState *dev, Error **errp) @@ -124,6 +421,13 @@ static void aspeed_sbc_realize(DeviceState *dev, Error **errp) TYPE_ASPEED_SBC, 0x1000); sysbus_init_mmio(sbd, &s->iomem); + + s->blk = init_otpmem(OTP_FILE_SIZE); + if (s->blk == NULL) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Failed to attach otpmem\n", + __func__); + } } static const VMStateDescription vmstate_aspeed_sbc = { diff --git a/include/hw/misc/aspeed_sbc.h b/include/hw/misc/aspeed_sbc.h index 405e6782b9..fbdef86a63 100644 --- a/include/hw/misc/aspeed_sbc.h +++ b/include/hw/misc/aspeed_sbc.h @@ -27,6 +27,18 @@ OBJECT_DECLARE_TYPE(AspeedSBCState, AspeedSBCClass, ASPEED_SBC) #define QSR_SHA384 (0x2 << 10) #define QSR_SHA512 (0x3 << 10) +#define READ_CMD (0x23b1e361) +#define WRITE_CMD (0x23b1e362) +#define PROG_CMD (0x23b1e364) + +#define OTP_DATA_DWORD_COUNT (0x800) +#define OTP_TOTAL_DWORD_COUNT (0x1000) +#define OTP_FILE_SIZE (OTP_TOTAL_DWORD_COUNT * sizeof(uint32_t)) + +#define MODE_REGISTER (0x1000) +#define MODE_REGISTER_A (0x3000) +#define MODE_REGISTER_B (0x5000) + struct AspeedSBCState { SysBusDevice parent; @@ -36,6 +48,8 @@ struct AspeedSBCState { MemoryRegion iomem; uint32_t regs[ASPEED_SBC_NR_REGS]; + + BlockBackend *blk; }; struct AspeedSBCClass { -- 2.43.0