} cmd[SDMMC_CMD_MAX], acmd[SDMMC_CMD_MAX];
} SDProto;
+#define RPMB_STUFF_LEN 196
+#define RPMB_KEY_MAC_LEN 32
+#define RPMB_DATA_LEN 256 /* one RPMB block is half a sector */
+#define RPMB_NONCE_LEN 16
+
+typedef struct QEMU_PACKED {
+ uint8_t stuff_bytes[RPMB_STUFF_LEN];
+ uint8_t key_mac[RPMB_KEY_MAC_LEN];
+ uint8_t data[RPMB_DATA_LEN];
+ uint8_t nonce[RPMB_NONCE_LEN];
+ uint32_t write_counter;
+ uint16_t address;
+ uint16_t block_count;
+ uint16_t result;
+ uint16_t req_resp;
+} RPMBDataFrame;
+
+QEMU_BUILD_BUG_MSG(sizeof(RPMBDataFrame) != 512,
+ "invalid RPMBDataFrame size");
+
struct SDState {
DeviceState parent_obj;
uint8_t spec_version;
uint64_t boot_part_size;
+ uint64_t rpmb_part_size;
BlockBackend *blk;
uint8_t boot_config;
uint32_t data_offset;
size_t data_size;
uint8_t data[512];
+ struct {
+ uint32_t write_counter;
+ uint8_t key[RPMB_KEY_MAC_LEN];
+ uint8_t key_set;
+ RPMBDataFrame result;
+ } rpmb;
QEMUTimer *ocr_power_timer;
uint8_t dat_lines;
bool cmd_line;
sd->ext_csd[205] = 0x46; /* Min read perf for 4bit@26Mhz */
sd->ext_csd[EXT_CSD_CARD_TYPE] = 0b11;
sd->ext_csd[EXT_CSD_STRUCTURE] = 2;
- sd->ext_csd[EXT_CSD_REV] = 3;
+ sd->ext_csd[EXT_CSD_REV] = 5;
+ sd->ext_csd[EXT_CSD_RPMB_MULT] = sd->rpmb_part_size / (128 * KiB);
+ sd->ext_csd[EXT_CSD_PARTITION_SUPPORT] = 0b111;
/* Mode segment (RW) */
sd->ext_csd[EXT_CSD_PART_CONFIG] = sd->boot_config;
/*
* This requires a disk image that has two boot partitions inserted at the
* beginning of it, followed by an RPMB partition. The size of the boot
- * partitions is the "boot-partition-size" property.
+ * partitions is the "boot-partition-size" property, the one of the RPMB
+ * partition is 'rpmb-partition-size'.
*/
static uint32_t sd_part_offset(SDState *sd)
{
& EXT_CSD_PART_CONFIG_ACC_MASK;
switch (partition_access) {
case EXT_CSD_PART_CONFIG_ACC_DEFAULT:
- return sd->boot_part_size * 2;
+ return sd->boot_part_size * 2 + sd->rpmb_part_size;
case EXT_CSD_PART_CONFIG_ACC_BOOT1:
return 0;
case EXT_CSD_PART_CONFIG_ACC_BOOT2:
return sd->boot_part_size * 1;
+ case EXT_CSD_PART_CONFIG_ACC_RPMB:
+ return sd->boot_part_size * 2;
default:
g_assert_not_reached();
}
}
size = sect << HWBLOCK_SHIFT;
if (sd_is_emmc(sd)) {
- size -= sd->boot_part_size * 2;
+ size -= sd->boot_part_size * 2 + sd->rpmb_part_size;
}
sect = sd_addr_to_wpnum(size) + 1;
},
};
+static bool vmstate_needed_for_rpmb(void *opaque)
+{
+ SDState *sd = opaque;
+
+ return sd->rpmb_part_size > 0;
+}
+
+static const VMStateDescription emmc_rpmb_vmstate = {
+ .name = "sd-card/ext_csd_modes-state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = vmstate_needed_for_rpmb,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(rpmb.result.key_mac, SDState, RPMB_KEY_MAC_LEN),
+ VMSTATE_UINT8_ARRAY(rpmb.result.data, SDState, RPMB_DATA_LEN),
+ VMSTATE_UINT8_ARRAY(rpmb.result.nonce, SDState, RPMB_NONCE_LEN),
+ VMSTATE_UINT32(rpmb.result.write_counter, SDState),
+ VMSTATE_UINT16(rpmb.result.address, SDState),
+ VMSTATE_UINT16(rpmb.result.block_count, SDState),
+ VMSTATE_UINT16(rpmb.result.result, SDState),
+ VMSTATE_UINT16(rpmb.result.req_resp, SDState),
+ VMSTATE_UINT32(rpmb.write_counter, SDState),
+ VMSTATE_UINT8_ARRAY(rpmb.key, SDState, 32),
+ VMSTATE_UINT8(rpmb.key_set, SDState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
static bool vmstate_needed_for_emmc(void *opaque)
{
SDState *sd = opaque;
.subsections = (const VMStateDescription * const []) {
&sd_ocr_vmstate,
&emmc_extcsd_vmstate,
+ &emmc_rpmb_vmstate,
NULL
},
};
}
}
+static void emmc_rpmb_blk_read(SDState *sd, uint64_t addr, uint32_t len)
+{
+ uint16_t resp = lduw_be_p(&sd->rpmb.result.req_resp);
+ uint16_t result = lduw_be_p(&sd->rpmb.result.result);
+ unsigned int curr_block = 0;
+
+ if ((result & ~RPMB_RESULT_COUTER_EXPIRED) == RPMB_RESULT_OK &&
+ resp == RPMB_RESP(RPMB_REQ_AUTH_DATA_READ)) {
+ curr_block = lduw_be_p(&sd->rpmb.result.address);
+ if (sd->rpmb.result.block_count == 0) {
+ stw_be_p(&sd->rpmb.result.block_count, sd->multi_blk_cnt);
+ } else {
+ curr_block += lduw_be_p(&sd->rpmb.result.block_count);
+ curr_block -= sd->multi_blk_cnt;
+ }
+ addr = curr_block * RPMB_DATA_LEN + sd_part_offset(sd);
+ if (blk_pread(sd->blk, addr, RPMB_DATA_LEN,
+ sd->rpmb.result.data, 0) < 0) {
+ error_report("sd_blk_read: read error on host side");
+ memset(sd->rpmb.result.data, 0, sizeof(sd->rpmb.result.data));
+ stw_be_p(&sd->rpmb.result.result,
+ RPMB_RESULT_READ_FAILURE
+ | (result & RPMB_RESULT_COUTER_EXPIRED));
+ }
+ }
+ memcpy(sd->data, &sd->rpmb.result, sizeof(sd->rpmb.result));
+
+ trace_sdcard_rpmb_read_block(resp, curr_block,
+ lduw_be_p(&sd->rpmb.result.result));
+}
+
+static void emmc_rpmb_blk_write(SDState *sd, uint64_t addr, uint32_t len)
+{
+ RPMBDataFrame *frame = (RPMBDataFrame *)sd->data;
+ uint16_t req = lduw_be_p(&frame->req_resp);
+
+ if (req == RPMB_REQ_READ_RESULT) {
+ /* just return the current result register */
+ goto exit;
+ }
+ memset(&sd->rpmb.result, 0, sizeof(sd->rpmb.result));
+ memcpy(sd->rpmb.result.nonce, frame->nonce, sizeof(sd->rpmb.result.nonce));
+ stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_OK);
+ stw_be_p(&sd->rpmb.result.req_resp, RPMB_RESP(req));
+
+ if (!sd->rpmb.key_set && req != RPMB_REQ_PROGRAM_AUTH_KEY) {
+ stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_NO_AUTH_KEY);
+ goto exit;
+ }
+
+ switch (req) {
+ case RPMB_REQ_PROGRAM_AUTH_KEY:
+ if (sd->rpmb.key_set) {
+ stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_WRITE_FAILURE);
+ break;
+ }
+ memcpy(sd->rpmb.key, frame->key_mac, sizeof(sd->rpmb.key));
+ sd->rpmb.key_set = 1;
+ break;
+ case RPMB_REQ_READ_WRITE_COUNTER:
+ stl_be_p(&sd->rpmb.result.write_counter, sd->rpmb.write_counter);
+ break;
+ case RPMB_REQ_AUTH_DATA_WRITE:
+ /* We only support single-block writes so far */
+ if (sd->multi_blk_cnt != 1) {
+ stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_GENERAL_FAILURE);
+ break;
+ }
+ if (sd->rpmb.write_counter == 0xffffffff) {
+ stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_WRITE_FAILURE);
+ break;
+ }
+ if (ldl_be_p(&frame->write_counter) != sd->rpmb.write_counter) {
+ stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_COUNTER_FAILURE);
+ break;
+ }
+ sd->rpmb.result.address = frame->address;
+ addr = lduw_be_p(&frame->address) * RPMB_DATA_LEN + sd_part_offset(sd);
+ if (blk_pwrite(sd->blk, addr, RPMB_DATA_LEN, frame->data, 0) < 0) {
+ error_report("sd_blk_write: write error on host side");
+ stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_WRITE_FAILURE);
+ } else {
+ sd->rpmb.write_counter++;
+ }
+ stl_be_p(&sd->rpmb.result.write_counter, sd->rpmb.write_counter);
+ break;
+ case RPMB_REQ_AUTH_DATA_READ:
+ sd->rpmb.result.address = frame->address;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "RPMB request %d not implemented\n", req);
+ stw_be_p(&sd->rpmb.result.result, RPMB_RESULT_GENERAL_FAILURE);
+ break;
+ }
+exit:
+ if (sd->rpmb.write_counter == 0xffffffff) {
+ stw_be_p(&sd->rpmb.result.result,
+ lduw_be_p(&sd->rpmb.result.result) | RPMB_RESULT_COUTER_EXPIRED);
+ }
+ trace_sdcard_rpmb_write_block(req, lduw_be_p(&sd->rpmb.result.result));
+}
+
static void sd_erase(SDState *sd)
{
uint64_t erase_start = sd->erase_start;
break;
}
+ if (index == EXT_CSD_PART_CONFIG) {
+ uint8_t part = b & EXT_CSD_PART_CONFIG_ACC_MASK;
+
+ if (((part == EXT_CSD_PART_CONFIG_ACC_BOOT1 ||
+ part == EXT_CSD_PART_CONFIG_ACC_BOOT2) && !sd->boot_part_size) ||
+ (part == EXT_CSD_PART_CONFIG_ACC_RPMB && !sd->rpmb_part_size)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "MMC switching to illegal partition\n");
+ sd->card_status |= R_CSR_SWITCH_ERROR_MASK;
+ return;
+ }
+ }
+
trace_sdcard_ext_csd_update(index, sd->ext_csd[index], b);
sd->ext_csd[index] = b;
}
static void sd_write_byte(SDState *sd, uint8_t value)
{
+ unsigned int partition_access;
int i;
if (!sd->blk || !blk_is_inserted(sd->blk)) {
if (sd->data_offset >= sd->blk_len) {
/* TODO: Check CRC before committing */
sd->state = sd_programming_state;
- sd_blk_write(sd, sd->data_start, sd->data_offset);
+ partition_access = sd->ext_csd[EXT_CSD_PART_CONFIG]
+ & EXT_CSD_PART_CONFIG_ACC_MASK;
+ if (partition_access == EXT_CSD_PART_CONFIG_ACC_RPMB) {
+ emmc_rpmb_blk_write(sd, sd->data_start, sd->data_offset);
+ } else {
+ sd_blk_write(sd, sd->data_start, sd->data_offset);
+ }
sd->blk_written++;
sd->data_start += sd->blk_len;
sd->data_offset = 0;
{
/* TODO: Append CRCs */
const uint8_t dummy_byte = 0x00;
+ unsigned int partition_access;
uint8_t ret;
uint32_t io_len;
sd->data_start, io_len)) {
return dummy_byte;
}
- sd_blk_read(sd, sd->data_start, io_len);
+ partition_access = sd->ext_csd[EXT_CSD_PART_CONFIG]
+ & EXT_CSD_PART_CONFIG_ACC_MASK;
+ if (partition_access == EXT_CSD_PART_CONFIG_ACC_RPMB) {
+ emmc_rpmb_blk_read(sd, sd->data_start, io_len);
+ } else {
+ sd_blk_read(sd, sd->data_start, io_len);
+ }
}
ret = sd->data[sd->data_offset ++];
blk_size = blk_getlength(sd->blk);
}
if (blk_size >= 0) {
- blk_size -= sd->boot_part_size * 2;
+ blk_size -= sd->boot_part_size * 2 + sd->rpmb_part_size;
if (blk_size > SDSC_MAX_CAPACITY) {
if (sd_is_emmc(sd) &&
!QEMU_IS_ALIGNED(blk_size, 1 << HWBLOCK_SHIFT)) {
"The boot partition size must be multiples of 128K"
"and not larger than 32640K.\n");
}
+ if (!QEMU_IS_ALIGNED(sd->rpmb_part_size, 128 * KiB) ||
+ sd->rpmb_part_size > 128 * 128 * KiB) {
+ char *size_str = size_to_str(sd->boot_part_size);
+
+ error_setg(errp, "Invalid RPMB partition size: %s", size_str);
+ g_free(size_str);
+ error_append_hint(errp,
+ "The RPMB partition size must be multiples of 128K"
+ "and not larger than 16384K.\n");
+ }
}
static void emmc_realize(DeviceState *dev, Error **errp)
{
SDState *sd = SDMMC_COMMON(dev);
- sd->spec_version = SD_PHY_SPECv3_01_VERS; /* Actually v4.3 */
+ sd->spec_version = SD_PHY_SPECv3_01_VERS; /* Actually v4.5 */
sd_realize(dev, errp);
}
static const Property emmc_properties[] = {
DEFINE_PROP_UINT64("boot-partition-size", SDState, boot_part_size, 0),
DEFINE_PROP_UINT8("boot-config", SDState, boot_config, 0x0),
+ DEFINE_PROP_UINT64("rpmb-partition-size", SDState, rpmb_part_size, 0),
};
static void sdmmc_common_class_init(ObjectClass *klass, const void *data)