| /* | 
 |  * OpRegion handler to allow AML to call native firmware | 
 |  * | 
 |  * (c) Copyright 2007 Hewlett-Packard Development Company, L.P. | 
 |  *	Bjorn Helgaas <bjorn.helgaas@hp.com> | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License version 2 as | 
 |  * published by the Free Software Foundation. | 
 |  * | 
 |  * This driver implements HP Open Source Review Board proposal 1842, | 
 |  * which was approved on 9/20/2006. | 
 |  * | 
 |  * For technical documentation, see the HP SPPA Firmware EAS, Appendix F. | 
 |  * | 
 |  * ACPI does not define a mechanism for AML methods to call native firmware | 
 |  * interfaces such as PAL or SAL.  This OpRegion handler adds such a mechanism. | 
 |  * After the handler is installed, an AML method can call native firmware by | 
 |  * storing the arguments and firmware entry point to specific offsets in the | 
 |  * OpRegion.  When AML reads the "return value" offset from the OpRegion, this | 
 |  * handler loads up the arguments, makes the firmware call, and returns the | 
 |  * result. | 
 |  */ | 
 |  | 
 | #include <linux/module.h> | 
 | #include <acpi/acpi_bus.h> | 
 | #include <acpi/acpi_drivers.h> | 
 | #include <asm/sal.h> | 
 |  | 
 | MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>"); | 
 | MODULE_LICENSE("GPL"); | 
 | MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls"); | 
 |  | 
 | static bool force_register; | 
 | module_param_named(force, force_register, bool, 0); | 
 | MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device"); | 
 |  | 
 | #define AML_NFW_SPACE		0xA1 | 
 |  | 
 | struct ia64_pdesc { | 
 | 	void *ip; | 
 | 	void *gp; | 
 | }; | 
 |  | 
 | /* | 
 |  * N.B.  The layout of this structure is defined in the HP SPPA FW EAS, and | 
 |  *	 the member offsets are embedded in AML methods. | 
 |  */ | 
 | struct ia64_nfw_context { | 
 | 	u64 arg[8]; | 
 | 	struct ia64_sal_retval ret; | 
 | 	u64 ip; | 
 | 	u64 gp; | 
 | 	u64 pad[2]; | 
 | }; | 
 |  | 
 | static void *virt_map(u64 address) | 
 | { | 
 | 	if (address & (1UL << 63)) | 
 | 		return (void *) (__IA64_UNCACHED_OFFSET | address); | 
 |  | 
 | 	return __va(address); | 
 | } | 
 |  | 
 | static void aml_nfw_execute(struct ia64_nfw_context *c) | 
 | { | 
 | 	struct ia64_pdesc virt_entry; | 
 | 	ia64_sal_handler entry; | 
 |  | 
 | 	virt_entry.ip = virt_map(c->ip); | 
 | 	virt_entry.gp = virt_map(c->gp); | 
 |  | 
 | 	entry = (ia64_sal_handler) &virt_entry; | 
 |  | 
 | 	IA64_FW_CALL(entry, c->ret, | 
 | 		     c->arg[0], c->arg[1], c->arg[2], c->arg[3], | 
 | 		     c->arg[4], c->arg[5], c->arg[6], c->arg[7]); | 
 | } | 
 |  | 
 | static void aml_nfw_read_arg(u8 *offset, u32 bit_width, u64 *value) | 
 | { | 
 | 	switch (bit_width) { | 
 | 	case 8: | 
 | 		*value = *(u8 *)offset; | 
 | 		break; | 
 | 	case 16: | 
 | 		*value = *(u16 *)offset; | 
 | 		break; | 
 | 	case 32: | 
 | 		*value = *(u32 *)offset; | 
 | 		break; | 
 | 	case 64: | 
 | 		*value = *(u64 *)offset; | 
 | 		break; | 
 | 	} | 
 | } | 
 |  | 
 | static void aml_nfw_write_arg(u8 *offset, u32 bit_width, u64 *value) | 
 | { | 
 | 	switch (bit_width) { | 
 | 	case 8: | 
 | 		*(u8 *) offset = *value; | 
 | 		break; | 
 | 	case 16: | 
 | 		*(u16 *) offset = *value; | 
 | 		break; | 
 | 	case 32: | 
 | 		*(u32 *) offset = *value; | 
 | 		break; | 
 | 	case 64: | 
 | 		*(u64 *) offset = *value; | 
 | 		break; | 
 | 	} | 
 | } | 
 |  | 
 | static acpi_status aml_nfw_handler(u32 function, acpi_physical_address address, | 
 | 	u32 bit_width, u64 *value, void *handler_context, | 
 | 	void *region_context) | 
 | { | 
 | 	struct ia64_nfw_context *context = handler_context; | 
 | 	u8 *offset = (u8 *) context + address; | 
 |  | 
 | 	if (bit_width !=  8 && bit_width != 16 && | 
 | 	    bit_width != 32 && bit_width != 64) | 
 | 		return AE_BAD_PARAMETER; | 
 |  | 
 | 	if (address + (bit_width >> 3) > sizeof(struct ia64_nfw_context)) | 
 | 		return AE_BAD_PARAMETER; | 
 |  | 
 | 	switch (function) { | 
 | 	case ACPI_READ: | 
 | 		if (address == offsetof(struct ia64_nfw_context, ret)) | 
 | 			aml_nfw_execute(context); | 
 | 		aml_nfw_read_arg(offset, bit_width, value); | 
 | 		break; | 
 | 	case ACPI_WRITE: | 
 | 		aml_nfw_write_arg(offset, bit_width, value); | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	return AE_OK; | 
 | } | 
 |  | 
 | static struct ia64_nfw_context global_context; | 
 | static int global_handler_registered; | 
 |  | 
 | static int aml_nfw_add_global_handler(void) | 
 | { | 
 | 	acpi_status status; | 
 |  | 
 | 	if (global_handler_registered) | 
 | 		return 0; | 
 |  | 
 | 	status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT, | 
 | 		AML_NFW_SPACE, aml_nfw_handler, NULL, &global_context); | 
 | 	if (ACPI_FAILURE(status)) | 
 | 		return -ENODEV; | 
 |  | 
 | 	global_handler_registered = 1; | 
 | 	printk(KERN_INFO "Global 0x%02X opregion handler registered\n", | 
 | 		AML_NFW_SPACE); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int aml_nfw_remove_global_handler(void) | 
 | { | 
 | 	acpi_status status; | 
 |  | 
 | 	if (!global_handler_registered) | 
 | 		return 0; | 
 |  | 
 | 	status = acpi_remove_address_space_handler(ACPI_ROOT_OBJECT, | 
 | 		AML_NFW_SPACE, aml_nfw_handler); | 
 | 	if (ACPI_FAILURE(status)) | 
 | 		return -ENODEV; | 
 |  | 
 | 	global_handler_registered = 0; | 
 | 	printk(KERN_INFO "Global 0x%02X opregion handler removed\n", | 
 | 		AML_NFW_SPACE); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int aml_nfw_add(struct acpi_device *device) | 
 | { | 
 | 	/* | 
 | 	 * We would normally allocate a new context structure and install | 
 | 	 * the address space handler for the specific device we found. | 
 | 	 * But the HP-UX implementation shares a single global context | 
 | 	 * and always puts the handler at the root, so we'll do the same. | 
 | 	 */ | 
 | 	return aml_nfw_add_global_handler(); | 
 | } | 
 |  | 
 | static int aml_nfw_remove(struct acpi_device *device, int type) | 
 | { | 
 | 	return aml_nfw_remove_global_handler(); | 
 | } | 
 |  | 
 | static const struct acpi_device_id aml_nfw_ids[] = { | 
 | 	{"HPQ5001", 0}, | 
 | 	{"", 0} | 
 | }; | 
 |  | 
 | static struct acpi_driver acpi_aml_nfw_driver = { | 
 | 	.name = "native firmware", | 
 | 	.ids = aml_nfw_ids, | 
 | 	.ops = { | 
 | 		.add = aml_nfw_add, | 
 | 		.remove = aml_nfw_remove, | 
 | 		}, | 
 | }; | 
 |  | 
 | static int __init aml_nfw_init(void) | 
 | { | 
 | 	int result; | 
 |  | 
 | 	if (force_register) | 
 | 		aml_nfw_add_global_handler(); | 
 |  | 
 | 	result = acpi_bus_register_driver(&acpi_aml_nfw_driver); | 
 | 	if (result < 0) { | 
 | 		aml_nfw_remove_global_handler(); | 
 | 		return result; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void __exit aml_nfw_exit(void) | 
 | { | 
 | 	acpi_bus_unregister_driver(&acpi_aml_nfw_driver); | 
 | 	aml_nfw_remove_global_handler(); | 
 | } | 
 |  | 
 | module_init(aml_nfw_init); | 
 | module_exit(aml_nfw_exit); |