Skip to content

Commit 6edc82a

Browse files
committed
[ot] hw/opentitan: ot_flash: Add memory protection functionality
Adds the functionality for the flash controller's Memory Protection, for both info and data partitions. For access to info partitions, each page has a separate register `BANKX_INFOY_PAGE_CFG_Z` which controls the permissions for that specific page. For access to data partitions, there are a sequence of 10 `MP_REGION_X` and `MP_REGION_CFG_X` registers which define the size/bounds and permissions of 10 different regions of flash memory respectively. Data MP regions are defined by a base page and a size in base pages, used to calculate their relative bounds. Any data page accesses that do not fall under any of these 10 (enabled) regions will instead use the memory protection permissions of the default region, which is configured through the DEFAULT_REGION register. All MP configuration registers (except the DEFAULT_REGION) can be enabled/disabled. If enabled, they will match and their permissions apply. For data pages, if multiple regions match a given address, the region with the lowest matching index is applied. Implemented MP permissions include read/program/erase. This commit also introduces a `no-protection` property to allow the optional disabling of these MP checks if not needed. Although manual testing suggests low overheads, this avoids the overhead introduced by querying the ranges of all 10 regions for every data word read/progs. Signed-off-by: Alex Jones <[email protected]>
1 parent e7964f6 commit 6edc82a

File tree

1 file changed

+210
-0
lines changed

1 file changed

+210
-0
lines changed

hw/opentitan/ot_flash.c

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,7 @@ struct OtFlashState {
755755
OtFlashStorage flash;
756756

757757
BlockBackend *blk; /* Flash backend */
758+
bool no_mem_prot; /* Flag to disable mem protection features */
758759
};
759760

760761
static void ot_flash_update_irqs(OtFlashState *s)
@@ -923,6 +924,121 @@ static void ot_flash_op_complete(OtFlashState *s)
923924
ot_flash_update_irqs(s);
924925
}
925926

