--- /dev/null
+/*
+ * 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);