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 <acpi/acpi_bus.h>
  27#include <acpi/acpi_drivers.h>
  28#include <asm/sal.h>
  29
  30MODULE_AUTHOR("Bjorn Helgaas <bjorn.helgaas@hp.com>");
  31MODULE_LICENSE("GPL");
  32MODULE_DESCRIPTION("ACPI opregion handler for native firmware calls");
  33
  34static bool force_register;
  35module_param_named(force, force_register, bool, 0);
  36MODULE_PARM_DESC(force, "Install opregion handler even without HPQ5001 device");
  37
  38#define AML_NFW_SPACE           0xA1
  39
  40struct 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 */
  49struct 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
  57static 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
  65static 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
  80static 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
  98static 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
 116static 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
 144static struct ia64_nfw_context global_context;
 145static int global_handler_registered;
 146
 147static 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
 165static 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
 183static 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
 194static int aml_nfw_remove(struct acpi_device *device)
 195{
 196        return aml_nfw_remove_global_handler();
 197}
 198
 199static const struct acpi_device_id aml_nfw_ids[] = {
 200        {"HPQ5001", 0},
 201        {"", 0}
 202};
 203
 204static 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
 213static 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
 229static void __exit aml_nfw_exit(void)
 230{
 231        acpi_bus_unregister_driver(&acpi_aml_nfw_driver);
 232        aml_nfw_remove_global_handler();
 233}
 234
 235module_init(aml_nfw_init);
 236module_exit(aml_nfw_exit);
 237