acpiphp_ibm.c 14.3 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/*
 * ACPI PCI Hot Plug IBM Extension
 *
 * Copyright (C) 2004 Vernon Mauery <vernux@us.ibm.com>
 * 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 <vernux@us.ibm.com>
 *
 */

28 29
#define pr_fmt(fmt) "acpiphp_ibm: " fmt

L
Linus Torvalds 已提交
30
#include <linux/init.h>
31
#include <linux/slab.h>
L
Linus Torvalds 已提交
32 33 34 35 36
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/moduleparam.h>
A
Alex Chiang 已提交
37
#include <linux/pci.h>
38
#include <asm/uaccess.h>
L
Linus Torvalds 已提交
39 40

#include "acpiphp.h"
A
Alex Chiang 已提交
41
#include "../pci.h"
L
Linus Torvalds 已提交
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57

#define DRIVER_VERSION	"1.0.1"
#define DRIVER_AUTHOR	"Irene Zubarev <zubarev@us.ibm.com>, Vernon Mauery <vernux@us.ibm.com>"
#define DRIVER_DESC	"ACPI Hot Plug PCI Controller Driver IBM extension"


MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);

#define FOUND_APCI 0x61504349
/* these are the names for the IBM ACPI pseudo-device */
#define IBM_HARDWARE_ID1 "IBM37D0"
#define IBM_HARDWARE_ID2 "IBM37D4"

58
#define hpslot_to_sun(A) (((struct slot *)((A)->private))->sun)
L
Linus Torvalds 已提交
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

/* 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;
		u8  res[3];
	} 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;
};

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);
100
static ssize_t ibm_read_apci_table(struct file *filp, struct kobject *kobj,
101 102
				   struct bin_attribute *bin_attr,
				   char *buffer, loff_t pos, size_t size);
L
Linus Torvalds 已提交
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
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 acpi_handle ibm_acpi_handle;
static struct notification ibm_note;
static struct bin_attribute ibm_apci_table_attr = {
	    .attr = {
		    .name = "apci_table",
		    .mode = S_IRUGO,
	    },
	    .read = ibm_read_apci_table,
	    .write = NULL,
};
118
static struct acpiphp_attention_info ibm_attention_info =
L
Linus Torvalds 已提交
119 120 121 122 123 124 125 126 127 128
{
	.set_attn = ibm_set_attention_status,
	.get_attn = ibm_get_attention_status,
	.owner = THIS_MODULE,
};

/**
 * ibm_slot_from_id - workaround for bad ibm hardware
 * @id: the slot number that linux refers to the slot by
 *
R
Randy Dunlap 已提交
129
 * Description: This method returns the aCPI slot descriptor
L
Linus Torvalds 已提交
130 131 132
 * corresponding to the Linux slot number.  This descriptor
 * has info about the aPCI slot id and attention status.
 * This descriptor must be freed using kfree when done.
R
Randy Dunlap 已提交
133
 */
L
Linus Torvalds 已提交
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
static union apci_descriptor *ibm_slot_from_id(int id)
{
	int ind = 0, size;
	union apci_descriptor *ret = NULL, *des;
	char *table;

	size = ibm_get_table_from_acpi(&table);
	des = (union apci_descriptor *)table;
	if (memcmp(des->header.sig, "aPCI", 4) != 0)
		goto ibm_slot_done;

	des = (union apci_descriptor *)&table[ind += des->header.len];
	while (ind < size && (des->generic.type != 0x82 ||
			des->slot.slot_num != id)) {
		des = (union apci_descriptor *)&table[ind += des->generic.len];
	}

	if (ind < size && des->slot.slot_num == id)
		ret = des;

ibm_slot_done:
	if (ret) {
		ret = kmalloc(sizeof(union apci_descriptor), GFP_KERNEL);
157 158
		if (ret)
			memcpy(ret, des, sizeof(union apci_descriptor));
L
Linus Torvalds 已提交
159 160 161 162 163 164 165 166 167 168
	}
	kfree(table);
	return ret;
}

/**
 * ibm_set_attention_status - callback method to set the attention LED
 * @slot: the hotplug_slot to work with
 * @status: what to set the LED to (0 or 1)
 *
R
Randy Dunlap 已提交
169 170 171
 * Description: This method is registered with the acpiphp module as a
 * callback to do the device specific task of setting the LED status.
 */
