mirror of
https://git.proxmox.com/git/mirror_ubuntu-kernels.git
synced 2025-12-06 15:32:35 +00:00
When ident_pud_init() uses only GB pages to create identity maps, large ranges of addresses not actually requested can be included in the resulting table; a 4K request will map a full GB. This can include a lot of extra address space past that requested, including areas marked reserved by the BIOS. That allows processor speculation into reserved regions, that on UV systems can cause system halts. Only use GB pages when map creation requests include the full GB page of space. Fall back to using smaller 2M pages when only portions of a GB page are included in the request. No attempt is made to coalesce mapping requests. If a request requires a map entry at the 2M (pmd) level, subsequent mapping requests within the same 1G region will also be at the pmd level, even if adjacent or overlapping such requests could have been combined to map a full GB page. Existing usage starts with larger regions and then adds smaller regions, so this should not have any great consequence. Signed-off-by: Steve Wahl <steve.wahl@hpe.com> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Tested-by: Pavin Joseph <me@pavinjoseph.com> Tested-by: Sarah Brofeldt <srhb@dbc.dk> Tested-by: Eric Hagberg <ehagberg@gmail.com> Link: https://lore.kernel.org/all/20240717213121.3064030-3-steve.wahl@hpe.com
234 lines
5.0 KiB
C
234 lines
5.0 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Helper routines for building identity mapping page tables. This is
|
|
* included by both the compressed kernel and the regular kernel.
|
|
*/
|
|
|
|
static void free_pte(struct x86_mapping_info *info, pmd_t *pmd)
|
|
{
|
|
pte_t *pte = pte_offset_kernel(pmd, 0);
|
|
|
|
info->free_pgt_page(pte, info->context);
|
|
}
|
|
|
|
static void free_pmd(struct x86_mapping_info *info, pud_t *pud)
|
|
{
|
|
pmd_t *pmd = pmd_offset(pud, 0);
|
|
int i;
|
|
|
|
for (i = 0; i < PTRS_PER_PMD; i++) {
|
|
if (!pmd_present(pmd[i]))
|
|
continue;
|
|
|
|
if (pmd_leaf(pmd[i]))
|
|
continue;
|
|
|
|
free_pte(info, &pmd[i]);
|
|
}
|
|
|
|
info->free_pgt_page(pmd, info->context);
|
|
}
|
|
|
|
static void free_pud(struct x86_mapping_info *info, p4d_t *p4d)
|
|
{
|
|
pud_t *pud = pud_offset(p4d, 0);
|
|
int i;
|
|
|
|
for (i = 0; i < PTRS_PER_PUD; i++) {
|
|
if (!pud_present(pud[i]))
|
|
continue;
|
|
|
|
if (pud_leaf(pud[i]))
|
|
continue;
|
|
|
|
free_pmd(info, &pud[i]);
|
|
}
|
|
|
|
info->free_pgt_page(pud, info->context);
|
|
}
|
|
|
|
static void free_p4d(struct x86_mapping_info *info, pgd_t *pgd)
|
|
{
|
|
p4d_t *p4d = p4d_offset(pgd, 0);
|
|
int i;
|
|
|
|
for (i = 0; i < PTRS_PER_P4D; i++) {
|
|
if (!p4d_present(p4d[i]))
|
|
continue;
|
|
|
|
free_pud(info, &p4d[i]);
|
|
}
|
|
|
|
if (pgtable_l5_enabled())
|
|
info->free_pgt_page(p4d, info->context);
|
|
}
|
|
|
|
void kernel_ident_mapping_free(struct x86_mapping_info *info, pgd_t *pgd)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < PTRS_PER_PGD; i++) {
|
|
if (!pgd_present(pgd[i]))
|
|
continue;
|
|
|
|
free_p4d(info, &pgd[i]);
|
|
}
|
|
|
|
info->free_pgt_page(pgd, info->context);
|
|
}
|
|
|
|
static void ident_pmd_init(struct x86_mapping_info *info, pmd_t *pmd_page,
|
|
unsigned long addr, unsigned long end)
|
|
{
|
|
addr &= PMD_MASK;
|
|
for (; addr < end; addr += PMD_SIZE) {
|
|
pmd_t *pmd = pmd_page + pmd_index(addr);
|
|
|
|
if (pmd_present(*pmd))
|
|
continue;
|
|
|
|
set_pmd(pmd, __pmd((addr - info->offset) | info->page_flag));
|
|
}
|
|
}
|
|
|
|
static int ident_pud_init(struct x86_mapping_info *info, pud_t *pud_page,
|
|
unsigned long addr, unsigned long end)
|
|
{
|
|
unsigned long next;
|
|
|
|
for (; addr < end; addr = next) {
|
|
pud_t *pud = pud_page + pud_index(addr);
|
|
pmd_t *pmd;
|
|
bool use_gbpage;
|
|
|
|
next = (addr & PUD_MASK) + PUD_SIZE;
|
|
if (next > end)
|
|
next = end;
|
|
|
|
/* if this is already a gbpage, this portion is already mapped */
|
|
if (pud_leaf(*pud))
|
|
continue;
|
|
|
|
/* Is using a gbpage allowed? */
|
|
use_gbpage = info->direct_gbpages;
|
|
|
|
/* Don't use gbpage if it maps more than the requested region. */
|
|
/* at the begining: */
|
|
use_gbpage &= ((addr & ~PUD_MASK) == 0);
|
|
/* ... or at the end: */
|
|
use_gbpage &= ((next & ~PUD_MASK) == 0);
|
|
|
|
/* Never overwrite existing mappings */
|
|
use_gbpage &= !pud_present(*pud);
|
|
|
|
if (use_gbpage) {
|
|
pud_t pudval;
|
|
|
|
pudval = __pud((addr - info->offset) | info->page_flag);
|
|
set_pud(pud, pudval);
|
|
continue;
|
|
}
|
|
|
|
if (pud_present(*pud)) {
|
|
pmd = pmd_offset(pud, 0);
|
|
ident_pmd_init(info, pmd, addr, next);
|
|
continue;
|
|
}
|
|
pmd = (pmd_t *)info->alloc_pgt_page(info->context);
|
|
if (!pmd)
|
|
return -ENOMEM;
|
|
ident_pmd_init(info, pmd, addr, next);
|
|
set_pud(pud, __pud(__pa(pmd) | info->kernpg_flag));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ident_p4d_init(struct x86_mapping_info *info, p4d_t *p4d_page,
|
|
unsigned long addr, unsigned long end)
|
|
{
|
|
unsigned long next;
|
|
int result;
|
|
|
|
for (; addr < end; addr = next) {
|
|
p4d_t *p4d = p4d_page + p4d_index(addr);
|
|
pud_t *pud;
|
|
|
|
next = (addr & P4D_MASK) + P4D_SIZE;
|
|
if (next > end)
|
|
next = end;
|
|
|
|
if (p4d_present(*p4d)) {
|
|
pud = pud_offset(p4d, 0);
|
|
result = ident_pud_init(info, pud, addr, next);
|
|
if (result)
|
|
return result;
|
|
|
|
continue;
|
|
}
|
|
pud = (pud_t *)info->alloc_pgt_page(info->context);
|
|
if (!pud)
|
|
return -ENOMEM;
|
|
|
|
result = ident_pud_init(info, pud, addr, next);
|
|
if (result)
|
|
return result;
|
|
|
|
set_p4d(p4d, __p4d(__pa(pud) | info->kernpg_flag));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int kernel_ident_mapping_init(struct x86_mapping_info *info, pgd_t *pgd_page,
|
|
unsigned long pstart, unsigned long pend)
|
|
{
|
|
unsigned long addr = pstart + info->offset;
|
|
unsigned long end = pend + info->offset;
|
|
unsigned long next;
|
|
int result;
|
|
|
|
/* Set the default pagetable flags if not supplied */
|
|
if (!info->kernpg_flag)
|
|
info->kernpg_flag = _KERNPG_TABLE;
|
|
|
|
/* Filter out unsupported __PAGE_KERNEL_* bits: */
|
|
info->kernpg_flag &= __default_kernel_pte_mask;
|
|
|
|
for (; addr < end; addr = next) {
|
|
pgd_t *pgd = pgd_page + pgd_index(addr);
|
|
p4d_t *p4d;
|
|
|
|
next = (addr & PGDIR_MASK) + PGDIR_SIZE;
|
|
if (next > end)
|
|
next = end;
|
|
|
|
if (pgd_present(*pgd)) {
|
|
p4d = p4d_offset(pgd, 0);
|
|
result = ident_p4d_init(info, p4d, addr, next);
|
|
if (result)
|
|
return result;
|
|
continue;
|
|
}
|
|
|
|
p4d = (p4d_t *)info->alloc_pgt_page(info->context);
|
|
if (!p4d)
|
|
return -ENOMEM;
|
|
result = ident_p4d_init(info, p4d, addr, next);
|
|
if (result)
|
|
return result;
|
|
if (pgtable_l5_enabled()) {
|
|
set_pgd(pgd, __pgd(__pa(p4d) | info->kernpg_flag));
|
|
} else {
|
|
/*
|
|
* With p4d folded, pgd is equal to p4d.
|
|
* The pgd entry has to point to the pud page table in this case.
|
|
*/
|
|
pud_t *pud = pud_offset(p4d, 0);
|
|
set_pgd(pgd, __pgd(__pa(pud) | info->kernpg_flag));
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|