linux/arch/ia64/hp/common/aml_nfw.c
<<
>>
Prefs
   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
  29MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
  30MODULE_LICENSE("GPL");
  31MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
  32
  33static bool force_register;
  34module_param_named(force, force_register, bool, 0);
  35MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device");
  36
  37#define AML_NFW_SPACE           0xA1
  38
  39struct 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 */
  48struct 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
  56static 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
  64static 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
  79static 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
  97static 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
 115static 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
 143static struct ia64_nfw_context global_context;
 144static int global_handler_registered;
 145
 146static 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
 164static 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
 182static 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
 193static int aml_nfw_remove(struct acpi_device *device)
 194{
 195        return aml_nfw_remove_global_handler();
 196}
 197
 198static const struct acpi_device_id aml_nfw_ids[] = {
 199        {"HPQ5001", 0},
 200        {"", 0}
 201};
 202
 203static 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
 212static 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
 228static void __exit aml_nfw_exit(void)
 229{
 230        acpi_bus_unregister_driver(&acpi_aml_nfw_driver);
 231        aml_nfw_remove_global_handler();
 232}
 233
 234module_init(aml_nfw_init);
 235module_exit(aml_nfw_exit);
 236