[PATCH] acpiphp: add dock event handling
authorKristen Accardi <kristen.c.accardi@intel.com>
Fri, 24 Feb 2006 01:56:03 +0000 (17:56 -0800)
committerGreg Kroah-Hartman <gregkh@suse.de>
Thu, 23 Mar 2006 22:35:15 +0000 (14:35 -0800)
These patches add generic dock event handling to acpiphp.  If there are
pci devices that need to be inserted/removed after the dock event, the
event notification will be handed down to the normal pci hotplug event
handler in acpiphp so that new bridges/devices can be enumerated.

Because some dock stations do not have pci bridges or pci devices that
need to be inserted after a dock, acpiphp will remain loaded to handle
dock events even if no hotpluggable pci slots are discovered.

You probably need to have the pci=assign-busses kernel parameter enabled
to use these patches, and you may not allow ibm_acpi to handle docking
notifications and use this patch.

This patch incorporates feedback provided by many.

Signed-off-by: Kristen Carlson Accardi <kristen.c.accardi@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/pci/hotplug/Makefile
drivers/pci/hotplug/acpiphp.h
drivers/pci/hotplug/acpiphp_core.c
drivers/pci/hotplug/acpiphp_dock.c [new file with mode: 0644]
drivers/pci/hotplug/acpiphp_glue.c

index 3c71e3077ff1dd9b5cbbc1f5b0c18df022a84451..1c0ed377ed91d6af54280f8dd868648bf90d5ec3 100644 (file)
@@ -37,7 +37,8 @@ ibmphp-objs           :=      ibmphp_core.o   \
                                ibmphp_hpc.o
 
 acpiphp-objs           :=      acpiphp_core.o  \
-                               acpiphp_glue.o
+                               acpiphp_glue.o  \
+                               acpiphp_dock.o
 
 rpaphp-objs            :=      rpaphp_core.o   \
                                rpaphp_pci.o    \
index 6a91cfb0f68837214c3b5666dd64f0731833adba..885838a2e93faf89d76ce3ea366f75c3ba957f96 100644 (file)
@@ -161,6 +161,25 @@ struct acpiphp_attention_info
        struct module *owner;
 };
 
+
+struct dependent_device {
+       struct list_head device_list;
+       struct list_head pci_list;
+       acpi_handle handle;
+       struct acpiphp_func *func;
+};
+
+
+struct acpiphp_dock_station {
+       acpi_handle handle;
+       u32 last_dock_time;
+       u32 flags;
+       struct acpiphp_func *dock_bridge;
+       struct list_head dependent_devices;
+       struct list_head pci_dependent_devices;
+};
+
+
 /* PCI bus bridge HID */
 #define ACPI_PCI_HOST_HID              "PNP0A03"
 
@@ -198,6 +217,12 @@ struct acpiphp_attention_info
 #define FUNC_HAS_PS1           (0x00000020)
 #define FUNC_HAS_PS2           (0x00000040)
 #define FUNC_HAS_PS3           (0x00000080)
+#define FUNC_HAS_DCK            (0x00000100)
+#define FUNC_IS_DD              (0x00000200)
+
+/* dock station flags */
+#define DOCK_DOCKING            (0x00000001)
+#define DOCK_HAS_BRIDGE         (0x00000002)
 
 /* function prototypes */
 
@@ -211,6 +236,7 @@ extern void acpiphp_glue_exit (void);
 extern int acpiphp_get_num_slots (void);
 extern struct acpiphp_slot *get_slot_from_id (int id);
 typedef int (*acpiphp_callback)(struct acpiphp_slot *slot, void *data);
+void handle_hotplug_event_func(acpi_handle, u32, void*);
 
 extern int acpiphp_enable_slot (struct acpiphp_slot *slot);
 extern int acpiphp_disable_slot (struct acpiphp_slot *slot);
@@ -220,6 +246,16 @@ extern u8 acpiphp_get_latch_status (struct acpiphp_slot *slot);
 extern u8 acpiphp_get_adapter_status (struct acpiphp_slot *slot);
 extern u32 acpiphp_get_address (struct acpiphp_slot *slot);
 
