309 lines
9.5 KiB
C
309 lines
9.5 KiB
C
/*
|
|
* ESP32 eFuse emulation
|
|
*
|
|
* Copyright (c) 2019 Espressif Systems (Shanghai) Co. Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 or
|
|
* (at your option) any later version.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/module.h"
|
|
#include "qapi/error.h"
|
|
#include "qemu/error-report.h"
|
|
#include "sysemu/sysemu.h"
|
|
#include "chardev/char-fe.h"
|
|
#include "hw/registerfields.h"
|
|
#include "hw/sysbus.h"
|
|
#include "hw/irq.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "hw/qdev-properties-system.h"
|
|
#include "hw/nvram/esp32_efuse.h"
|
|
|
|
static void esp32_efuse_read_op(Esp32EfuseState *s);
|
|
static void esp32_efuse_program_op(Esp32EfuseState *s);
|
|
static void esp32_efuse_update_irq(Esp32EfuseState *s);
|
|
static void esp32_efuse_op_timer_start(Esp32EfuseState *s);
|
|
|
|
static uint64_t esp32_efuse_read(void *opaque, hwaddr addr, unsigned int size)
|
|
{
|
|
Esp32EfuseState *s = ESP32_EFUSE(opaque);
|
|
uint64_t r = 0;
|
|
int idx;
|
|
switch (addr) {
|
|
case A_EFUSE_BLK0_RDATA0 ... A_EFUSE_BLK0_WDATA0 - 4:
|
|
idx = (addr - A_EFUSE_BLK0_RDATA0) / 4;
|
|
r = s->efuse_rd.blk0[idx] & ~(s->efuse_rd_dis.blk0[idx]);
|
|
break;
|
|
case A_EFUSE_BLK0_WDATA0 ... A_EFUSE_BLK1_RDATA0 - 4:
|
|
r = s->efuse_wr.blk0[(addr - A_EFUSE_BLK0_WDATA0) / 4];
|
|
break;
|
|
case A_EFUSE_BLK1_RDATA0 ... A_EFUSE_BLK2_RDATA0 - 4:
|
|
idx = (addr - A_EFUSE_BLK1_RDATA0) / 4;
|
|
r = s->efuse_rd.blk1[idx] & ~(s->efuse_rd_dis.blk1[idx]);
|
|
break;
|
|
case A_EFUSE_BLK2_RDATA0 ... A_EFUSE_BLK3_RDATA0 - 4:
|
|
idx = (addr - A_EFUSE_BLK2_RDATA0) / 4;
|
|
r = s->efuse_rd.blk2[idx] & ~(s->efuse_rd_dis.blk2[idx]);
|
|
break;
|
|
case A_EFUSE_BLK3_RDATA0 ... A_EFUSE_BLK1_WDATA0 - 4:
|
|
idx = (addr - A_EFUSE_BLK3_RDATA0) / 4;
|
|
r = s->efuse_rd.blk3[idx] & ~(s->efuse_rd_dis.blk3[idx]);
|
|
break;
|
|
case A_EFUSE_BLK1_WDATA0 ... A_EFUSE_BLK2_WDATA0 - 4:
|
|
r = s->efuse_wr.blk1[(addr - A_EFUSE_BLK1_WDATA0) / 4];
|
|
break;
|
|
case A_EFUSE_BLK2_WDATA0 ... A_EFUSE_BLK3_WDATA0 - 4:
|
|
r = s->efuse_wr.blk2[(addr - A_EFUSE_BLK2_WDATA0) / 4];
|
|
break;
|
|
case A_EFUSE_BLK3_WDATA0 ... A_EFUSE_CLK - 4:
|
|
r = s->efuse_wr.blk3[(addr - A_EFUSE_BLK3_WDATA0) / 4];
|
|
break;
|
|
case A_EFUSE_CLK:
|
|
r = s->clk_reg;
|
|
break;
|
|
case A_EFUSE_CONF:
|
|
r = s->conf_reg;
|
|
break;
|
|
case A_EFUSE_CMD:
|
|
r = s->cmd_reg;
|
|
break;
|
|
case A_EFUSE_STATUS:
|
|
r = 0;
|
|
break;
|
|
case A_EFUSE_DAC_CONF:
|
|
r = s->dac_conf_reg;
|
|
break;
|
|
case A_EFUSE_INT_RAW:
|
|
r = s->int_raw_reg;
|
|
break;
|
|
case A_EFUSE_INT_ST:
|
|
r = s->int_st_reg;
|
|
break;
|
|
case A_EFUSE_INT_ENA:
|
|
r = s->int_ena_reg;
|
|
break;
|
|
case A_EFUSE_DEC_STATUS:
|
|
r = 0;
|
|
break;
|
|
case A_EFUSE_DATE:
|
|
r = 0x16042600;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static void esp32_efuse_write(void *opaque, hwaddr addr,
|
|
uint64_t value, unsigned int size)
|
|
{
|
|
Esp32EfuseState *s = ESP32_EFUSE(opaque);
|
|
switch (addr) {
|
|
case A_EFUSE_CLK:
|
|
s->clk_reg = value;
|
|
break;
|
|
case A_EFUSE_CONF:
|
|
s->conf_reg = value;
|
|
break;
|
|
case A_EFUSE_CMD:
|
|
if ((value & EFUSE_READ) && s->conf_reg == EFUSE_READ_OP_CODE) {
|
|
esp32_efuse_read_op(s);
|
|
}
|
|
if ((value & EFUSE_PGM) && s->conf_reg == EFUSE_PGM_OP_CODE) {
|
|
esp32_efuse_program_op(s);
|
|
}
|
|
break;
|
|
case A_EFUSE_DAC_CONF:
|
|
s->dac_conf_reg = value;
|
|
break;
|
|
case A_EFUSE_INT_ENA:
|
|
s->int_ena_reg = value;
|
|
esp32_efuse_update_irq(s);
|
|
break;
|
|
case A_EFUSE_INT_CLR:
|
|
s->int_raw_reg &= ~value;
|
|
esp32_efuse_update_irq(s);
|
|
break;
|
|
case A_EFUSE_BLK0_WDATA0 ... A_EFUSE_BLK1_RDATA0 - 4:
|
|
s->efuse_wr.blk0[(addr - A_EFUSE_BLK0_WDATA0) / 4] = value;
|
|
break;
|
|
case A_EFUSE_BLK1_WDATA0 ... A_EFUSE_BLK2_WDATA0 - 4:
|
|
s->efuse_wr.blk1[(addr - A_EFUSE_BLK1_WDATA0) / 4] = value;
|
|
break;
|
|
case A_EFUSE_BLK2_WDATA0 ... A_EFUSE_BLK3_WDATA0 - 4:
|
|
s->efuse_wr.blk2[(addr - A_EFUSE_BLK2_WDATA0) / 4] = value;
|
|
break;
|
|
case A_EFUSE_BLK3_WDATA0 ... A_EFUSE_CLK - 4:
|
|
s->efuse_wr.blk3[(addr - A_EFUSE_BLK3_WDATA0) / 4] = value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define APPLY_DIS(rdwr_, ctrl_field_, dest_field_) \
|
|
if (s->efuse_ ## rdwr_ .blk0_d0.ctrl_field_) { \
|
|
memset(&s->efuse_ ## rdwr_ ## _dis.dest_field_, 0xff, sizeof(s->efuse_ ## rdwr_ ## _dis.dest_field_)); \
|
|
}
|
|
|
|
#define APPLY_DIS_FIELD(rdwr_, ctrl_field_, dest_field_) \
|
|
if (s->efuse_ ## rdwr_ .blk0_d0.ctrl_field_) { \
|
|
s->efuse_ ## rdwr_ ## _dis.dest_field_ = 0; \
|
|
s->efuse_ ## rdwr_ ## _dis.dest_field_ -= 1; \
|
|
}
|
|
|
|
|
|
static void esp32_efuse_read_op(Esp32EfuseState *s)
|
|
{
|
|
s->cmd_reg = EFUSE_READ;
|
|
if (s->blk != NULL) {
|
|
int ret = blk_pread(s->blk, 0, sizeof(s->efuse_rd), &s->efuse_rd, 0);
|
|
if (ret < 0) {
|
|
error_report("%s: failed to read the block device (%d)", __func__, ret);
|
|
return;
|
|
}
|
|
}
|
|
|
|
memset(&s->efuse_rd_dis, 0, sizeof(s->efuse_rd_dis));
|
|
memset(&s->efuse_wr_dis, 0, sizeof(s->efuse_wr_dis));
|
|
|
|
APPLY_DIS(rd, rd_dis_blk1, blk1);
|
|
APPLY_DIS(rd, rd_dis_blk2, blk2);
|
|
APPLY_DIS(rd, rd_dis_blk3, blk3);
|
|
APPLY_DIS_FIELD(rd, rd_dis_blk0_partial, blk0_d5.flash_crypt_config);
|
|
APPLY_DIS_FIELD(rd, rd_dis_blk0_partial, blk0_d6.coding_scheme);
|
|
APPLY_DIS_FIELD(rd, rd_dis_blk0_partial, blk0_d6.key_status);
|
|
|
|
APPLY_DIS(wr, wr_dis_blk1, blk1);
|
|
APPLY_DIS(wr, wr_dis_blk2, blk2);
|
|
APPLY_DIS(wr, wr_dis_blk3, blk3);
|
|
|
|
/* Other wr_dis bits are not emulated, but can be handled here if necessary */
|
|
|
|
esp32_efuse_op_timer_start(s);
|
|
qemu_irq_pulse(s->efuse_update_gpio);
|
|
}
|
|
|
|
static void esp32_efuse_program_op(Esp32EfuseState *s)
|
|
{
|
|
s->cmd_reg = EFUSE_PGM;
|
|
|
|
Esp32EfuseRegs result;
|
|
uint32_t* dst = (uint32_t*) &result;
|
|
uint32_t* rd = (uint32_t*) &s->efuse_rd;
|
|
uint32_t* wr = (uint32_t*) &s->efuse_wr;
|
|
uint32_t* wr_dis = (uint32_t*) &s->efuse_wr_dis;
|
|
for (int i = 0; i < sizeof(result) / sizeof(uint32_t); ++i) {
|
|
uint32_t wr_word = wr[i];
|
|
uint32_t wr_dis_word = wr_dis[i];
|
|
uint32_t rd_word = rd[i];
|
|
dst[i] = (wr_word & (~wr_dis_word)) | rd_word;
|
|
}
|
|
|
|
if (s->blk != NULL) {
|
|
int ret = blk_pwrite(s->blk, 0, sizeof(result), &result, 0);
|
|
if (ret < 0) {
|
|
error_report("%s: failed to write to block device (%d)", __func__, ret);
|
|
}
|
|
}
|
|
|
|
esp32_efuse_op_timer_start(s);
|
|
}
|
|
|
|
static void esp32_efuse_update_irq(Esp32EfuseState *s)
|
|
{
|
|
s->int_st_reg = s->int_ena_reg & s->int_raw_reg;
|
|
int level = s->int_st_reg != 0;
|
|
qemu_set_irq(s->irq, level);
|
|
}
|
|
|
|
static void esp32_efuse_op_timer_start(Esp32EfuseState *s)
|
|
{
|
|
uint64_t ns_now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
|
uint64_t interval_ns = 100000000; /* 10 ms, make this depend on EFUSE_CLK register */
|
|
timer_mod_anticipate_ns(&s->op_timer, ns_now + interval_ns);
|
|
}
|
|
|
|
static void esp32_efuse_timer_cb(void *opaque)
|
|
{
|
|
Esp32EfuseState *s = ESP32_EFUSE(opaque);
|
|
uint32_t cmd = s->cmd_reg;
|
|
s->cmd_reg = 0;
|
|
s->int_raw_reg |= cmd;
|
|
esp32_efuse_update_irq(s);
|
|
}
|
|
|
|
static const MemoryRegionOps esp32_efuse_ops = {
|
|
.read = esp32_efuse_read,
|
|
.write = esp32_efuse_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
};
|
|
|
|
static void esp32_efuse_reset_hold(Object *obj, ResetType type)
|
|
{
|
|
Esp32EfuseState *s = ESP32_EFUSE(obj);
|
|
esp32_efuse_read_op(s);
|
|
}
|
|
|
|
static void esp32_efuse_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
Esp32EfuseState *s = ESP32_EFUSE(dev);
|
|
if (s->blk != NULL) {
|
|
if (!blk_supports_write_perm(s->blk)) {
|
|
error_setg(errp, "%s: block device is not writeable", __func__);
|
|
return;
|
|
}
|
|
uint64_t perm = BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE;
|
|
int ret = blk_set_perm(s->blk, perm, BLK_PERM_ALL, NULL);
|
|
if (ret != 0) {
|
|
error_setg(errp, "%s: failed to set permission (%d)", __func__, ret);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void esp32_efuse_init(Object *obj)
|
|
{
|
|
Esp32EfuseState *s = ESP32_EFUSE(obj);
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
|
|
|
memory_region_init_io(&s->iomem, obj, &esp32_efuse_ops, s,
|
|
TYPE_ESP32_EFUSE, A_EFUSE_DATE + 4);
|
|
sysbus_init_mmio(sbd, &s->iomem);
|
|
sysbus_init_irq(sbd, &s->irq);
|
|
|
|
timer_init_ns(&s->op_timer, QEMU_CLOCK_VIRTUAL, esp32_efuse_timer_cb, s);
|
|
qdev_init_gpio_out_named(DEVICE(sbd), &s->efuse_update_gpio, ESP32_EFUSE_UPDATE_GPIO, 1);
|
|
|
|
memset(&s->efuse_rd, 0, sizeof(s->efuse_rd));
|
|
memset(&s->efuse_wr, 0, sizeof(s->efuse_wr));
|
|
}
|
|
|
|
static Property esp32_efuse_properties[] = {
|
|
DEFINE_PROP_DRIVE("drive", Esp32EfuseState, blk),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void esp32_efuse_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
ResettableClass *rc = RESETTABLE_CLASS(klass);
|
|
|
|
rc->phases.hold = esp32_efuse_reset_hold;
|
|
dc->realize = esp32_efuse_realize;
|
|
device_class_set_props(dc, esp32_efuse_properties);
|
|
}
|
|
|
|
static const TypeInfo esp32_efuse_info = {
|
|
.name = TYPE_ESP32_EFUSE,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(Esp32EfuseState),
|
|
.instance_init = esp32_efuse_init,
|
|
.class_init = esp32_efuse_class_init
|
|
};
|
|
|
|
static void esp32_efuse_register_types(void)
|
|
{
|
|
type_register_static(&esp32_efuse_info);
|
|
}
|
|
|
|
type_init(esp32_efuse_register_types)
|