blob: 6d1a77048f9a2f22d59f362c150a9940b7a02868 [file] [log] [blame]
/*
* pcpu.c - management physical cpu in dom0 environment
*/
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <asm/xen/hypervisor.h>
#include <asm/xen/hypercall.h>
#include <linux/cpu.h>
#include <xen/xenbus.h>
#include <xen/pcpu.h>
#include <xen/events.h>
#include <xen/acpi.h>
static struct sysdev_class xen_pcpu_sysdev_class = {
.name = "xen_pcpu",
};
static DEFINE_MUTEX(xen_pcpu_lock);
static RAW_NOTIFIER_HEAD(xen_pcpu_chain);
/* No need for irq disable since hotplug notify is in workqueue context */
#define get_pcpu_lock() mutex_lock(&xen_pcpu_lock);
#define put_pcpu_lock() mutex_unlock(&xen_pcpu_lock);
struct xen_pcpus {
struct list_head list;
int present;
};
static struct xen_pcpus xen_pcpus;
int register_xen_pcpu_notifier(struct notifier_block *nb)
{
int ret;
/* All refer to the chain notifier is protected by the pcpu_lock */
get_pcpu_lock();
ret = raw_notifier_chain_register(&xen_pcpu_chain, nb);
put_pcpu_lock();
return ret;
}
EXPORT_SYMBOL_GPL(register_xen_pcpu_notifier);
void unregister_xen_pcpu_notifier(struct notifier_block *nb)
{
get_pcpu_lock();
raw_notifier_chain_unregister(&xen_pcpu_chain, nb);
put_pcpu_lock();
}
EXPORT_SYMBOL_GPL(unregister_xen_pcpu_notifier);
static int xen_pcpu_down(uint32_t xen_id)
{
int ret;
xen_platform_op_t op = {
.cmd = XENPF_cpu_offline,
.interface_version = XENPF_INTERFACE_VERSION,
.u.cpu_ol.cpuid = xen_id,
};
ret = HYPERVISOR_dom0_op(&op);
return ret;
}
static int xen_pcpu_up(uint32_t xen_id)
{
int ret;
xen_platform_op_t op = {
.cmd = XENPF_cpu_online,
.interface_version = XENPF_INTERFACE_VERSION,
.u.cpu_ol.cpuid = xen_id,
};
ret = HYPERVISOR_dom0_op(&op);
return ret;
}
static ssize_t show_online(struct sys_device *dev,
struct sysdev_attribute *attr,
char *buf)
{
struct pcpu *cpu = container_of(dev, struct pcpu, sysdev);
return sprintf(buf, "%u\n", !!(cpu->flags & XEN_PCPU_FLAGS_ONLINE));
}
static ssize_t __ref store_online(struct sys_device *dev,
struct sysdev_attribute *attr,
const char *buf, size_t count)
{
struct pcpu *cpu = container_of(dev, struct pcpu, sysdev);
ssize_t ret;
switch (buf[0]) {
case '0':
ret = xen_pcpu_down(cpu->xen_id);
break;
case '1':
ret = xen_pcpu_up(cpu->xen_id);
break;
default:
ret = -EINVAL;
}
if (ret >= 0)
ret = count;
return ret;
}
static SYSDEV_ATTR(online, 0644, show_online, store_online);
static ssize_t show_apicid(struct sys_device *dev,
struct sysdev_attribute *attr,
char *buf)
{
struct pcpu *cpu = container_of(dev, struct pcpu, sysdev);
return sprintf(buf, "%u\n", cpu->apic_id);
}
static ssize_t show_acpiid(struct sys_device *dev,
struct sysdev_attribute *attr,
char *buf)
{
struct pcpu *cpu = container_of(dev, struct pcpu, sysdev);
return sprintf(buf, "%u\n", cpu->acpi_id);
}
static SYSDEV_ATTR(apic_id, 0444, show_apicid, NULL);
static SYSDEV_ATTR(acpi_id, 0444, show_acpiid, NULL);
static int xen_pcpu_free(struct pcpu *pcpu)
{
if (!pcpu)
return 0;
sysdev_remove_file(&pcpu->sysdev, &attr_online);
sysdev_unregister(&pcpu->sysdev);
list_del(&pcpu->pcpu_list);
kfree(pcpu);
return 0;
}
static inline int same_pcpu(struct xenpf_pcpuinfo *info,
struct pcpu *pcpu)
{
return (pcpu->apic_id == info->apic_id) &&
(pcpu->xen_id == info->xen_cpuid);
}
/*
* Return 1 if online status changed
*/
static int xen_pcpu_online_check(struct xenpf_pcpuinfo *info,
struct pcpu *pcpu)
{
int result = 0;
if (info->xen_cpuid != pcpu->xen_id)
return 0;
if (xen_pcpu_online(info->flags) && !xen_pcpu_online(pcpu->flags)) {
/* the pcpu is onlined */
pcpu->flags |= XEN_PCPU_FLAGS_ONLINE;
kobject_uevent(&pcpu->sysdev.kobj, KOBJ_ONLINE);
raw_notifier_call_chain(&xen_pcpu_chain,
XEN_PCPU_ONLINE, (void *)(long)pcpu->xen_id);
result = 1;
} else if (!xen_pcpu_online(info->flags) &&
xen_pcpu_online(pcpu->flags)) {
/* The pcpu is offlined now */
pcpu->flags &= ~XEN_PCPU_FLAGS_ONLINE;
kobject_uevent(&pcpu->sysdev.kobj, KOBJ_OFFLINE);
raw_notifier_call_chain(&xen_pcpu_chain,
XEN_PCPU_OFFLINE, (void *)(long)pcpu->xen_id);
result = 1;
}
return result;
}
static int pcpu_sysdev_init(struct pcpu *cpu)
{
int error;
error = sysdev_register(&cpu->sysdev);
if (error) {
printk(KERN_WARNING "xen_pcpu_add: Failed to register pcpu\n");
kfree(cpu);
return -1;
}
sysdev_create_file(&cpu->sysdev, &attr_online);
sysdev_create_file(&cpu->sysdev, &attr_apic_id);
sysdev_create_file(&cpu->sysdev, &attr_acpi_id);
return 0;
}
static struct pcpu *get_pcpu(int xen_id)
{
struct pcpu *pcpu = NULL;
list_for_each_entry(pcpu, &xen_pcpus.list, pcpu_list) {
if (pcpu->xen_id == xen_id)
return pcpu;
}
return NULL;
}
static struct pcpu *init_pcpu(struct xenpf_pcpuinfo *info)
{
struct pcpu *pcpu;
if (info->flags & XEN_PCPU_FLAGS_INVALID)
return NULL;
/* The PCPU is just added */
pcpu = kzalloc(sizeof(struct pcpu), GFP_KERNEL);
if (!pcpu)
return NULL;
INIT_LIST_HEAD(&pcpu->pcpu_list);
pcpu->xen_id = info->xen_cpuid;
pcpu->apic_id = info->apic_id;
pcpu->acpi_id = info->acpi_id;
pcpu->flags = info->flags;
pcpu->sysdev.cls = &xen_pcpu_sysdev_class;
pcpu->sysdev.id = info->xen_cpuid;
if (pcpu_sysdev_init(pcpu)) {
kfree(pcpu);
return NULL;
}
list_add_tail(&pcpu->pcpu_list, &xen_pcpus.list);
raw_notifier_call_chain(&xen_pcpu_chain,
XEN_PCPU_ADD,
(void *)(long)pcpu->xen_id);
return pcpu;
}
#define PCPU_NO_CHANGE 0
#define PCPU_ADDED 1
#define PCPU_ONLINE_OFFLINE 2
#define PCPU_REMOVED 3
/*
* Caller should hold the pcpu lock
* < 0: Something wrong
* 0: No changes
* > 0: State changed
*/
static struct pcpu *_sync_pcpu(int cpu_num, int *max_id, int *result)
{
struct pcpu *pcpu = NULL;
struct xenpf_pcpuinfo *info;
xen_platform_op_t op = {
.cmd = XENPF_get_cpuinfo,
.interface_version = XENPF_INTERFACE_VERSION,
};
int ret;
*result = -1;
info = &op.u.pcpu_info;
info->xen_cpuid = cpu_num;
ret = HYPERVISOR_dom0_op(&op);
if (ret)
return NULL;
if (max_id)
*max_id = op.u.pcpu_info.max_present;
pcpu = get_pcpu(cpu_num);
if (info->flags & XEN_PCPU_FLAGS_INVALID) {
/* The pcpu has been removed */
*result = PCPU_NO_CHANGE;
if (pcpu) {
raw_notifier_call_chain(&xen_pcpu_chain,
XEN_PCPU_REMOVE,
(void *)(long)pcpu->xen_id);
xen_pcpu_free(pcpu);
*result = PCPU_REMOVED;
}
return NULL;
}
if (!pcpu) {
*result = PCPU_ADDED;
pcpu = init_pcpu(info);
if (pcpu == NULL) {
printk(KERN_WARNING "Failed to init pcpu %x\n",
info->xen_cpuid);
*result = -1;
}
} else {
*result = PCPU_NO_CHANGE;
/*
* Old PCPU is replaced with a new pcpu, this means
* several virq is missed, will it happen?
*/
if (!same_pcpu(info, pcpu)) {
printk(KERN_WARNING "Pcpu %x changed!\n",
pcpu->xen_id);
pcpu->apic_id = info->apic_id;
pcpu->acpi_id = info->acpi_id;
}
if (xen_pcpu_online_check(info, pcpu))
*result = PCPU_ONLINE_OFFLINE;
}
return pcpu;
}
int xen_pcpu_index(uint32_t id, int is_acpiid)
{
int cpu_num = 0, max_id = 0, ret;
xen_platform_op_t op = {
.cmd = XENPF_get_cpuinfo,
.interface_version = XENPF_INTERFACE_VERSION,
};
struct xenpf_pcpuinfo *info = &op.u.pcpu_info;
info->xen_cpuid = 0;
ret = HYPERVISOR_dom0_op(&op);
if (ret)
return -1;
max_id = op.u.pcpu_info.max_present;
while ((cpu_num <= max_id)) {
info->xen_cpuid = cpu_num;
ret = HYPERVISOR_dom0_op(&op);
if (ret)
continue;
if (op.u.pcpu_info.max_present > max_id)
max_id = op.u.pcpu_info.max_present;
if (id == (is_acpiid ? info->acpi_id : info->apic_id))
return cpu_num;
cpu_num++;
}
return -1;
}
EXPORT_SYMBOL(xen_pcpu_index);
/*
* Sync dom0's pcpu information with xen hypervisor's
*/
static int xen_sync_pcpus(void)
{
/*
* Boot cpu always have cpu_id 0 in xen
*/
int cpu_num = 0, max_id = 0, result = 0, present = 0;
struct list_head *elem, *tmp;
struct pcpu *pcpu;
get_pcpu_lock();
while ((result >= 0) && (cpu_num <= max_id)) {
pcpu = _sync_pcpu(cpu_num, &max_id, &result);
printk(KERN_DEBUG "sync cpu %x get result %x max_id %x\n",
cpu_num, result, max_id);
switch (result) {
case PCPU_NO_CHANGE:
if (pcpu)
present++;
break;
case PCPU_ADDED:
case PCPU_ONLINE_OFFLINE:
present++;
case PCPU_REMOVED:
break;
default:
printk(KERN_WARNING "Failed to sync pcpu %x\n",
cpu_num);
break;
}
cpu_num++;
}
if (result < 0) {
list_for_each_safe(elem, tmp, &xen_pcpus.list) {
pcpu = list_entry(elem, struct pcpu, pcpu_list);
xen_pcpu_free(pcpu);
}
present = 0;
}
xen_pcpus.present = present;
put_pcpu_lock();
return 0;
}
static void xen_pcpu_dpc(struct work_struct *work)
{
if (xen_sync_pcpus() < 0)
printk(KERN_WARNING
"xen_pcpu_dpc: Failed to sync pcpu information\n");
}
static DECLARE_WORK(xen_pcpu_work, xen_pcpu_dpc);
int xen_pcpu_hotplug(int type, uint32_t apic_id)
{
schedule_work(&xen_pcpu_work);
return 0;
}
EXPORT_SYMBOL(xen_pcpu_hotplug);
static irqreturn_t xen_pcpu_interrupt(int irq, void *dev_id)
{
schedule_work(&xen_pcpu_work);
return IRQ_HANDLED;
}
static int __init xen_pcpu_init(void)
{
int err;
if (!xen_initial_domain())
return 0;
err = sysdev_class_register(&xen_pcpu_sysdev_class);
if (err) {
printk(KERN_WARNING
"xen_pcpu_init: register xen_pcpu sysdev Failed!\n");
return err;
}
INIT_LIST_HEAD(&xen_pcpus.list);
xen_pcpus.present = 0;
xen_sync_pcpus();
if (xen_pcpus.present > 0)
err = bind_virq_to_irqhandler(VIRQ_PCPU_STATE,
0, xen_pcpu_interrupt, 0, "pcpu", NULL);
if (err < 0)
printk(KERN_WARNING "xen_pcpu_init: "
"Failed to bind pcpu_state virq\n"
"You will lost latest information! \n");
return err;
}
arch_initcall(xen_pcpu_init);