+/* acpiphp_dock.c */
+extern int find_dock_station(void);
+extern void remove_dock_station(void);
+extern void add_dependent_device(struct dependent_device *new_dd);
+extern void add_pci_dependent_device(struct dependent_device *new_dd);
+extern struct dependent_device *get_dependent_device(acpi_handle handle);
+extern int is_dependent_device(acpi_handle handle);
+extern int detect_dependent_devices(acpi_handle *bridge_handle);
+extern struct dependent_device *alloc_dependent_device(acpi_handle handle);
+
 /* variables */
 extern int acpiphp_debug;
 
index 60c4c38047a3a63f4b308d269ddbb520c171a9e2..bce71c933478cc24e1f0e0e061610dd31c3dc4b6 100644 (file)
@@ -429,14 +429,17 @@ static void __exit cleanup_slots (void)
 static int __init acpiphp_init(void)
 {
        int retval;
+       int docking_station;
 
        info(DRIVER_DESC " version: " DRIVER_VERSION "\n");
 
        acpiphp_debug = debug;
 
+       docking_station = find_dock_station();
+
        /* read all the ACPI info from the system */
        retval = init_acpi();
-       if (retval)
+       if (retval && !(docking_station))
                return retval;
 
        return init_slots();
@@ -448,6 +451,8 @@ static void __exit acpiphp_exit(void)
        cleanup_slots();
        /* deallocate internal data structures etc. */
        acpiphp_glue_exit();
+
+       remove_dock_station();
 }
 
 module_init(acpiphp_init);
