|
@@ -0,0 +1,471 @@
|
|
|
+/*
|
|
|
+ * 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 as published by
|
|
|
+ * the Free Software Foundation; either version 2 of the License, or (at
|
|
|
+ * your option) any later version.
|
|
|
+ *
|
|
|
+ * This program is distributed in the hope that it will be useful, but
|
|
|
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
|
|
|
+ * NON INFRINGEMENT. See the GNU General Public License for more
|
|
|
+ * details.
|
|
|
+ */
|
|
|
+
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/init.h>
|
|
|
+#include <linux/types.h>
|
|
|
+#include <linux/cpu.h>
|
|
|
+#include <linux/acpi.h>
|
|
|
+#include <linux/uaccess.h>
|
|
|
+#include <acpi/acpi_bus.h>
|
|
|
+#include <acpi/acpi_drivers.h>
|
|
|
+#include <acpi/processor.h>
|
|
|
+
|
|
|
+#include <xen/acpi.h>
|
|
|
+#include <xen/interface/platform.h>
|
|
|
+#include <asm/xen/hypercall.h>
|
|
|
+
|
|
|
+#define PREFIX "ACPI:xen_cpu_hotplug:"
|
|
|
+
|
|
|
+#define INSTALL_NOTIFY_HANDLER 0
|
|
|
+#define UNINSTALL_NOTIFY_HANDLER 1
|
|
|
+
|
|
|
+static acpi_status xen_acpi_cpu_hotadd(struct acpi_processor *pr);
|
|
|
+
|
|
|
+/* --------------------------------------------------------------------------
|
|
|
+ Driver Interface
|
|
|
+-------------------------------------------------------------------------- */
|
|
|
+
|
|
|
+static int xen_acpi_processor_enable(struct acpi_device *device)
|
|
|
+{
|
|
|
+ acpi_status status = 0;
|
|
|
+ unsigned long long value;
|
|
|
+ union acpi_object object = { 0 };
|
|
|
+ struct acpi_buffer buffer = { sizeof(union acpi_object), &object };
|
|
|
+ struct acpi_processor *pr;
|
|
|
+
|
|
|
+ pr = acpi_driver_data(device);
|
|
|
+ if (!pr) {
|
|
|
+ pr_err(PREFIX "Cannot find driver data\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!strcmp(acpi_device_hid(device), ACPI_PROCESSOR_OBJECT_HID)) {
|
|
|
+ /* Declared with "Processor" statement; match ProcessorID */
|
|
|
+ status = acpi_evaluate_object(pr->handle, NULL, NULL, &buffer);
|
|
|
+ if (ACPI_FAILURE(status)) {
|
|
|
+ pr_err(PREFIX "Evaluating processor object\n");
|
|
|
+ return -ENODEV;
|
|
|
+ }
|
|
|
+
|
|
|
+ pr->acpi_id = object.processor.proc_id;
|
|
|
+ } else {
|
|
|
+ /* Declared with "Device" statement; match _UID */
|
|
|
+ status = acpi_evaluate_integer(pr->handle, METHOD_NAME__UID,
|
|
|
+ NULL, &value);
|
|
|
+ if (ACPI_FAILURE(status)) {
|
|
|
+ pr_err(PREFIX "Evaluating processor _UID\n");
|
|
|
+ return -ENODEV;
|
|
|
+ }
|
|
|
+
|
|
|
+ pr->acpi_id = value;
|
|
|
+ }
|
|
|
+
|
|
|
+ pr->id = xen_pcpu_id(pr->acpi_id);
|
|
|
+
|
|
|
+ if ((int)pr->id < 0)
|
|
|
+ /* This cpu is not presented at hypervisor, try to hotadd it */
|
|
|
+ if (ACPI_FAILURE(xen_acpi_cpu_hotadd(pr))) {
|
|
|
+ pr_err(PREFIX "Hotadd CPU (acpi_id = %d) failed.\n",
|
|
|
+ pr->acpi_id);
|
|
|
+ return -ENODEV;
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int __cpuinit xen_acpi_processor_add(struct acpi_device *device)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ struct acpi_processor *pr;
|
|
|
+
|
|
|
+ if (!device)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ pr = kzalloc(sizeof(struct acpi_processor), GFP_KERNEL);
|
|
|
+ if (!pr)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ pr->handle = device->handle;
|
|
|
+ strcpy(acpi_device_name(device), ACPI_PROCESSOR_DEVICE_NAME);
|
|
|
+ strcpy(acpi_device_class(device), ACPI_PROCESSOR_CLASS);
|
|
|
+ device->driver_data = pr;
|
|
|
+
|
|
|
+ ret = xen_acpi_processor_enable(device);
|
|
|
+ if (ret)
|
|
|
+ pr_err(PREFIX "Error when enabling Xen processor\n");
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static int xen_acpi_processor_remove(struct acpi_device *device, int type)
|
|
|
+{
|
|
|
+ struct acpi_processor *pr;
|
|
|
+
|
|
|
+ if (!device)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ pr = acpi_driver_data(device);
|
|
|
+ if (!pr)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ kfree(pr);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*--------------------------------------------------------------
|
|
|
+ Acpi processor hotplug support
|
|
|
+--------------------------------------------------------------*/
|
|
|
+
|
|
|
+static int is_processor_present(acpi_handle handle)
|
|
|
+{
|
|
|
+ acpi_status status;
|
|
|
+ unsigned long long sta = 0;
|
|
|
+
|
|
|
+
|
|
|
+ status = acpi_evaluate_integer(handle, "_STA", NULL, &sta);
|
|
|
+
|
|
|
+ if (ACPI_SUCCESS(status) && (sta & ACPI_STA_DEVICE_PRESENT))
|
|
|
+ return 1;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * _STA is mandatory for a processor that supports hot plug
|
|
|
+ */
|
|
|
+ if (status == AE_NOT_FOUND)
|
|
|
+ pr_info(PREFIX "Processor does not support hot plug\n");
|
|
|
+ else
|
|
|
+ pr_info(PREFIX "Processor Device is not present");
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int xen_apic_id(acpi_handle handle)
|
|
|
+{
|
|
|
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
|
|
|
+ union acpi_object *obj;
|
|
|
+ struct acpi_madt_local_apic *lapic;
|
|
|
+ int apic_id;
|
|
|
+
|
|
|
+ if (ACPI_FAILURE(acpi_evaluate_object(handle, "_MAT", NULL, &buffer)))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (!buffer.length || !buffer.pointer)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ obj = buffer.pointer;
|
|
|
+ if (obj->type != ACPI_TYPE_BUFFER ||
|
|
|
+ obj->buffer.length < sizeof(*lapic)) {
|
|
|
+ kfree(buffer.pointer);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ lapic = (struct acpi_madt_local_apic *)obj->buffer.pointer;
|
|
|
+
|
|
|
+ if (lapic->header.type != ACPI_MADT_TYPE_LOCAL_APIC ||
|
|
|
+ !(lapic->lapic_flags & ACPI_MADT_ENABLED)) {
|
|
|
+ kfree(buffer.pointer);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ apic_id = (uint32_t)lapic->id;
|
|
|
+ kfree(buffer.pointer);
|
|
|
+ buffer.length = ACPI_ALLOCATE_BUFFER;
|
|
|
+ buffer.pointer = NULL;
|
|
|
+
|
|
|
+ return apic_id;
|
|
|
+}
|
|
|
+
|
|
|
+static int xen_hotadd_cpu(struct acpi_processor *pr)
|
|
|
+{
|
|
|
+ int cpu_id, apic_id, pxm;
|
|
|
+ struct xen_platform_op op;
|
|
|
+
|
|
|
+ apic_id = xen_apic_id(pr->handle);
|
|
|
+ if (apic_id < 0) {
|
|
|
+ pr_err(PREFIX "Failed to get apic_id for acpi_id %d\n",
|
|
|
+ pr->acpi_id);
|
|
|
+ return -ENODEV;
|
|
|
+ }
|
|
|
+
|
|
|
+ pxm = xen_acpi_get_pxm(pr->handle);
|
|
|
+ if (pxm < 0) {
|
|
|
+ pr_err(PREFIX "Failed to get _PXM for acpi_id %d\n",
|
|
|
+ pr->acpi_id);
|
|
|
+ return pxm;
|
|
|
+ }
|
|
|
+
|
|
|
+ op.cmd = XENPF_cpu_hotadd;
|
|
|
+ op.u.cpu_add.apic_id = apic_id;
|
|
|
+ op.u.cpu_add.acpi_id = pr->acpi_id;
|
|
|
+ op.u.cpu_add.pxm = pxm;
|
|
|
+
|
|
|
+ cpu_id = HYPERVISOR_dom0_op(&op);
|
|
|
+ if (cpu_id < 0)
|
|
|
+ pr_err(PREFIX "Failed to hotadd CPU for acpi_id %d\n",
|
|
|
+ pr->acpi_id);
|
|
|
+
|
|
|
+ return cpu_id;
|
|
|
+}
|
|
|
+
|
|
|
+static acpi_status xen_acpi_cpu_hotadd(struct acpi_processor *pr)
|
|
|
+{
|
|
|
+ if (!is_processor_present(pr->handle))
|
|
|
+ return AE_ERROR;
|
|
|
+
|
|
|
+ pr->id = xen_hotadd_cpu(pr);
|
|
|
+ if ((int)pr->id < 0)
|
|
|
+ return AE_ERROR;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Sync with Xen hypervisor, providing new /sys/.../xen_cpuX
|
|
|
+ * interface after cpu hotadded.
|
|
|
+ */
|
|
|
+ xen_pcpu_hotplug_sync();
|
|
|
+
|
|
|
+ return AE_OK;
|
|
|
+}
|
|
|
+
|
|
|
+static
|
|
|
+int acpi_processor_device_add(acpi_handle handle, struct acpi_device **device)
|
|
|
+{
|
|
|
+ acpi_handle phandle;
|
|
|
+ struct acpi_device *pdev;
|
|
|
+
|
|
|
+ if (acpi_get_parent(handle, &phandle))
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ if (acpi_bus_get_device(phandle, &pdev))
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ if (acpi_bus_add(device, pdev, handle, ACPI_BUS_TYPE_PROCESSOR))
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int acpi_processor_device_remove(struct acpi_device *device)
|
|
|
+{
|
|
|
+ pr_debug(PREFIX "Xen does not support CPU hotremove\n");
|
|
|
+
|
|
|
+ return -ENOSYS;
|
|
|
+}
|
|
|
+
|
|
|
+static void acpi_processor_hotplug_notify(acpi_handle handle,
|
|
|
+ u32 event, void *data)
|
|
|
+{
|
|
|
+ struct acpi_processor *pr;
|
|
|
+ struct acpi_device *device = NULL;
|
|
|
+ u32 ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE; /* default */
|
|
|
+ int result;
|
|
|
+
|
|
|
+ switch (event) {
|
|
|
+ case ACPI_NOTIFY_BUS_CHECK:
|
|
|
+ case ACPI_NOTIFY_DEVICE_CHECK:
|
|
|
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO,
|
|
|
+ "Processor driver received %s event\n",
|
|
|
+ (event == ACPI_NOTIFY_BUS_CHECK) ?
|
|
|
+ "ACPI_NOTIFY_BUS_CHECK" : "ACPI_NOTIFY_DEVICE_CHECK"));
|
|
|
+
|
|
|
+ if (!is_processor_present(handle))
|
|
|
+ break;
|
|
|
+
|
|
|
+ if (!acpi_bus_get_device(handle, &device))
|
|
|
+ break;
|
|
|
+
|
|
|
+ result = acpi_processor_device_add(handle, &device);
|
|
|
+ if (result) {
|
|
|
+ pr_err(PREFIX "Unable to add the device\n");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ ost_code = ACPI_OST_SC_SUCCESS;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case ACPI_NOTIFY_EJECT_REQUEST:
|
|
|
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO,
|
|
|
+ "received ACPI_NOTIFY_EJECT_REQUEST\n"));
|
|
|
+
|
|
|
+ if (acpi_bus_get_device(handle, &device)) {
|
|
|
+ pr_err(PREFIX "Device don't exist, dropping EJECT\n");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ pr = acpi_driver_data(device);
|
|
|
+ if (!pr) {
|
|
|
+ pr_err(PREFIX "Driver data is NULL, dropping EJECT\n");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * TBD: implement acpi_processor_device_remove if Xen support
|
|
|
+ * CPU hotremove in the future.
|
|
|
+ */
|
|
|
+ acpi_processor_device_remove(device);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO,
|
|
|
+ "Unsupported event [0x%x]\n", event));
|
|
|
+
|
|
|
+ /* non-hotplug event; possibly handled by other handler */
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ (void) acpi_evaluate_hotplug_ost(handle, event, ost_code, NULL);
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+static acpi_status is_processor_device(acpi_handle handle)
|
|
|
+{
|
|
|
+ struct acpi_device_info *info;
|
|
|
+ char *hid;
|
|
|
+ acpi_status status;
|
|
|
+
|
|
|
+ status = acpi_get_object_info(handle, &info);
|
|
|
+ if (ACPI_FAILURE(status))
|
|
|
+ return status;
|
|
|
+
|
|
|
+ if (info->type == ACPI_TYPE_PROCESSOR) {
|
|
|
+ kfree(info);
|
|
|
+ return AE_OK; /* found a processor object */
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!(info->valid & ACPI_VALID_HID)) {
|
|
|
+ kfree(info);
|
|
|
+ return AE_ERROR;
|
|
|
+ }
|
|
|
+
|
|
|
+ hid = info->hardware_id.string;
|
|
|
+ if ((hid == NULL) || strcmp(hid, ACPI_PROCESSOR_DEVICE_HID)) {
|
|
|
+ kfree(info);
|
|
|
+ return AE_ERROR;
|
|
|
+ }
|
|
|
+
|
|
|
+ kfree(info);
|
|
|
+ return AE_OK; /* found a processor device object */
|
|
|
+}
|
|
|
+
|
|
|
+static acpi_status
|
|
|
+processor_walk_namespace_cb(acpi_handle handle,
|
|
|
+ u32 lvl, void *context, void **rv)
|
|
|
+{
|
|
|
+ acpi_status status;
|
|
|
+ int *action = context;
|
|
|
+
|
|
|
+ status = is_processor_device(handle);
|
|
|
+ if (ACPI_FAILURE(status))
|
|
|
+ return AE_OK; /* not a processor; continue to walk */
|
|
|
+
|
|
|
+ switch (*action) {
|
|
|
+ case INSTALL_NOTIFY_HANDLER:
|
|
|
+ acpi_install_notify_handler(handle,
|
|
|
+ ACPI_SYSTEM_NOTIFY,
|
|
|
+ acpi_processor_hotplug_notify,
|
|
|
+ NULL);
|
|
|
+ break;
|
|
|
+ case UNINSTALL_NOTIFY_HANDLER:
|
|
|
+ acpi_remove_notify_handler(handle,
|
|
|
+ ACPI_SYSTEM_NOTIFY,
|
|
|
+ acpi_processor_hotplug_notify);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* found a processor; skip walking underneath */
|
|
|
+ return AE_CTRL_DEPTH;
|
|
|
+}
|
|
|
+
|
|
|
+static
|
|
|
+void acpi_processor_install_hotplug_notify(void)
|
|
|
+{
|
|
|
+ int action = INSTALL_NOTIFY_HANDLER;
|
|
|
+ acpi_walk_namespace(ACPI_TYPE_ANY,
|
|
|
+ ACPI_ROOT_OBJECT,
|
|
|
+ ACPI_UINT32_MAX,
|
|
|
+ processor_walk_namespace_cb, NULL, &action, NULL);
|
|
|
+}
|
|
|
+
|
|
|
+static
|
|
|
+void acpi_processor_uninstall_hotplug_notify(void)
|
|
|
+{
|
|
|
+ int action = UNINSTALL_NOTIFY_HANDLER;
|
|
|
+ acpi_walk_namespace(ACPI_TYPE_ANY,
|
|
|
+ ACPI_ROOT_OBJECT,
|
|
|
+ ACPI_UINT32_MAX,
|
|
|
+ processor_walk_namespace_cb, NULL, &action, NULL);
|
|
|
+}
|
|
|
+
|
|
|
+static const struct acpi_device_id processor_device_ids[] = {
|
|
|
+ {ACPI_PROCESSOR_OBJECT_HID, 0},
|
|
|
+ {ACPI_PROCESSOR_DEVICE_HID, 0},
|
|
|
+ {"", 0},
|
|
|
+};
|
|
|
+MODULE_DEVICE_TABLE(acpi, processor_device_ids);
|
|
|
+
|
|
|
+static struct acpi_driver xen_acpi_processor_driver = {
|
|
|
+ .name = "processor",
|
|
|
+ .class = ACPI_PROCESSOR_CLASS,
|
|
|
+ .ids = processor_device_ids,
|
|
|
+ .ops = {
|
|
|
+ .add = xen_acpi_processor_add,
|
|
|
+ .remove = xen_acpi_processor_remove,
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+static int __init xen_acpi_processor_init(void)
|
|
|
+{
|
|
|
+ int result = 0;
|
|
|
+
|
|
|
+ if (!xen_initial_domain())
|
|
|
+ return -ENODEV;
|
|
|
+
|
|
|
+ /* unregister the stub which only used to reserve driver space */
|
|
|
+ xen_stub_processor_exit();
|
|
|
+
|
|
|
+ result = acpi_bus_register_driver(&xen_acpi_processor_driver);
|
|
|
+ if (result < 0) {
|
|
|
+ xen_stub_processor_init();
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ acpi_processor_install_hotplug_notify();
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void __exit xen_acpi_processor_exit(void)
|
|
|
+{
|
|
|
+ if (!xen_initial_domain())
|
|
|
+ return;
|
|
|
+
|
|
|
+ acpi_processor_uninstall_hotplug_notify();
|
|
|
+
|
|
|
+ acpi_bus_unregister_driver(&xen_acpi_processor_driver);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * stub reserve space again to prevent any chance of native
|
|
|
+ * driver loading.
|
|
|
+ */
|
|
|
+ xen_stub_processor_init();
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+module_init(xen_acpi_processor_init);
|
|
|
+module_exit(xen_acpi_processor_exit);
|
|
|
+ACPI_MODULE_NAME("xen-acpi-cpuhotplug");
|
|
|
+MODULE_AUTHOR("Liu Jinsong <jinsong.liu@intel.com>");
|
|
|
+MODULE_DESCRIPTION("Xen Hotplug CPU Driver");
|
|
|
+MODULE_LICENSE("GPL");
|