acpiphp_ibm.c 14.4 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 29 30 31 32 33 34 35
/*
 * 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>
#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 已提交
36
#include <linux/pci.h>
L
Linus Torvalds 已提交
37 38

#include "acpiphp.h"
A
Alex Chiang 已提交
39
#include "../pci.h"
L
Linus Torvalds 已提交
40 41 42 43 44 45 46 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

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

static int debug;

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);
static ssize_t ibm_read_apci_table(struct kobject *kobj,
111 112
				   struct bin_attribute *bin_attr,
				   char *buffer, loff_t pos, size_t size);
L
Linus Torvalds 已提交
113 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
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 已提交
139
 * Description: This method returns the aCPI slot descriptor
L
Linus Torvalds 已提交
140 141 142
 * 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 已提交
143
 */
L
Linus Torvalds 已提交
144 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
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 已提交
178 179 180
 * 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 已提交
181 182 183 184 185
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; 
186
	unsigned long long rc;
L
Linus Torvalds 已提交
187 188 189 190
	union apci_descriptor *ibm_slot;

	ibm_slot = ibm_slot_from_id(hpslot_to_sun(slot));

191
	dbg("%s: set slot %d (%d) attention status to %d\n", __func__,
L
Linus Torvalds 已提交
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
			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) {
		err("APLS method failed:  0x%08lx\n", rc);
		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 已提交
218 219
 * 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 已提交
220 221 222 223
 * 
 * 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 已提交
224
 */
L
Linus Torvalds 已提交
225 226 227 228 229 230 231 232 233 234 235
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;

236
	dbg("%s: get slot %d (%d) attention status is %d\n", __func__,
L
Linus Torvalds 已提交
237 238 239 240 241 242 243 244 245 246 247 248 249
			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 已提交
250 251
 * 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 已提交
252 253 254 255 256 257 258 259 260
 *
 * 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 已提交
261
 */
L
Linus Torvalds 已提交
262 263 264 265 266 267
static void ibm_handle_events(acpi_handle handle, u32 event, void *context)
{
	u8 detail = event & 0x0f;
	u8 subevent = event & 0xf0;
	struct notification *note = context;

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

	if (subevent == 0x80) {
271
		dbg("%s: generationg bus event\n", __func__);
272
		acpi_bus_generate_proc_event(note->device, note->event, detail);
273 274 275
		acpi_bus_generate_netlink_event(note->device->pnp.device_class,
						  note->device->dev.bus_id,
						  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
		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
		err("%s:  Invalid APCI object\n", __func__);
L
Linus Torvalds 已提交
313 314 315 316 317
		goto read_table_done;
	}

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

	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 已提交
391
 * Description: Used as a callback when calling acpi_walk_namespace
L
Linus Torvalds 已提交
392
 * to find our device.  When this method returns non-zero
R
Randy Dunlap 已提交
393 394
 * acpi_walk_namespace quits its search and returns our value.
 */
L
Linus Torvalds 已提交
395 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;
	acpi_status status; 
400 401 402
	struct acpi_device_info *info;
	struct acpi_buffer info_buffer = { ACPI_ALLOCATE_BUFFER, NULL };
	int retval = 0;
L
Linus Torvalds 已提交
403 404 405

	status = acpi_get_object_info(handle, &info_buffer);
	if (ACPI_FAILURE(status)) {
406
		err("%s:  Failed to get device information status=0x%x\n",
407
			__func__, status);
408
		return retval;
L
Linus Torvalds 已提交
409
	}
410 411 412 413 414 415 416 417
	info = info_buffer.pointer;
	info->hardware_id.value[sizeof(info->hardware_id.value) - 1] = '\0';

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

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

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

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

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

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

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

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

module_init(ibm_acpiphp_init);
module_exit(ibm_acpiphp_exit);