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 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 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 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 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 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 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 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 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 194 static int aml_nfw_remove(struct acpi_device *device) 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 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 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