PageRenderTime 74ms CodeModel.GetById 36ms app.highlight 13ms RepoModel.GetById 1ms app.codeStats 0ms

/arch/ia64/hp/common/aml_nfw.c

https://bitbucket.org/thekraven/iscream_thunderc-2.6.35
C | 236 lines | 165 code | 38 blank | 33 comment | 20 complexity | b5becf56ed2d2b20275ab25ec46f411b MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0, AGPL-1.0
  1/*
  2 * OpRegion handler to allow AML to call native firmware
  3 *
  4 * (c) Copyright 2007 Hewlett-Packard Development Company, L.P.
  5 *	Bjorn Helgaas <bjorn.helgaas@hp.com>
  6 *
  7 * This program is free software; you can redistribute it and/or modify
  8 * it under the terms of the GNU General Public License version 2 as
  9 * published by the Free Software Foundation.
 10 *
 11 * This driver implements HP Open Source Review Board proposal 1842,
 12 * which was approved on 9/20/2006.
 13 *
 14 * For technical documentation, see the HP SPPA Firmware EAS, Appendix F.
 15 *
 16 * ACPI does not define a mechanism for AML methods to call native firmware
 17 * interfaces such as PAL or SAL.  This OpRegion handler adds such a mechanism.
 18 * After the handler is installed, an AML method can call native firmware by
 19 * storing the arguments and firmware entry point to specific offsets in the
 20 * OpRegion.  When AML reads the "return value" offset from the OpRegion, this
 21 * handler loads up the arguments, makes the firmware call, and returns the
 22 * result.
 23 */
 24
 25#include <linux/module.h>
 26#include <acpi/acpi_bus.h>
 27#include <acpi/acpi_drivers.h>
 28#include <asm/sal.h>
 29
 30MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
 31MODULE_LICENSE("GPL");
 32MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
 33
 34static int force_register;
 35module_param_named(force, force_register, bool, 0);
 36MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device");
 37
 38#define AML_NFW_SPACE		0xA1
 39
 40struct ia64_pdesc {
 41	void *ip;
 42	void *gp;
 43};
 44
 45/*
 46 * N.B.  The layout of this structure is defined in the HP SPPA FW EAS, and
 47 *	 the member offsets are embedded in AML methods.
 48 */
 49struct ia64_nfw_context {
 50	u64 arg[8];
 51	struct ia64_sal_retval ret;
 52	u64 ip;
 53	u64 gp;
 54	u64 pad[2];
 55};
 56
 57static void *virt_map(u64 address)
 58{
 59	if (address & (1UL << 63))
 60		return (void *) (__IA64_UNCACHED_OFFSET | address);
 61
 62	return __va(address);
 63}
 64
 65static void aml_nfw_execute(struct ia64_nfw_context *c)
 66{
 67	struct ia64_pdesc virt_entry;
 68	ia64_sal_handler entry;
 69
 70	virt_entry.ip = virt_map(c->ip);
 71	virt_entry.gp = virt_map(c->gp);
 72
 73	entry = (ia64_sal_handler) &virt_entry;
 74
 75	IA64_FW_CALL(entry, c->ret,
 76		     c->arg[0], c->arg[1], c->arg[2], c->arg[3],
 77		     c->arg[4], c->arg[5], c->arg[6], c->arg[7]);
 78}
 79
 80static void aml_nfw_read_arg(u8 *offset, u32 bit_width, u64 *value)
 81{
 82	switch (bit_width) {
 83	case 8:
 84		*value = *(u8 *)offset;
 85		break;
 86	case 16:
 87		*value = *(u16 *)offset;
 88		break;
 89	case 32:
 90		*value = *(u32 *)offset;
 91		break;
 92	case 64:
 93		*value = *(u64 *)offset;
 94		break;
 95	}
 96}
 97
 98static void aml_nfw_write_arg(u8 *offset, u32 bit_width, u64 *value)
 99{
100	switch (bit_width) {
101	case 8:
102		*(u8 *) offset = *value;
103		break;
104	case 16:
105		*(u16 *) offset = *value;
106		break;
107	case 32:
108		*(u32 *) offset = *value;
109		break;
110	case 64:
111		*(u64 *) offset = *value;
112		break;
113	}
114}
115
116static acpi_status aml_nfw_handler(u32 function, acpi_physical_address address,
117	u32 bit_width, u64 *value, void *handler_context,
118	void *region_context)
119{
120	struct ia64_nfw_context *context = handler_context;
121	u8 *offset = (u8 *) context + address;
122
123	if (bit_width !=  8 && bit_width != 16 &&
124	    bit_width != 32 && bit_width != 64)
125		return AE_BAD_PARAMETER;
126
127	if (address + (bit_width >> 3) > sizeof(struct ia64_nfw_context))
128		return AE_BAD_PARAMETER;
129
130	switch (function) {
131	case ACPI_READ:
132		if (address == offsetof(struct ia64_nfw_context, ret))
133			aml_nfw_execute(context);
134		aml_nfw_read_arg(offset, bit_width, value);
135		break;
136	case ACPI_WRITE:
137		aml_nfw_write_arg(offset, bit_width, value);
138		break;
139	}
140
141	return AE_OK;
142}
143
144static struct ia64_nfw_context global_context;
145static int global_handler_registered;
146
147static int aml_nfw_add_global_handler(void)
148{
149	acpi_status status;
150
151	if (global_handler_registered)
152		return 0;
153
154	status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT,
155		AML_NFW_SPACE, aml_nfw_handler, NULL, &global_context);
156	if (ACPI_FAILURE(status))
157		return -ENODEV;
158
159	global_handler_registered = 1;
160	printk(KERN_INFO "Global 0x%02X opregion handler registered\n",
161		AML_NFW_SPACE);
162	return 0;
163}
164
165static int aml_nfw_remove_global_handler(void)
166{
167	acpi_status status;
168
169	if (!global_handler_registered)
170		return 0;
171
172	status = acpi_remove_address_space_handler(ACPI_ROOT_OBJECT,
173		AML_NFW_SPACE, aml_nfw_handler);
174	if (ACPI_FAILURE(status))
175		return -ENODEV;
176
177	global_handler_registered = 0;
178	printk(KERN_INFO "Global 0x%02X opregion handler removed\n",
179		AML_NFW_SPACE);
180	return 0;
181}
182
183static int aml_nfw_add(struct acpi_device *device)
184{
185	/*
186	 * We would normally allocate a new context structure and install
187	 * the address space handler for the specific device we found.
188	 * But the HP-UX implementation shares a single global context
189	 * and always puts the handler at the root, so we'll do the same.
190	 */
191	return aml_nfw_add_global_handler();
192}
193
194static int aml_nfw_remove(struct acpi_device *device, int type)
195{
196	return aml_nfw_remove_global_handler();
197}
198
199static const struct acpi_device_id aml_nfw_ids[] = {
200	{"HPQ5001", 0},
201	{"", 0}
202};
203
204static struct acpi_driver acpi_aml_nfw_driver = {
205	.name = "native firmware",
206	.ids = aml_nfw_ids,
207	.ops = {
208		.add = aml_nfw_add,
209		.remove = aml_nfw_remove,
210		},
211};
212
213static int __init aml_nfw_init(void)
214{
215	int result;
216
217	if (force_register)
218		aml_nfw_add_global_handler();
219
220	result = acpi_bus_register_driver(&acpi_aml_nfw_driver);
221	if (result < 0) {
222		aml_nfw_remove_global_handler();
223		return result;
224	}
225
226	return 0;
227}
228
229static void __exit aml_nfw_exit(void)
230{
231	acpi_bus_unregister_driver(&acpi_aml_nfw_driver);
232	aml_nfw_remove_global_handler();
233}
234
235module_init(aml_nfw_init);
236module_exit(aml_nfw_exit);