xref: /openbmc/linux/arch/ia64/hp/common/aml_nfw.c (revision 9cfc5c90)
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 <linux/acpi.h>
27 #include <asm/sal.h>
28 
29 MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
30 MODULE_LICENSE("GPL");
31 MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
32 
33 static bool force_register;
34 module_param_named(force, force_register, bool, 0);
35 MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device");
36 
37 #define AML_NFW_SPACE		0xA1
38 
39 struct ia64_pdesc {
40 	void *ip;
41 	void *gp;
42 };
43 
44 /*
45  * N.B.  The layout of this structure is defined in the HP SPPA FW EAS, and
46  *	 the member offsets are embedded in AML methods.
47  */
48 struct ia64_nfw_context {
49 	u64 arg[8];
50 	struct ia64_sal_retval ret;
51 	u64 ip;
52 	u64 gp;
53 	u64 pad[2];
54 };
55 
56 static void *virt_map(u64 address)
57 {
58 	if (address & (1UL << 63))
59 		return (void *) (__IA64_UNCACHED_OFFSET | address);
60 
61 	return __va(address);
62 }
63 
64 static void aml_nfw_execute(struct ia64_nfw_context *c)
65 {
66 	struct ia64_pdesc virt_entry;
67 	ia64_sal_handler entry;
68 
69 	virt_entry.ip = virt_map(c->ip);
70 	virt_entry.gp = virt_map(c->gp);
71 
72 	entry = (ia64_sal_handler) &virt_entry;
73 
74 	IA64_FW_CALL(entry, c->ret,
75 		     c->arg[0], c->arg[1], c->arg[2], c->arg[3],
76 		     c->arg[4], c->arg[5], c->arg[6], c->arg[7]);
77 }
78 
79 static void aml_nfw_read_arg(u8 *offset, u32 bit_width, u64 *value)
80 {
81 	switch (bit_width) {
82 	case 8:
83 		*value = *(u8 *)offset;
84 		break;
85 	case 16:
86 		*value = *(u16 *)offset;
87 		break;
88 	case 32:
89 		*value = *(u32 *)offset;
90 		break;
91 	case 64:
92 		*value = *(u64 *)offset;
93 		break;
94 	}
95 }
96 
97 static void aml_nfw_write_arg(u8 *offset, u32 bit_width, u64 *value)
98 {
99 	switch (bit_width) {
100 	case 8:
101 		*(u8 *) offset = *value;
102 		break;
103 	case 16:
104 		*(u16 *) offset = *value;
105 		break;
106 	case 32:
107 		*(u32 *) offset = *value;
108 		break;
109 	case 64:
110 		*(u64 *) offset = *value;
111 		break;
112 	}
113 }
114 
115 static acpi_status aml_nfw_handler(u32 function, acpi_physical_address address,
116 	u32 bit_width, u64 *value, void *handler_context,
117 	void *region_context)
118 {
119 	struct ia64_nfw_context *context = handler_context;
120 	u8 *offset = (u8 *) context + address;
121 
122 	if (bit_width !=  8 && bit_width != 16 &&
123 	    bit_width != 32 && bit_width != 64)
124 		return AE_BAD_PARAMETER;
125 
126 	if (address + (bit_width >> 3) > sizeof(struct ia64_nfw_context))
127 		return AE_BAD_PARAMETER;
128 
129 	switch (function) {
130 	case ACPI_READ:
131 		if (address == offsetof(struct ia64_nfw_context, ret))
132 			aml_nfw_execute(context);
133 		aml_nfw_read_arg(offset, bit_width, value);
134 		break;
135 	case ACPI_WRITE:
136 		aml_nfw_write_arg(offset, bit_width, value);
137 		break;
138 	}
139 
140 	return AE_OK;
141 }
142 
143 static struct ia64_nfw_context global_context;
144 static int global_handler_registered;
145 
146 static int aml_nfw_add_global_handler(void)
147 {
148 	acpi_status status;
149 
150 	if (global_handler_registered)
151 		return 0;
152 
153 	status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT,
154 		AML_NFW_SPACE, aml_nfw_handler, NULL, &global_context);
155 	if (ACPI_FAILURE(status))
156 		return -ENODEV;
157 
158 	global_handler_registered = 1;
159 	printk(KERN_INFO "Global 0x%02X opregion handler registered\n",
160 		AML_NFW_SPACE);
161 	return 0;
162 }
163 
164 static int aml_nfw_remove_global_handler(void)
165 {
166 	acpi_status status;
167 
168 	if (!global_handler_registered)
169 		return 0;
170 
171 	status = acpi_remove_address_space_handler(ACPI_ROOT_OBJECT,
172 		AML_NFW_SPACE, aml_nfw_handler);
173 	if (ACPI_FAILURE(status))
174 		return -ENODEV;
175 
176 	global_handler_registered = 0;
177 	printk(KERN_INFO "Global 0x%02X opregion handler removed\n",
178 		AML_NFW_SPACE);
179 	return 0;
180 }
181 
182 static int aml_nfw_add(struct acpi_device *device)
183 {
184 	/*
185 	 * We would normally allocate a new context structure and install
186 	 * the address space handler for the specific device we found.
187 	 * But the HP-UX implementation shares a single global context
188 	 * and always puts the handler at the root, so we'll do the same.
189 	 */
190 	return aml_nfw_add_global_handler();
191 }
192 
193 static int aml_nfw_remove(struct acpi_device *device)
194 {
195 	return aml_nfw_remove_global_handler();
196 }
197 
198 static const struct acpi_device_id aml_nfw_ids[] = {
199 	{"HPQ5001", 0},
200 	{"", 0}
201 };
202 
203 static struct acpi_driver acpi_aml_nfw_driver = {
204 	.name = "native firmware",
205 	.ids = aml_nfw_ids,
206 	.ops = {
207 		.add = aml_nfw_add,
208 		.remove = aml_nfw_remove,
209 		},
210 };
211 
212 static int __init aml_nfw_init(void)
213 {
214 	int result;
215 
216 	if (force_register)
217 		aml_nfw_add_global_handler();
218 
219 	result = acpi_bus_register_driver(&acpi_aml_nfw_driver);
220 	if (result < 0) {
221 		aml_nfw_remove_global_handler();
222 		return result;
223 	}
224 
225 	return 0;
226 }
227 
228 static void __exit aml_nfw_exit(void)
229 {
230 	acpi_bus_unregister_driver(&acpi_aml_nfw_driver);
231 	aml_nfw_remove_global_handler();
232 }
233 
234 module_init(aml_nfw_init);
235 module_exit(aml_nfw_exit);
236