diff options
Diffstat (limited to 'drivers/xen/pcpu.c')
| -rw-r--r-- | drivers/xen/pcpu.c | 406 | 
1 files changed, 406 insertions, 0 deletions
diff --git a/drivers/xen/pcpu.c b/drivers/xen/pcpu.c new file mode 100644 index 00000000000..0aac403d53f --- /dev/null +++ b/drivers/xen/pcpu.c @@ -0,0 +1,406 @@ +/****************************************************************************** + * pcpu.c + * Management physical cpu in dom0, get pcpu info and provide sys interface + * + * Copyright (c) 2012 Intel Corporation + * Author: Liu, Jinsong <jinsong.liu@intel.com> + * Author: Jiang, Yunhong <yunhong.jiang@intel.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation; or, when distributed + * separately from the Linux kernel or incorporated into other + * software packages, subject to the following license: + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this source file (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#define pr_fmt(fmt) "xen_cpu: " fmt + +#include <linux/interrupt.h> +#include <linux/spinlock.h> +#include <linux/cpu.h> +#include <linux/stat.h> +#include <linux/capability.h> + +#include <xen/xen.h> +#include <xen/acpi.h> +#include <xen/xenbus.h> +#include <xen/events.h> +#include <xen/interface/platform.h> +#include <asm/xen/hypervisor.h> +#include <asm/xen/hypercall.h> + + +/* + * @cpu_id: Xen physical cpu logic number + * @flags: Xen physical cpu status flag + * - XEN_PCPU_FLAGS_ONLINE: cpu is online + * - XEN_PCPU_FLAGS_INVALID: cpu is not present + */ +struct pcpu { +	struct list_head list; +	struct device dev; +	uint32_t cpu_id; +	uint32_t flags; +}; + +static struct bus_type xen_pcpu_subsys = { +	.name = "xen_cpu", +	.dev_name = "xen_cpu", +}; + +static DEFINE_MUTEX(xen_pcpu_lock); + +static LIST_HEAD(xen_pcpus); + +static int xen_pcpu_down(uint32_t cpu_id) +{ +	struct xen_platform_op op = { +		.cmd			= XENPF_cpu_offline, +		.interface_version	= XENPF_INTERFACE_VERSION, +		.u.cpu_ol.cpuid		= cpu_id, +	}; + +	return HYPERVISOR_dom0_op(&op); +} + +static int xen_pcpu_up(uint32_t cpu_id) +{ +	struct xen_platform_op op = { +		.cmd			= XENPF_cpu_online, +		.interface_version	= XENPF_INTERFACE_VERSION, +		.u.cpu_ol.cpuid		= cpu_id, +	}; + +	return HYPERVISOR_dom0_op(&op); +} + +static ssize_t show_online(struct device *dev, +			   struct device_attribute *attr, +			   char *buf) +{ +	struct pcpu *cpu = container_of(dev, struct pcpu, dev); + +	return sprintf(buf, "%u\n", !!(cpu->flags & XEN_PCPU_FLAGS_ONLINE)); +} + +static ssize_t __ref store_online(struct device *dev, +				  struct device_attribute *attr, +				  const char *buf, size_t count) +{ +	struct pcpu *pcpu = container_of(dev, struct pcpu, dev); +	unsigned long long val; +	ssize_t ret; + +	if (!capable(CAP_SYS_ADMIN)) +		return -EPERM; + +	if (kstrtoull(buf, 0, &val) < 0) +		return -EINVAL; + +	switch (val) { +	case 0: +		ret = xen_pcpu_down(pcpu->cpu_id); +		break; +	case 1: +		ret = xen_pcpu_up(pcpu->cpu_id); +		break; +	default: +		ret = -EINVAL; +	} + +	if (ret >= 0) +		ret = count; +	return ret; +} +static DEVICE_ATTR(online, S_IRUGO | S_IWUSR, show_online, store_online); + +static bool xen_pcpu_online(uint32_t flags) +{ +	return !!(flags & XEN_PCPU_FLAGS_ONLINE); +} + +static void pcpu_online_status(struct xenpf_pcpuinfo *info, +			       struct pcpu *pcpu) +{ +	if (xen_pcpu_online(info->flags) && +	   !xen_pcpu_online(pcpu->flags)) { +		/* the pcpu is onlined */ +		pcpu->flags |= XEN_PCPU_FLAGS_ONLINE; +		kobject_uevent(&pcpu->dev.kobj, KOBJ_ONLINE); +	} else if (!xen_pcpu_online(info->flags) && +		    xen_pcpu_online(pcpu->flags)) { +		/* The pcpu is offlined */ +		pcpu->flags &= ~XEN_PCPU_FLAGS_ONLINE; +		kobject_uevent(&pcpu->dev.kobj, KOBJ_OFFLINE); +	} +} + +static struct pcpu *get_pcpu(uint32_t cpu_id) +{ +	struct pcpu *pcpu; + +	list_for_each_entry(pcpu, &xen_pcpus, list) { +		if (pcpu->cpu_id == cpu_id) +			return pcpu; +	} + +	return NULL; +} + +static void pcpu_release(struct device *dev) +{ +	struct pcpu *pcpu = container_of(dev, struct pcpu, dev); + +	list_del(&pcpu->list); +	kfree(pcpu); +} + +static void unregister_and_remove_pcpu(struct pcpu *pcpu) +{ +	struct device *dev; + +	if (!pcpu) +		return; + +	dev = &pcpu->dev; +	if (dev->id) +		device_remove_file(dev, &dev_attr_online); + +	/* pcpu remove would be implicitly done */ +	device_unregister(dev); +} + +static int register_pcpu(struct pcpu *pcpu) +{ +	struct device *dev; +	int err = -EINVAL; + +	if (!pcpu) +		return err; + +	dev = &pcpu->dev; +	dev->bus = &xen_pcpu_subsys; +	dev->id = pcpu->cpu_id; +	dev->release = pcpu_release; + +	err = device_register(dev); +	if (err) { +		pcpu_release(dev); +		return err; +	} + +	/* +	 * Xen never offline cpu0 due to several restrictions +	 * and assumptions. This basically doesn't add a sys control +	 * to user, one cannot attempt to offline BSP. +	 */ +	if (dev->id) { +		err = device_create_file(dev, &dev_attr_online); +		if (err) { +			device_unregister(dev); +			return err; +		} +	} + +	return 0; +} + +static struct pcpu *create_and_register_pcpu(struct xenpf_pcpuinfo *info) +{ +	struct pcpu *pcpu; +	int err; + +	if (info->flags & XEN_PCPU_FLAGS_INVALID) +		return ERR_PTR(-ENODEV); + +	pcpu = kzalloc(sizeof(struct pcpu), GFP_KERNEL); +	if (!pcpu) +		return ERR_PTR(-ENOMEM); + +	INIT_LIST_HEAD(&pcpu->list); +	pcpu->cpu_id = info->xen_cpuid; +	pcpu->flags = info->flags; + +	/* Need hold on xen_pcpu_lock before pcpu list manipulations */ +	list_add_tail(&pcpu->list, &xen_pcpus); + +	err = register_pcpu(pcpu); +	if (err) { +		pr_warn("Failed to register pcpu%u\n", info->xen_cpuid); +		return ERR_PTR(-ENOENT); +	} + +	return pcpu; +} + +/* + * Caller should hold the xen_pcpu_lock + */ +static int sync_pcpu(uint32_t cpu, uint32_t *max_cpu) +{ +	int ret; +	struct pcpu *pcpu = NULL; +	struct xenpf_pcpuinfo *info; +	struct xen_platform_op op = { +		.cmd                   = XENPF_get_cpuinfo, +		.interface_version     = XENPF_INTERFACE_VERSION, +		.u.pcpu_info.xen_cpuid = cpu, +	}; + +	ret = HYPERVISOR_dom0_op(&op); +	if (ret) +		return ret; + +	info = &op.u.pcpu_info; +	if (max_cpu) +		*max_cpu = info->max_present; + +	pcpu = get_pcpu(cpu); + +	/* +	 * Only those at cpu present map has its sys interface. +	 */ +	if (info->flags & XEN_PCPU_FLAGS_INVALID) { +		unregister_and_remove_pcpu(pcpu); +		return 0; +	} + +	if (!pcpu) { +		pcpu = create_and_register_pcpu(info); +		if (IS_ERR_OR_NULL(pcpu)) +			return -ENODEV; +	} else +		pcpu_online_status(info, pcpu); + +	return 0; +} + +/* + * Sync dom0's pcpu information with xen hypervisor's + */ +static int xen_sync_pcpus(void) +{ +	/* +	 * Boot cpu always have cpu_id 0 in xen +	 */ +	uint32_t cpu = 0, max_cpu = 0; +	int err = 0; +	struct pcpu *pcpu, *tmp; + +	mutex_lock(&xen_pcpu_lock); + +	while (!err && (cpu <= max_cpu)) { +		err = sync_pcpu(cpu, &max_cpu); +		cpu++; +	} + +	if (err) +		list_for_each_entry_safe(pcpu, tmp, &xen_pcpus, list) +			unregister_and_remove_pcpu(pcpu); + +	mutex_unlock(&xen_pcpu_lock); + +	return err; +} + +static void xen_pcpu_work_fn(struct work_struct *work) +{ +	xen_sync_pcpus(); +} +static DECLARE_WORK(xen_pcpu_work, xen_pcpu_work_fn); + +static irqreturn_t xen_pcpu_interrupt(int irq, void *dev_id) +{ +	schedule_work(&xen_pcpu_work); +	return IRQ_HANDLED; +} + +/* Sync with Xen hypervisor after cpu hotadded */ +void xen_pcpu_hotplug_sync(void) +{ +	schedule_work(&xen_pcpu_work); +} +EXPORT_SYMBOL_GPL(xen_pcpu_hotplug_sync); + +/* + * For hypervisor presented cpu, return logic cpu id; + * For hypervisor non-presented cpu, return -ENODEV. + */ +int xen_pcpu_id(uint32_t acpi_id) +{ +	int cpu_id = 0, max_id = 0; +	struct xen_platform_op op; + +	op.cmd = XENPF_get_cpuinfo; +	while (cpu_id <= max_id) { +		op.u.pcpu_info.xen_cpuid = cpu_id; +		if (HYPERVISOR_dom0_op(&op)) { +			cpu_id++; +			continue; +		} + +		if (acpi_id == op.u.pcpu_info.acpi_id) +			return cpu_id; +		if (op.u.pcpu_info.max_present > max_id) +			max_id = op.u.pcpu_info.max_present; +		cpu_id++; +	} + +	return -ENODEV; +} +EXPORT_SYMBOL_GPL(xen_pcpu_id); + +static int __init xen_pcpu_init(void) +{ +	int irq, ret; + +	if (!xen_initial_domain()) +		return -ENODEV; + +	irq = bind_virq_to_irqhandler(VIRQ_PCPU_STATE, 0, +				      xen_pcpu_interrupt, 0, +				      "xen-pcpu", NULL); +	if (irq < 0) { +		pr_warn("Failed to bind pcpu virq\n"); +		return irq; +	} + +	ret = subsys_system_register(&xen_pcpu_subsys, NULL); +	if (ret) { +		pr_warn("Failed to register pcpu subsys\n"); +		goto err1; +	} + +	ret = xen_sync_pcpus(); +	if (ret) { +		pr_warn("Failed to sync pcpu info\n"); +		goto err2; +	} + +	return 0; + +err2: +	bus_unregister(&xen_pcpu_subsys); +err1: +	unbind_from_irqhandler(irq, NULL); +	return ret; +} +arch_initcall(xen_pcpu_init);  | 