diff --git a/drivers/pci/hotplug/acpiphp_dock.c b/drivers/pci/hotplug/acpiphp_dock.c
new file mode 100644 (file)
index 0000000..4f1aaf1
--- /dev/null
@@ -0,0 +1,438 @@
+/*
+ * ACPI PCI HotPlug dock functions to ACPI CA subsystem
+ *
+ * Copyright (C) 2006 Kristen Carlson Accardi (kristen.c.accardi@intel.com)
+ * Copyright (C) 2006 Intel Corporation
+ *
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Send feedback to <kristen.c.accardi@intel.com>
+ *
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+
+#include <linux/kernel.h>
+#include <linux/pci.h>
+#include <linux/smp_lock.h>
+#include <linux/mutex.h>
+
+#include "../pci.h"
+#include "pci_hotplug.h"
+#include "acpiphp.h"
+
+static struct acpiphp_dock_station *ds;
+#define MY_NAME "acpiphp_dock"
+
+
+int is_dependent_device(acpi_handle handle)
+{
+       return (get_dependent_device(handle) ? 1 : 0);
+}
+
+
+static acpi_status
+find_dependent_device(acpi_handle handle, u32 lvl, void *context, void **rv)
+{
+       int *count = (int *)context;
+
+       if (is_dependent_device(handle)) {
+               (*count)++;
+               return AE_CTRL_TERMINATE;
+       } else {
+               return AE_OK;
+       }
+}
+
+
+
+
+void add_dependent_device(struct dependent_device *new_dd)
+{
+       list_add_tail(&new_dd->device_list, &ds->dependent_devices);
+}
+
+
+void add_pci_dependent_device(struct dependent_device *new_dd)
+{
+       list_add_tail(&new_dd->pci_list, &ds->pci_dependent_devices);
+}
+
+
+
+struct dependent_device * get_dependent_device(acpi_handle handle)
+{
+       struct dependent_device *dd;
+
+       if (!ds)
+               return NULL;
+
+       list_for_each_entry(dd, &ds->dependent_devices, device_list) {
+               if (handle == dd->handle)
+                       return dd;
+       }
+       return NULL;
+}
+
+
+
+struct dependent_device *alloc_dependent_device(acpi_handle handle)
+{
+       struct dependent_device *dd;
+
+       dd = kzalloc(sizeof(*dd), GFP_KERNEL);
+       if (dd) {
+               INIT_LIST_HEAD(&dd->pci_list);
+               INIT_LIST_HEAD(&dd->device_list);
+               dd->handle = handle;
+       }
+       return dd;
+}
+
+
+
+static int is_dock(acpi_handle handle)
+{
+       acpi_status status;
+       acpi_handle tmp;
+
+       status = acpi_get_handle(handle, "_DCK", &tmp);
+       if (ACPI_FAILURE(status)) {
+               return 0;
+       }
+       return 1;
+}
+
+
+
+static int dock_present(void)
+{
+       unsigned long sta;
+       acpi_status status;
+
+       if (ds) {
+               status = acpi_evaluate_integer(ds->handle, "_STA", NULL, &sta);
+               if (ACPI_SUCCESS(status) && sta)
+                       return 1;
+       }
+       return 0;
+}
+
+
+
+static void eject_dock(void)
+{
+       struct acpi_object_list arg_list;
+       union acpi_object arg;
+
+       arg_list.count = 1;
+       arg_list.pointer = &arg;
+       arg.type = ACPI_TYPE_INTEGER;
+       arg.integer.value = 1;
+
+       if (ACPI_FAILURE(acpi_evaluate_object(ds->handle, "_EJ0",
+                                       &arg_list, NULL)) || dock_present())
+               warn("%s: failed to eject dock!\n", __FUNCTION__);
+
+       return;
+}
+
+
+
+
+static acpi_status handle_dock(int dock)
+{
+       acpi_status status;
+       struct acpi_object_list arg_list;
+       union acpi_object arg;
+       struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER, NULL};
+
+       dbg("%s: %s\n", __FUNCTION__, dock ? "docking" : "undocking");
+
+       /* _DCK method has one argument */
+       arg_list.count = 1;
+       arg_list.pointer = &arg;
+       arg.type = ACPI_TYPE_INTEGER;
+       arg.integer.value = dock;
+       status = acpi_evaluate_object(ds->handle, "_DCK",
+                                       &arg_list, &buffer);
+       if (ACPI_FAILURE(status))
+               err("%s: failed to execute _DCK\n", __FUNCTION__);
+       acpi_os_free(buffer.pointer);
+
+       return status;
+}
+
+
+
+static inline void dock(void)
+{
+       handle_dock(1);
+}
+
+
+
+static inline void undock(void)
+{
+       handle_dock(0);
+}
+
+
+
+/*
+ * the _DCK method can do funny things... and sometimes not
+ * hah-hah funny.
+ *
+ * TBD - figure out a way to only call fixups for
+ * systems that require them.
+ */
+static void post_dock_fixups(void)
+{
+       struct pci_bus *bus;
+       u32 buses;
+       struct dependent_device *dd;
+
+       list_for_each_entry(dd, &ds->pci_dependent_devices, pci_list) {
+               bus = dd->func->slot->bridge->pci_bus;
+
+               /* fixup bad _DCK function that rewrites
+                * secondary bridge on slot
+                */
+               pci_read_config_dword(bus->self,
+                               PCI_PRIMARY_BUS,
+                               &buses);
+
+               if (((buses >> 8) & 0xff) != bus->secondary) {
+                       buses = (buses & 0xff000000)
+                               | ((unsigned int)(bus->primary)     <<  0)
+                               | ((unsigned int)(bus->secondary)   <<  8)
+                               | ((unsigned int)(bus->subordinate) << 16);
+                       pci_write_config_dword(bus->self,
+                                       PCI_PRIMARY_BUS,
+                                       buses);
+               }
+       }
+}
+
+
+
+static void hotplug_pci(u32 type)
+{
+       struct dependent_device *dd;
+
+       list_for_each_entry(dd, &ds->pci_dependent_devices, pci_list)
+               handle_hotplug_event_func(dd->handle, type, dd->func);
+}
+
+
+
+static inline void begin_dock(void)
+{
+       ds->flags |= DOCK_DOCKING;
+}
+
+
+static inline void complete_dock(void)
+{
+       ds->flags &= ~(DOCK_DOCKING);
+       ds->last_dock_time = jiffies;
+}
+
+
+static int dock_in_progress(void)
+{
+       if (ds->flags & DOCK_DOCKING ||
+               ds->last_dock_time == jiffies) {
+               dbg("dock in progress\n");
+               return 1;
+       }
+       return 0;
+}
+
+
+
+static void
+handle_hotplug_event_dock(acpi_handle handle, u32 type, void *context)
+{
+       dbg("%s: enter\n", __FUNCTION__);
+
+       switch (type) {
+               case ACPI_NOTIFY_BUS_CHECK:
+                       dbg("BUS Check\n");
+                       if (!dock_in_progress() && dock_present()) {
+                               begin_dock();
+                               dock();
+                               if (!dock_present()) {
+                                       err("Unable to dock!\n");
+                                       break;
+                               }
+                               post_dock_fixups();
+                               hotplug_pci(type);
+                               complete_dock();
+                       }
+                       break;
+               case ACPI_NOTIFY_EJECT_REQUEST:
+                       dbg("EJECT request\n");
+                       if (!dock_in_progress() && dock_present()) {
+                               hotplug_pci(type);
+                               undock();
+                               eject_dock();
+                               if (dock_present())
+                                       err("Unable to undock!\n");
+                       }
+                       break;
+       }
+}
+
+
+
+
+static acpi_status
+find_dock_ejd(acpi_handle handle, u32 lvl, void *context, void **rv)
+{
+       acpi_status status;
+       acpi_handle tmp;
+       acpi_handle dck_handle = (acpi_handle) context;
+       char objname[64];
+       struct acpi_buffer buffer = { .length = sizeof(objname),
+                                     .pointer = objname };
+       struct acpi_buffer ejd_buffer = {ACPI_ALLOCATE_BUFFER, NULL};
+       union acpi_object *ejd_obj;
+
+       status = acpi_get_handle(handle, "_EJD", &tmp);
+       if (ACPI_FAILURE(status))
+               return AE_OK;
+
+       /* make sure we are dependent on the dock device,
+        * by executing the _EJD method, then getting a handle
+        * to the device referenced by that name.  If that
+        * device handle is the same handle as the dock station
+        * handle, then we are a device dependent on the dock station
+        */
+       acpi_get_name(dck_handle, ACPI_FULL_PATHNAME, &buffer);
+       status = acpi_evaluate_object(handle, "_EJD", NULL, &ejd_buffer);
+       if (ACPI_FAILURE(status)) {
+               err("Unable to execute _EJD!\n");
+               goto find_ejd_out;
+       }
+       ejd_obj = ejd_buffer.pointer;
+       status = acpi_get_handle(NULL, ejd_obj->string.pointer, &tmp);
+       if (ACPI_FAILURE(status))
+               goto find_ejd_out;
+
+       if (tmp == dck_handle) {
+               struct dependent_device *dd;
+               dbg("%s: found device dependent on dock\n", __FUNCTION__);
+               dd = alloc_dependent_device(handle);
+               if (!dd) {
+                       err("Can't allocate memory for dependent device!\n");
+                       goto find_ejd_out;
+               }
+               add_dependent_device(dd);
+       }
+
+find_ejd_out:
+       acpi_os_free(ejd_buffer.pointer);
+       return AE_OK;
+}
+
+
+
+int detect_dependent_devices(acpi_handle *bridge_handle)
+{
+       acpi_status status;
+       int count;
+
+       count = 0;
+
+       status = acpi_walk_namespace(ACPI_TYPE_DEVICE, bridge_handle,
+                                       (u32)1, find_dependent_device,
+                                       (void *)&count, NULL);
+
+       return count;
+}
+
+
+
+
+
+static acpi_status
+find_dock(acpi_handle handle, u32 lvl, void *context, void **rv)
+{
+       int *count = (int *)context;
+
+       if (is_dock(handle)) {
+               dbg("%s: found dock\n", __FUNCTION__);
+               ds = kzalloc(sizeof(*ds), GFP_KERNEL);
+               ds->handle = handle;
+               INIT_LIST_HEAD(&ds->dependent_devices);
+               INIT_LIST_HEAD(&ds->pci_dependent_devices);
+
+               /* look for devices dependent on dock station */
+               acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
+                       ACPI_UINT32_MAX, find_dock_ejd, handle, NULL);
+
+               acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+                       handle_hotplug_event_dock, ds);
+               (*count)++;
+       }
+
+       return AE_OK;
+}
+
+
+
+
+int find_dock_station(void)
+{
+       int num = 0;
+
+       ds = NULL;
+
+       /* start from the root object, because some laptops define
+        * _DCK methods outside the scope of PCI (IBM x-series laptop)
+        */
+       acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
+                       ACPI_UINT32_MAX, find_dock, &num, NULL);
+
+       return num;
+}
+
+
+
+void remove_dock_station(void)
+{
+       struct dependent_device *dd, *tmp;
+       if (ds) {
+               if (ACPI_FAILURE(acpi_remove_notify_handler(ds->handle,
+                       ACPI_SYSTEM_NOTIFY, handle_hotplug_event_dock)))
+                       err("failed to remove dock notify handler\n");
+
+               /* free all dependent devices */
+               list_for_each_entry_safe(dd, tmp, &ds->dependent_devices,
+                               device_list)
+                       kfree(dd);
+
+               /* no need to touch the pci_dependent_device list,
+                * cause all memory was freed above
+                */
+               kfree(ds);
+       }
+}
+
+
index fe0a6b7662f726d788937bc57db404bfda7e0f6a..22d0f1cf136299867dd526b0def29bdba3e6a5c9 100644 (file)
@@ -57,7 +57,6 @@ static LIST_HEAD(bridge_list);
 #define MY_NAME "acpiphp_glue"
 
 static void handle_hotplug_event_bridge (acpi_handle, u32, void *);
