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 37 38
#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 已提交
39
#include <linux/pci.h>
L
Linus Torvalds 已提交
40 41

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

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

59
#define hpslot_to_sun(A) (((struct slot *)((A)->private))->sun)
L
Linus Torvalds 已提交
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

/* 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);
101
static ssize_t ibm_read_apci_table(struct file *filp, struct kobject *kobj,
102 103
				   struct bin_attribute *bin_attr,
				   char *buffer, loff_t pos, size_t size);
L
Linus Torvalds 已提交
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
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,
};
119
static struct acpiphp_attention_info ibm_attention_info =
L
Linus Torvalds 已提交
120 121 122 123 124 125 126 127 128 129
{
	.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 已提交
130
 * Description: This method returns the aCPI slot descriptor
L
Linus Torvalds 已提交
131 132 133
 * 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 已提交
134
 */
L
Linus Torvalds 已提交
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 168
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 已提交
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 179 180 181
	union apci_descriptor *ibm_slot;

	ibm_slot = ibm_slot_from_id(hpslot_to_sun(slot));

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

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

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

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

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

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

	if (bufp == NULL)
		goto read_table_done;

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

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

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

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

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

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

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

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

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

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

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

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

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

module_init(ibm_acpiphp_init);
module_exit(ibm_acpiphp_exit);