L
Linus Torvalds 已提交
172 173
static int ibm_set_attention_status(struct hotplug_slot *slot, u8 status)
{
174
	union acpi_object args[2];
L
Linus Torvalds 已提交
175
	struct acpi_object_list params = { .pointer = args, .count = 2 };
176
	acpi_status stat;
177
	unsigned long long rc;
L
Linus Torvalds 已提交
178
	union apci_descriptor *ibm_slot;
179
	int id = hpslot_to_sun(slot);
L
Linus Torvalds 已提交
180

181 182 183 184 185
	ibm_slot = ibm_slot_from_id(id);
	if (!ibm_slot) {
		pr_err("APLS null ACPI descriptor for slot %d\n", id);
		return -ENODEV;
	}
L
Linus Torvalds 已提交
186

187
	pr_debug("%s: set slot %d (%d) attention status to %d\n", __func__,
L
Linus Torvalds 已提交
188 189 190 191 192 193 194 195 196 197 198 199
			ibm_slot->slot.slot_num, ibm_slot->slot.slot_id,
			(status ? 1 : 0));

	args[0].type = ACPI_TYPE_INTEGER;
	args[0].integer.value = ibm_slot->slot.slot_id;
	args[1].type = ACPI_TYPE_INTEGER;
	args[1].integer.value = (status) ? 1 : 0;

	kfree(ibm_slot);

	stat = acpi_evaluate_integer(ibm_acpi_handle, "APLS", &params, &rc);
	if (ACPI_FAILURE(stat)) {
200
		pr_err("APLS evaluation failed:  0x%08x\n", stat);
L
Linus Torvalds 已提交
201 202
		return -ENODEV;
	} else if (!rc) {
203
		pr_err("APLS method failed:  0x%08llx\n", rc);
L
Linus Torvalds 已提交
204 205 206 207 208 209 210 211 212 213
		return -ERANGE;
	}
	return 0;
}

/**
 * ibm_get_attention_status - callback method to get attention LED status
 * @slot: the hotplug_slot to work with
 * @status: returns what the LED is set to (0 or 1)
 *
R
Randy Dunlap 已提交
214 215
 * Description: This method is registered with the acpiphp module as a
 * callback to do the device specific task of getting the LED status.
216
 *
L
Linus Torvalds 已提交
217 218 219
 * 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.
R
Randy Dunlap 已提交
220
 */
L
Linus Torvalds 已提交
221 222 223
static int ibm_get_attention_status(struct hotplug_slot *slot, u8 *status)
{
	union apci_descriptor *ibm_slot;
224
	int id = hpslot_to_sun(slot);
L
Linus Torvalds 已提交
225

226 227 228 229 230
	ibm_slot = ibm_slot_from_id(id);
	if (!ibm_slot) {
		pr_err("APLS null ACPI descriptor for slot %d\n", id);
		return -ENODEV;
	}
L
Linus Torvalds 已提交
231 232 233 234 235 236

	if (ibm_slot->slot.attn & 0xa0 || ibm_slot->slot.status[1] & 0x08)
		*status = 1;
	else
		*status = 0;

237
	pr_debug("%s: get slot %d (%d) attention status is %d\n", __func__,
L
Linus Torvalds 已提交
238 239 240 241 242 243 244 245 246 247 248 249 250
			ibm_slot->slot.slot_num, ibm_slot->slot.slot_id,
			*status);

	kfree(ibm_slot);
	return 0;
}

/**
 * ibm_handle_events - listens for ACPI events for the IBM37D0 device
 * @handle: an ACPI handle to the device that caused the event
 * @event: the event info (device specific)
 * @context: passed context (our notification struct)
 *
R
Randy Dunlap 已提交
251 252
 * Description: 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.
L
Linus Torvalds 已提交
253 254 255 256 257 258 259 260 261
 *
 * 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.
 *
 * From section 5.6.2.2 of the ACPI 2.0 spec, I understand that the OSPM will
 * only re-enable the interrupt that causes this event AFTER this method
 * has returned, thereby enforcing serial access for the notification struct.
R
Randy Dunlap 已提交
262
 */
L
Linus Torvalds 已提交
263 264 265 266 267 268
static void ibm_handle_events(acpi_handle handle, u32 event, void *context)
{
	u8 detail = event & 0x0f;
	u8 subevent = event & 0xf0;
	struct notification *note = context;

269
	pr_debug("%s: Received notification %02x\n", __func__, event);
L
Linus Torvalds 已提交
270 271

	if (subevent == 0x80) {
272
		pr_debug("%s: generating bus event\n", __func__);
273
		acpi_bus_generate_netlink_event(note->device->pnp.device_class,
274
						  dev_name(&note->device->dev),
275
						  note->event, detail);
L
Linus Torvalds 已提交
276 277 278 279 280 281 282 283
	} else
		note->event = event;
}