-static void handle_hotplug_event_func (acpi_handle, u32, void *);
 static void acpiphp_sanitize_bus(struct pci_bus *bus);
 static void acpiphp_set_hpp_values(acpi_handle handle, struct pci_bus *bus);
 
@@ -125,6 +124,7 @@ register_slot(acpi_handle handle, u32 lvl, void *context, void **rv)
        struct acpiphp_bridge *bridge = (struct acpiphp_bridge *)context;
        struct acpiphp_slot *slot;
        struct acpiphp_func *newfunc;
+       struct dependent_device *dd;
        acpi_handle tmp;
        acpi_status status = AE_OK;
        unsigned long adr, sun;
@@ -138,7 +138,7 @@ register_slot(acpi_handle handle, u32 lvl, void *context, void **rv)
 
        status = acpi_get_handle(handle, "_EJ0", &tmp);
 
-       if (ACPI_FAILURE(status))
+       if (ACPI_FAILURE(status) && !(is_dependent_device(handle)))
                return AE_OK;
 
        device = (adr >> 16) & 0xffff;
@@ -152,7 +152,8 @@ register_slot(acpi_handle handle, u32 lvl, void *context, void **rv)
        INIT_LIST_HEAD(&newfunc->sibling);
        newfunc->handle = handle;
        newfunc->function = function;
