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