/**
 * ibm_get_table_from_acpi - reads the APLS buffer from ACPI
 * @bufp: address to pointer to allocate for the table
 *
R
Randy Dunlap 已提交
284
 * Description: This method reads the APLS buffer in from ACPI and
L
Linus Torvalds 已提交
285
 * stores the "stripped" table into a single buffer
R
Randy Dunlap 已提交
286
 * it allocates and passes the address back in bufp.
L
Linus Torvalds 已提交
287 288 289
 *
 * If NULL is passed in as buffer, this method only calculates
 * the size of the table and returns that without filling
R
Randy Dunlap 已提交
290
 * in the buffer.
L
Linus Torvalds 已提交
291
 *
R
Randy Dunlap 已提交
292 293
 * Returns < 0 on error or the size of the table on success.
 */
L
Linus Torvalds 已提交
294 295 296 297 298 299 300 301 302 303
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)) {
304
		pr_err("%s:  APCI evaluation failed\n", __func__);
L
Linus Torvalds 已提交
305 306 307 308
		return -ENODEV;
	}

	package = (union acpi_object *) buffer.pointer;
309
	if (!(package) ||
L
Linus Torvalds 已提交
310 311
			(package->type != ACPI_TYPE_PACKAGE) ||
			!(package->package.elements)) {
312
		pr_err("%s:  Invalid APCI object\n", __func__);
L
Linus Torvalds 已提交
313 314 315
		goto read_table_done;
	}

316
	for (size = 0, i = 0; i < package->package.count; i++) {
L
Linus Torvalds 已提交
317
		if (package->package.elements[i].type != ACPI_TYPE_BUFFER) {
318
			pr_err("%s:  Invalid APCI element %d\n", __func__, i);
L
Linus Torvalds 已提交
319 320 321 322 323 324 325 326
			goto read_table_done;
		}
		size += package->package.elements[i].buffer.length;
	}

	if (bufp == NULL)
		goto read_table_done;

327
	lbuf = kzalloc(size, GFP_KERNEL);
328
	pr_debug("%s: element count: %i, ASL table size: %i, &table = 0x%p\n",
329
			__func__, package->package.count, size, lbuf);
L
Linus Torvalds 已提交
330 331 332 333 334 335 336 337 338

	if (lbuf) {
		*bufp = lbuf;
	} else {
		size = -ENOMEM;
		goto read_table_done;
	}

	size = 0;
