Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0+
2 : : /*
3 : : * ACPI PCI Hot Plug IBM Extension
4 : : *
5 : : * Copyright (C) 2004 Vernon Mauery <vernux@us.ibm.com>
6 : : * Copyright (C) 2004 IBM Corp.
7 : : *
8 : : * All rights reserved.
9 : : *
10 : : * Send feedback to <vernux@us.ibm.com>
11 : : *
12 : : */
13 : :
14 : : #define pr_fmt(fmt) "acpiphp_ibm: " fmt
15 : :
16 : : #include <linux/init.h>
17 : : #include <linux/slab.h>
18 : : #include <linux/module.h>
19 : : #include <linux/kernel.h>
20 : : #include <linux/sysfs.h>
21 : : #include <linux/kobject.h>
22 : : #include <linux/moduleparam.h>
23 : : #include <linux/pci.h>
24 : : #include <linux/uaccess.h>
25 : :
26 : : #include "acpiphp.h"
27 : : #include "../pci.h"
28 : :
29 : : #define DRIVER_VERSION "1.0.1"
30 : : #define DRIVER_AUTHOR "Irene Zubarev <zubarev@us.ibm.com>, Vernon Mauery <vernux@us.ibm.com>"
31 : : #define DRIVER_DESC "ACPI Hot Plug PCI Controller Driver IBM extension"
32 : :
33 : :
34 : : MODULE_AUTHOR(DRIVER_AUTHOR);
35 : : MODULE_DESCRIPTION(DRIVER_DESC);
36 : : MODULE_LICENSE("GPL");
37 : : MODULE_VERSION(DRIVER_VERSION);
38 : :
39 : : #define FOUND_APCI 0x61504349
40 : : /* these are the names for the IBM ACPI pseudo-device */
41 : : #define IBM_HARDWARE_ID1 "IBM37D0"
42 : : #define IBM_HARDWARE_ID2 "IBM37D4"
43 : :
44 : : #define hpslot_to_sun(A) (to_slot(A)->sun)
45 : :
46 : : /* union apci_descriptor - allows access to the
47 : : * various device descriptors that are embedded in the
48 : : * aPCI table
49 : : */
50 : : union apci_descriptor {
51 : : struct {
52 : : char sig[4];
53 : : u8 len;
54 : : } header;
55 : : struct {
56 : : u8 type;
57 : : u8 len;
58 : : u16 slot_id;
59 : : u8 bus_id;
60 : : u8 dev_num;
61 : : u8 slot_num;
62 : : u8 slot_attr[2];
63 : : u8 attn;
64 : : u8 status[2];
65 : : u8 sun;
66 : : u8 res[3];
67 : : } slot;
68 : : struct {
69 : : u8 type;
70 : : u8 len;
71 : : } generic;
72 : : };
73 : :
74 : : /* struct notification - keeps info about the device
75 : : * that cause the ACPI notification event
76 : : */
77 : : struct notification {
78 : : struct acpi_device *device;
79 : : u8 event;
80 : : };
81 : :
82 : : static int ibm_set_attention_status(struct hotplug_slot *slot, u8 status);
83 : : static int ibm_get_attention_status(struct hotplug_slot *slot, u8 *status);
84 : : static void ibm_handle_events(acpi_handle handle, u32 event, void *context);
85 : : static int ibm_get_table_from_acpi(char **bufp);
86 : : static ssize_t ibm_read_apci_table(struct file *filp, struct kobject *kobj,
87 : : struct bin_attribute *bin_attr,
88 : : char *buffer, loff_t pos, size_t size);
89 : : static acpi_status __init ibm_find_acpi_device(acpi_handle handle,
90 : : u32 lvl, void *context, void **rv);
91 : : static int __init ibm_acpiphp_init(void);
92 : : static void __exit ibm_acpiphp_exit(void);
93 : :
94 : : static acpi_handle ibm_acpi_handle;
95 : : static struct notification ibm_note;
96 : : static struct bin_attribute ibm_apci_table_attr __ro_after_init = {
97 : : .attr = {
98 : : .name = "apci_table",
99 : : .mode = S_IRUGO,
100 : : },
101 : : .read = ibm_read_apci_table,
102 : : .write = NULL,
103 : : };
104 : : static struct acpiphp_attention_info ibm_attention_info =
105 : : {
106 : : .set_attn = ibm_set_attention_status,
107 : : .get_attn = ibm_get_attention_status,
108 : : .owner = THIS_MODULE,
109 : : };
110 : :
111 : : /**
112 : : * ibm_slot_from_id - workaround for bad ibm hardware
113 : : * @id: the slot number that linux refers to the slot by
114 : : *
115 : : * Description: This method returns the aCPI slot descriptor
116 : : * corresponding to the Linux slot number. This descriptor
117 : : * has info about the aPCI slot id and attention status.
118 : : * This descriptor must be freed using kfree when done.
119 : : */
120 : 0 : static union apci_descriptor *ibm_slot_from_id(int id)
121 : : {
122 : 0 : int ind = 0, size;
123 : 0 : union apci_descriptor *ret = NULL, *des;
124 : 0 : char *table;
125 : :
126 : 0 : size = ibm_get_table_from_acpi(&table);
127 [ # # ]: 0 : if (size < 0)
128 : : return NULL;
129 : 0 : des = (union apci_descriptor *)table;
130 [ # # ]: 0 : if (memcmp(des->header.sig, "aPCI", 4) != 0)
131 : 0 : goto ibm_slot_done;
132 : :
133 : 0 : des = (union apci_descriptor *)&table[ind += des->header.len];
134 [ # # # # ]: 0 : while (ind < size && (des->generic.type != 0x82 ||
135 [ # # ]: 0 : des->slot.slot_num != id)) {
136 : 0 : des = (union apci_descriptor *)&table[ind += des->generic.len];
137 : : }
138 : :
139 [ # # # # ]: 0 : if (ind < size && des->slot.slot_num == id)
140 : 0 : ret = des;
141 : :
142 : 0 : ibm_slot_done:
143 [ # # ]: 0 : if (ret) {
144 : 0 : ret = kmalloc(sizeof(union apci_descriptor), GFP_KERNEL);
145 [ # # ]: 0 : if (ret)
146 : 0 : memcpy(ret, des, sizeof(union apci_descriptor));
147 : : }
148 : 0 : kfree(table);
149 : 0 : return ret;
150 : : }
151 : :
152 : : /**
153 : : * ibm_set_attention_status - callback method to set the attention LED
154 : : * @slot: the hotplug_slot to work with
155 : : * @status: what to set the LED to (0 or 1)
156 : : *
157 : : * Description: This method is registered with the acpiphp module as a
158 : : * callback to do the device specific task of setting the LED status.
159 : : */
160 : 0 : static int ibm_set_attention_status(struct hotplug_slot *slot, u8 status)
161 : : {
162 : 0 : union acpi_object args[2];
163 : 0 : struct acpi_object_list params = { .pointer = args, .count = 2 };
164 : 0 : acpi_status stat;
165 : 0 : unsigned long long rc;
166 : 0 : union apci_descriptor *ibm_slot;
167 : 0 : int id = hpslot_to_sun(slot);
168 : :
169 : 0 : ibm_slot = ibm_slot_from_id(id);
170 [ # # ]: 0 : if (!ibm_slot) {
171 : 0 : pr_err("APLS null ACPI descriptor for slot %d\n", id);
172 : 0 : return -ENODEV;
173 : : }
174 : :
175 : 0 : pr_debug("%s: set slot %d (%d) attention status to %d\n", __func__,
176 : : ibm_slot->slot.slot_num, ibm_slot->slot.slot_id,
177 : : (status ? 1 : 0));
178 : :
179 : 0 : args[0].type = ACPI_TYPE_INTEGER;
180 : 0 : args[0].integer.value = ibm_slot->slot.slot_id;
181 : 0 : args[1].type = ACPI_TYPE_INTEGER;
182 : 0 : args[1].integer.value = (status) ? 1 : 0;
183 : :
184 : 0 : kfree(ibm_slot);
185 : :
186 : 0 : stat = acpi_evaluate_integer(ibm_acpi_handle, "APLS", ¶ms, &rc);
187 [ # # ]: 0 : if (ACPI_FAILURE(stat)) {
188 : 0 : pr_err("APLS evaluation failed: 0x%08x\n", stat);
189 : 0 : return -ENODEV;
190 [ # # ]: 0 : } else if (!rc) {
191 : 0 : pr_err("APLS method failed: 0x%08llx\n", rc);
192 : 0 : return -ERANGE;
193 : : }
194 : : return 0;
195 : : }
196 : :
197 : : /**
198 : : * ibm_get_attention_status - callback method to get attention LED status
199 : : * @slot: the hotplug_slot to work with
200 : : * @status: returns what the LED is set to (0 or 1)
201 : : *
202 : : * Description: This method is registered with the acpiphp module as a
203 : : * callback to do the device specific task of getting the LED status.
204 : : *
205 : : * Because there is no direct method of getting the LED status directly
206 : : * from an ACPI call, we read the aPCI table and parse out our
207 : : * slot descriptor to read the status from that.
208 : : */
209 : 0 : static int ibm_get_attention_status(struct hotplug_slot *slot, u8 *status)
210 : : {
211 : 0 : union apci_descriptor *ibm_slot;
212 : 0 : int id = hpslot_to_sun(slot);
213 : :
214 : 0 : ibm_slot = ibm_slot_from_id(id);
215 [ # # ]: 0 : if (!ibm_slot) {
216 : 0 : pr_err("APLS null ACPI descriptor for slot %d\n", id);
217 : 0 : return -ENODEV;
218 : : }
219 : :
220 [ # # # # ]: 0 : if (ibm_slot->slot.attn & 0xa0 || ibm_slot->slot.status[1] & 0x08)
221 : 0 : *status = 1;
222 : : else
223 : 0 : *status = 0;
224 : :
225 : 0 : pr_debug("%s: get slot %d (%d) attention status is %d\n", __func__,
226 : : ibm_slot->slot.slot_num, ibm_slot->slot.slot_id,
227 : : *status);
228 : :
229 : 0 : kfree(ibm_slot);
230 : 0 : return 0;
231 : : }
232 : :
233 : : /**
234 : : * ibm_handle_events - listens for ACPI events for the IBM37D0 device
235 : : * @handle: an ACPI handle to the device that caused the event
236 : : * @event: the event info (device specific)
237 : : * @context: passed context (our notification struct)
238 : : *
239 : : * Description: This method is registered as a callback with the ACPI
240 : : * subsystem it is called when this device has an event to notify the OS of.
241 : : *
242 : : * The events actually come from the device as two events that get
243 : : * synthesized into one event with data by this function. The event
244 : : * ID comes first and then the slot number that caused it. We report
245 : : * this as one event to the OS.
246 : : *
247 : : * From section 5.6.2.2 of the ACPI 2.0 spec, I understand that the OSPM will
248 : : * only re-enable the interrupt that causes this event AFTER this method
249 : : * has returned, thereby enforcing serial access for the notification struct.
250 : : */
251 : 0 : static void ibm_handle_events(acpi_handle handle, u32 event, void *context)
252 : : {
253 : 0 : u8 detail = event & 0x0f;
254 : 0 : u8 subevent = event & 0xf0;
255 : 0 : struct notification *note = context;
256 : :
257 : 0 : pr_debug("%s: Received notification %02x\n", __func__, event);
258 : :
259 [ # # ]: 0 : if (subevent == 0x80) {
260 : 0 : pr_debug("%s: generating bus event\n", __func__);
261 : 0 : acpi_bus_generate_netlink_event(note->device->pnp.device_class,
262 : 0 : dev_name(¬e->device->dev),
263 [ # # ]: 0 : note->event, detail);
264 : : } else
265 : 0 : note->event = event;
266 : 0 : }
267 : :
268 : : /**
269 : : * ibm_get_table_from_acpi - reads the APLS buffer from ACPI
270 : : * @bufp: address to pointer to allocate for the table
271 : : *
272 : : * Description: This method reads the APLS buffer in from ACPI and
273 : : * stores the "stripped" table into a single buffer
274 : : * it allocates and passes the address back in bufp.
275 : : *
276 : : * If NULL is passed in as buffer, this method only calculates
277 : : * the size of the table and returns that without filling
278 : : * in the buffer.
279 : : *
280 : : * Returns < 0 on error or the size of the table on success.
281 : : */
282 : 0 : static int ibm_get_table_from_acpi(char **bufp)
283 : : {
284 : 0 : union acpi_object *package;
285 : 0 : struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
286 : 0 : acpi_status status;
287 : 0 : char *lbuf = NULL;
288 : 0 : int i, size = -EIO;
289 : :
290 : 0 : status = acpi_evaluate_object(ibm_acpi_handle, "APCI", NULL, &buffer);
291 [ # # ]: 0 : if (ACPI_FAILURE(status)) {
292 : 0 : pr_err("%s: APCI evaluation failed\n", __func__);
293 : 0 : return -ENODEV;
294 : : }
295 : :
296 : 0 : package = (union acpi_object *) buffer.pointer;
297 [ # # ]: 0 : if (!(package) ||
298 [ # # ]: 0 : (package->type != ACPI_TYPE_PACKAGE) ||
299 [ # # ]: 0 : !(package->package.elements)) {
300 : 0 : pr_err("%s: Invalid APCI object\n", __func__);
301 : 0 : goto read_table_done;
302 : : }
303 : :
304 [ # # ]: 0 : for (size = 0, i = 0; i < package->package.count; i++) {
305 [ # # ]: 0 : if (package->package.elements[i].type != ACPI_TYPE_BUFFER) {
306 : 0 : pr_err("%s: Invalid APCI element %d\n", __func__, i);
307 : 0 : goto read_table_done;
308 : : }
309 : 0 : size += package->package.elements[i].buffer.length;
310 : : }
311 : :
312 [ # # ]: 0 : if (bufp == NULL)
313 : 0 : goto read_table_done;
314 : :
315 : 0 : lbuf = kzalloc(size, GFP_KERNEL);
316 : 0 : pr_debug("%s: element count: %i, ASL table size: %i, &table = 0x%p\n",
317 : : __func__, package->package.count, size, lbuf);
318 : :
319 [ # # ]: 0 : if (lbuf) {
320 : 0 : *bufp = lbuf;
321 : : } else {
322 : 0 : size = -ENOMEM;
323 : 0 : goto read_table_done;
324 : : }
325 : :
326 : 0 : size = 0;
327 [ # # ]: 0 : for (i = 0; i < package->package.count; i++) {
328 : 0 : memcpy(&lbuf[size],
329 : 0 : package->package.elements[i].buffer.pointer,
330 : 0 : package->package.elements[i].buffer.length);
331 : 0 : size += package->package.elements[i].buffer.length;
332 : : }
333 : :
334 : 0 : read_table_done:
335 : 0 : kfree(buffer.pointer);
336 : 0 : return size;
337 : : }
338 : :
339 : : /**
340 : : * ibm_read_apci_table - callback for the sysfs apci_table file
341 : : * @filp: the open sysfs file
342 : : * @kobj: the kobject this binary attribute is a part of
343 : : * @bin_attr: struct bin_attribute for this file
344 : : * @buffer: the kernel space buffer to fill
345 : : * @pos: the offset into the file
346 : : * @size: the number of bytes requested
347 : : *
348 : : * Description: Gets registered with sysfs as the reader callback
349 : : * to be executed when /sys/bus/pci/slots/apci_table gets read.
350 : : *
351 : : * Since we don't get notified on open and close for this file,
352 : : * things get really tricky here...
353 : : * our solution is to only allow reading the table in all at once.
354 : : */
355 : 0 : static ssize_t ibm_read_apci_table(struct file *filp, struct kobject *kobj,
356 : : struct bin_attribute *bin_attr,
357 : : char *buffer, loff_t pos, size_t size)
358 : : {
359 : 0 : int bytes_read = -EINVAL;
360 : 0 : char *table = NULL;
361 : :
362 : 0 : pr_debug("%s: pos = %d, size = %zd\n", __func__, (int)pos, size);
363 : :
364 [ # # ]: 0 : if (pos == 0) {
365 : 0 : bytes_read = ibm_get_table_from_acpi(&table);
366 [ # # # # ]: 0 : if (bytes_read > 0 && bytes_read <= size)
367 : 0 : memcpy(buffer, table, bytes_read);
368 : 0 : kfree(table);
369 : : }
370 : 0 : return bytes_read;
371 : : }
372 : :
373 : : /**
374 : : * ibm_find_acpi_device - callback to find our ACPI device
375 : : * @handle: the ACPI handle of the device we are inspecting
376 : : * @lvl: depth into the namespace tree
377 : : * @context: a pointer to our handle to fill when we find the device
378 : : * @rv: a return value to fill if desired
379 : : *
380 : : * Description: Used as a callback when calling acpi_walk_namespace
381 : : * to find our device. When this method returns non-zero
382 : : * acpi_walk_namespace quits its search and returns our value.
383 : : */
384 : 1650 : static acpi_status __init ibm_find_acpi_device(acpi_handle handle,
385 : : u32 lvl, void *context, void **rv)
386 : : {
387 : 1650 : acpi_handle *phandle = (acpi_handle *)context;
388 : 1650 : unsigned long long current_status = 0;
389 : 1650 : acpi_status status;
390 : 1650 : struct acpi_device_info *info;
391 : 1650 : int retval = 0;
392 : :
393 : 1650 : status = acpi_get_object_info(handle, &info);
394 [ - + ]: 1650 : if (ACPI_FAILURE(status)) {
395 : 0 : pr_err("%s: Failed to get device information status=0x%x\n",
396 : : __func__, status);
397 : 0 : return retval;
398 : : }
399 : :
400 : 1650 : acpi_bus_get_status_handle(handle, ¤t_status);
401 : :
402 [ + + + + ]: 1650 : if (current_status && (info->valid & ACPI_VALID_HID) &&
403 [ + - ]: 540 : (!strcmp(info->hardware_id.string, IBM_HARDWARE_ID1) ||
404 [ - + ]: 540 : !strcmp(info->hardware_id.string, IBM_HARDWARE_ID2))) {
405 : 0 : pr_debug("found hardware: %s, handle: %p\n",
406 : : info->hardware_id.string, handle);
407 : 0 : *phandle = handle;
408 : : /* returning non-zero causes the search to stop
409 : : * and returns this value to the caller of
410 : : * acpi_walk_namespace, but it also causes some warnings
411 : : * in the acpi debug code to print...
412 : : */
413 : 0 : retval = FOUND_APCI;
414 : : }
415 : 1650 : kfree(info);
416 : 1650 : return retval;
417 : : }
418 : :
419 : 30 : static int __init ibm_acpiphp_init(void)
420 : : {
421 : 30 : int retval = 0;
422 : 30 : acpi_status status;
423 : 30 : struct acpi_device *device;
424 : 30 : struct kobject *sysdir = &pci_slots_kset->kobj;
425 : :
426 : 30 : pr_debug("%s\n", __func__);
427 : :
428 [ + - ]: 30 : if (acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
429 : : ACPI_UINT32_MAX, ibm_find_acpi_device, NULL,
430 : : &ibm_acpi_handle, NULL) != FOUND_APCI) {
431 : 30 : pr_err("%s: acpi_walk_namespace failed\n", __func__);
432 : 30 : retval = -ENODEV;
433 : 30 : goto init_return;
434 : : }
435 : 0 : pr_debug("%s: found IBM aPCI device\n", __func__);
436 [ # # ]: 0 : if (acpi_bus_get_device(ibm_acpi_handle, &device)) {
437 : 0 : pr_err("%s: acpi_bus_get_device failed\n", __func__);
438 : 0 : retval = -ENODEV;
439 : 0 : goto init_return;
440 : : }
441 [ # # ]: 0 : if (acpiphp_register_attention(&ibm_attention_info)) {
442 : 0 : retval = -ENODEV;
443 : 0 : goto init_return;
444 : : }
445 : :
446 : 0 : ibm_note.device = device;
447 : 0 : status = acpi_install_notify_handler(ibm_acpi_handle,
448 : : ACPI_DEVICE_NOTIFY, ibm_handle_events,
449 : : &ibm_note);
450 [ # # ]: 0 : if (ACPI_FAILURE(status)) {
451 : 0 : pr_err("%s: Failed to register notification handler\n",
452 : : __func__);
453 : 0 : retval = -EBUSY;
454 : 0 : goto init_cleanup;
455 : : }
456 : :
457 : 0 : ibm_apci_table_attr.size = ibm_get_table_from_acpi(NULL);
458 : 0 : retval = sysfs_create_bin_file(sysdir, &ibm_apci_table_attr);
459 : :
460 : 0 : return retval;
461 : :
462 : : init_cleanup:
463 : 0 : acpiphp_unregister_attention(&ibm_attention_info);
464 : : init_return:
465 : : return retval;
466 : : }
467 : :
468 : 0 : static void __exit ibm_acpiphp_exit(void)
469 : : {
470 : 0 : acpi_status status;
471 : 0 : struct kobject *sysdir = &pci_slots_kset->kobj;
472 : :
473 : 0 : pr_debug("%s\n", __func__);
474 : :
475 [ # # ]: 0 : if (acpiphp_unregister_attention(&ibm_attention_info))
476 : 0 : pr_err("%s: attention info deregistration failed", __func__);
477 : :
478 : 0 : status = acpi_remove_notify_handler(
479 : : ibm_acpi_handle,
480 : : ACPI_DEVICE_NOTIFY,
481 : : ibm_handle_events);
482 [ # # ]: 0 : if (ACPI_FAILURE(status))
483 : 0 : pr_err("%s: Notification handler removal failed\n", __func__);
484 : : /* remove the /sys entries */
485 : 0 : sysfs_remove_bin_file(sysdir, &ibm_apci_table_attr);
486 : 0 : }
487 : :
488 : : module_init(ibm_acpiphp_init);
489 : : module_exit(ibm_acpiphp_exit);
|