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