-       newfunc->flags = FUNC_HAS_EJ0;
+       if (ACPI_SUCCESS(status))
+               newfunc->flags = FUNC_HAS_EJ0;
 
        if (ACPI_SUCCESS(acpi_get_handle(handle, "_STA", &tmp)))
                newfunc->flags |= FUNC_HAS_STA;
@@ -163,6 +164,19 @@ register_slot(acpi_handle handle, u32 lvl, void *context, void **rv)
        if (ACPI_SUCCESS(acpi_get_handle(handle, "_PS3", &tmp)))
                newfunc->flags |= FUNC_HAS_PS3;
 
+       if (ACPI_SUCCESS(acpi_get_handle(handle, "_DCK", &tmp))) {
+               newfunc->flags |= FUNC_HAS_DCK;
+               /* add to devices dependent on dock station,
+                * because this may actually be the dock bridge
+                */
+               dd = alloc_dependent_device(handle);
+                if (!dd)
+                        err("Can't allocate memory for "
+                               "new dependent device!\n");
+               else
+                       add_dependent_device(dd);
+       }
+
        status = acpi_evaluate_integer(handle, "_SUN", NULL, &sun);
        if (ACPI_FAILURE(status))
                sun = -1;
@@ -210,18 +224,35 @@ register_slot(acpi_handle handle, u32 lvl, void *context, void **rv)
                slot->flags |= (SLOT_ENABLED | SLOT_POWEREDON);
        }
 
