123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 |
- /*
- * 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);
- }
- }
|