927+
static uint32_t ot_flash_get_info_page_cfg_reg(
928+
unsigned bank, unsigned info_partition, unsigned page)
929+
{
930+
switch (bank) {
931+
case 0u:
932+
switch (info_partition) {
933+
case 0u:
934+
return R_BANK0_INFO0_PAGE_CFG_0 + page;
935+
case 1u:
936+
return R_BANK0_INFO1_PAGE_CFG;
937+
case 2u:
938+
return R_BANK0_INFO2_PAGE_CFG_0 + page;
939+
default:
940+
qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid info partition: %u\n",
941+
__func__, info_partition);
942+
return 0u;
943+
}
944+
case 1u:
945+
switch (info_partition) {
946+
case 0u:
947+
return R_BANK1_INFO0_PAGE_CFG_0 + page;
948+
case 1u:
949+
return R_BANK1_INFO1_PAGE_CFG;
950+
case 2u:
951+
return R_BANK1_INFO2_PAGE_CFG_0 + page;
952+
default:
953+
qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid info partition: %u\n",
954+
__func__, info_partition);
955+
return 0u;
956+
}
957+
default:
958+
qemu_log_mask(LOG_GUEST_ERROR, "%s: invalid bank: %d\n", __func__,
959+
bank);
960+
return 0u;
961+
}
962+
}
963+
964+
static bool
965+
ot_flash_info_page_cfg_op_enabled(OtFlashState *s, uint32_t info_page_cfg_reg)
966+
{
967+
unsigned en_field;
968+
switch (s->op.kind) {
969+
case OP_READ:
970+
en_field = SHARED_FIELD_EX32(s->regs[info_page_cfg_reg],
971+
BANK_INFO_PAGE_CFG_RD_EN);
972+
break;
973+
case OP_PROG:
974+
en_field = SHARED_FIELD_EX32(s->regs[info_page_cfg_reg],
975+
BANK_INFO_PAGE_CFG_PROG_EN);
976+
break;
977+
case OP_ERASE:
978+
en_field = SHARED_FIELD_EX32(s->regs[info_page_cfg_reg],
979+
BANK_INFO_PAGE_CFG_ERASE_EN);
980+
break;
981+
case OP_NONE:
982+
xtrace_ot_flash_error("cannot check mp without operation");
983+
return false;
984+
default:
985+
xtrace_ot_flash_error("unsupported operation?");
986+
return false;
987+
}
988+
return en_field == OT_MULTIBITBOOL4_TRUE;
989+
}
990+
991+
static bool
992+
ot_flash_mp_region_cfg_op_enabled(OtFlashState *s, uint32_t mp_cfg_reg)
993+
{
994+
unsigned en_field;
995+
switch (s->op.kind) {
996+
case OP_READ:
997+
en_field = SHARED_FIELD_EX32(s->regs[mp_cfg_reg], MP_REGION_CFG_RD_EN);
998+
break;
999+
case OP_PROG:
1000+
en_field =
1001+
SHARED_FIELD_EX32(s->regs[mp_cfg_reg], MP_REGION_CFG_PROG_EN);
1002+
break;
1003+
case OP_ERASE:
1004+
en_field =
1005+
SHARED_FIELD_EX32(s->regs[mp_cfg_reg], MP_REGION_CFG_ERASE_EN);
1006+
break;
1007+
case OP_NONE:
1008+
xtrace_ot_flash_error("cannot check mp without operation");
1009+
return false;
1010+
default:
1011+
xtrace_ot_flash_error("unsupported operation?");
1012+
return false;
1013+
}
1014+
return en_field == OT_MULTIBITBOOL4_TRUE;
1015+
}
1016+
1017+
static bool ot_flash_default_region_cfg_op_enabled(OtFlashState *s)
1018+
{
1019+
unsigned en_field;
1020+
switch (s->op.kind) {
1021+
case OP_READ:
1022+
en_field = FIELD_EX32(s->regs[R_DEFAULT_REGION], DEFAULT_REGION, RD_EN);
1023+
break;
1024+
case OP_PROG:
1025+
en_field =
1026+
FIELD_EX32(s->regs[R_DEFAULT_REGION], DEFAULT_REGION, PROG_EN);
1027+
break;
1028+
case OP_ERASE:
1029+
en_field =
1030+
FIELD_EX32(s->regs[R_DEFAULT_REGION], DEFAULT_REGION, ERASE_EN);
1031+
break;
1032+
case OP_NONE:
1033+
xtrace_ot_flash_error("cannot check mp without operation");
1034+
return false;
1035+
default:
1036+
xtrace_ot_flash_error("unsupported operation?");
1037+
return false;
1038+
}
1039+
return en_field == OT_MULTIBITBOOL4_TRUE;
1040+
}
1041+
9261042
static bool ot_flash_can_erase_bank(const OtFlashState *s, unsigned bank)
9271043
{
9281044
switch (bank) {
@@ -987,6 +1103,35 @@ static unsigned ot_flash_next_info_address(OtFlashState *s)
9871103
unsigned address = address_in_bank + bank_offset + info_part_offset;
9881104
trace_ot_flash_info_part(s->op.address, s->op.count, s->op.remaining, bank,
9891105
info_partition, address);
1106+
if (s->no_mem_prot ||
1107+
(s->op.kind == OP_ERASE && s->op.erase_sel == ERASE_SEL_BANK)) {
1108+
return address;
1109+
}
1110+
1111+
/* Check the matching info partition page config register */
1112+
unsigned page = address_in_bank / BYTES_PER_PAGE;
1113+
uint32_t info_page_cfg_reg =
1114+
ot_flash_get_info_page_cfg_reg(bank, info_partition, page);
1115+
if (!info_page_cfg_reg) {
1116+
ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, op_address);
1117+
s->op.failed = true;
1118+
return address;
1119+
}
1120+
if (SHARED_FIELD_EX32(s->regs[info_page_cfg_reg], BANK_INFO_PAGE_CFG_EN) !=
1121+
OT_MULTIBITBOOL4_TRUE) {
1122+
return address; /* page config is disabled; so access is permitted. */
1123+
}
1124+
if (!ot_flash_info_page_cfg_op_enabled(s, info_page_cfg_reg)) {
1125+
qemu_log_mask(LOG_GUEST_ERROR,
1126+
"%s: operation %s on info page %u in partition %u of "
1127+
"bank %u is disabled by page config\n",
1128+
__func__, OP_NAME(s->op.kind), page, bank,
1129+
info_partition);
1130+
ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, op_address);
1131+
s->op.failed = true;
1132+
return address;
1133+
}
1134+
9901135
return address;
9911136
}
9921137

