arch/arm64/mmu: fix page table reference counting

Existing code confused table usage and table reference counts together.
This obviously doesn't work. A table with one reference to it and one
populated PTE is not the same as a table with 2 references to it and
no PTe in use.

So split the two concepts and adjust the code accordingly. A page needs
to have its PTE usage count drop to zero before the last reference is
released. When both counts are 0 then the page is free.

Signed-off-by: Nicolas Pitre <npitre@baylibre.com>
This commit is contained in:
Nicolas Pitre 2024-05-10 17:57:35 -04:00 committed by Anas Nashif
parent 0a2e4d1b5f
commit f7e11649fd

View file

@ -28,9 +28,13 @@ LOG_MODULE_DECLARE(os, CONFIG_KERNEL_LOG_LEVEL);
static uint64_t xlat_tables[CONFIG_MAX_XLAT_TABLES * Ln_XLAT_NUM_ENTRIES] static uint64_t xlat_tables[CONFIG_MAX_XLAT_TABLES * Ln_XLAT_NUM_ENTRIES]
__aligned(Ln_XLAT_NUM_ENTRIES * sizeof(uint64_t)); __aligned(Ln_XLAT_NUM_ENTRIES * sizeof(uint64_t));
static uint16_t xlat_use_count[CONFIG_MAX_XLAT_TABLES]; static int xlat_use_count[CONFIG_MAX_XLAT_TABLES];
static struct k_spinlock xlat_lock; static struct k_spinlock xlat_lock;
/* Usage count value range */
#define XLAT_PTE_COUNT_MASK GENMASK(15, 0)
#define XLAT_REF_COUNT_UNIT BIT(16)
/* Returns a reference to a free table */ /* Returns a reference to a free table */
static uint64_t *new_table(void) static uint64_t *new_table(void)
{ {
@ -39,9 +43,9 @@ static uint64_t *new_table(void)
/* Look for a free table. */ /* Look for a free table. */
for (i = 0U; i < CONFIG_MAX_XLAT_TABLES; i++) { for (i = 0U; i < CONFIG_MAX_XLAT_TABLES; i++) {
if (xlat_use_count[i] == 0U) { if (xlat_use_count[i] == 0) {
table = &xlat_tables[i * Ln_XLAT_NUM_ENTRIES]; table = &xlat_tables[i * Ln_XLAT_NUM_ENTRIES];
xlat_use_count[i] = 1U; xlat_use_count[i] = XLAT_REF_COUNT_UNIT;
MMU_DEBUG("allocating table [%d]%p\n", i, table); MMU_DEBUG("allocating table [%d]%p\n", i, table);
return table; return table;
} }
@ -59,29 +63,43 @@ static inline unsigned int table_index(uint64_t *pte)
return i; return i;
} }
/* Makes a table free for reuse. */
static void free_table(uint64_t *table)
{
unsigned int i = table_index(table);
MMU_DEBUG("freeing table [%d]%p\n", i, table);
__ASSERT(xlat_use_count[i] == 1U, "table still in use");
xlat_use_count[i] = 0U;
}
/* Adjusts usage count and returns current count. */ /* Adjusts usage count and returns current count. */
static int table_usage(uint64_t *table, int adjustment) static int table_usage(uint64_t *table, int adjustment)
{ {
unsigned int i = table_index(table); unsigned int i = table_index(table);
int prev_count = xlat_use_count[i];
int new_count = prev_count + adjustment;
xlat_use_count[i] += adjustment; if (IS_ENABLED(DUMP_PTE) || new_count == 0) {
__ASSERT(xlat_use_count[i] > 0, "usage count underflow"); MMU_DEBUG("table [%d]%p: usage %#x -> %#x\n", i, table, prev_count, new_count);
return xlat_use_count[i]; }
__ASSERT(new_count >= 0,
"table use count underflow");
__ASSERT(new_count == 0 || new_count >= XLAT_REF_COUNT_UNIT,
"table in use with no reference to it");
__ASSERT((new_count & XLAT_PTE_COUNT_MASK) <= Ln_XLAT_NUM_ENTRIES,
"table PTE count overflow");
xlat_use_count[i] = new_count;
return new_count;
}
static inline void inc_table_ref(uint64_t *table)
{
table_usage(table, XLAT_REF_COUNT_UNIT);
}
static inline void dec_table_ref(uint64_t *table)
{
int ref_unit = XLAT_REF_COUNT_UNIT;
table_usage(table, -ref_unit);
} }
static inline bool is_table_unused(uint64_t *table) static inline bool is_table_unused(uint64_t *table)
{ {
return table_usage(table, 0) == 1; return (table_usage(table, 0) & XLAT_PTE_COUNT_MASK) == 0;
} }
static inline bool is_free_desc(uint64_t desc) static inline bool is_free_desc(uint64_t desc)
@ -225,7 +243,6 @@ static uint64_t *expand_to_table(uint64_t *pte, unsigned int level)
/* Link the new table in place of the pte it replaces */ /* Link the new table in place of the pte it replaces */
set_pte_table_desc(pte, table, level); set_pte_table_desc(pte, table, level);
table_usage(table, 1);
return table; return table;
} }
@ -300,7 +317,7 @@ static int set_mapping(struct arm_mmu_ptables *ptables,
/* recursively free unused tables if any */ /* recursively free unused tables if any */
while (level != BASE_XLAT_LEVEL && while (level != BASE_XLAT_LEVEL &&
is_table_unused(pte)) { is_table_unused(pte)) {
free_table(pte); dec_table_ref(pte);
pte = ptes[--level]; pte = ptes[--level];
set_pte_block_desc(pte, 0, level); set_pte_block_desc(pte, 0, level);
table_usage(pte, -1); table_usage(pte, -1);
@ -347,8 +364,8 @@ static uint64_t *dup_table(uint64_t *src_table, unsigned int level)
} }
dst_table[i] = src_table[i]; dst_table[i] = src_table[i];
if (is_table_desc(src_table[i], level)) { if (is_table_desc(dst_table[i], level)) {
table_usage(pte_desc_table(src_table[i]), 1); inc_table_ref(pte_desc_table(dst_table[i]));
} }
if (!is_free_desc(dst_table[i])) { if (!is_free_desc(dst_table[i])) {
table_usage(dst_table, 1); table_usage(dst_table, 1);
@ -388,8 +405,7 @@ static int privatize_table(uint64_t *dst_table, uint64_t *src_table,
return -ENOMEM; return -ENOMEM;
} }
set_pte_table_desc(&dst_table[i], dst_subtable, level); set_pte_table_desc(&dst_table[i], dst_subtable, level);
table_usage(dst_subtable, 1); dec_table_ref(src_subtable);
table_usage(src_subtable, -1);
} }
ret = privatize_table(dst_subtable, src_subtable, ret = privatize_table(dst_subtable, src_subtable,
@ -436,15 +452,14 @@ static void discard_table(uint64_t *table, unsigned int level)
for (i = 0U; i < Ln_XLAT_NUM_ENTRIES; i++) { for (i = 0U; i < Ln_XLAT_NUM_ENTRIES; i++) {
if (is_table_desc(table[i], level)) { if (is_table_desc(table[i], level)) {
table_usage(pte_desc_table(table[i]), -1);
discard_table(pte_desc_table(table[i]), level + 1); discard_table(pte_desc_table(table[i]), level + 1);
dec_table_ref(pte_desc_table(table[i]));
} }
if (!is_free_desc(table[i])) { if (!is_free_desc(table[i])) {
table[i] = 0U; table[i] = 0U;
table_usage(table, -1); table_usage(table, -1);
} }
} }
free_table(table);
} }
static int globalize_table(uint64_t *dst_table, uint64_t *src_table, static int globalize_table(uint64_t *dst_table, uint64_t *src_table,
@ -497,15 +512,15 @@ static int globalize_table(uint64_t *dst_table, uint64_t *src_table,
table_usage(dst_table, -1); table_usage(dst_table, -1);
} }
if (is_table_desc(src_table[i], level)) { if (is_table_desc(src_table[i], level)) {
table_usage(pte_desc_table(src_table[i]), 1); inc_table_ref(pte_desc_table(src_table[i]));
} }
dst_table[i] = src_table[i]; dst_table[i] = src_table[i];
debug_show_pte(&dst_table[i], level); debug_show_pte(&dst_table[i], level);
if (old_table) { if (old_table) {
/* we can discard the whole branch */ /* we can discard the whole branch */
table_usage(old_table, -1);
discard_table(old_table, level + 1); discard_table(old_table, level + 1);
dec_table_ref(old_table);
} }
} }