b3cf8528bb
Commitf5775e0b61
("x86/xen: discard RAM regions above the maximum reservation") left host memory not assigned to dom0 as available for memory hotplug. Unfortunately this also meant that those regions could be used by others. Specifically, commitfa564ad963
("x86/PCI: Enable a 64bit BAR on AMD Family 15h (Models 00-1f, 30-3f, 60-7f)") may try to map those addresses as MMIO. To prevent this mark unallocated host memory as E820_TYPE_UNUSABLE (thus effectively revertingf5775e0b61
) and keep track of that region as a hostmem resource that can be used for the hotplug. Signed-off-by: Boris Ostrovsky <boris.ostrovsky@oracle.com> Reviewed-by: Juergen Gross <jgross@suse.com>
415 lines
11 KiB
C
415 lines
11 KiB
C
#ifdef CONFIG_XEN_BALLOON_MEMORY_HOTPLUG
|
|
#include <linux/bootmem.h>
|
|
#endif
|
|
#include <linux/cpu.h>
|
|
#include <linux/kexec.h>
|
|
|
|
#include <xen/features.h>
|
|
#include <xen/page.h>
|
|
#include <xen/interface/memory.h>
|
|
|
|
#include <asm/xen/hypercall.h>
|
|
#include <asm/xen/hypervisor.h>
|
|
#include <asm/cpu.h>
|
|
#include <asm/e820/api.h>
|
|
|
|
#include "xen-ops.h"
|
|
#include "smp.h"
|
|
#include "pmu.h"
|
|
|
|
EXPORT_SYMBOL_GPL(hypercall_page);
|
|
|
|
/*
|
|
* Pointer to the xen_vcpu_info structure or
|
|
* &HYPERVISOR_shared_info->vcpu_info[cpu]. See xen_hvm_init_shared_info
|
|
* and xen_vcpu_setup for details. By default it points to share_info->vcpu_info
|
|
* but if the hypervisor supports VCPUOP_register_vcpu_info then it can point
|
|
* to xen_vcpu_info. The pointer is used in __xen_evtchn_do_upcall to
|
|
* acknowledge pending events.
|
|
* Also more subtly it is used by the patched version of irq enable/disable
|
|
* e.g. xen_irq_enable_direct and xen_iret in PV mode.
|
|
*
|
|
* The desire to be able to do those mask/unmask operations as a single
|
|
* instruction by using the per-cpu offset held in %gs is the real reason
|
|
* vcpu info is in a per-cpu pointer and the original reason for this
|
|
* hypercall.
|
|
*
|
|
*/
|
|
DEFINE_PER_CPU(struct vcpu_info *, xen_vcpu);
|
|
|
|
/*
|
|
* Per CPU pages used if hypervisor supports VCPUOP_register_vcpu_info
|
|
* hypercall. This can be used both in PV and PVHVM mode. The structure
|
|
* overrides the default per_cpu(xen_vcpu, cpu) value.
|
|
*/
|
|
DEFINE_PER_CPU(struct vcpu_info, xen_vcpu_info);
|
|
|
|
/* Linux <-> Xen vCPU id mapping */
|
|
DEFINE_PER_CPU(uint32_t, xen_vcpu_id);
|
|
EXPORT_PER_CPU_SYMBOL(xen_vcpu_id);
|
|
|
|
enum xen_domain_type xen_domain_type = XEN_NATIVE;
|
|
EXPORT_SYMBOL_GPL(xen_domain_type);
|
|
|
|
unsigned long *machine_to_phys_mapping = (void *)MACH2PHYS_VIRT_START;
|
|
EXPORT_SYMBOL(machine_to_phys_mapping);
|
|
unsigned long machine_to_phys_nr;
|
|
EXPORT_SYMBOL(machine_to_phys_nr);
|
|
|
|
struct start_info *xen_start_info;
|
|
EXPORT_SYMBOL_GPL(xen_start_info);
|
|
|
|
struct shared_info xen_dummy_shared_info;
|
|
|
|
__read_mostly int xen_have_vector_callback;
|
|
EXPORT_SYMBOL_GPL(xen_have_vector_callback);
|
|
|
|
/*
|
|
* Point at some empty memory to start with. We map the real shared_info
|
|
* page as soon as fixmap is up and running.
|
|
*/
|
|
struct shared_info *HYPERVISOR_shared_info = &xen_dummy_shared_info;
|
|
|
|
/*
|
|
* Flag to determine whether vcpu info placement is available on all
|
|
* VCPUs. We assume it is to start with, and then set it to zero on
|
|
* the first failure. This is because it can succeed on some VCPUs
|
|
* and not others, since it can involve hypervisor memory allocation,
|
|
* or because the guest failed to guarantee all the appropriate
|
|
* constraints on all VCPUs (ie buffer can't cross a page boundary).
|
|
*
|
|
* Note that any particular CPU may be using a placed vcpu structure,
|
|
* but we can only optimise if the all are.
|
|
*
|
|
* 0: not available, 1: available
|
|
*/
|
|
int xen_have_vcpu_info_placement = 1;
|
|
|
|
static int xen_cpu_up_online(unsigned int cpu)
|
|
{
|
|
xen_init_lock_cpu(cpu);
|
|
return 0;
|
|
}
|
|
|
|
int xen_cpuhp_setup(int (*cpu_up_prepare_cb)(unsigned int),
|
|
int (*cpu_dead_cb)(unsigned int))
|
|
{
|
|
int rc;
|
|
|
|
rc = cpuhp_setup_state_nocalls(CPUHP_XEN_PREPARE,
|
|
"x86/xen/guest:prepare",
|
|
cpu_up_prepare_cb, cpu_dead_cb);
|
|
if (rc >= 0) {
|
|
rc = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN,
|
|
"x86/xen/guest:online",
|
|
xen_cpu_up_online, NULL);
|
|
if (rc < 0)
|
|
cpuhp_remove_state_nocalls(CPUHP_XEN_PREPARE);
|
|
}
|
|
|
|
return rc >= 0 ? 0 : rc;
|
|
}
|
|
|
|
static int xen_vcpu_setup_restore(int cpu)
|
|
{
|
|
int rc = 0;
|
|
|
|
/* Any per_cpu(xen_vcpu) is stale, so reset it */
|
|
xen_vcpu_info_reset(cpu);
|
|
|
|
/*
|
|
* For PVH and PVHVM, setup online VCPUs only. The rest will
|
|
* be handled by hotplug.
|
|
*/
|
|
if (xen_pv_domain() ||
|
|
(xen_hvm_domain() && cpu_online(cpu))) {
|
|
rc = xen_vcpu_setup(cpu);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* On restore, set the vcpu placement up again.
|
|
* If it fails, then we're in a bad state, since
|
|
* we can't back out from using it...
|
|
*/
|
|
void xen_vcpu_restore(void)
|
|
{
|
|
int cpu, rc;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
bool other_cpu = (cpu != smp_processor_id());
|
|
bool is_up;
|
|
|
|
if (xen_vcpu_nr(cpu) == XEN_VCPU_ID_INVALID)
|
|
continue;
|
|
|
|
/* Only Xen 4.5 and higher support this. */
|
|
is_up = HYPERVISOR_vcpu_op(VCPUOP_is_up,
|
|
xen_vcpu_nr(cpu), NULL) > 0;
|
|
|
|
if (other_cpu && is_up &&
|
|
HYPERVISOR_vcpu_op(VCPUOP_down, xen_vcpu_nr(cpu), NULL))
|
|
BUG();
|
|
|
|
if (xen_pv_domain() || xen_feature(XENFEAT_hvm_safe_pvclock))
|
|
xen_setup_runstate_info(cpu);
|
|
|
|
rc = xen_vcpu_setup_restore(cpu);
|
|
if (rc)
|
|
pr_emerg_once("vcpu restore failed for cpu=%d err=%d. "
|
|
"System will hang.\n", cpu, rc);
|
|
/*
|
|
* In case xen_vcpu_setup_restore() fails, do not bring up the
|
|
* VCPU. This helps us avoid the resulting OOPS when the VCPU
|
|
* accesses pvclock_vcpu_time via xen_vcpu (which is NULL.)
|
|
* Note that this does not improve the situation much -- now the
|
|
* VM hangs instead of OOPSing -- with the VCPUs that did not
|
|
* fail, spinning in stop_machine(), waiting for the failed
|
|
* VCPUs to come up.
|
|
*/
|
|
if (other_cpu && is_up && (rc == 0) &&
|
|
HYPERVISOR_vcpu_op(VCPUOP_up, xen_vcpu_nr(cpu), NULL))
|
|
BUG();
|
|
}
|
|
}
|
|
|
|
void xen_vcpu_info_reset(int cpu)
|
|
{
|
|
if (xen_vcpu_nr(cpu) < MAX_VIRT_CPUS) {
|
|
per_cpu(xen_vcpu, cpu) =
|
|
&HYPERVISOR_shared_info->vcpu_info[xen_vcpu_nr(cpu)];
|
|
} else {
|
|
/* Set to NULL so that if somebody accesses it we get an OOPS */
|
|
per_cpu(xen_vcpu, cpu) = NULL;
|
|
}
|
|
}
|
|
|
|
int xen_vcpu_setup(int cpu)
|
|
{
|
|
struct vcpu_register_vcpu_info info;
|
|
int err;
|
|
struct vcpu_info *vcpup;
|
|
|
|
BUG_ON(HYPERVISOR_shared_info == &xen_dummy_shared_info);
|
|
|
|
/*
|
|
* This path is called on PVHVM at bootup (xen_hvm_smp_prepare_boot_cpu)
|
|
* and at restore (xen_vcpu_restore). Also called for hotplugged
|
|
* VCPUs (cpu_init -> xen_hvm_cpu_prepare_hvm).
|
|
* However, the hypercall can only be done once (see below) so if a VCPU
|
|
* is offlined and comes back online then let's not redo the hypercall.
|
|
*
|
|
* For PV it is called during restore (xen_vcpu_restore) and bootup
|
|
* (xen_setup_vcpu_info_placement). The hotplug mechanism does not
|
|
* use this function.
|
|
*/
|
|
if (xen_hvm_domain()) {
|
|
if (per_cpu(xen_vcpu, cpu) == &per_cpu(xen_vcpu_info, cpu))
|
|
return 0;
|
|
}
|
|
|
|
if (xen_have_vcpu_info_placement) {
|
|
vcpup = &per_cpu(xen_vcpu_info, cpu);
|
|
info.mfn = arbitrary_virt_to_mfn(vcpup);
|
|
info.offset = offset_in_page(vcpup);
|
|
|
|
/*
|
|
* Check to see if the hypervisor will put the vcpu_info
|
|
* structure where we want it, which allows direct access via
|
|
* a percpu-variable.
|
|
* N.B. This hypercall can _only_ be called once per CPU.
|
|
* Subsequent calls will error out with -EINVAL. This is due to
|
|
* the fact that hypervisor has no unregister variant and this
|
|
* hypercall does not allow to over-write info.mfn and
|
|
* info.offset.
|
|
*/
|
|
err = HYPERVISOR_vcpu_op(VCPUOP_register_vcpu_info,
|
|
xen_vcpu_nr(cpu), &info);
|
|
|
|
if (err) {
|
|
pr_warn_once("register_vcpu_info failed: cpu=%d err=%d\n",
|
|
cpu, err);
|
|
xen_have_vcpu_info_placement = 0;
|
|
} else {
|
|
/*
|
|
* This cpu is using the registered vcpu info, even if
|
|
* later ones fail to.
|
|
*/
|
|
per_cpu(xen_vcpu, cpu) = vcpup;
|
|
}
|
|
}
|
|
|
|
if (!xen_have_vcpu_info_placement)
|
|
xen_vcpu_info_reset(cpu);
|
|
|
|
return ((per_cpu(xen_vcpu, cpu) == NULL) ? -ENODEV : 0);
|
|
}
|
|
|
|
void xen_reboot(int reason)
|
|
{
|
|
struct sched_shutdown r = { .reason = reason };
|
|
int cpu;
|
|
|
|
for_each_online_cpu(cpu)
|
|
xen_pmu_finish(cpu);
|
|
|
|
if (HYPERVISOR_sched_op(SCHEDOP_shutdown, &r))
|
|
BUG();
|
|
}
|
|
|
|
void xen_emergency_restart(void)
|
|
{
|
|
xen_reboot(SHUTDOWN_reboot);
|
|
}
|
|
|
|
static int
|
|
xen_panic_event(struct notifier_block *this, unsigned long event, void *ptr)
|
|
{
|
|
if (!kexec_crash_loaded())
|
|
xen_reboot(SHUTDOWN_crash);
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block xen_panic_block = {
|
|
.notifier_call = xen_panic_event,
|
|
.priority = INT_MIN
|
|
};
|
|
|
|
int xen_panic_handler_init(void)
|
|
{
|
|
atomic_notifier_chain_register(&panic_notifier_list, &xen_panic_block);
|
|
return 0;
|
|
}
|
|
|
|
void xen_pin_vcpu(int cpu)
|
|
{
|
|
static bool disable_pinning;
|
|
struct sched_pin_override pin_override;
|
|
int ret;
|
|
|
|
if (disable_pinning)
|
|
return;
|
|
|
|
pin_override.pcpu = cpu;
|
|
ret = HYPERVISOR_sched_op(SCHEDOP_pin_override, &pin_override);
|
|
|
|
/* Ignore errors when removing override. */
|
|
if (cpu < 0)
|
|
return;
|
|
|
|
switch (ret) {
|
|
case -ENOSYS:
|
|
pr_warn("Unable to pin on physical cpu %d. In case of problems consider vcpu pinning.\n",
|
|
cpu);
|
|
disable_pinning = true;
|
|
break;
|
|
case -EPERM:
|
|
WARN(1, "Trying to pin vcpu without having privilege to do so\n");
|
|
disable_pinning = true;
|
|
break;
|
|
case -EINVAL:
|
|
case -EBUSY:
|
|
pr_warn("Physical cpu %d not available for pinning. Check Xen cpu configuration.\n",
|
|
cpu);
|
|
break;
|
|
case 0:
|
|
break;
|
|
default:
|
|
WARN(1, "rc %d while trying to pin vcpu\n", ret);
|
|
disable_pinning = true;
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_HOTPLUG_CPU
|
|
void xen_arch_register_cpu(int num)
|
|
{
|
|
arch_register_cpu(num);
|
|
}
|
|
EXPORT_SYMBOL(xen_arch_register_cpu);
|
|
|
|
void xen_arch_unregister_cpu(int num)
|
|
{
|
|
arch_unregister_cpu(num);
|
|
}
|
|
EXPORT_SYMBOL(xen_arch_unregister_cpu);
|
|
#endif
|
|
|
|
#ifdef CONFIG_XEN_BALLOON_MEMORY_HOTPLUG
|
|
void __init arch_xen_balloon_init(struct resource *hostmem_resource)
|
|
{
|
|
struct xen_memory_map memmap;
|
|
int rc;
|
|
unsigned int i, last_guest_ram;
|
|
phys_addr_t max_addr = PFN_PHYS(max_pfn);
|
|
struct e820_table *xen_e820_table;
|
|
const struct e820_entry *entry;
|
|
struct resource *res;
|
|
|
|
if (!xen_initial_domain())
|
|
return;
|
|
|
|
xen_e820_table = kmalloc(sizeof(*xen_e820_table), GFP_KERNEL);
|
|
if (!xen_e820_table)
|
|
return;
|
|
|
|
memmap.nr_entries = ARRAY_SIZE(xen_e820_table->entries);
|
|
set_xen_guest_handle(memmap.buffer, xen_e820_table->entries);
|
|
rc = HYPERVISOR_memory_op(XENMEM_machine_memory_map, &memmap);
|
|
if (rc) {
|
|
pr_warn("%s: Can't read host e820 (%d)\n", __func__, rc);
|
|
goto out;
|
|
}
|
|
|
|
last_guest_ram = 0;
|
|
for (i = 0; i < memmap.nr_entries; i++) {
|
|
if (xen_e820_table->entries[i].addr >= max_addr)
|
|
break;
|
|
if (xen_e820_table->entries[i].type == E820_TYPE_RAM)
|
|
last_guest_ram = i;
|
|
}
|
|
|
|
entry = &xen_e820_table->entries[last_guest_ram];
|
|
if (max_addr >= entry->addr + entry->size)
|
|
goto out; /* No unallocated host RAM. */
|
|
|
|
hostmem_resource->start = max_addr;
|
|
hostmem_resource->end = entry->addr + entry->size;
|
|
|
|
/*
|
|
* Mark non-RAM regions between the end of dom0 RAM and end of host RAM
|
|
* as unavailable. The rest of that region can be used for hotplug-based
|
|
* ballooning.
|
|
*/
|
|
for (; i < memmap.nr_entries; i++) {
|
|
entry = &xen_e820_table->entries[i];
|
|
|
|
if (entry->type == E820_TYPE_RAM)
|
|
continue;
|
|
|
|
if (entry->addr >= hostmem_resource->end)
|
|
break;
|
|
|
|
res = kzalloc(sizeof(*res), GFP_KERNEL);
|
|
if (!res)
|
|
goto out;
|
|
|
|
res->name = "Unavailable host RAM";
|
|
res->start = entry->addr;
|
|
res->end = (entry->addr + entry->size < hostmem_resource->end) ?
|
|
entry->addr + entry->size : hostmem_resource->end;
|
|
rc = insert_resource(hostmem_resource, res);
|
|
if (rc) {
|
|
pr_warn("%s: Can't insert [%llx - %llx) (%d)\n",
|
|
__func__, res->start, res->end, rc);
|
|
kfree(res);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
out:
|
|
kfree(xen_e820_table);
|
|
}
|
|
#endif /* CONFIG_XEN_BALLOON_MEMORY_HOTPLUG */
|