|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [PATCH 3/3] x86/PVH: Support relocatable dom0 kernels
On Wed, 6 Mar 2024, Jason Andryuk wrote:
> Xen tries to load a PVH dom0 kernel at the fixed guest physical address
> from the elf headers. For Linux, this defaults to 0x1000000 (16MB), but
> it can be configured.
>
> Unfortunately there exist firmwares that have reserved regions at this
> address, so Xen fails to load the dom0 kernel since it's not RAM.
>
> The PVH entry code is not relocatable - it loads from absolute
> addresses, which fail when the kernel is loaded at a different address.
> With a suitably modified kernel, a reloctable entry point is possible.
>
> Add the XENFEAT_pvh_relocatable flag to let a kernel indicate that it
> supports a relocatable entry path.
>
> Change the loading to check for an acceptable load address. If the
> kernel is relocatable, support finding an alternate load address.
>
> Linux cares about its physical alignment. This can be pulled out of the
> bzImage header, but not from the vmlinux ELF file. If an alignment
> can't be found, use 2MB.
>
> Signed-off-by: Jason Andryuk <jason.andryuk@xxxxxxx>
> ---
> Put alignment as a new ELF note? Presence of that note would indicate
> relocation support without a new XENFEAT flag.
>
> Default alignment to a multiple of 2MB to make more cases work? It has
> to be a power of two, and a multiple might allow loading a customized
> kernel. A larger alignment would limit the number of possible load
> locations.
> ---
> xen/arch/x86/hvm/dom0_build.c | 109 ++++++++++++++++++++++++++++++++++
> xen/include/public/features.h | 5 ++
> 2 files changed, 114 insertions(+)
>
> diff --git a/xen/arch/x86/hvm/dom0_build.c b/xen/arch/x86/hvm/dom0_build.c
> index bbae8a5645..34d68ee8fb 100644
> --- a/xen/arch/x86/hvm/dom0_build.c
> +++ b/xen/arch/x86/hvm/dom0_build.c
> @@ -537,6 +537,109 @@ static paddr_t __init find_memory(
> return INVALID_PADDR;
> }
>
> +static bool __init check_load_address(
> + const struct domain *d, const struct elf_binary *elf)
> +{
> + paddr_t kernel_start = (paddr_t)elf->dest_base & PAGE_MASK;
> + paddr_t kernel_end = ROUNDUP((paddr_t)elf->dest_base + elf->dest_size,
> + PAGE_SIZE);
> + unsigned int i;
> +
> + /*
> + * The memory map is sorted and all RAM regions starts and sizes are
> + * aligned to page boundaries.
> + */
> + for ( i = 0; i < d->arch.nr_e820; i++ )
> + {
> + paddr_t start = d->arch.e820[i].addr;
> + paddr_t end = d->arch.e820[i].addr + d->arch.e820[i].size;
> +
> + if ( start <= kernel_start &&
> + end >= kernel_end &&
> + d->arch.e820[i].type == E820_RAM )
> + return true;
> + }
> +
> + return false;
> +}
> +
> +/*
> + * Find an e820 RAM region that fits the kernel at a suitable alignment.
> + */
> +static paddr_t find_kernel_memory(
> + const struct domain *d, struct elf_binary *elf, paddr_t align)
> +{
> + paddr_t kernel_start = (paddr_t)elf->dest_base & PAGE_MASK;
> + paddr_t kernel_end = ROUNDUP((paddr_t)elf->dest_base + elf->dest_size,
> + PAGE_SIZE);
> + unsigned int i;
> +
> + /*
> + * The memory map is sorted and all RAM regions starts and sizes are
> + * aligned to page boundaries.
> + */
> + for ( i = 0; i < d->arch.nr_e820; i++ )
> + {
> + paddr_t start = d->arch.e820[i].addr;
> + paddr_t end = d->arch.e820[i].addr + d->arch.e820[i].size;
> + paddr_t kstart, kend, offset;
> +
> + if ( d->arch.e820[i].type != E820_RAM )
> + continue;
> +
> + if ( d->arch.e820[i].size < elf->dest_size )
> + continue;
> +
> + if ( end < kernel_end )
> + continue;
Why this check? Is it to make sure we look for e820 regions that are
higher in terms of addresses? If so, couldn't we start from
d->arch.nr_e820 and go down instead of starting from 0 and going up?
The PVH entry point is a 32-bit entry point if I remember right? Do we
need a 32-bit check? If so then it might not be a good idea to start
from arch.nr_e820 and go down.
> + kstart = ROUNDUP(start, align);
> + offset = kstart - kernel_start;
> + kend = kernel_end + offset;
> +
> + if ( kend <= end )
> + return offset;
> + }
> +
> + return 0;
> +}
> +
> +/*
> + * Check the kernel load address, and adjust if necessary and possible.
> + */
> +static bool __init adjust_load_address(
> + const struct domain *d, struct elf_binary *elf, struct elf_dom_parms
> *parms,
> + paddr_t align)
> +{
> + paddr_t offset;
> +
> + /* Check load address */
> + if ( check_load_address(d, elf) )
> + return true;
> +
> + if ( !test_bit(XENFEAT_pvh_relocatable, parms->f_supported) )
> + {
> + printk("Address conflict and %pd kernel is not relocatable\n", d);
> + return false;
> + }
> +
> + if ( align == 0 )
> + align = MB(2);
> +
> + offset = find_kernel_memory(d, elf, align);
> + if ( offset == 0 )
> + {
> + printk("Failed find a load offset for the kernel\n");
> + return false;
> + }
> +
> + printk("Adjusting load address by %#lx\n", offset);
> + elf->dest_base += offset;
> + parms->phys_entry += offset;
> +
> + return true;
> +}
> +
> static int __init pvh_load_kernel(struct domain *d, const module_t *image,
> unsigned long image_headroom,
> module_t *initrd, void *image_base,
> @@ -587,6 +690,12 @@ static int __init pvh_load_kernel(struct domain *d,
> const module_t *image,
> elf.dest_base = (void *)(parms.virt_kstart - parms.virt_base);
> elf.dest_size = parms.virt_kend - parms.virt_kstart;
>
> + if ( !adjust_load_address(d, &elf, &parms, align) )
> + {
> + printk("Unable to load kernel\n");
> + return -ENOMEM;
> + }
> +
> elf_set_vcpu(&elf, v);
> rc = elf_load_binary(&elf);
> if ( rc < 0 )
> diff --git a/xen/include/public/features.h b/xen/include/public/features.h
> index 4437f25d25..300480cb22 100644
> --- a/xen/include/public/features.h
> +++ b/xen/include/public/features.h
> @@ -120,6 +120,11 @@
> #define XENFEAT_runstate_phys_area 18
> #define XENFEAT_vcpu_time_phys_area 19
>
> +/*
> + * PVH: If set, the guest supports relocation in load address.
> + */
> +#define XENFEAT_pvh_relocatable 20
> +
> #define XENFEAT_NR_SUBMAPS 1
>
> #endif /* __XEN_PUBLIC_FEATURES_H__ */
> --
> 2.44.0
>
>
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |