Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S266151AbUF2XxF (ORCPT ); Tue, 29 Jun 2004 19:53:05 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S265941AbUF2Xw4 (ORCPT ); Tue, 29 Jun 2004 19:52:56 -0400 Received: from e6.ny.us.ibm.com ([32.97.182.106]:1488 "EHLO e6.ny.us.ibm.com") by vger.kernel.org with ESMTP id S266146AbUF2XvG (ORCPT ); Tue, 29 Jun 2004 19:51:06 -0400 Subject: [PATCH] [2/2] acpiphp extension for 2.6.7 From: Vernon Mauery To: Greg KH Cc: lkml , Pat Gaughen , Chris McDermott , Jess Botts , pcihpd-discuss@lists.sourceforge.net In-Reply-To: <20040624214555.GA1800@us.ibm.com> References: <1087934028.2068.57.camel@bluerat> <20040624214555.GA1800@us.ibm.com> Content-Type: text/plain Message-Id: <1088553055.25961.67.camel@bluerat> Mime-Version: 1.0 X-Mailer: Ximian Evolution 1.4.6 Date: Tue, 29 Jun 2004 16:50:55 -0700 Content-Transfer-Encoding: 7bit Sender: linux-kernel-owner@vger.kernel.org X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 16554 Lines: 542 02 - acpiphp_ibm-v1.0.1c.patch This patch adds the first driver that actually uses the callback function for attention LEDs that the acpiphp-attention patch adds. It searches the ACPI namespace for IBM hardware, sets up the callbacks and sets up a handler to read ACPI events and forward them on to /proc/acpi/event. It also exports an ACPI table that shows current hotplug status to userland. Kconfig | 12 + Makefile | 1 acpiphp_ibm.c | 476 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 489 insertions(+) Signed-off-by: Vernon Mauery ===================================================================== diff -ruN -X dontdiff linux-2.6.7.orig/drivers/pci/hotplug/Kconfig linux-2.6.7-apci/drivers/pci/hotplug/Kconfig --- linux-2.6.7.orig/drivers/pci/hotplug/Kconfig 2004-06-15 22:19:17.000000000 -0700 +++ linux-2.6.7-apci/drivers/pci/hotplug/Kconfig 2004-06-22 11:49:38.000000000 -0700 @@ -88,6 +88,18 @@ When in doubt, say N. +config HOTPLUG_PCI_ACPI_IBM + tristate "ACPI PCI Hotplug driver IBM extensions" + depends on HOTPLUG_PCI_ACPI + help + Say Y here if you have an IBM system that supports PCI Hotplug using + ACPI. + + To compile this driver as a module, choose M here: the + module will be called acpiphp_ibm. + + When in doubt, say N. + config HOTPLUG_PCI_CPCI bool "CompactPCI Hotplug driver" depends on HOTPLUG_PCI diff -ruN -X dontdiff linux-2.6.7.orig/drivers/pci/hotplug/Makefile linux-2.6.7-apci/drivers/pci/hotplug/Makefile --- linux-2.6.7.orig/drivers/pci/hotplug/Makefile 2004-06-15 22:19:43.000000000 -0700 +++ linux-2.6.7-apci/drivers/pci/hotplug/Makefile 2004-06-22 11:49:38.000000000 -0700 @@ -7,6 +7,7 @@ obj-$(CONFIG_HOTPLUG_PCI_COMPAQ) += cpqphp.o obj-$(CONFIG_HOTPLUG_PCI_IBM) += ibmphp.o obj-$(CONFIG_HOTPLUG_PCI_ACPI) += acpiphp.o +obj-$(CONFIG_HOTPLUG_PCI_ACPI_IBM) += acpiphp_ibm.o obj-$(CONFIG_HOTPLUG_PCI_CPCI_ZT5550) += cpcihp_zt5550.o obj-$(CONFIG_HOTPLUG_PCI_CPCI_GENERIC) += cpcihp_generic.o obj-$(CONFIG_HOTPLUG_PCI_PCIE) += pciehp.o diff -ruN -X dontdiff linux-2.6.7.orig/drivers/pci/hotplug/acpiphp_ibm.c linux-2.6.7-apci/drivers/pci/hotplug/acpiphp_ibm.c --- linux-2.6.7.orig/drivers/pci/hotplug/acpiphp_ibm.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.6.7-apci/drivers/pci/hotplug/acpiphp_ibm.c 2004-06-29 16:06:59.877502816 -0700 @@ -0,0 +1,476 @@ +/* + * ACPI PCI Hot Plug IBM Extension + * + * Copyright (C) 2004 Vernon Mauery + * Copyright (C) 2004 IBM Corp. + * + * 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 +//#include + +#include "acpiphp.h" +#include "pci_hotplug.h" + +#define DRIVER_VERSION "1.0.1" +#define DRIVER_AUTHOR "Irene Zubarev , Vernon Mauery " +#define DRIVER_DESC "ACPI Hot Plug PCI Controller Driver IBM extension" + +static int debug; + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_PARM(debug, "i"); +MODULE_PARM_DESC(debug, " Debugging mode enabled or not"); +#define MY_NAME "acpiphp_ibm" + +#ifdef dbg +#undef dbg +#endif +#define dbg(format, arg...) \ +do { \ + if (debug) \ + printk(KERN_DEBUG "%s: " format, \ + MY_NAME , ## arg); \ +} while (0) + +#define FOUND_APCI 0x61504349 +/* these are the names for the IBM ACPI pseudo-device */ +#define IBM_HARDWARE_ID1 "IBM37D0" +#define IBM_HARDWARE_ID2 "IBM37D4" + +/* union apci_descriptor - allows access to the + * various device descriptors that are embedded in the + * aPCI table + */ +union apci_descriptor { + struct { + char sig[4]; + u8 len; + } header; + struct { + u8 type; + u8 len; + u16 slot_id; + u8 bus_id; + u8 dev_num; + u8 slot_num; + u8 slot_attr[2]; + u8 attn; + u8 status[2]; + u8 sun; + } slot; + struct { + u8 type; + u8 len; + } generic; +}; + +/* struct notification - keeps info about the device + * that cause the ACPI notification event + */ +struct notification { + struct acpi_device *device; + u8 event; +}; + +/* forward declarations of functions for this module + */ +static int ibm_set_attention_status (struct hotplug_slot *slot, u8 status); +static int ibm_get_attention_status (struct hotplug_slot *slot, u8 *status); +static void ibm_handle_events(acpi_handle handle, u32 event, void *context); +static int ibm_get_table_from_acpi(char **bufp); +static ssize_t ibm_read_apci_table(struct kobject *kobj, + char *buffer, loff_t pos, size_t size); +static acpi_status __init ibm_find_acpi_device(acpi_handle handle, + u32 lvl, void *context, void **rv); +static int __init ibm_acpiphp_init(void); +static void __exit ibm_acpiphp_exit(void); + +/* static global variables + */ +static acpi_handle ibm_acpi_handle = NULL; +static struct notification ibm_note; +static struct bin_attribute ibm_apci_table_attr = { + .attr = { + .name = "apci_table", + .owner = THIS_MODULE, + .mode = S_IRUGO, + }, + .read = ibm_read_apci_table, + .write = NULL, +}; +static struct acpiphp_attention_info ibm_attention_info = +{ + .set_attn = ibm_set_attention_status, + .get_attn = ibm_get_attention_status, + .owner = THIS_MODULE, +}; + + +/* + * ibm_set_attention_status - callback method to set the attention LED + * + * this method is registered with the acpiphp module as a callback + * to do the device specific task of setting the LED status + * + */ +static int ibm_set_attention_status (struct hotplug_slot *slot, u8 status) +{ + int retval = 0; + union acpi_object args[2]; + struct acpi_object_list params = { .pointer = args, .count = 2 }; + acpi_status stat; + unsigned long rc = 0; + struct acpiphp_slot *acpi_slot; + + if (!slot || !slot->private) + return -EINVAL; + acpi_slot = ((struct slot *)(slot->private))->acpi_slot; + if (!acpi_slot) + return -EINVAL; + + dbg("%s: set slot %d attention status to %d\n", __FUNCTION__, + acpi_slot->sun, (status ? 1 : 0)); + + args[0].type = ACPI_TYPE_INTEGER; + args[0].integer.value = acpi_slot->sun; + args[1].type = ACPI_TYPE_INTEGER; + args[1].integer.value = (status) ? 1 : 0; + + stat = acpi_evaluate_integer(ibm_acpi_handle, "APLS", ¶ms, &rc); + if (ACPI_FAILURE(stat)) { + retval = -ENODEV; + err("APLS evaluation failed: 0x%08x\n", stat); + } else if (!rc) { + retval = -ERANGE; + err("APLS method failed: 0x%08lx\n", rc); + } + return retval; +} + +/* + * ibm_get_attention_status - callback method to get attention LED status + * + * this method is registered with the acpiphp module as a callback + * to do the device specific task of getting the LED status + * + * Because there is no direct method of getting the LED status directly + * from an ACPI call, we read the aPCI table and parse out our + * slot descriptor to read the status from that. + * + */ +static int ibm_get_attention_status (struct hotplug_slot *slot, u8 *status) +{ + int retval = -EINVAL, ind = 0, size; + char *table = NULL; + struct acpiphp_slot *acpi_slot; + union apci_descriptor *des; + + if (!slot || !slot->private) + goto get_attn_done; + acpi_slot = ((struct slot *)(slot->private))->acpi_slot; + if (!acpi_slot) + goto get_attn_done; + + size = ibm_get_table_from_acpi(&table); + if (size <= 0 || !table) + goto get_attn_done; + // read the header + des = (union apci_descriptor *)&table[ind]; + if (memcmp(des->header.sig, "aPCI", 4) != 0) + goto get_attn_done; + des = (union apci_descriptor *)&table[ind += des->header.len]; + while (ind < size && (des->generic.type != 0x82 || + des->slot.slot_id != acpi_slot->sun)) + des = (union apci_descriptor *)&table[ind += des->generic.len]; + if (ind < size && des->slot.slot_id == acpi_slot->sun) { + retval = 0; + if (des->slot.attn & 0xa0 || des->slot.status[1] & 0x08) + *status = 1; + else + *status = 0; + } + + dbg("%s: get slot %d attention status is %d retval=%x\n", + __FUNCTION__, acpi_slot->sun, *status, retval); + +get_attn_done: + if (table) + kfree(table); + return retval; +} + +/* + * ibm_handle_events - listens for ACPI events for the IBM37D0 device + * + * this method is registered as a callback with the ACPI subsystem + * it is called when this device has an event to notify the OS of + * + * the events actually come from the device as two events that get + * synthesized into one event with data by this function. The event + * ID comes first and then the slot number that caused it. We report + * this as one event to the OS. + * + */ +static void ibm_handle_events(acpi_handle handle, u32 event, void *context) +{ + // FIXME: this may have re-entrancy errors with event being static... + u8 detail = event & 0x0f; + u8 subevent = event & 0xf0; + + struct notification *note = (struct notification *)context; + + dbg("%s: Received notification %02x\n", __FUNCTION__, event); + + if (subevent == 0x80) { + dbg("%s: generationg bus event\n", __FUNCTION__); + acpi_bus_generate_event(note->device, note->event, detail); + } else + note->event = event; +} + +/* + * ibm_get_table_from_acpi - reads the APLS buffer from ACPI + * + * this method reads the APLS buffer in from ACPI and + * stores the "stripped" table into a single buffer + * it allocates and passes the address back in bufp + * + * if NULL is passed in as buffer, this method only calculates + * the size of the table and returns that without filling + * in the buffer + * + * returns < 0 on error or the size of the table on success + */ +static int ibm_get_table_from_acpi(char **bufp) +{ + union acpi_object *package; + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + char *lbuf = NULL; + int i, size = -EIO; + + status = acpi_evaluate_object(ibm_acpi_handle, "APCI", NULL, &buffer); + if (ACPI_FAILURE(status)) { + err("%s: APCI evaluation failed\n", __FUNCTION__); + return -ENODEV; + } + + package = (union acpi_object *) buffer.pointer; + if(!(package) || + (package->type != ACPI_TYPE_PACKAGE) || + !(package->package.elements)) { + err("%s: Invalid APCI object\n", __FUNCTION__); + goto read_table_done; + } + + for(size = 0, i = 0; i < package->package.count; i++) { + if (package->package.elements[i].type != ACPI_TYPE_BUFFER) { + err("%s: Invalid APCI element %d\n", __FUNCTION__, i); + goto read_table_done; + } + size += package->package.elements[i].buffer.length; + } + + if (bufp == NULL) + goto read_table_done; + + lbuf = kmalloc(size, GFP_KERNEL); + dbg("%s: element count: %i, ASL table size: %i, &table = 0x%x\n", + __FUNCTION__, package->package.count, size, (int)lbuf); + + if (lbuf) { + *bufp = lbuf; + memset(lbuf, 0, size); + } else { + size = -ENOMEM; + goto read_table_done; + } + + size = 0; + for (i=0; ipackage.count; i++) { + memcpy(&lbuf[size], + package->package.elements[i].buffer.pointer, + package->package.elements[i].buffer.length); + size += package->package.elements[i].buffer.length; + } + +read_table_done: + kfree(buffer.pointer); + return size; +} + +/* ibm_read_apci_table - callback for the sysfs apci_table file + * + * gets registered with sysfs as the reader callback + * to be executed when /sys/bus/pci/slots/apci_table gets read + * + * since we don't get notified on open and close for this file, + * things get really tricky here... + * our solution is to only allow reading the table in all at once + */ +static ssize_t ibm_read_apci_table(struct kobject *kobj, + char *buffer, loff_t pos, size_t size) +{ + int bytes_read = -EINVAL; + char *table = NULL; + + dbg("%s: pos = %d, size = %d\n", __FUNCTION__, (int)pos, size); + + if (pos == 0) { + bytes_read = ibm_get_table_from_acpi(&table); + if (bytes_read > 0 && bytes_read <= size) + memcpy(buffer, table, bytes_read); + if (table) + kfree(table); + } + return bytes_read; +} + +/* ibm_find_acpi_device - callback to find our ACPI device + * + * used as a callback when calling acpi_walk_namespace + * to find our device. When this method returns non-zero + * acpi_walk_namespace quits its search and returns our value + */ +static acpi_status __init ibm_find_acpi_device(acpi_handle handle, + u32 lvl, void *context, void **rv) +{ + acpi_handle *phandle = (acpi_handle *)context; + acpi_status status; + struct acpi_device_info info; + struct acpi_buffer info_buffer = { + .length = sizeof(struct acpi_device_info), + .pointer = &info, + }; + + status = acpi_get_object_info(handle, &info_buffer); + if (ACPI_FAILURE(status)) { + err("%s: Failed to get device information", __FUNCTION__); + return 0; + } + info.hardware_id.value[sizeof(info.hardware_id.value) - 1] = '\0'; + + if(info.current_status && (info.valid & ACPI_VALID_HID) && + (!strcmp(info.hardware_id.value, IBM_HARDWARE_ID1) || + !strcmp(info.hardware_id.value, IBM_HARDWARE_ID2))) { + dbg("found hardware: %s, handle: %x\n", info.hardware_id.value, + (unsigned int)handle); + *phandle = handle; + /* returning non-zero causes the search to stop + * and returns this value to the caller of + * acpi_walk_namespace, but it also causes some warnings + * in the acpi debug code to print... + */ + return FOUND_APCI; + } + return 0; +} + +static int __init ibm_acpiphp_init(void) +{ + int retval = 0; + acpi_status status; + struct acpi_device *device; + struct kobject *sysdir = &pci_hotplug_slots_subsys.kset.kobj; + + dbg("%s\n", __FUNCTION__); + + if (acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, ibm_find_acpi_device, + &ibm_acpi_handle, NULL) != FOUND_APCI) { + err("%s: acpi_walk_namespace failed\n", __FUNCTION__); + retval = -ENODEV; + goto init_return; + } + dbg("%s: found IBM aPCI device\n", __FUNCTION__); + if (acpi_bus_get_device(ibm_acpi_handle, &device)) { + err("%s: acpi_bus_get_device failed\n", __FUNCTION__); + retval = -ENODEV; + goto init_return; + } + if (acpiphp_register_attention_info(&ibm_attention_info)) { + retval = -ENODEV; + goto init_return; + } + + ibm_note.device = device; + status = acpi_install_notify_handler( + ibm_acpi_handle, + ACPI_DEVICE_NOTIFY, + ibm_handle_events, + (void *)&ibm_note); + if (ACPI_FAILURE(status)) + { + err("%s: Failed to register notification handler\n", + __FUNCTION__); + retval = -EBUSY; + goto init_cleanup; + } + + ibm_apci_table_attr.size = ibm_get_table_from_acpi(NULL); + retval = sysfs_create_bin_file(sysdir, &ibm_apci_table_attr); + + return retval; + +init_cleanup: + acpiphp_unregister_attention_info(&ibm_attention_info); +init_return: + return retval; +} + +static void __exit ibm_acpiphp_exit(void) +{ + acpi_status status; + struct kobject *sysdir = &pci_hotplug_slots_subsys.kset.kobj; + + dbg("%s\n", __FUNCTION__); + + if (acpiphp_unregister_attention_info(&ibm_attention_info)) + err("%s: attention info deregistration failed", __FUNCTION__); + + status = acpi_remove_notify_handler( + ibm_acpi_handle, + ACPI_DEVICE_NOTIFY, + ibm_handle_events); + if (ACPI_FAILURE(status)) + err("%s: Notification handler removal failed\n", + __FUNCTION__); + // remove the /sys entries + if (sysfs_remove_bin_file(sysdir, &ibm_apci_table_attr)) + err("%s: removal of sysfs file apci_table failed\n", + __FUNCTION__); +} + +module_init(ibm_acpiphp_init); +module_exit(ibm_acpiphp_exit); + + - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/