+       /* if this is a device dependent on a dock station,
+        * associate the acpiphp_func to the dependent_device
+        * struct.
+        */
+       if ((dd = get_dependent_device(handle))) {
+               newfunc->flags |= FUNC_IS_DD;
+               /*
+                * we don't want any devices which is dependent
+                * on the dock to have it's _EJ0 method executed.
+                * because we need to run _DCK first.
+                */
+               newfunc->flags &= ~FUNC_HAS_EJ0;
+               dd->func = newfunc;
+               add_pci_dependent_device(dd);
+       }
+
        /* install notify handler */
-       status = acpi_install_notify_handler(handle,
+       if (!(newfunc->flags & FUNC_HAS_DCK)) {
+               status = acpi_install_notify_handler(handle,
                                             ACPI_SYSTEM_NOTIFY,
                                             handle_hotplug_event_func,
                                             newfunc);
 
-       if (ACPI_FAILURE(status)) {
-               err("failed to register interrupt notify handler\n");
-               return status;
-       }
+               if (ACPI_FAILURE(status))
+                       err("failed to register interrupt notify handler\n");
+       } else
+               status = AE_OK;
 
-       return AE_OK;
+       return status;
 }
 
 
@@ -410,7 +441,8 @@ find_p2p_bridge(acpi_handle handle, u32 lvl, void *context, void **rv)
                goto out;
 
        /* check if this bridge has ejectable slots */
-       if (detect_ejectable_slots(handle) > 0) {
+       if ((detect_ejectable_slots(handle) > 0) ||
+               (detect_dependent_devices(handle) > 0)) {
                dbg("found PCI-to-PCI bridge at PCI %s\n", pci_name(dev));
                add_p2p_bridge(handle, dev);
        }
@@ -512,11 +544,13 @@ static void cleanup_bridge(struct acpiphp_bridge *bridge)
                list_for_each_safe (list, tmp, &slot->funcs) {
                        struct acpiphp_func *func;
                        func = list_entry(list, struct acpiphp_func, sibling);
-                       status = acpi_remove_notify_handler(func->handle,
+                       if (!(func->flags & FUNC_HAS_DCK)) {
+                               status = acpi_remove_notify_handler(func->handle,
                                                ACPI_SYSTEM_NOTIFY,
                                                handle_hotplug_event_func);
-                       if (ACPI_FAILURE(status))
-                               err("failed to remove notify handler\n");
+                               if (ACPI_FAILURE(status))
+                                       err("failed to remove notify handler\n");
+                       }
                        pci_dev_put(func->pci_dev);
                        list_del(list);
                        kfree(func);
@@ -828,14 +862,21 @@ static int acpiphp_bus_add(struct acpiphp_func *func)
                dbg("no parent device, assuming NULL\n");
                pdevice = NULL;
        }
-       if (acpi_bus_get_device(func->handle, &device)) {
-               ret_val = acpi_bus_add(&device, pdevice, func->handle,
-                       ACPI_BUS_TYPE_DEVICE);
-               if (ret_val) {
-                       dbg("error adding bus, %x\n",
-                               -ret_val);
-                       goto acpiphp_bus_add_out;
-               }
+       if (!acpi_bus_get_device(func->handle, &device)) {
+               dbg("bus exists... trim\n");
+               /* this shouldn't be in here, so remove
+                * the bus then re-add it...
+                */
+               ret_val = acpi_bus_trim(device, 1);
+               dbg("acpi_bus_trim return %x\n", ret_val);
+       }
+
+       ret_val = acpi_bus_add(&device, pdevice, func->handle,
+               ACPI_BUS_TYPE_DEVICE);
+       if (ret_val) {
+               dbg("error adding bus, %x\n",
+                       -ret_val);
+               goto acpiphp_bus_add_out;
        }
        /*
         * try to start anyway.  We could have failed to add
@@ -1307,7 +1348,7 @@ static void handle_hotplug_event_bridge(acpi_handle handle, u32 type, void *cont
  * handles ACPI event notification on slots
  *
  */
-static void handle_hotplug_event_func(acpi_handle handle, u32 type, void *context)
+void handle_hotplug_event_func(acpi_handle handle, u32 type, void *context)
 {
        struct acpiphp_func *func;
        char objname[64];