summaryrefslogtreecommitdiffstats
path: root/drivers/acpi
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/acpi')
-rw-r--r--drivers/acpi/internal.h1
-rw-r--r--drivers/acpi/osl.c24
-rw-r--r--drivers/acpi/pci_root.c130
-rw-r--r--drivers/acpi/scan.c3
4 files changed, 156 insertions, 2 deletions
diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h
index e050254ae143..0f24148a2b2a 100644
--- a/drivers/acpi/internal.h
+++ b/drivers/acpi/internal.h
@@ -68,6 +68,7 @@ struct acpi_ec {
extern struct acpi_ec *first_ec;
int acpi_pci_root_init(void);
+void acpi_pci_root_hp_init(void);
int acpi_ec_init(void);
int acpi_ec_ecdt_probe(void);
int acpi_boot_ec_enable(void);
diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c
index 3ff267861541..59ec5f52e849 100644
--- a/drivers/acpi/osl.c
+++ b/drivers/acpi/osl.c
@@ -84,8 +84,7 @@ static acpi_osd_handler acpi_irq_handler;
static void *acpi_irq_context;
static struct workqueue_struct *kacpid_wq;
static struct workqueue_struct *kacpi_notify_wq;
-struct workqueue_struct *kacpi_hotplug_wq;
-EXPORT_SYMBOL(kacpi_hotplug_wq);
+static struct workqueue_struct *kacpi_hotplug_wq;
/*
* This list of permanent mappings is for memory that may be accessed from
@@ -1778,3 +1777,24 @@ void acpi_os_set_prepare_sleep(int (*func)(u8 sleep_state,
{
__acpi_os_prepare_sleep = func;
}
+
+void alloc_acpi_hp_work(acpi_handle handle, u32 type, void *context,
+ void (*func)(struct work_struct *work))
+{
+ struct acpi_hp_work *hp_work;
+ int ret;
+
+ hp_work = kmalloc(sizeof(*hp_work), GFP_KERNEL);
+ if (!hp_work)
+ return;
+
+ hp_work->handle = handle;
+ hp_work->type = type;
+ hp_work->context = context;
+
+ INIT_WORK(&hp_work->work, func);
+ ret = queue_work(kacpi_hotplug_wq, &hp_work->work);
+ if (!ret)
+ kfree(hp_work);
+}
+EXPORT_SYMBOL_GPL(alloc_acpi_hp_work);
diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c
index bf5108ad4d63..417487a201fb 100644
--- a/drivers/acpi/pci_root.c
+++ b/drivers/acpi/pci_root.c
@@ -655,3 +655,133 @@ int __init acpi_pci_root_init(void)
return 0;
}
+/* Support root bridge hotplug */
+
+static void handle_root_bridge_insertion(acpi_handle handle)
+{
+ struct acpi_device *device;
+
+ if (!acpi_bus_get_device(handle, &device)) {
+ printk(KERN_DEBUG "acpi device exists...\n");
+ return;
+ }
+
+ if (acpi_bus_scan(handle))
+ printk(KERN_ERR "cannot add bridge to acpi list\n");
+}
+
+static void handle_root_bridge_removal(struct acpi_device *device)
+{
+ struct acpi_eject_event *ej_event;
+
+ ej_event = kmalloc(sizeof(*ej_event), GFP_KERNEL);
+ if (!ej_event) {
+ /* Inform firmware the hot-remove operation has error */
+ (void) acpi_evaluate_hotplug_ost(device->handle,
+ ACPI_NOTIFY_EJECT_REQUEST,
+ ACPI_OST_SC_NON_SPECIFIC_FAILURE,
+ NULL);
+ return;
+ }
+
+ ej_event->device = device;
+ ej_event->event = ACPI_NOTIFY_EJECT_REQUEST;
+
+ acpi_bus_hot_remove_device(ej_event);
+}
+
+static void _handle_hotplug_event_root(struct work_struct *work)
+{
+ struct acpi_pci_root *root;
+ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER };
+ struct acpi_hp_work *hp_work;
+ acpi_handle handle;
+ u32 type;
+
+ hp_work = container_of(work, struct acpi_hp_work, work);
+ handle = hp_work->handle;
+ type = hp_work->type;
+
+ root = acpi_pci_find_root(handle);
+
+ acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
+
+ switch (type) {
+ case ACPI_NOTIFY_BUS_CHECK:
+ /* bus enumerate */
+ printk(KERN_DEBUG "%s: Bus check notify on %s\n", __func__,
+ (char *)buffer.pointer);
+ if (!root)
+ handle_root_bridge_insertion(handle);
+
+ break;
+
+ case ACPI_NOTIFY_DEVICE_CHECK:
+ /* device check */
+ printk(KERN_DEBUG "%s: Device check notify on %s\n", __func__,
+ (char *)buffer.pointer);
+ if (!root)
+ handle_root_bridge_insertion(handle);
+ break;
+
+ case ACPI_NOTIFY_EJECT_REQUEST:
+ /* request device eject */
+ printk(KERN_DEBUG "%s: Device eject notify on %s\n", __func__,
+ (char *)buffer.pointer);
+ if (root)
+ handle_root_bridge_removal(root->device);
+ break;
+ default:
+ printk(KERN_WARNING "notify_handler: unknown event type 0x%x for %s\n",
+ type, (char *)buffer.pointer);
+ break;
+ }
+
+ kfree(hp_work); /* allocated in handle_hotplug_event_bridge */
+ kfree(buffer.pointer);
+}
+
+static void handle_hotplug_event_root(acpi_handle handle, u32 type,
+ void *context)
+{
+ alloc_acpi_hp_work(handle, type, context,
+ _handle_hotplug_event_root);
+}
+
+static acpi_status __init
+find_root_bridges(acpi_handle handle, u32 lvl, void *context, void **rv)
+{
+ acpi_status status;
+ char objname[64];
+ struct acpi_buffer buffer = { .length = sizeof(objname),
+ .pointer = objname };
+ int *count = (int *)context;
+
+ if (!acpi_is_root_bridge(handle))
+ return AE_OK;
+
+ (*count)++;
+
+ acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
+
+ status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+ handle_hotplug_event_root, NULL);
+ if (ACPI_FAILURE(status))
+ printk(KERN_DEBUG "acpi root: %s notify handler is not installed, exit status: %u\n",
+ objname, (unsigned int)status);
+ else
+ printk(KERN_DEBUG "acpi root: %s notify handler is installed\n",
+ objname);
+
+ return AE_OK;
+}
+
+void __init acpi_pci_root_hp_init(void)
+{
+ int num = 0;
+
+ acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
+ ACPI_UINT32_MAX, find_root_bridges, NULL, &num, NULL);
+
+ printk(KERN_DEBUG "Found %d acpi root devices\n", num);
+}
diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c
index 7c43bdc36abc..bc2f33790e83 100644
--- a/drivers/acpi/scan.c
+++ b/drivers/acpi/scan.c
@@ -1706,5 +1706,8 @@ int __init acpi_scan_init(void)
}
acpi_update_all_gpes();
+
+ acpi_pci_root_hp_init();
+
return 0;
}