Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /*
0003  * ACPI PCI Hot Plug IBM Extension
0004  *
0005  * Copyright (C) 2004 Vernon Mauery <vernux@us.ibm.com>
0006  * Copyright (C) 2004 IBM Corp.
0007  *
0008  * All rights reserved.
0009  *
0010  * Send feedback to <vernux@us.ibm.com>
0011  *
0012  */
0013 
0014 #define pr_fmt(fmt) "acpiphp_ibm: " fmt
0015 
0016 #include <linux/init.h>
0017 #include <linux/slab.h>
0018 #include <linux/module.h>
0019 #include <linux/kernel.h>
0020 #include <linux/sysfs.h>
0021 #include <linux/kobject.h>
0022 #include <linux/moduleparam.h>
0023 #include <linux/pci.h>
0024 #include <linux/uaccess.h>
0025 
0026 #include "acpiphp.h"
0027 #include "../pci.h"
0028 
0029 #define DRIVER_VERSION  "1.0.1"
0030 #define DRIVER_AUTHOR   "Irene Zubarev <zubarev@us.ibm.com>, Vernon Mauery <vernux@us.ibm.com>"
0031 #define DRIVER_DESC "ACPI Hot Plug PCI Controller Driver IBM extension"
0032 
0033 
0034 MODULE_AUTHOR(DRIVER_AUTHOR);
0035 MODULE_DESCRIPTION(DRIVER_DESC);
0036 MODULE_LICENSE("GPL");
0037 MODULE_VERSION(DRIVER_VERSION);
0038 
0039 #define FOUND_APCI 0x61504349
0040 /* these are the names for the IBM ACPI pseudo-device */
0041 #define IBM_HARDWARE_ID1 "IBM37D0"
0042 #define IBM_HARDWARE_ID2 "IBM37D4"
0043 
0044 #define hpslot_to_sun(A) (to_slot(A)->sun)
0045 
0046 /* union apci_descriptor - allows access to the
0047  * various device descriptors that are embedded in the
0048  * aPCI table
0049  */
0050 union apci_descriptor {
0051     struct {
0052         char sig[4];
0053         u8   len;
0054     } header;
0055     struct {
0056         u8  type;
0057         u8  len;
0058         u16 slot_id;
0059         u8  bus_id;
0060         u8  dev_num;
0061         u8  slot_num;
0062         u8  slot_attr[2];
0063         u8  attn;
0064         u8  status[2];
0065         u8  sun;
0066         u8  res[3];
0067     } slot;
0068     struct {
0069         u8 type;
0070         u8 len;
0071     } generic;
0072 };
0073 
0074 /* struct notification - keeps info about the device
0075  * that cause the ACPI notification event
0076  */
0077 struct notification {
0078     struct acpi_device *device;
0079     u8                  event;
0080 };
0081 
0082 static int ibm_set_attention_status(struct hotplug_slot *slot, u8 status);
0083 static int ibm_get_attention_status(struct hotplug_slot *slot, u8 *status);
0084 static void ibm_handle_events(acpi_handle handle, u32 event, void *context);
0085 static int ibm_get_table_from_acpi(char **bufp);
0086 static ssize_t ibm_read_apci_table(struct file *filp, struct kobject *kobj,
0087                    struct bin_attribute *bin_attr,
0088                    char *buffer, loff_t pos, size_t size);
0089 static acpi_status __init ibm_find_acpi_device(acpi_handle handle,
0090         u32 lvl, void *context, void **rv);
0091 static int __init ibm_acpiphp_init(void);
0092 static void __exit ibm_acpiphp_exit(void);
0093 
0094 static acpi_handle ibm_acpi_handle;
0095 static struct notification ibm_note;
0096 static struct bin_attribute ibm_apci_table_attr __ro_after_init = {
0097         .attr = {
0098             .name = "apci_table",
0099             .mode = S_IRUGO,
0100         },
0101         .read = ibm_read_apci_table,
0102         .write = NULL,
0103 };
0104 static struct acpiphp_attention_info ibm_attention_info =
0105 {
0106     .set_attn = ibm_set_attention_status,
0107     .get_attn = ibm_get_attention_status,
0108     .owner = THIS_MODULE,
0109 };
0110 
0111 /**
0112  * ibm_slot_from_id - workaround for bad ibm hardware
0113  * @id: the slot number that linux refers to the slot by
0114  *
0115  * Description: This method returns the aCPI slot descriptor
0116  * corresponding to the Linux slot number.  This descriptor
0117  * has info about the aPCI slot id and attention status.
0118  * This descriptor must be freed using kfree when done.
0119  */
0120 static union apci_descriptor *ibm_slot_from_id(int id)
0121 {
0122     int ind = 0, size;
0123     union apci_descriptor *ret = NULL, *des;
0124     char *table;
0125 
0126     size = ibm_get_table_from_acpi(&table);
0127     if (size < 0)
0128         return NULL;
0129     des = (union apci_descriptor *)table;
0130     if (memcmp(des->header.sig, "aPCI", 4) != 0)
0131         goto ibm_slot_done;
0132 
0133     des = (union apci_descriptor *)&table[ind += des->header.len];
0134     while (ind < size && (des->generic.type != 0x82 ||
0135             des->slot.slot_num != id)) {
0136         des = (union apci_descriptor *)&table[ind += des->generic.len];
0137     }
0138 
0139     if (ind < size && des->slot.slot_num == id)
0140         ret = des;
0141 
0142 ibm_slot_done:
0143     if (ret) {
0144         ret = kmalloc(sizeof(union apci_descriptor), GFP_KERNEL);
0145         if (ret)
0146             memcpy(ret, des, sizeof(union apci_descriptor));
0147     }
0148     kfree(table);
0149     return ret;
0150 }
0151 
0152 /**
0153  * ibm_set_attention_status - callback method to set the attention LED
0154  * @slot: the hotplug_slot to work with
0155  * @status: what to set the LED to (0 or 1)
0156  *
0157  * Description: This method is registered with the acpiphp module as a
0158  * callback to do the device specific task of setting the LED status.
0159  */
0160 static int ibm_set_attention_status(struct hotplug_slot *slot, u8 status)
0161 {
0162     union acpi_object args[2];
0163     struct acpi_object_list params = { .pointer = args, .count = 2 };
0164     acpi_status stat;
0165     unsigned long long rc;
0166     union apci_descriptor *ibm_slot;
0167     int id = hpslot_to_sun(slot);
0168 
0169     ibm_slot = ibm_slot_from_id(id);
0170     if (!ibm_slot) {
0171         pr_err("APLS null ACPI descriptor for slot %d\n", id);
0172         return -ENODEV;
0173     }
0174 
0175     pr_debug("%s: set slot %d (%d) attention status to %d\n", __func__,
0176             ibm_slot->slot.slot_num, ibm_slot->slot.slot_id,
0177             (status ? 1 : 0));
0178 
0179     args[0].type = ACPI_TYPE_INTEGER;
0180     args[0].integer.value = ibm_slot->slot.slot_id;
0181     args[1].type = ACPI_TYPE_INTEGER;
0182     args[1].integer.value = (status) ? 1 : 0;
0183 
0184     kfree(ibm_slot);
0185 
0186     stat = acpi_evaluate_integer(ibm_acpi_handle, "APLS", &params, &rc);
0187     if (ACPI_FAILURE(stat)) {
0188         pr_err("APLS evaluation failed:  0x%08x\n", stat);
0189         return -ENODEV;
0190     } else if (!rc) {
0191         pr_err("APLS method failed:  0x%08llx\n", rc);
0192         return -ERANGE;
0193     }
0194     return 0;
0195 }
0196 
0197 /**
0198  * ibm_get_attention_status - callback method to get attention LED status
0199  * @slot: the hotplug_slot to work with
0200  * @status: returns what the LED is set to (0 or 1)
0201  *
0202  * Description: This method is registered with the acpiphp module as a
0203  * callback to do the device specific task of getting the LED status.
0204  *
0205  * Because there is no direct method of getting the LED status directly
0206  * from an ACPI call, we read the aPCI table and parse out our
0207  * slot descriptor to read the status from that.
0208  */
0209 static int ibm_get_attention_status(struct hotplug_slot *slot, u8 *status)
0210 {
0211     union apci_descriptor *ibm_slot;
0212     int id = hpslot_to_sun(slot);
0213 
0214     ibm_slot = ibm_slot_from_id(id);
0215     if (!ibm_slot) {
0216         pr_err("APLS null ACPI descriptor for slot %d\n", id);
0217         return -ENODEV;
0218     }
0219 
0220     if (ibm_slot->slot.attn & 0xa0 || ibm_slot->slot.status[1] & 0x08)
0221         *status = 1;
0222     else
0223         *status = 0;
0224 
0225     pr_debug("%s: get slot %d (%d) attention status is %d\n", __func__,
0226             ibm_slot->slot.slot_num, ibm_slot->slot.slot_id,
0227             *status);
0228 
0229     kfree(ibm_slot);
0230     return 0;
0231 }
0232 
0233 /**
0234  * ibm_handle_events - listens for ACPI events for the IBM37D0 device
0235  * @handle: an ACPI handle to the device that caused the event
0236  * @event: the event info (device specific)
0237  * @context: passed context (our notification struct)
0238  *
0239  * Description: This method is registered as a callback with the ACPI
0240  * subsystem it is called when this device has an event to notify the OS of.
0241  *
0242  * The events actually come from the device as two events that get
0243  * synthesized into one event with data by this function.  The event
0244  * ID comes first and then the slot number that caused it.  We report
0245  * this as one event to the OS.
0246  *
0247  * From section 5.6.2.2 of the ACPI 2.0 spec, I understand that the OSPM will
0248  * only re-enable the interrupt that causes this event AFTER this method
0249  * has returned, thereby enforcing serial access for the notification struct.
0250  */
0251 static void ibm_handle_events(acpi_handle handle, u32 event, void *context)
0252 {
0253     u8 detail = event & 0x0f;
0254     u8 subevent = event & 0xf0;
0255     struct notification *note = context;
0256 
0257     pr_debug("%s: Received notification %02x\n", __func__, event);
0258 
0259     if (subevent == 0x80) {
0260         pr_debug("%s: generating bus event\n", __func__);
0261         acpi_bus_generate_netlink_event(note->device->pnp.device_class,
0262                           dev_name(&note->device->dev),
0263                           note->event, detail);
0264     } else
0265         note->event = event;
0266 }
0267 
0268 /**
0269  * ibm_get_table_from_acpi - reads the APLS buffer from ACPI
0270  * @bufp: address to pointer to allocate for the table
0271  *
0272  * Description: This method reads the APLS buffer in from ACPI and
0273  * stores the "stripped" table into a single buffer
0274  * it allocates and passes the address back in bufp.
0275  *
0276  * If NULL is passed in as buffer, this method only calculates
0277  * the size of the table and returns that without filling
0278  * in the buffer.
0279  *
0280  * Returns < 0 on error or the size of the table on success.
0281  */
0282 static int ibm_get_table_from_acpi(char **bufp)
0283 {
0284     union acpi_object *package;
0285     struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
0286     acpi_status status;
0287     char *lbuf = NULL;
0288     int i, size = -EIO;
0289 
0290     status = acpi_evaluate_object(ibm_acpi_handle, "APCI", NULL, &buffer);
0291     if (ACPI_FAILURE(status)) {
0292         pr_err("%s:  APCI evaluation failed\n", __func__);
0293         return -ENODEV;
0294     }
0295 
0296     package = (union acpi_object *) buffer.pointer;
0297     if (!(package) ||
0298             (package->type != ACPI_TYPE_PACKAGE) ||
0299             !(package->package.elements)) {
0300         pr_err("%s:  Invalid APCI object\n", __func__);
0301         goto read_table_done;
0302     }
0303 
0304     for (size = 0, i = 0; i < package->package.count; i++) {
0305         if (package->package.elements[i].type != ACPI_TYPE_BUFFER) {
0306             pr_err("%s:  Invalid APCI element %d\n", __func__, i);
0307             goto read_table_done;
0308         }
0309         size += package->package.elements[i].buffer.length;
0310     }
0311 
0312     if (bufp == NULL)
0313         goto read_table_done;
0314 
0315     lbuf = kzalloc(size, GFP_KERNEL);
0316     pr_debug("%s: element count: %i, ASL table size: %i, &table = 0x%p\n",
0317             __func__, package->package.count, size, lbuf);
0318 
0319     if (lbuf) {
0320         *bufp = lbuf;
0321     } else {
0322         size = -ENOMEM;
0323         goto read_table_done;
0324     }
0325 
0326     size = 0;
0327     for (i = 0; i < package->package.count; i++) {
0328         memcpy(&lbuf[size],
0329                 package->package.elements[i].buffer.pointer,
0330                 package->package.elements[i].buffer.length);
0331         size += package->package.elements[i].buffer.length;
0332     }
0333 
0334 read_table_done:
0335     kfree(buffer.pointer);
0336     return size;
0337 }
0338 
0339 /**
0340  * ibm_read_apci_table - callback for the sysfs apci_table file
0341  * @filp: the open sysfs file
0342  * @kobj: the kobject this binary attribute is a part of
0343  * @bin_attr: struct bin_attribute for this file
0344  * @buffer: the kernel space buffer to fill
0345  * @pos: the offset into the file
0346  * @size: the number of bytes requested
0347  *
0348  * Description: Gets registered with sysfs as the reader callback
0349  * to be executed when /sys/bus/pci/slots/apci_table gets read.
0350  *
0351  * Since we don't get notified on open and close for this file,
0352  * things get really tricky here...
0353  * our solution is to only allow reading the table in all at once.
0354  */
0355 static ssize_t ibm_read_apci_table(struct file *filp, struct kobject *kobj,
0356                    struct bin_attribute *bin_attr,
0357                    char *buffer, loff_t pos, size_t size)
0358 {
0359     int bytes_read = -EINVAL;
0360     char *table = NULL;
0361 
0362     pr_debug("%s: pos = %d, size = %zd\n", __func__, (int)pos, size);
0363 
0364     if (pos == 0) {
0365         bytes_read = ibm_get_table_from_acpi(&table);
0366         if (bytes_read > 0 && bytes_read <= size)
0367             memcpy(buffer, table, bytes_read);
0368         kfree(table);
0369     }
0370     return bytes_read;
0371 }
0372 
0373 /**
0374  * ibm_find_acpi_device - callback to find our ACPI device
0375  * @handle: the ACPI handle of the device we are inspecting
0376  * @lvl: depth into the namespace tree
0377  * @context: a pointer to our handle to fill when we find the device
0378  * @rv: a return value to fill if desired
0379  *
0380  * Description: Used as a callback when calling acpi_walk_namespace
0381  * to find our device.  When this method returns non-zero
0382  * acpi_walk_namespace quits its search and returns our value.
0383  */
0384 static acpi_status __init ibm_find_acpi_device(acpi_handle handle,
0385         u32 lvl, void *context, void **rv)
0386 {
0387     acpi_handle *phandle = (acpi_handle *)context;
0388     unsigned long long current_status = 0;
0389     acpi_status status;
0390     struct acpi_device_info *info;
0391     int retval = 0;
0392 
0393     status = acpi_get_object_info(handle, &info);
0394     if (ACPI_FAILURE(status)) {
0395         pr_err("%s:  Failed to get device information status=0x%x\n",
0396             __func__, status);
0397         return retval;
0398     }
0399 
0400     acpi_bus_get_status_handle(handle, &current_status);
0401 
0402     if (current_status && (info->valid & ACPI_VALID_HID) &&
0403             (!strcmp(info->hardware_id.string, IBM_HARDWARE_ID1) ||
0404              !strcmp(info->hardware_id.string, IBM_HARDWARE_ID2))) {
0405         pr_debug("found hardware: %s, handle: %p\n",
0406             info->hardware_id.string, handle);
0407         *phandle = handle;
0408         /* returning non-zero causes the search to stop
0409          * and returns this value to the caller of
0410          * acpi_walk_namespace, but it also causes some warnings
0411          * in the acpi debug code to print...
0412          */
0413         retval = FOUND_APCI;
0414     }
0415     kfree(info);
0416     return retval;
0417 }
0418 
0419 static int __init ibm_acpiphp_init(void)
0420 {
0421     int retval = 0;
0422     acpi_status status;
0423     struct acpi_device *device;
0424     struct kobject *sysdir = &pci_slots_kset->kobj;
0425 
0426     pr_debug("%s\n", __func__);
0427 
0428     if (acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
0429             ACPI_UINT32_MAX, ibm_find_acpi_device, NULL,
0430             &ibm_acpi_handle, NULL) != FOUND_APCI) {
0431         pr_err("%s: acpi_walk_namespace failed\n", __func__);
0432         retval = -ENODEV;
0433         goto init_return;
0434     }
0435     pr_debug("%s: found IBM aPCI device\n", __func__);
0436     device = acpi_fetch_acpi_dev(ibm_acpi_handle);
0437     if (!device) {
0438         pr_err("%s: acpi_fetch_acpi_dev failed\n", __func__);
0439         retval = -ENODEV;
0440         goto init_return;
0441     }
0442     if (acpiphp_register_attention(&ibm_attention_info)) {
0443         retval = -ENODEV;
0444         goto init_return;
0445     }
0446 
0447     ibm_note.device = device;
0448     status = acpi_install_notify_handler(ibm_acpi_handle,
0449             ACPI_DEVICE_NOTIFY, ibm_handle_events,
0450             &ibm_note);
0451     if (ACPI_FAILURE(status)) {
0452         pr_err("%s: Failed to register notification handler\n",
0453                 __func__);
0454         retval = -EBUSY;
0455         goto init_cleanup;
0456     }
0457 
0458     ibm_apci_table_attr.size = ibm_get_table_from_acpi(NULL);
0459     retval = sysfs_create_bin_file(sysdir, &ibm_apci_table_attr);
0460 
0461     return retval;
0462 
0463 init_cleanup:
0464     acpiphp_unregister_attention(&ibm_attention_info);
0465 init_return:
0466     return retval;
0467 }
0468 
0469 static void __exit ibm_acpiphp_exit(void)
0470 {
0471     acpi_status status;
0472     struct kobject *sysdir = &pci_slots_kset->kobj;
0473 
0474     pr_debug("%s\n", __func__);
0475 
0476     if (acpiphp_unregister_attention(&ibm_attention_info))
0477         pr_err("%s: attention info deregistration failed", __func__);
0478 
0479     status = acpi_remove_notify_handler(
0480                ibm_acpi_handle,
0481                ACPI_DEVICE_NOTIFY,
0482                ibm_handle_events);
0483     if (ACPI_FAILURE(status))
0484         pr_err("%s: Notification handler removal failed\n", __func__);
0485     /* remove the /sys entries */
0486     sysfs_remove_bin_file(sysdir, &ibm_apci_table_attr);
0487 }
0488 
0489 module_init(ibm_acpiphp_init);
0490 module_exit(ibm_acpiphp_exit);