acpiphp_ibm.c 14.1 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 157 158 159 160 161 162 163 164 165 166 167
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);
		memcpy(ret, des, sizeof(union apci_descriptor));
	}
	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 已提交
168 169 170
 * 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 已提交
171 172
static int ibm_set_attention_status(struct hotplug_slot *slot, u8 status)
{
173
	union acpi_object args[2];
L
Linus Torvalds 已提交
174
	struct acpi_object_list params = { .pointer = args, .count = 2 };
175
	acpi_status stat;
176
	unsigned long long rc;
L
Linus Torvalds 已提交
177 178 179 180
	union apci_descriptor *ibm_slot;

	ibm_slot = ibm_slot_from_id(hpslot_to_sun(slot));

181
	pr_debug("%s: set slot %d (%d) attention status to %d\n", __func__,
L
Linus Torvalds 已提交
182 183 184 185 186 187 188 189 190 191 192 193
			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)) {
194
		pr_err("APLS evaluation failed:  0x%08x\n", stat);
L
Linus Torvalds 已提交
195 196
		return -ENODEV;
	} else if (!rc) {
197
		pr_err("APLS method failed:  0x%08llx\n", rc);
L
Linus Torvalds 已提交
198 199 200 201 202 203 204 205 206 207
		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 已提交
208 209
 * Description: This method is registered with the acpiphp module as a
 * callback to do the device specific task of getting the LED status.
210
 *
L
Linus Torvalds 已提交
211 212 213
 * 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 已提交
214
 */
L
Linus Torvalds 已提交
215 216 217 218 219 220 221 222 223 224 225
static int ibm_get_attention_status(struct hotplug_slot *slot, u8 *status)
{
	union apci_descriptor *ibm_slot;

	ibm_slot = ibm_slot_from_id(hpslot_to_sun(slot));

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

226
	pr_debug("%s: get slot %d (%d) attention status is %d\n", __func__,
L
Linus Torvalds 已提交
227 228 229 230 231 232 233 234 235 236 237 238 239
			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 已提交
240 241
 * 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 已提交
242 243 244 245 246 247 248 249 250
 *
 * 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 已提交
251
 */
L
Linus Torvalds 已提交
252 253 254 255 256 257
static void ibm_handle_events(acpi_handle handle, u32 event, void *context)
{
	u8 detail = event & 0x0f;
	u8 subevent = event & 0xf0;
	struct notification *note = context;

258
	pr_debug("%s: Received notification %02x\n", __func__, event);
L
Linus Torvalds 已提交
259 260

	if (subevent == 0x80) {
261
		pr_debug("%s: generating bus event\n", __func__);
262
		acpi_bus_generate_netlink_event(note->device->pnp.device_class,
263
						  dev_name(&note->device->dev),
264
						  note->event, detail);
L
Linus Torvalds 已提交
265 266 267 268 269 270 271 272
	} 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 已提交
273
 * Description: This method reads the APLS buffer in from ACPI and
L
Linus Torvalds 已提交
274
 * stores the "stripped" table into a single buffer
R
Randy Dunlap 已提交
275
 * it allocates and passes the address back in bufp.
L
Linus Torvalds 已提交
276 277 278
 *
 * If NULL is passed in as buffer, this method only calculates
 * the size of the table and returns that without filling
R
Randy Dunlap 已提交
279
 * in the buffer.
L
Linus Torvalds 已提交
280
 *
R
Randy Dunlap 已提交
281 282
 * Returns < 0 on error or the size of the table on success.
 */
L
Linus Torvalds 已提交
283 284 285 286 287 288 289 290 291 292
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)) {
293
		pr_err("%s:  APCI evaluation failed\n", __func__);
L
Linus Torvalds 已提交
294 295 296 297
		return -ENODEV;
	}

	package = (union acpi_object *) buffer.pointer;
298
	if (!(package) ||
L
Linus Torvalds 已提交
299 300
			(package->type != ACPI_TYPE_PACKAGE) ||
			!(package->package.elements)) {
301
		pr_err("%s:  Invalid APCI object\n", __func__);
L
Linus Torvalds 已提交
302 303 304 305 306
		goto read_table_done;
	}

	for(size = 0, i = 0; i < package->package.count; i++) {
		if (package->package.elements[i].type != ACPI_TYPE_BUFFER) {
307
			pr_err("%s:  Invalid APCI element %d\n", __func__, i);
L
Linus Torvalds 已提交
308 309 310 311 312 313 314 315
			goto read_table_done;
		}
		size += package->package.elements[i].buffer.length;
	}

	if (bufp == NULL)
		goto read_table_done;

316
	lbuf = kzalloc(size, GFP_KERNEL);
317
	pr_debug("%s: element count: %i, ASL table size: %i, &table = 0x%p\n",
318
			__func__, package->package.count, size, lbuf);
L
Linus Torvalds 已提交
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341

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

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

	pr_debug("%s: pos = %d, size = %zd\n", __func__, (int)pos, size);
L
Linus Torvalds 已提交
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380

	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 已提交
381
 * Description: Used as a callback when calling acpi_walk_namespace
L
Linus Torvalds 已提交
382
 * to find our device.  When this method returns non-zero
R
Randy Dunlap 已提交
383 384
 * acpi_walk_namespace quits its search and returns our value.
 */
L
Linus Torvalds 已提交
385 386 387 388
static acpi_status __init ibm_find_acpi_device(acpi_handle handle,
		u32 lvl, void *context, void **rv)
{
	acpi_handle *phandle = (acpi_handle *)context;
389
	acpi_status status;
390 391
	struct acpi_device_info *info;
	int retval = 0;
L
Linus Torvalds 已提交
392

393
	status = acpi_get_object_info(handle, &info);
L
Linus Torvalds 已提交
394
	if (ACPI_FAILURE(status)) {
395
		pr_err("%s:  Failed to get device information status=0x%x\n",
396
			__func__, status);
397
		return retval;
L
Linus Torvalds 已提交
398
	}
399 400

	if (info->current_status && (info->valid & ACPI_VALID_HID) &&
401 402
			(!strcmp(info->hardware_id.string, IBM_HARDWARE_ID1) ||
			 !strcmp(info->hardware_id.string, IBM_HARDWARE_ID2))) {
403
		pr_debug("found hardware: %s, handle: %p\n",
404
			info->hardware_id.string, handle);
L
Linus Torvalds 已提交
405 406
		*phandle = handle;
		/* returning non-zero causes the search to stop
407
		 * and returns this value to the caller of
L
Linus Torvalds 已提交
408 409 410
		 * acpi_walk_namespace, but it also causes some warnings
		 * in the acpi debug code to print...
		 */
411
		retval = FOUND_APCI;
L
Linus Torvalds 已提交
412
	}
413 414
	kfree(info);
	return retval;
L
Linus Torvalds 已提交
415 416 417 418 419 420 421
}

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

424
	pr_debug("%s\n", __func__);
L
Linus Torvalds 已提交
425 426

	if (acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
427
			ACPI_UINT32_MAX, ibm_find_acpi_device, NULL,
L
Linus Torvalds 已提交
428
			&ibm_acpi_handle, NULL) != FOUND_APCI) {
429
		pr_err("%s: acpi_walk_namespace failed\n", __func__);
L
Linus Torvalds 已提交
430 431 432
		retval = -ENODEV;
		goto init_return;
	}
433
	pr_debug("%s: found IBM aPCI device\n", __func__);
L
Linus Torvalds 已提交
434
	if (acpi_bus_get_device(ibm_acpi_handle, &device)) {
435
		pr_err("%s: acpi_bus_get_device failed\n", __func__);
L
Linus Torvalds 已提交
436 437 438 439 440 441 442 443 444
		retval = -ENODEV;
		goto init_return;
	}
	if (acpiphp_register_attention(&ibm_attention_info)) {
		retval = -ENODEV;
		goto init_return;
	}

	ibm_note.device = device;
445 446
	status = acpi_install_notify_handler(ibm_acpi_handle,
			ACPI_DEVICE_NOTIFY, ibm_handle_events,
L
Linus Torvalds 已提交
447 448
			&ibm_note);
	if (ACPI_FAILURE(status)) {
449
		pr_err("%s: Failed to register notification handler\n",
450
				__func__);
L
Linus Torvalds 已提交
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
		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 已提交
469
	struct kobject *sysdir = &pci_slots_kset->kobj;
L
Linus Torvalds 已提交
470

471
	pr_debug("%s\n", __func__);
L
Linus Torvalds 已提交
472 473

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

476
	status = acpi_remove_notify_handler(
L
Linus Torvalds 已提交
477 478 479
			   ibm_acpi_handle,
			   ACPI_DEVICE_NOTIFY,
			   ibm_handle_events);
480
	if (ACPI_FAILURE(status))
481
		pr_err("%s: Notification handler removal failed\n", __func__);
482
	/* remove the /sys entries */
483
	sysfs_remove_bin_file(sysdir, &ibm_apci_table_attr);
L
Linus Torvalds 已提交
484 485 486 487
}

module_init(ibm_acpiphp_init);
module_exit(ibm_acpiphp_exit);