@@ -1008,6 +1153,68 @@ static unsigned ot_flash_next_data_address(OtFlashState *s)
10081153
}
10091154
trace_ot_flash_data_part(s->op.address, s->op.count, s->op.remaining, bank,
10101155
address);
1156+
1157+
if (s->no_mem_prot ||
1158+
(s->op.kind == OP_ERASE && s->op.erase_sel == ERASE_SEL_BANK)) {
1159+
return address;
1160+
}
1161+
1162+
/*
1163+
* Check through the memory protection regions 0->9, and see if the current
1164+
* page falls within a region. If so, and it is enabled, apply its perms.
1165+
* Otherwise apply the default region's permissions. Note that MP region
1166+
* page indexes are cumulative across banks.
1167+
*
1168+
* If any two MP regions overlap, the lower index region has priority.
1169+
*/
1170+
unsigned page = address / BYTES_PER_PAGE;
1171+
bool matching_region_found = false;
1172+
for (unsigned region = 0u; region < NUM_REGIONS; region++) {
1173+
/* Ignore disabled regions */
1174+
unsigned r_region_cfg = R_MP_REGION_CFG_0 + region;
1175+
if (SHARED_FIELD_EX32(s->regs[r_region_cfg], MP_REGION_CFG_EN) !=
1176+
OT_MULTIBITBOOL4_TRUE) {
1177+
continue;
1178+
}
1179+
1180+
/*
1181+
* Check if the current flash word falls in this region.
1182+
* Size is inclusive at the base, but exclusive at (base+size).
1183+
*/
1184+
unsigned r_region = R_MP_REGION_0 + region;
1185+
unsigned region_base_page =
1186+
SHARED_FIELD_EX32(s->regs[r_region], MP_REGION_BASE);
1187+
unsigned region_size =
1188+
SHARED_FIELD_EX32(s->regs[r_region], MP_REGION_SIZE);
1189+
if (page < region_base_page ||
1190+
page >= (region_base_page + region_size)) {
1191+
continue;
1192+
}
1193+
matching_region_found = true;
1194+
1195+
/* Page does fall in this region, so check if enabled for operation. */
1196+
if (!ot_flash_mp_region_cfg_op_enabled(s, r_region_cfg)) {
1197+
qemu_log_mask(LOG_GUEST_ERROR,
1198+
"%s: operation %s on page %u of data partition in "
1199+
"bank %u is disabled by MP region %u\n",
1200+
__func__, OP_NAME(s->op.kind), page, bank, region);
1201+
ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, address);
1202+
s->op.failed = true;
1203+
return address;
1204+
}
1205+
break;
1206+
}
1207+
1208+
/* If page not in any region, apply the default region's permissions. */
1209+
if (!matching_region_found && !ot_flash_default_region_cfg_op_enabled(s)) {
1210+
qemu_log_mask(LOG_GUEST_ERROR,
1211+
"%s: operation %s on page %u of data partition in bank "
1212+
"%u is disabled by default region\n",
1213+
__func__, OP_NAME(s->op.kind), page, bank);
1214+
ot_flash_set_error(s, R_ERR_CODE_MP_ERR_MASK, address);
1215+
s->op.failed = true;
1216+
return address;
1217+
}
10111218
return address;
10121219
}
10131220

@@ -2002,6 +2209,9 @@ static void ot_flash_csrs_write(void *opaque, hwaddr addr, uint64_t val64,
20022209

20032210
static Property ot_flash_properties[] = {
20042211
DEFINE_PROP_DRIVE("drive", OtFlashState, blk),
2212+
/* Optionally disable memory protection, as searching for valid memory
2213+
regions and checking their config can slow down regular operation. */
2214+
DEFINE_PROP_BOOL("no-mem-prot", OtFlashState, no_mem_prot, false),
20052215
DEFINE_PROP_END_OF_LIST(),
20062216
};
20072217

0 commit comments

Comments
 (0)