[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [RFC 21/38] x86/hyperlaunch: move remaining pvh dom0 construction
Move pvh_load_kernel() and its helper functions to the domain builder. With this move, it is now possible to move the remaining logic of dom0_construct_pvh() to the domain builder. With all the logic moved, the function can be dropped. Signed-off-by: Daniel P. Smith <dpsmith@xxxxxxxxxxxxxxxxxxxx> --- xen/arch/x86/hvm/dom0_build.c | 365 -------------------------- xen/arch/x86/hvm/dom_build.c | 362 ++++++++++++++++++++++++- xen/arch/x86/include/asm/dom0_build.h | 1 - 3 files changed, 361 insertions(+), 367 deletions(-) diff --git a/xen/arch/x86/hvm/dom0_build.c b/xen/arch/x86/hvm/dom0_build.c index 73ce33fb17f1..23b46ef86c9f 100644 --- a/xen/arch/x86/hvm/dom0_build.c +++ b/xen/arch/x86/hvm/dom0_build.c @@ -478,335 +478,6 @@ int __init dom0_pvh_populate_p2m(struct domain *d) #undef MB1_PAGES } -static paddr_t __init find_memory( - const struct domain *d, const struct elf_binary *elf, size_t size) -{ - 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, end = d->arch.e820[i].addr + d->arch.e820[i].size; - - /* Don't use memory below 1MB, as it could overwrite BDA/EBDA/IBFT. */ - if ( end <= MB(1) || d->arch.e820[i].type != E820_RAM ) - continue; - - start = MAX(ROUNDUP(d->arch.e820[i].addr, PAGE_SIZE), MB(1)); - - ASSERT(IS_ALIGNED(start, PAGE_SIZE) && IS_ALIGNED(end, PAGE_SIZE)); - - /* - * NB: Even better would be to use rangesets to determine a suitable - * range, in particular in case a kernel requests multiple heavily - * discontiguous regions (which right now we fold all into one big - * region). - */ - if ( end <= kernel_start || start >= kernel_end ) - { - /* No overlap, just check whether the region is large enough. */ - if ( end - start >= size ) - return start; - } - /* Deal with the kernel already being loaded in the region. */ - else if ( kernel_start > start && kernel_start - start >= size ) - return start; - else if ( kernel_end < end && end - kernel_end >= size ) - return kernel_end; - } - - return INVALID_PADDR; -} - -static bool __init check_load_address( - const struct domain *d, const struct elf_binary *elf) -{ - paddr_t kernel_start = (uintptr_t)elf->dest_base; - paddr_t kernel_end = kernel_start + elf->dest_size; - unsigned int i; - - /* Relies on a sorted memory map with adjacent entries merged. */ - for ( i = 0; i < d->arch.nr_e820; i++ ) - { - paddr_t start = d->arch.e820[i].addr; - paddr_t end = start + d->arch.e820[i].size; - - if ( start >= kernel_end ) - return false; - - if ( d->arch.e820[i].type == E820_RAM && - start <= kernel_start && - end >= kernel_end ) - return true; - } - - return false; -} - -/* Find an e820 RAM region that fits the kernel at a suitable alignment. */ -static paddr_t __init find_kernel_memory( - const struct domain *d, struct elf_binary *elf, - const struct elf_dom_parms *parms) -{ - paddr_t kernel_size = elf->dest_size; - unsigned int align; - unsigned int i; - - if ( parms->phys_align != UNSET_ADDR32 ) - align = parms->phys_align; - else if ( elf->palign >= PAGE_SIZE ) - align = elf->palign; - else - align = MB(2); - - /* Search backwards to find the highest address. */ - for ( i = d->arch.nr_e820; i--; ) - { - paddr_t start = d->arch.e820[i].addr; - paddr_t end = start + d->arch.e820[i].size; - paddr_t kstart, kend; - - if ( d->arch.e820[i].type != E820_RAM || - d->arch.e820[i].size < kernel_size ) - continue; - - if ( start > parms->phys_max ) - continue; - - if ( end - 1 > parms->phys_max ) - end = parms->phys_max + 1; - - kstart = (end - kernel_size) & ~(align - 1); - kend = kstart + kernel_size; - - if ( kstart < parms->phys_min ) - return 0; - - if ( kstart >= start && kend <= end ) - return kstart; - } - - return 0; -} - -/* Check the kernel load address, and adjust if necessary and possible. */ -static bool __init check_and_adjust_load_address( - const struct domain *d, struct elf_binary *elf, struct elf_dom_parms *parms) -{ - paddr_t reloc_base; - - if ( check_load_address(d, elf) ) - return true; - - if ( !parms->phys_reloc ) - { - printk("%pd kernel: Address conflict and not relocatable\n", d); - return false; - } - - reloc_base = find_kernel_memory(d, elf, parms); - if ( !reloc_base ) - { - printk("%pd kernel: Failed find a load address\n", d); - return false; - } - - if ( opt_dom0_verbose ) - printk("%pd kernel: Moving [%p, %p] -> [%"PRIpaddr", %"PRIpaddr"]\n", d, - elf->dest_base, elf->dest_base + elf->dest_size - 1, - reloc_base, reloc_base + elf->dest_size - 1); - - parms->phys_entry = - reloc_base + (parms->phys_entry - (uintptr_t)elf->dest_base); - elf->dest_base = (char *)reloc_base; - - return true; -} - -static int __init pvh_load_kernel( - const struct boot_domain *bd, paddr_t *entry, paddr_t *start_info_addr) -{ - struct domain *d = bd->d; - struct boot_module *image = bd->kernel; - struct boot_module *initrd = bd->ramdisk; - void *image_base = bootstrap_map_bm(image); - void *image_start = image_base + image->headroom; - unsigned long image_len = image->size; - unsigned long initrd_len = initrd ? initrd->size : 0; - const char *initrd_cmdline = NULL; - struct elf_binary elf; - struct elf_dom_parms parms; - size_t extra_space; - paddr_t last_addr; - struct hvm_start_info start_info = { 0 }; - struct hvm_modlist_entry mod = { 0 }; - struct vcpu *v = d->vcpu[0]; - int rc; - - if ( (rc = bzimage_parse(image_base, &image_start, &image_len)) != 0 ) - { - printk("Error trying to detect bz compressed kernel\n"); - return rc; - } - - if ( (rc = elf_init(&elf, image_start, image_len)) != 0 ) - { - printk("Unable to init ELF\n"); - return rc; - } - if ( opt_dom0_verbose ) - elf_set_verbose(&elf); - elf_parse_binary(&elf); - if ( (rc = elf_xen_parse(&elf, &parms, true)) != 0 ) - { - printk("Unable to parse kernel for ELFNOTES\n"); - if ( elf_check_broken(&elf) ) - printk("%pd kernel: broken ELF: %s\n", d, elf_check_broken(&elf)); - return rc; - } - - if ( parms.phys_entry == UNSET_ADDR32 ) - { - printk("Unable to find XEN_ELFNOTE_PHYS32_ENTRY address\n"); - return -EINVAL; - } - - /* Copy the OS image and free temporary buffer. */ - elf.dest_base = (void *)(parms.virt_kstart - parms.virt_base); - elf.dest_size = parms.virt_kend - parms.virt_kstart; - - if ( !check_and_adjust_load_address(d, &elf, &parms) ) - return -ENOSPC; - - elf_set_vcpu(&elf, v); - rc = elf_load_binary(&elf); - if ( rc < 0 ) - { - printk("Failed to load kernel: %d\n", rc); - if ( elf_check_broken(&elf) ) - printk("%pd kernel: broken ELF: %s\n", d, elf_check_broken(&elf)); - return rc; - } - - /* - * Find a RAM region big enough (and that doesn't overlap with the loaded - * kernel) in order to load the initrd and the metadata. Note it could be - * split into smaller allocations, done as a single region in order to - * simplify it. - */ - extra_space = sizeof(start_info); - - if ( initrd ) - { - size_t initrd_space = elf_round_up(&elf, initrd_len); - - if ( initrd->cmdline_pa ) - { - initrd_cmdline = __va(initrd->cmdline_pa); - if ( !*initrd_cmdline ) - initrd_cmdline = NULL; - } - if ( initrd_cmdline ) - initrd_space += strlen(initrd_cmdline) + 1; - - if ( initrd_space ) - extra_space += ROUNDUP(initrd_space, PAGE_SIZE) + sizeof(mod); - else - initrd = NULL; - } - - if ( bd->cmdline ) - extra_space += elf_round_up(&elf, strlen(bd->cmdline) + 1); - - last_addr = find_memory(d, &elf, extra_space); - if ( last_addr == INVALID_PADDR ) - { - printk("Unable to find a memory region to load initrd and metadata\n"); - return -ENOMEM; - } - - if ( initrd != NULL ) - { - rc = hvm_copy_to_guest_phys(last_addr, __va(initrd->start), - initrd_len, v); - if ( rc ) - { - printk("Unable to copy initrd to guest\n"); - return rc; - } - - mod.paddr = last_addr; - mod.size = initrd_len; - last_addr += elf_round_up(&elf, initrd_len); - if ( initrd_cmdline ) - { - size_t len = strlen(initrd_cmdline) + 1; - - rc = hvm_copy_to_guest_phys(last_addr, initrd_cmdline, len, v); - if ( rc ) - { - printk("Unable to copy module command line\n"); - return rc; - } - mod.cmdline_paddr = last_addr; - last_addr += len; - } - last_addr = ROUNDUP(last_addr, PAGE_SIZE); - } - - /* Free temporary buffers. */ - free_boot_modules(); - - if ( bd->cmdline ) - { - rc = hvm_copy_to_guest_phys(last_addr, bd->cmdline, - strlen(bd->cmdline) + 1, v); - if ( rc ) - { - printk("Unable to copy guest command line\n"); - return rc; - } - start_info.cmdline_paddr = last_addr; - /* - * Round up to 32/64 bits (depending on the guest kernel bitness) so - * the modlist/start_info is aligned. - */ - last_addr += elf_round_up(&elf, strlen(bd->cmdline) + 1); - } - if ( initrd != NULL ) - { - rc = hvm_copy_to_guest_phys(last_addr, &mod, sizeof(mod), v); - if ( rc ) - { - printk("Unable to copy guest modules\n"); - return rc; - } - start_info.modlist_paddr = last_addr; - start_info.nr_modules = 1; - last_addr += sizeof(mod); - } - - start_info.magic = XEN_HVM_START_MAGIC_VALUE; - start_info.flags = SIF_PRIVILEGED | SIF_INITDOMAIN; - rc = hvm_copy_to_guest_phys(last_addr, &start_info, sizeof(start_info), v); - if ( rc ) - { - printk("Unable to copy start info to guest\n"); - return rc; - } - - *entry = parms.phys_entry; - *start_info_addr = last_addr; - - return 0; -} - static int __init cf_check acpi_count_intr_ovr( struct acpi_subtable_header *header, const unsigned long end) { @@ -1255,42 +926,6 @@ int __init dom0_pvh_setup_acpi(struct domain *d, paddr_t start_info) return 0; } -int __init dom0_construct_pvh(struct boot_domain *bd) -{ - paddr_t entry, start_info; - struct domain *d = bd->d; - int rc; - - rc = pvh_load_kernel(bd, &entry, &start_info); - if ( rc ) - { - printk("Failed to load Dom0 kernel\n"); - return rc; - } - - rc = hvm_setup_cpus(bd->d, entry, start_info); - if ( rc ) - { - printk("Failed to setup Dom0 CPUs: %d\n", rc); - return rc; - } - - rc = dom0_pvh_setup_acpi(bd->d, start_info); - if ( rc ) - { - printk("Failed to setup Dom0 ACPI tables: %d\n", rc); - return rc; - } - - if ( opt_dom0_verbose ) - { - printk("Dom%u memory map:\n", d->domain_id); - print_e820_memory_map(d->arch.e820, d->arch.nr_e820); - } - - return 0; -} - /* * Local variables: * mode: C diff --git a/xen/arch/x86/hvm/dom_build.c b/xen/arch/x86/hvm/dom_build.c index 9421dc431ba9..2e47ca489a71 100644 --- a/xen/arch/x86/hvm/dom_build.c +++ b/xen/arch/x86/hvm/dom_build.c @@ -16,10 +16,12 @@ #include <acpi/actables.h> +#include <public/arch-x86/hvm/start_info.h> #include <public/hvm/e820.h> #include <public/hvm/hvm_vcpu.h> #include <asm/bootinfo.h> +#include <asm/bzimage.h> #include <asm/dom0_build.h> #include <asm/domain-builder.h> #include <asm/hvm/io.h> @@ -276,8 +278,338 @@ static int __init hvm_populate_p2m(struct domain *d) return 0; } +static paddr_t __init find_memory( + const struct domain *d, const struct elf_binary *elf, size_t size) +{ + 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, end = d->arch.e820[i].addr + d->arch.e820[i].size; + + /* Don't use memory below 1MB, as it could overwrite BDA/EBDA/IBFT. */ + if ( end <= MB(1) || d->arch.e820[i].type != E820_RAM ) + continue; + + start = MAX(ROUNDUP(d->arch.e820[i].addr, PAGE_SIZE), MB(1)); + + ASSERT(IS_ALIGNED(start, PAGE_SIZE) && IS_ALIGNED(end, PAGE_SIZE)); + + /* + * NB: Even better would be to use rangesets to determine a suitable + * range, in particular in case a kernel requests multiple heavily + * discontiguous regions (which right now we fold all into one big + * region). + */ + if ( end <= kernel_start || start >= kernel_end ) + { + /* No overlap, just check whether the region is large enough. */ + if ( end - start >= size ) + return start; + } + /* Deal with the kernel already being loaded in the region. */ + else if ( kernel_start > start && kernel_start - start >= size ) + return start; + else if ( kernel_end < end && end - kernel_end >= size ) + return kernel_end; + } + + return INVALID_PADDR; +} + +static bool __init check_load_address( + const struct domain *d, const struct elf_binary *elf) +{ + paddr_t kernel_start = (uintptr_t)elf->dest_base; + paddr_t kernel_end = kernel_start + elf->dest_size; + unsigned int i; + + /* Relies on a sorted memory map with adjacent entries merged. */ + for ( i = 0; i < d->arch.nr_e820; i++ ) + { + paddr_t start = d->arch.e820[i].addr; + paddr_t end = start + d->arch.e820[i].size; + + if ( start >= kernel_end ) + return false; + + if ( d->arch.e820[i].type == E820_RAM && + start <= kernel_start && + end >= kernel_end ) + return true; + } + + return false; +} + +/* Find an e820 RAM region that fits the kernel at a suitable alignment. */ +static paddr_t __init find_kernel_memory( + const struct domain *d, struct elf_binary *elf, + const struct elf_dom_parms *parms) +{ + paddr_t kernel_size = elf->dest_size; + unsigned int align; + unsigned int i; + + if ( parms->phys_align != UNSET_ADDR32 ) + align = parms->phys_align; + else if ( elf->palign >= PAGE_SIZE ) + align = elf->palign; + else + align = MB(2); + + /* Search backwards to find the highest address. */ + for ( i = d->arch.nr_e820; i--; ) + { + paddr_t start = d->arch.e820[i].addr; + paddr_t end = start + d->arch.e820[i].size; + paddr_t kstart, kend; + + if ( d->arch.e820[i].type != E820_RAM || + d->arch.e820[i].size < kernel_size ) + continue; + + if ( start > parms->phys_max ) + continue; + + if ( end - 1 > parms->phys_max ) + end = parms->phys_max + 1; + + kstart = (end - kernel_size) & ~(align - 1); + kend = kstart + kernel_size; + + if ( kstart < parms->phys_min ) + return 0; + + if ( kstart >= start && kend <= end ) + return kstart; + } + + return 0; +} + +/* Check the kernel load address, and adjust if necessary and possible. */ +static bool __init check_and_adjust_load_address( + const struct domain *d, struct elf_binary *elf, struct elf_dom_parms *parms) +{ + paddr_t reloc_base; + + if ( check_load_address(d, elf) ) + return true; + + if ( !parms->phys_reloc ) + { + printk("%pd kernel: Address conflict and not relocatable\n", d); + return false; + } + + reloc_base = find_kernel_memory(d, elf, parms); + if ( !reloc_base ) + { + printk("%pd kernel: Failed find a load address\n", d); + return false; + } + + if ( opt_dom0_verbose ) + printk("%pd kernel: Moving [%p, %p] -> [%"PRIpaddr", %"PRIpaddr"]\n", d, + elf->dest_base, elf->dest_base + elf->dest_size - 1, + reloc_base, reloc_base + elf->dest_size - 1); + + parms->phys_entry = + reloc_base + (parms->phys_entry - (uintptr_t)elf->dest_base); + elf->dest_base = (char *)reloc_base; + + return true; +} + +static int __init pvh_load_kernel( + const struct boot_domain *bd, paddr_t *entry, paddr_t *start_info_addr) +{ + struct domain *d = bd->d; + struct boot_module *image = bd->kernel; + struct boot_module *initrd = bd->ramdisk; + void *image_base = bootstrap_map_bm(image); + void *image_start = image_base + image->headroom; + unsigned long image_len = image->size; + unsigned long initrd_len = initrd ? initrd->size : 0; + const char *initrd_cmdline = NULL; + struct elf_binary elf; + struct elf_dom_parms parms; + size_t extra_space; + paddr_t last_addr; + struct hvm_start_info start_info = { 0 }; + struct hvm_modlist_entry mod = { 0 }; + struct vcpu *v = d->vcpu[0]; + int rc; + + if ( (rc = bzimage_parse(image_base, &image_start, &image_len)) != 0 ) + { + printk("Error trying to detect bz compressed kernel\n"); + return rc; + } + + if ( (rc = elf_init(&elf, image_start, image_len)) != 0 ) + { + printk("Unable to init ELF\n"); + return rc; + } + if ( opt_dom0_verbose ) + elf_set_verbose(&elf); + elf_parse_binary(&elf); + if ( (rc = elf_xen_parse(&elf, &parms, true)) != 0 ) + { + printk("Unable to parse kernel for ELFNOTES\n"); + if ( elf_check_broken(&elf) ) + printk("%pd kernel: broken ELF: %s\n", d, elf_check_broken(&elf)); + return rc; + } + + if ( parms.phys_entry == UNSET_ADDR32 ) + { + printk("Unable to find XEN_ELFNOTE_PHYS32_ENTRY address\n"); + return -EINVAL; + } + + /* Copy the OS image and free temporary buffer. */ + elf.dest_base = (void *)(parms.virt_kstart - parms.virt_base); + elf.dest_size = parms.virt_kend - parms.virt_kstart; + + if ( !check_and_adjust_load_address(d, &elf, &parms) ) + return -ENOSPC; + + elf_set_vcpu(&elf, v); + rc = elf_load_binary(&elf); + if ( rc < 0 ) + { + printk("Failed to load kernel: %d\n", rc); + if ( elf_check_broken(&elf) ) + printk("%pd kernel: broken ELF: %s\n", d, elf_check_broken(&elf)); + return rc; + } + + /* + * Find a RAM region big enough (and that doesn't overlap with the loaded + * kernel) in order to load the initrd and the metadata. Note it could be + * split into smaller allocations, done as a single region in order to + * simplify it. + */ + extra_space = sizeof(start_info); + + if ( initrd ) + { + size_t initrd_space = elf_round_up(&elf, initrd_len); + + if ( initrd->cmdline_pa ) + { + initrd_cmdline = __va(initrd->cmdline_pa); + if ( !*initrd_cmdline ) + initrd_cmdline = NULL; + } + if ( initrd_cmdline ) + initrd_space += strlen(initrd_cmdline) + 1; + + if ( initrd_space ) + extra_space += ROUNDUP(initrd_space, PAGE_SIZE) + sizeof(mod); + else + initrd = NULL; + } + + if ( bd->cmdline ) + extra_space += elf_round_up(&elf, strlen(bd->cmdline) + 1); + + last_addr = find_memory(d, &elf, extra_space); + if ( last_addr == INVALID_PADDR ) + { + printk("Unable to find a memory region to load initrd and metadata\n"); + return -ENOMEM; + } + + if ( initrd != NULL ) + { + rc = hvm_copy_to_guest_phys(last_addr, __va(initrd->start), + initrd_len, v); + if ( rc ) + { + printk("Unable to copy initrd to guest\n"); + return rc; + } + + mod.paddr = last_addr; + mod.size = initrd_len; + last_addr += elf_round_up(&elf, initrd_len); + if ( initrd_cmdline ) + { + size_t len = strlen(initrd_cmdline) + 1; + + rc = hvm_copy_to_guest_phys(last_addr, initrd_cmdline, len, v); + if ( rc ) + { + printk("Unable to copy module command line\n"); + return rc; + } + mod.cmdline_paddr = last_addr; + last_addr += len; + } + last_addr = ROUNDUP(last_addr, PAGE_SIZE); + } + + /* Free temporary buffers. */ + free_boot_modules(); + + if ( bd->cmdline ) + { + rc = hvm_copy_to_guest_phys(last_addr, bd->cmdline, + strlen(bd->cmdline) + 1, v); + if ( rc ) + { + printk("Unable to copy guest command line\n"); + return rc; + } + start_info.cmdline_paddr = last_addr; + /* + * Round up to 32/64 bits (depending on the guest kernel bitness) so + * the modlist/start_info is aligned. + */ + last_addr += elf_round_up(&elf, strlen(bd->cmdline) + 1); + } + if ( initrd != NULL ) + { + rc = hvm_copy_to_guest_phys(last_addr, &mod, sizeof(mod), v); + if ( rc ) + { + printk("Unable to copy guest modules\n"); + return rc; + } + start_info.modlist_paddr = last_addr; + start_info.nr_modules = 1; + last_addr += sizeof(mod); + } + + start_info.magic = XEN_HVM_START_MAGIC_VALUE; + start_info.flags = SIF_PRIVILEGED | SIF_INITDOMAIN; + rc = hvm_copy_to_guest_phys(last_addr, &start_info, sizeof(start_info), v); + if ( rc ) + { + printk("Unable to copy start info to guest\n"); + return rc; + } + + *entry = parms.phys_entry; + *start_info_addr = last_addr; + + return 0; +} + int __init dom_construct_pvh(struct boot_domain *bd) { + paddr_t entry, start_info; int rc; printk(XENLOG_INFO "*** Building a PVH Dom%d ***\n", bd->domid); @@ -327,7 +659,35 @@ int __init dom_construct_pvh(struct boot_domain *bd) return rc; } - return dom0_construct_pvh(bd); + rc = pvh_load_kernel(bd, &entry, &start_info); + if ( rc ) + { + printk("Failed to load Dom0 kernel\n"); + return rc; + } + + rc = hvm_setup_cpus(bd->d, entry, start_info); + if ( rc ) + { + printk("Failed to setup Dom0 CPUs: %d\n", rc); + return rc; + } + + rc = dom0_pvh_setup_acpi(bd->d, start_info); + if ( rc ) + { + printk("Failed to setup Dom0 ACPI tables: %d\n", rc); + return rc; + } + + if ( opt_dom0_verbose ) + { + printk("Dom%u memory map:\n", bd->domid); + print_e820_memory_map(bd->d->arch.e820, bd->d->arch.nr_e820); + } + + printk("WARNING: PVH is an experimental mode with limited functionality\n"); + return 0; } /* diff --git a/xen/arch/x86/include/asm/dom0_build.h b/xen/arch/x86/include/asm/dom0_build.h index 3819b3f4e7a4..6947aaa1dce3 100644 --- a/xen/arch/x86/include/asm/dom0_build.h +++ b/xen/arch/x86/include/asm/dom0_build.h @@ -24,7 +24,6 @@ int dom0_pvh_setup_acpi(struct domain *d, paddr_t start_info); int dom0_pvh_populate_p2m(struct domain *d); int dom0_construct_pv(struct boot_domain *bd); -int dom0_construct_pvh(struct boot_domain *bd); void dom0_update_physmap(bool compat, unsigned long pfn, unsigned long mfn, unsigned long vphysmap_s); -- 2.30.2
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |