Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 20416ea5 authored by Kristen Accardi's avatar Kristen Accardi Committed by Greg Kroah-Hartman
Browse files

[PATCH] acpiphp: add dock event handling



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: default avatarKristen Carlson Accardi <kristen.c.accardi@intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent ceaba663
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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	\
+36 −0
Original line number Diff line number Diff line
@@ -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;

+6 −1
Original line number Diff line number Diff line
@@ -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);
+438 −0
Original line number Diff line number Diff line
/*
 * 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);
	}
}

+63 −22
Original line number Diff line number Diff line
@@ -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,6 +152,7 @@ register_slot(acpi_handle handle, u32 lvl, void *context, void **rv)
	INIT_LIST_HEAD(&newfunc->sibling);
	newfunc->handle = handle;
	newfunc->function = function;
	if (ACPI_SUCCESS(status))
		newfunc->flags = FUNC_HAS_EJ0;

	if (ACPI_SUCCESS(acpi_get_handle(handle, "_STA", &tmp)))
@@ -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 */
	if (!(newfunc->flags & FUNC_HAS_DCK)) {
		status = acpi_install_notify_handler(handle,
					     ACPI_SYSTEM_NOTIFY,
					     handle_hotplug_event_func,
					     newfunc);

	if (ACPI_FAILURE(status)) {
		if (ACPI_FAILURE(status))
			err("failed to register interrupt notify handler\n");
		return status;
	}
	} 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);
			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");
			}
			pci_dev_put(func->pci_dev);
			list_del(list);
			kfree(func);
@@ -828,7 +862,15 @@ 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)) {
	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) {
@@ -836,7 +878,6 @@ static int acpiphp_bus_add(struct acpiphp_func *func)
			-ret_val);
		goto acpiphp_bus_add_out;
	}
	}
	/*
	 * try to start anyway.  We could have failed to add
	 * simply because this bus had previously been added
@@ -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];