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 28
/*
 * 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>
 *
 */

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

#include "acpiphp.h"
A
Alex Chiang 已提交
40
#include "../pci.h"
L
Linus Torvalds 已提交
41 42 43 44 45

#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"

46
static bool debug;
L
Linus Torvalds 已提交
47 48 49 50 51 52 53 54 55 56 57 58 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 100 101 102 103 104 105 106 107 108 109 110

MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL");
MODULE_VERSION(DRIVER_VERSION);
module_param(debug, bool, 0644);
MODULE_PARM_DESC(debug, " Debugging mode enabled or not");
#define MY_NAME "acpiphp_ibm"

#undef dbg
#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"

#define hpslot_to_sun(A) (((struct slot *)((A)->private))->acpi_slot->sun)

/* 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);
111
static ssize_t ibm_read_apci_table(struct file *filp, struct kobject *kobj,
112 113
				   struct bin_attribute *bin_attr,
				   char *buffer, loff_t pos, size_t size);
L
Linus Torvalds 已提交
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
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,
};
static struct acpiphp_attention_info ibm_attention_info = 
{
	.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 已提交
140
 * Description: This method returns the aCPI slot descriptor
L
Linus Torvalds 已提交
141 142 143
 * 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 已提交
144
 */
L
Linus Torvalds 已提交
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
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 已提交
179 180 181
 * 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 已提交
182 183 184 185 186
static int ibm_set_attention_status(struct hotplug_slot *slot, u8 status)
{
	union acpi_object args[2]; 
	struct acpi_object_list params = { .pointer = args, .count = 2 };
	acpi_status stat; 
187
	unsigned long long rc;
L
Linus Torvalds 已提交
188 189 190 191
	union apci_descriptor *ibm_slot;

	ibm_slot = ibm_slot_from_id(hpslot_to_sun(slot));

192
	dbg("%s: set slot %d (%d) attention status to %d\n", __func__,
L
Linus Torvalds 已提交
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
			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)) {
		err("APLS evaluation failed:  0x%08x\n", stat);
		return -ENODEV;
	} else if (!rc) {
R
Randy Dunlap 已提交
208
		err("APLS method failed:  0x%08llx\n", rc);
L
Linus Torvalds 已提交
209 210 211 212 213 214 215 216 217 218
		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 已提交
219 220
 * Description: This method is registered with the acpiphp module as a
 * callback to do the device specific task of getting the LED status.
L
Linus Torvalds 已提交
221 222 223 224
 * 
 * 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 已提交
225
 */
L
Linus Torvalds 已提交
226 227 228 229 230 231 232 233 234 235 236
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;

237
	dbg("%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
	dbg("%s: Received notification %02x\n", __func__, event);
L
Linus Torvalds 已提交
270 271

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

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

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

	if (bufp == NULL)
		goto read_table_done;

328
	lbuf = kzalloc(size, GFP_KERNEL);
L
Linus Torvalds 已提交
329
	dbg("%s: element count: %i, ASL table size: %i, &table = 0x%p\n",
330
			__func__, package->package.count, size, lbuf);
L
Linus Torvalds 已提交
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353

	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
354
 * @filp: the open sysfs file
L
Linus Torvalds 已提交
355
 * @kobj: the kobject this binary attribute is a part of
R
Randy Dunlap 已提交
356
 * @bin_attr: struct bin_attribute for this file
L
Linus Torvalds 已提交
357 358 359 360
 * @buffer: the kernel space buffer to fill
 * @pos: the offset into the file
 * @size: the number of bytes requested
 *
R
Randy Dunlap 已提交
361 362
 * Description: Gets registered with sysfs as the reader callback
 * to be executed when /sys/bus/pci/slots/apci_table gets read.
L
Linus Torvalds 已提交
363 364 365
 *
 * Since we don't get notified on open and close for this file,
 * things get really tricky here...
R
Randy Dunlap 已提交
366 367
 * our solution is to only allow reading the table in all at once.
 */
368
static ssize_t ibm_read_apci_table(struct file *filp, struct kobject *kobj,
369 370
				   struct bin_attribute *bin_attr,
				   char *buffer, loff_t pos, size_t size)
L
Linus Torvalds 已提交
371 372 373 374
{
	int bytes_read = -EINVAL;
	char *table = NULL;
	
375
	dbg("%s: pos = %d, size = %zd\n", __func__, (int)pos, size);
L
Linus Torvalds 已提交
376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392

	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 已提交
393
 * Description: Used as a callback when calling acpi_walk_namespace
L
Linus Torvalds 已提交
394
 * to find our device.  When this method returns non-zero
R
Randy Dunlap 已提交
395 396
 * acpi_walk_namespace quits its search and returns our value.
 */
L
Linus Torvalds 已提交
397 398 399 400 401
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; 
402 403
	struct acpi_device_info *info;
	int retval = 0;
L
Linus Torvalds 已提交
404

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

	if (info->current_status && (info->valid & ACPI_VALID_HID) &&
413 414
			(!strcmp(info->hardware_id.string, IBM_HARDWARE_ID1) ||
			 !strcmp(info->hardware_id.string, IBM_HARDWARE_ID2))) {
415
		dbg("found hardware: %s, handle: %p\n",
416
			info->hardware_id.string, handle);
L
Linus Torvalds 已提交
417 418 419 420 421 422
		*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...
		 */
423
		retval = FOUND_APCI;
L
Linus Torvalds 已提交
424
	}
425 426
	kfree(info);
	return retval;
L
Linus Torvalds 已提交
427 428 429 430 431 432 433
}

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

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

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

	ibm_note.device = device;
457 458
	status = acpi_install_notify_handler(ibm_acpi_handle,
			ACPI_DEVICE_NOTIFY, ibm_handle_events,
L
Linus Torvalds 已提交
459 460
			&ibm_note);
	if (ACPI_FAILURE(status)) {
461
		err("%s: Failed to register notification handler\n",
462
				__func__);
L
Linus Torvalds 已提交
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480
		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 已提交
481
	struct kobject *sysdir = &pci_slots_kset->kobj;
L
Linus Torvalds 已提交
482

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

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

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

module_init(ibm_acpiphp_init);
module_exit(ibm_acpiphp_exit);