1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
228eda5b8SBjorn Helgaas /*
328eda5b8SBjorn Helgaas * OpRegion handler to allow AML to call native firmware
428eda5b8SBjorn Helgaas *
528eda5b8SBjorn Helgaas * (c) Copyright 2007 Hewlett-Packard Development Company, L.P.
628eda5b8SBjorn Helgaas * Bjorn Helgaas <bjorn.helgaas@hp.com>
728eda5b8SBjorn Helgaas *
828eda5b8SBjorn Helgaas * This driver implements HP Open Source Review Board proposal 1842,
928eda5b8SBjorn Helgaas * which was approved on 9/20/2006.
1028eda5b8SBjorn Helgaas *
1128eda5b8SBjorn Helgaas * For technical documentation, see the HP SPPA Firmware EAS, Appendix F.
1228eda5b8SBjorn Helgaas *
1328eda5b8SBjorn Helgaas * ACPI does not define a mechanism for AML methods to call native firmware
1428eda5b8SBjorn Helgaas * interfaces such as PAL or SAL. This OpRegion handler adds such a mechanism.
1528eda5b8SBjorn Helgaas * After the handler is installed, an AML method can call native firmware by
1628eda5b8SBjorn Helgaas * storing the arguments and firmware entry point to specific offsets in the
1728eda5b8SBjorn Helgaas * OpRegion. When AML reads the "return value" offset from the OpRegion, this
1828eda5b8SBjorn Helgaas * handler loads up the arguments, makes the firmware call, and returns the
1928eda5b8SBjorn Helgaas * result.
2028eda5b8SBjorn Helgaas */
2128eda5b8SBjorn Helgaas
2228eda5b8SBjorn Helgaas #include <linux/module.h>
238b48463fSLv Zheng #include <linux/acpi.h>
2428eda5b8SBjorn Helgaas #include <asm/sal.h>
2528eda5b8SBjorn Helgaas
2628eda5b8SBjorn Helgaas MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
2728eda5b8SBjorn Helgaas MODULE_LICENSE("GPL");
2828eda5b8SBjorn Helgaas MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
2928eda5b8SBjorn Helgaas
30476bc001SRusty Russell static bool force_register;
3128eda5b8SBjorn Helgaas module_param_named(force, force_register, bool, 0);
3228eda5b8SBjorn Helgaas MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device");
3328eda5b8SBjorn Helgaas
3428eda5b8SBjorn Helgaas #define AML_NFW_SPACE 0xA1
3528eda5b8SBjorn Helgaas
3628eda5b8SBjorn Helgaas struct ia64_pdesc {
3728eda5b8SBjorn Helgaas void *ip;
3828eda5b8SBjorn Helgaas void *gp;
3928eda5b8SBjorn Helgaas };
4028eda5b8SBjorn Helgaas
4128eda5b8SBjorn Helgaas /*
4228eda5b8SBjorn Helgaas * N.B. The layout of this structure is defined in the HP SPPA FW EAS, and
4328eda5b8SBjorn Helgaas * the member offsets are embedded in AML methods.
4428eda5b8SBjorn Helgaas */
4528eda5b8SBjorn Helgaas struct ia64_nfw_context {
4628eda5b8SBjorn Helgaas u64 arg[8];
4728eda5b8SBjorn Helgaas struct ia64_sal_retval ret;
4828eda5b8SBjorn Helgaas u64 ip;
4928eda5b8SBjorn Helgaas u64 gp;
5028eda5b8SBjorn Helgaas u64 pad[2];
5128eda5b8SBjorn Helgaas };
5228eda5b8SBjorn Helgaas
virt_map(u64 address)5328eda5b8SBjorn Helgaas static void *virt_map(u64 address)
5428eda5b8SBjorn Helgaas {
5528eda5b8SBjorn Helgaas if (address & (1UL << 63))
5628eda5b8SBjorn Helgaas return (void *) (__IA64_UNCACHED_OFFSET | address);
5728eda5b8SBjorn Helgaas
5828eda5b8SBjorn Helgaas return __va(address);
5928eda5b8SBjorn Helgaas }
6028eda5b8SBjorn Helgaas
aml_nfw_execute(struct ia64_nfw_context * c)6128eda5b8SBjorn Helgaas static void aml_nfw_execute(struct ia64_nfw_context *c)
6228eda5b8SBjorn Helgaas {
6328eda5b8SBjorn Helgaas struct ia64_pdesc virt_entry;
6428eda5b8SBjorn Helgaas ia64_sal_handler entry;
6528eda5b8SBjorn Helgaas
6628eda5b8SBjorn Helgaas virt_entry.ip = virt_map(c->ip);
6728eda5b8SBjorn Helgaas virt_entry.gp = virt_map(c->gp);
6828eda5b8SBjorn Helgaas
6928eda5b8SBjorn Helgaas entry = (ia64_sal_handler) &virt_entry;
7028eda5b8SBjorn Helgaas
7128eda5b8SBjorn Helgaas IA64_FW_CALL(entry, c->ret,
7228eda5b8SBjorn Helgaas c->arg[0], c->arg[1], c->arg[2], c->arg[3],
7328eda5b8SBjorn Helgaas c->arg[4], c->arg[5], c->arg[6], c->arg[7]);
7428eda5b8SBjorn Helgaas }
7528eda5b8SBjorn Helgaas
aml_nfw_read_arg(u8 * offset,u32 bit_width,u64 * value)76439913ffSLin Ming static void aml_nfw_read_arg(u8 *offset, u32 bit_width, u64 *value)
7728eda5b8SBjorn Helgaas {
7828eda5b8SBjorn Helgaas switch (bit_width) {
7928eda5b8SBjorn Helgaas case 8:
8028eda5b8SBjorn Helgaas *value = *(u8 *)offset;
8128eda5b8SBjorn Helgaas break;
8228eda5b8SBjorn Helgaas case 16:
8328eda5b8SBjorn Helgaas *value = *(u16 *)offset;
8428eda5b8SBjorn Helgaas break;
8528eda5b8SBjorn Helgaas case 32:
8628eda5b8SBjorn Helgaas *value = *(u32 *)offset;
8728eda5b8SBjorn Helgaas break;
8828eda5b8SBjorn Helgaas case 64:
8928eda5b8SBjorn Helgaas *value = *(u64 *)offset;
9028eda5b8SBjorn Helgaas break;
9128eda5b8SBjorn Helgaas }
9228eda5b8SBjorn Helgaas }
9328eda5b8SBjorn Helgaas
aml_nfw_write_arg(u8 * offset,u32 bit_width,u64 * value)94439913ffSLin Ming static void aml_nfw_write_arg(u8 *offset, u32 bit_width, u64 *value)
9528eda5b8SBjorn Helgaas {
9628eda5b8SBjorn Helgaas switch (bit_width) {
9728eda5b8SBjorn Helgaas case 8:
9828eda5b8SBjorn Helgaas *(u8 *) offset = *value;
9928eda5b8SBjorn Helgaas break;
10028eda5b8SBjorn Helgaas case 16:
10128eda5b8SBjorn Helgaas *(u16 *) offset = *value;
10228eda5b8SBjorn Helgaas break;
10328eda5b8SBjorn Helgaas case 32:
10428eda5b8SBjorn Helgaas *(u32 *) offset = *value;
10528eda5b8SBjorn Helgaas break;
10628eda5b8SBjorn Helgaas case 64:
10728eda5b8SBjorn Helgaas *(u64 *) offset = *value;
10828eda5b8SBjorn Helgaas break;
10928eda5b8SBjorn Helgaas }
11028eda5b8SBjorn Helgaas }
11128eda5b8SBjorn Helgaas
aml_nfw_handler(u32 function,acpi_physical_address address,u32 bit_width,u64 * value,void * handler_context,void * region_context)11228eda5b8SBjorn Helgaas static acpi_status aml_nfw_handler(u32 function, acpi_physical_address address,
113439913ffSLin Ming u32 bit_width, u64 *value, void *handler_context,
11428eda5b8SBjorn Helgaas void *region_context)
11528eda5b8SBjorn Helgaas {
11628eda5b8SBjorn Helgaas struct ia64_nfw_context *context = handler_context;
11728eda5b8SBjorn Helgaas u8 *offset = (u8 *) context + address;
11828eda5b8SBjorn Helgaas
11928eda5b8SBjorn Helgaas if (bit_width != 8 && bit_width != 16 &&
12028eda5b8SBjorn Helgaas bit_width != 32 && bit_width != 64)
12128eda5b8SBjorn Helgaas return AE_BAD_PARAMETER;
12228eda5b8SBjorn Helgaas
12328eda5b8SBjorn Helgaas if (address + (bit_width >> 3) > sizeof(struct ia64_nfw_context))
12428eda5b8SBjorn Helgaas return AE_BAD_PARAMETER;
12528eda5b8SBjorn Helgaas
12628eda5b8SBjorn Helgaas switch (function) {
12728eda5b8SBjorn Helgaas case ACPI_READ:
12828eda5b8SBjorn Helgaas if (address == offsetof(struct ia64_nfw_context, ret))
12928eda5b8SBjorn Helgaas aml_nfw_execute(context);
13028eda5b8SBjorn Helgaas aml_nfw_read_arg(offset, bit_width, value);
13128eda5b8SBjorn Helgaas break;
13228eda5b8SBjorn Helgaas case ACPI_WRITE:
13328eda5b8SBjorn Helgaas aml_nfw_write_arg(offset, bit_width, value);
13428eda5b8SBjorn Helgaas break;
13528eda5b8SBjorn Helgaas }
13628eda5b8SBjorn Helgaas
13728eda5b8SBjorn Helgaas return AE_OK;
13828eda5b8SBjorn Helgaas }
13928eda5b8SBjorn Helgaas
14028eda5b8SBjorn Helgaas static struct ia64_nfw_context global_context;
14128eda5b8SBjorn Helgaas static int global_handler_registered;
14228eda5b8SBjorn Helgaas
aml_nfw_add_global_handler(void)14328eda5b8SBjorn Helgaas static int aml_nfw_add_global_handler(void)
14428eda5b8SBjorn Helgaas {
14528eda5b8SBjorn Helgaas acpi_status status;
14628eda5b8SBjorn Helgaas
14728eda5b8SBjorn Helgaas if (global_handler_registered)
14828eda5b8SBjorn Helgaas return 0;
14928eda5b8SBjorn Helgaas
15028eda5b8SBjorn Helgaas status = acpi_install_address_space_handler(ACPI_ROOT_OBJECT,
15128eda5b8SBjorn Helgaas AML_NFW_SPACE, aml_nfw_handler, NULL, &global_context);
15228eda5b8SBjorn Helgaas if (ACPI_FAILURE(status))
15328eda5b8SBjorn Helgaas return -ENODEV;
15428eda5b8SBjorn Helgaas
15528eda5b8SBjorn Helgaas global_handler_registered = 1;
15628eda5b8SBjorn Helgaas printk(KERN_INFO "Global 0x%02X opregion handler registered\n",
15728eda5b8SBjorn Helgaas AML_NFW_SPACE);
15828eda5b8SBjorn Helgaas return 0;
15928eda5b8SBjorn Helgaas }
16028eda5b8SBjorn Helgaas
aml_nfw_remove_global_handler(void)16128eda5b8SBjorn Helgaas static int aml_nfw_remove_global_handler(void)
16228eda5b8SBjorn Helgaas {
16328eda5b8SBjorn Helgaas acpi_status status;
16428eda5b8SBjorn Helgaas
16528eda5b8SBjorn Helgaas if (!global_handler_registered)
16628eda5b8SBjorn Helgaas return 0;
16728eda5b8SBjorn Helgaas
16828eda5b8SBjorn Helgaas status = acpi_remove_address_space_handler(ACPI_ROOT_OBJECT,
16928eda5b8SBjorn Helgaas AML_NFW_SPACE, aml_nfw_handler);
17028eda5b8SBjorn Helgaas if (ACPI_FAILURE(status))
17128eda5b8SBjorn Helgaas return -ENODEV;
17228eda5b8SBjorn Helgaas
17328eda5b8SBjorn Helgaas global_handler_registered = 0;
17428eda5b8SBjorn Helgaas printk(KERN_INFO "Global 0x%02X opregion handler removed\n",
17528eda5b8SBjorn Helgaas AML_NFW_SPACE);
17628eda5b8SBjorn Helgaas return 0;
17728eda5b8SBjorn Helgaas }
17828eda5b8SBjorn Helgaas
aml_nfw_add(struct acpi_device * device)17928eda5b8SBjorn Helgaas static int aml_nfw_add(struct acpi_device *device)
18028eda5b8SBjorn Helgaas {
18128eda5b8SBjorn Helgaas /*
18228eda5b8SBjorn Helgaas * We would normally allocate a new context structure and install
18328eda5b8SBjorn Helgaas * the address space handler for the specific device we found.
18428eda5b8SBjorn Helgaas * But the HP-UX implementation shares a single global context
18528eda5b8SBjorn Helgaas * and always puts the handler at the root, so we'll do the same.
18628eda5b8SBjorn Helgaas */
18728eda5b8SBjorn Helgaas return aml_nfw_add_global_handler();
18828eda5b8SBjorn Helgaas }
18928eda5b8SBjorn Helgaas
aml_nfw_remove(struct acpi_device * device)190*6c0eb5baSDawei Li static void aml_nfw_remove(struct acpi_device *device)
19128eda5b8SBjorn Helgaas {
192*6c0eb5baSDawei Li aml_nfw_remove_global_handler();
19328eda5b8SBjorn Helgaas }
19428eda5b8SBjorn Helgaas
19528eda5b8SBjorn Helgaas static const struct acpi_device_id aml_nfw_ids[] = {
19628eda5b8SBjorn Helgaas {"HPQ5001", 0},
19728eda5b8SBjorn Helgaas {"", 0}
19828eda5b8SBjorn Helgaas };
19928eda5b8SBjorn Helgaas
20028eda5b8SBjorn Helgaas static struct acpi_driver acpi_aml_nfw_driver = {
20128eda5b8SBjorn Helgaas .name = "native firmware",
20228eda5b8SBjorn Helgaas .ids = aml_nfw_ids,
20328eda5b8SBjorn Helgaas .ops = {
20428eda5b8SBjorn Helgaas .add = aml_nfw_add,
20528eda5b8SBjorn Helgaas .remove = aml_nfw_remove,
20628eda5b8SBjorn Helgaas },
20728eda5b8SBjorn Helgaas };
20828eda5b8SBjorn Helgaas
aml_nfw_init(void)20928eda5b8SBjorn Helgaas static int __init aml_nfw_init(void)
21028eda5b8SBjorn Helgaas {
21128eda5b8SBjorn Helgaas int result;
21228eda5b8SBjorn Helgaas
21328eda5b8SBjorn Helgaas if (force_register)
21428eda5b8SBjorn Helgaas aml_nfw_add_global_handler();
21528eda5b8SBjorn Helgaas
21628eda5b8SBjorn Helgaas result = acpi_bus_register_driver(&acpi_aml_nfw_driver);
21728eda5b8SBjorn Helgaas if (result < 0) {
21828eda5b8SBjorn Helgaas aml_nfw_remove_global_handler();
21928eda5b8SBjorn Helgaas return result;
22028eda5b8SBjorn Helgaas }
22128eda5b8SBjorn Helgaas
22228eda5b8SBjorn Helgaas return 0;
22328eda5b8SBjorn Helgaas }
22428eda5b8SBjorn Helgaas
aml_nfw_exit(void)22528eda5b8SBjorn Helgaas static void __exit aml_nfw_exit(void)
22628eda5b8SBjorn Helgaas {
22728eda5b8SBjorn Helgaas acpi_bus_unregister_driver(&acpi_aml_nfw_driver);
22828eda5b8SBjorn Helgaas aml_nfw_remove_global_handler();
22928eda5b8SBjorn Helgaas }
23028eda5b8SBjorn Helgaas
23128eda5b8SBjorn Helgaas module_init(aml_nfw_init);
23228eda5b8SBjorn Helgaas module_exit(aml_nfw_exit);
233