x86/arch_vm: Allow arch_vm_set_memory_type to override type.

We allow requesting an explicit memory type when calling
map_physical_memory but default to the uncached B_MTR_UC when not given.

When called without an explicitly requested memory type, allow
arch_vm_set_memory_type to modify and return an effective memory type.
When an overlapping range already exists, the effective memory type is
set to the one of the existing mapping. If there is an explicit memory
type request that conflicts with an existing range, or if multiple
overlaps with conflicting types would be produced, the mapping is
disallow (and a panic is triggered under KDEBUG).

This effectively detects and panics when conflicting aliases of physical
memory would be created. This is also useful on an MTRR based setup,
as such overlaps cannot be properly represented.

When using the page attribute table (PAT) to set the memory type on a
per page virtual memory mapping basis, this is needed to prevent
aliasing of the same physical memory with different types. As per the
specs, such aliasing is unsupported and may result in undefined
operations that lead to system failure.

The mechanism is extended to the general arch_vm_set_memory_type as such
aliasing prevention also seems to apply to other architectures (at least
on ARM, aliasing is also strongly discouraged).

Change-Id: I7aaf6ea8415e92e74cd1643b67793a6857619eea
Reviewed-on: https://review.haiku-os.org/c/haiku/+/8339
Tested-by: Commit checker robot <[email protected]>
Reviewed-by: waddlesplash <[email protected]>
This commit is contained in:
Michael Lotz
2024-09-17 20:45:28 +00:00
committed by waddlesplash
parent 5a8a2b5066
commit 8271b4a8bf
9 changed files with 85 additions and 46 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ void arch_vm_aspace_swap(struct VMAddressSpace *from,
bool arch_vm_supports_protection(uint32 protection); bool arch_vm_supports_protection(uint32 protection);
status_t arch_vm_set_memory_type(struct VMArea *area, phys_addr_t physicalBase, status_t arch_vm_set_memory_type(struct VMArea *area, phys_addr_t physicalBase,
uint32 type); uint32 type, uint32 *effectiveType);
void arch_vm_unset_memory_type(struct VMArea *area); void arch_vm_unset_memory_type(struct VMArea *area);
#ifdef __cplusplus #ifdef __cplusplus
+2 -1
View File
@@ -132,7 +132,8 @@ arch_vm_unset_memory_type(VMArea *area)
status_t status_t
arch_vm_set_memory_type(VMArea *area, phys_addr_t physicalBase, uint32 type) arch_vm_set_memory_type(VMArea *area, phys_addr_t physicalBase, uint32 type,
uint32 *effectiveType)
{ {
return B_OK; return B_OK;
} }
+2 -1
View File
@@ -130,7 +130,8 @@ arch_vm_unset_memory_type(VMArea* area)
status_t status_t
arch_vm_set_memory_type(VMArea* area, phys_addr_t physicalBase, uint32 type) arch_vm_set_memory_type(VMArea* area, phys_addr_t physicalBase, uint32 type,
uint32 *effectiveType)
{ {
// Memory type is set in page tables during mapping, // Memory type is set in page tables during mapping,
// no need to do anything more here. // no need to do anything more here.
+2 -1
View File
@@ -131,7 +131,8 @@ arch_vm_unset_memory_type(VMArea *area)
status_t status_t
arch_vm_set_memory_type(VMArea *area, phys_addr_t physicalBase, uint32 type) arch_vm_set_memory_type(VMArea *area, phys_addr_t physicalBase, uint32 type,
uint32 *effectiveType)
{ {
if (type == 0) if (type == 0)
return B_OK; return B_OK;
+2 -1
View File
@@ -170,7 +170,8 @@ arch_vm_unset_memory_type(VMArea *area)
status_t status_t
arch_vm_set_memory_type(VMArea *area, phys_addr_t physicalBase, uint32 type) arch_vm_set_memory_type(VMArea *area, phys_addr_t physicalBase, uint32 type,
uint32 *effectiveType)
{ {
if (type == 0) if (type == 0)
return B_OK; return B_OK;
+2 -1
View File
@@ -378,7 +378,8 @@ arch_vm_unset_memory_type(VMArea *area)
status_t status_t
arch_vm_set_memory_type(VMArea *area, phys_addr_t physicalBase, uint32 type) arch_vm_set_memory_type(VMArea *area, phys_addr_t physicalBase, uint32 type,
uint32 *effectiveType)
{ {
return B_OK; return B_OK;
} }
+2 -1
View File
@@ -113,7 +113,8 @@ arch_vm_unset_memory_type(VMArea *area)
status_t status_t
arch_vm_set_memory_type(VMArea *area, phys_addr_t physicalBase, uint32 type) arch_vm_set_memory_type(VMArea *area, phys_addr_t physicalBase, uint32 type,
uint32 *effectiveType)
{ {
if (type == 0) if (type == 0)
return B_OK; return B_OK;
+64 -35
View File
@@ -130,6 +130,26 @@ set_mtrrs()
static bool static bool
add_used_mtrr(uint64 base, uint64 size, uint32 type) add_used_mtrr(uint64 base, uint64 size, uint32 type)
{ {
switch (type) {
case B_MTR_UC:
type = IA32_MTR_UNCACHED;
break;
case B_MTR_WC:
type = IA32_MTR_WRITE_COMBINING;
break;
case B_MTR_WT:
type = IA32_MTR_WRITE_THROUGH;
break;
case B_MTR_WP:
type = IA32_MTR_WRITE_PROTECTED;
break;
case B_MTR_WB:
type = IA32_MTR_WRITE_BACK;
break;
default:
return false;
}
if (sMemoryTypeRegistersUsed == sMemoryTypeRegisterCount) if (sMemoryTypeRegistersUsed == sMemoryTypeRegisterCount)
return false; return false;
@@ -308,7 +328,7 @@ ensure_temporary_ranges_space(int32 count)
} }
status_t static status_t
update_mtrrs(update_mtrr_info& updateInfo) update_mtrrs(update_mtrr_info& updateInfo)
{ {
// resize the temporary points/ranges arrays, if necessary // resize the temporary points/ranges arrays, if necessary
@@ -320,7 +340,7 @@ update_mtrrs(update_mtrr_info& updateInfo)
int32 pointCount = 0; int32 pointCount = 0;
for (MemoryTypeRangeList::Iterator it = sMemoryTypeRanges.GetIterator(); for (MemoryTypeRangeList::Iterator it = sMemoryTypeRanges.GetIterator();
memory_type_range* range = it.Next();) { memory_type_range* range = it.Next();) {
if (range->type == IA32_MTR_UNCACHED) { if (range->type == B_MTR_UC) {
// Ignore uncacheable ranges below a certain size, if requested. // Ignore uncacheable ranges below a certain size, if requested.
// Since we always enforce uncacheability via the PTE attributes, // Since we always enforce uncacheability via the PTE attributes,
// this is no problem (though not recommended for performance // this is no problem (though not recommended for performance
@@ -437,11 +457,11 @@ update_mtrrs(update_mtrr_info& updateInfo)
rangeList.Add(&ranges[i]); rangeList.Add(&ranges[i]);
static const uint32 kMemoryTypes[] = { static const uint32 kMemoryTypes[] = {
IA32_MTR_UNCACHED, B_MTR_UC,
IA32_MTR_WRITE_COMBINING, B_MTR_WC,
IA32_MTR_WRITE_PROTECTED, B_MTR_WP,
IA32_MTR_WRITE_THROUGH, B_MTR_WT,
IA32_MTR_WRITE_BACK B_MTR_WB
}; };
static const int32 kMemoryTypeCount = sizeof(kMemoryTypes) static const int32 kMemoryTypeCount = sizeof(kMemoryTypes)
/ sizeof(*kMemoryTypes); / sizeof(*kMemoryTypes);
@@ -452,8 +472,7 @@ update_mtrrs(update_mtrr_info& updateInfo)
// Remove uncached and write-through ranges after processing them. This // Remove uncached and write-through ranges after processing them. This
// let's us leverage their intersection property with any other // let's us leverage their intersection property with any other
// respectively write-back ranges. // respectively write-back ranges.
bool removeRanges = type == IA32_MTR_UNCACHED bool removeRanges = type == B_MTR_UC || type == B_MTR_WT;
|| type == IA32_MTR_WRITE_THROUGH;
optimize_memory_ranges(rangeList, type, removeRanges); optimize_memory_ranges(rangeList, type, removeRanges);
} }
@@ -475,7 +494,7 @@ update_mtrrs(update_mtrr_info& updateInfo)
uint32 type = kMemoryTypes[i]; uint32 type = kMemoryTypes[i];
// skip write-back ranges -- that'll be the default type anyway // skip write-back ranges -- that'll be the default type anyway
if (type == IA32_MTR_WRITE_BACK) if (type == B_MTR_WB)
continue; continue;
for (int32 i = 0; i < rangeCount; i++) { for (int32 i = 0; i < rangeCount; i++) {
@@ -493,7 +512,7 @@ update_mtrrs(update_mtrr_info& updateInfo)
} }
status_t static status_t
update_mtrrs() update_mtrrs()
{ {
// Until we know how many MTRRs we have, pretend everything is OK. // Until we know how many MTRRs we have, pretend everything is OK.
@@ -535,37 +554,46 @@ update_mtrrs()
static status_t static status_t
add_memory_type_range(area_id areaID, uint64 base, uint64 size, uint32 type) add_memory_type_range(area_id areaID, uint64 base, uint64 size, uint32 type,
uint32 *effectiveType)
{ {
// translate the type // translate the type
if (type == 0) if (type == 0)
return B_OK; return B_OK;
switch (type) {
case B_MTR_UC:
type = IA32_MTR_UNCACHED;
break;
case B_MTR_WC:
type = IA32_MTR_WRITE_COMBINING;
break;
case B_MTR_WT:
type = IA32_MTR_WRITE_THROUGH;
break;
case B_MTR_WP:
type = IA32_MTR_WRITE_PROTECTED;
break;
case B_MTR_WB:
type = IA32_MTR_WRITE_BACK;
break;
default:
return B_BAD_VALUE;
}
TRACE_MTRR2("add_memory_type_range(%" B_PRId32 ", %#" B_PRIx64 ", %#" TRACE_MTRR2("add_memory_type_range(%" B_PRId32 ", %#" B_PRIx64 ", %#"
B_PRIx64 ", %" B_PRIu32 ")\n", areaID, base, size, type); B_PRIx64 ", %" B_PRIu32 ")\n", areaID, base, size, type);
MutexLocker locker(sMemoryTypeLock); MutexLocker locker(sMemoryTypeLock);
for (MemoryTypeRangeList::Iterator it = sMemoryTypeRanges.GetIterator();
memory_type_range* range = it.Next();) {
if (range->area == areaID || range->type == type
|| base + size <= range->base
|| base >= range->base + range->size) {
continue;
}
if (effectiveType != NULL) {
type = *effectiveType = range->type;
effectiveType = NULL;
dprintf("assuming memory type %" B_PRIx32 " for overlapping %#"
B_PRIx64 ", %#" B_PRIx64 " area %" B_PRId32 " from existing %#"
B_PRIx64 ", %#" B_PRIx64 " area %" B_PRId32 "\n", type,
base, size, areaID, range->base, range->size, range->area);
continue;
}
(KDEBUG ? panic : dprintf)("incompatible overlapping memory %#" B_PRIx64
", %#" B_PRIx64 " type %" B_PRIx32 " area %" B_PRId32
" with existing %#" B_PRIx64 ", %#" B_PRIx64 " type %" B_PRIx32
" area %" B_PRId32 "\n", base, size, type, areaID, range->base,
range->size, range->type, range->area);
return B_BUSY;
}
memory_type_range* range = areaID >= 0 ? find_range(areaID) : NULL; memory_type_range* range = areaID >= 0 ? find_range(areaID) : NULL;
int32 oldRangeType = -1; int32 oldRangeType = -1;
if (range != NULL) { if (range != NULL) {
@@ -699,7 +727,7 @@ arch_vm_init_post_modules(kernel_args *args)
// set the physical memory ranges to write-back mode // set the physical memory ranges to write-back mode
for (uint32 i = 0; i < args->num_physical_memory_ranges; i++) { for (uint32 i = 0; i < args->num_physical_memory_ranges; i++) {
add_memory_type_range(-1, args->physical_memory_range[i].start, add_memory_type_range(-1, args->physical_memory_range[i].start,
args->physical_memory_range[i].size, B_MTR_WB); args->physical_memory_range[i].size, B_MTR_WB, NULL);
} }
return B_OK; return B_OK;
@@ -758,7 +786,8 @@ arch_vm_unset_memory_type(struct VMArea *area)
status_t status_t
arch_vm_set_memory_type(struct VMArea *area, phys_addr_t physicalBase, arch_vm_set_memory_type(struct VMArea *area, phys_addr_t physicalBase,
uint32 type) uint32 type, uint32 *effectiveType)
{ {
return add_memory_type_range(area->id, physicalBase, area->Size(), type); return add_memory_type_range(area->id, physicalBase, area->Size(), type,
effectiveType);
} }
+8 -4
View File
@@ -1948,14 +1948,18 @@ vm_map_physical_memory(team_id team, const char* name, void** _address,
cache->Unlock(); cache->Unlock();
if (status == B_OK) { if (status == B_OK) {
// set requested memory type -- use uncached, if not given // Set requested memory type -- use uncached if not given but allow it
// to be overridden by ranges that may already exist
uint32 memoryType = addressSpec & B_MTR_MASK; uint32 memoryType = addressSpec & B_MTR_MASK;
if (memoryType == 0) bool weak = memoryType == 0;
if (weak)
memoryType = B_MTR_UC; memoryType = B_MTR_UC;
status = arch_vm_set_memory_type(area, physicalAddress, memoryType,
weak ? &memoryType : NULL);
area->SetMemoryType(memoryType); area->SetMemoryType(memoryType);
status = arch_vm_set_memory_type(area, physicalAddress, memoryType);
if (status != B_OK) if (status != B_OK)
delete_area(locker.AddressSpace(), area, false); delete_area(locker.AddressSpace(), area, false);
} }
@@ -5345,7 +5349,7 @@ vm_set_area_memory_type(area_id id, phys_addr_t physicalBase, uint32 type)
map->Unlock(); map->Unlock();
// set the physical memory type // set the physical memory type
status_t error = arch_vm_set_memory_type(area, physicalBase, type); status_t error = arch_vm_set_memory_type(area, physicalBase, type, NULL);
if (error != B_OK) { if (error != B_OK) {
// reset the memory type of the area and the mapped pages // reset the memory type of the area and the mapped pages
map->Lock(); map->Lock();