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 
30 MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
31 MODULE_LICENSE("GPL");
32 MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
33 
34 static bool force_register;
35 module_param_named(force, force_register, bool, 0);
36 MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device");
37 
38 #define AML_NFW_SPACE		0xA1
39 
40 struct 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  */
49 struct 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 
virt_map(u64 address)57 static 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 
aml_nfw_execute(struct ia64_nfw_context * c)65 static 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 
aml_nfw_read_arg(u8 * offset,u32 bit_width,u64 * value)80 static 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 
aml_nfw_write_arg(u8 * offset,u32 bit_width,u64 * value)98 static 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 
aml_nfw_handler(u32 function,acpi_physical_address address,u32 bit_width,u64 * value,void * handler_context,void * region_context)116 static 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 
144 static struct ia64_nfw_context global_context;
145 static int global_handler_registered;
146 
aml_nfw_add_global_handler(void)147 static 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 
aml_nfw_remove_global_handler(void)165 static 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 
aml_nfw_add(struct acpi_device * device)183 static 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 
aml_nfw_remove(struct acpi_device * device,int type)194 static int aml_nfw_remove(struct acpi_device *device, int type)
195 {
196 	return aml_nfw_remove_global_handler();
197 }
198 
199 static const struct acpi_device_id aml_nfw_ids[] = {
200 	{"HPQ5001", 0},
201 	{"", 0}
202 };
203 
204 static 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 
aml_nfw_init(void)213 static 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 
aml_nfw_exit(void)229 static void __exit aml_nfw_exit(void)
230 {
231 	acpi_bus_unregister_driver(&acpi_aml_nfw_driver);
232 	aml_nfw_remove_global_handler();
233 }
234 
235 module_init(aml_nfw_init);
236 module_exit(aml_nfw_exit);
237