B
Bogicevic Sasa 已提交
339
	for (i = 0; i < package->package.count; i++) {
L
Linus Torvalds 已提交
340 341 342 343 344 345 346 347 348 349 350 351 352
		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
353
 * @filp: the open sysfs file
L
Linus Torvalds 已提交
354
 * @kobj: the kobject this binary attribute is a part of
R
Randy Dunlap 已提交
355
 * @bin_attr: struct bin_attribute for this file
L
Linus Torvalds 已提交
356 357 358 359
 * @buffer: the kernel space buffer to fill
 * @pos: the offset into the file
 * @size: the number of bytes requested
 *
R
Randy Dunlap 已提交
360 361
 * Description: Gets registered with sysfs as the reader callback
 * to be executed when /sys/bus/pci/slots/apci_table gets read.
L
Linus Torvalds 已提交
362 363 364
 *
 * Since we don't get notified on open and close for this file,
 * things get really tricky here...
R
Randy Dunlap 已提交
365 366
 * our solution is to only allow reading the table in all at once.
 */
367
static ssize_t ibm_read_apci_table(struct file *filp, struct kobject *kobj,
368 369
				   struct bin_attribute *bin_attr,
				   char *buffer, loff_t pos, size_t size)
L
Linus Torvalds 已提交
370 371 372
{
	int bytes_read = -EINVAL;
	char *table = NULL;
373 374

	pr_debug("%s: pos = %d, size = %zd\n", __func__, (int)pos, size);
L
Linus Torvalds 已提交
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391

	if (pos == 0) {
		bytes_read = ibm_get_table_from_acpi(&table);
		if (bytes_read > 0 && bytes_read <= size)
			memcpy(buffer, table, bytes_read);
		kfree(table);
	}
	return bytes_read;
}

/**
 * ibm_find_acpi_device - callback to find our ACPI device
 * @handle: the ACPI handle of the device we are inspecting
 * @lvl: depth into the namespace tree
 * @context: a pointer to our handle to fill when we find the device
 * @rv: a return value to fill if desired
 *
R
Randy Dunlap 已提交
392
 * Description: Used as a callback when calling acpi_walk_namespace
L
Linus Torvalds 已提交
393
 * to find our device.  When this method returns non-zero
R
Randy Dunlap 已提交
394 395
 * acpi_walk_namespace quits its search and returns our value.
 */
L
Linus Torvalds 已提交
396 397 398 399
static acpi_status __init ibm_find_acpi_device(acpi_handle handle,
		u32 lvl, void *context, void **rv)
{
	acpi_handle *phandle = (acpi_handle *)context;
400
	acpi_status status;
401 402
	struct acpi_device_info *info;
	int retval = 0;
L
Linus Torvalds 已提交
403

404
	status = acpi_get_object_info(handle, &info);
L
Linus Torvalds 已提交
405
	if (ACPI_FAILURE(status)) {
406
		pr_err("%s:  Failed to get device information status=0x%x\n",
407
			__func__, status);
408
		return retval;
L
Linus Torvalds 已提交
409
	}
410 411

	if (info->current_status && (info->valid & ACPI_VALID_HID) &&
412 413
			(!strcmp(info->hardware_id.string, IBM_HARDWARE_ID1) ||
			 !strcmp(info->hardware_id.string, IBM_HARDWARE_ID2))) {
414
		pr_debug("found hardware: %s, handle: %p\n",
415
			info->hardware_id.string, handle);
L
Linus Torvalds 已提交
416 417
		*phandle = handle;
		/* returning non-zero causes the search to stop
418
		 * and returns this value to the caller of
L
Linus Torvalds 已提交
419 420 421
		 * acpi_walk_namespace, but it also causes some warnings
		 * in the acpi debug code to print...
		 */
422
		retval = FOUND_APCI;
L
Linus Torvalds 已提交
423
	}
424 425
	kfree(info);
	return retval;
L
Linus Torvalds 已提交
426 427 428 429 430 431 432
}

static int __init ibm_acpiphp_init(void)
{
	int retval = 0;
	acpi_status status;
	struct acpi_device *device;
A
Alex Chiang 已提交
433
	struct kobject *sysdir = &pci_slots_kset->kobj;
L
Linus Torvalds 已提交
434

435
	pr_debug("%s\n", __func__);
L
Linus Torvalds 已提交
436 437

	if (acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
438
			ACPI_UINT32_MAX, ibm_find_acpi_device, NULL,
L
Linus Torvalds 已提交
439
			&ibm_acpi_handle, NULL) != FOUND_APCI) {
440
		pr_err("%s: acpi_walk_namespace failed\n", __func__);
L
Linus Torvalds 已提交
441 442 443
		retval = -ENODEV;
		goto init_return;
	}
444
	pr_debug("%s: found IBM aPCI device\n", __func__);
L
Linus Torvalds 已提交
445
	if (acpi_bus_get_device(ibm_acpi_handle, &device)) {
446
		pr_err("%s: acpi_bus_get_device failed\n", __func__);
L
Linus Torvalds 已提交
447 448 449 450 451 452 453 454 455
		retval = -ENODEV;
		goto init_return;
	}
	if (acpiphp_register_attention(&ibm_attention_info)) {
		retval = -ENODEV;
		goto init_return;
	}

	ibm_note.device = device;
456 457
	status = acpi_install_notify_handler(ibm_acpi_handle,
			ACPI_DEVICE_NOTIFY, ibm_handle_events,
L
Linus Torvalds 已提交
458 459
			&ibm_note);
	if (ACPI_FAILURE(status)) {
460
		pr_err("%s: Failed to register notification handler\n",
461
				__func__);
L
Linus Torvalds 已提交
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
		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(&ibm_attention_info);
init_return:
	return retval;
}

static void __exit ibm_acpiphp_exit(void)
{
	acpi_status status;
A
Alex Chiang 已提交
480
	struct kobject *sysdir = &pci_slots_kset->kobj;
L
Linus Torvalds 已提交
481

482
	pr_debug("%s\n", __func__);
L
Linus Torvalds 已提交
483 484

	if (acpiphp_unregister_attention(&ibm_attention_info))
485
		pr_err("%s: attention info deregistration failed", __func__);
L
Linus Torvalds 已提交
486

487
	status = acpi_remove_notify_handler(
L
Linus Torvalds 已提交
488 489 490
			   ibm_acpi_handle,
			   ACPI_DEVICE_NOTIFY,
			   ibm_handle_events);
491
	if (ACPI_FAILURE(status))
492
		pr_err("%s: Notification handler removal failed\n", __func__);
493
	/* remove the /sys entries */
494
	sysfs_remove_bin_file(sysdir, &ibm_apci_table_attr);
L
Linus Torvalds 已提交
495 496 497 498
}

module_init(ibm_acpiphp_init);
module_exit(ibm_acpiphp_exit);