diff options
Diffstat (limited to 'sys/dev/acpica')
30 files changed, 11844 insertions, 0 deletions
diff --git a/sys/dev/acpica/Osd/OsdDebug.c b/sys/dev/acpica/Osd/OsdDebug.c new file mode 100644 index 0000000..3df9e45 --- /dev/null +++ b/sys/dev/acpica/Osd/OsdDebug.c @@ -0,0 +1,119 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * 6.8 : Debugging support + */ + +#include "opt_ddb.h" +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/cons.h> +#include <sys/kernel.h> + +#include <sys/bus.h> +#include <machine/resource.h> +#include <machine/bus.h> +#include <sys/rman.h> + +#include <ddb/ddb.h> +#include <ddb/db_output.h> + +#include "acpi.h" +#include "acdebug.h" +#include <dev/acpica/acpivar.h> + +UINT32 +AcpiOsGetLine(char *Buffer) +{ +#ifdef DDB + char *cp; + + db_readline(Buffer, 80); + for (cp = Buffer; *cp != 0; cp++) + if (*cp == '\n') + *cp = 0; + return(AE_OK); +#else + printf("AcpiOsGetLine called but no input support"); + return(AE_NOT_EXIST); +#endif +} + +void +AcpiOsDbgAssert(void *FailedAssertion, void *FileName, UINT32 LineNumber, char *Message) +{ + printf("ACPI: %s:%d - %s\n", (char *)FileName, LineNumber, Message); + printf("ACPI: assertion %s\n", (char *)FailedAssertion); +} + +ACPI_STATUS +AcpiOsSignal ( + UINT32 Function, + void *Info) +{ + ACPI_SIGNAL_FATAL_INFO *fatal; + char *message; + + switch(Function) { + case ACPI_SIGNAL_FATAL: + fatal = (ACPI_SIGNAL_FATAL_INFO *)Info; + printf("ACPI fatal signal, type 0x%x code 0x%x argument 0x%x", + fatal->Type, fatal->Code, fatal->Argument); + Debugger("AcpiOsSignal"); + break; + + case ACPI_SIGNAL_BREAKPOINT: + message = (char *)Info; + Debugger(message); + break; + + default: + return(AE_BAD_PARAMETER); + } + return(AE_OK); +} + +#ifdef ACPI_DEBUGGER +void +acpi_EnterDebugger(void) +{ + ACPI_PARSE_OBJECT obj; + static int initted = 0; + + if (!initted) { + printf("Initialising ACPICA debugger...\n"); + AcpiDbInitialize(); + initted = 1; + } + + printf("Entering ACPICA debugger...\n"); + AcpiDbUserCommands('A', &obj); +} +#endif diff --git a/sys/dev/acpica/Osd/OsdHardware.c b/sys/dev/acpica/Osd/OsdHardware.c new file mode 100644 index 0000000..ab996c6 --- /dev/null +++ b/sys/dev/acpica/Osd/OsdHardware.c @@ -0,0 +1,263 @@ +/*- + * Copyright (c) 2000, 2001 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * 6.7 : Hardware Abstraction + */ + +#include "acpi.h" + +#include <machine/bus_pio.h> +#include <machine/bus.h> +#include <machine/pci_cfgreg.h> +#if __FreeBSD_version >= 500000 +#include <dev/pci/pcireg.h> +#else +#include <pci/pcireg.h> +#endif + +/* + * ACPICA's rather gung-ho approach to hardware resource ownership is a little + * troublesome insofar as there is no easy way for us to know in advance + * exactly which I/O resources it's going to want to use. + * + * In order to deal with this, we ignore resource ownership entirely, and simply + * use the native I/O space accessor functionality. This is Evil, but it works. + * + * XXX use an intermediate #define for the tag/handle + */ + +#ifdef __i386__ +#define ACPI_BUS_SPACE_IO I386_BUS_SPACE_IO +#define ACPI_BUS_HANDLE 0 +#endif +#ifdef __ia64__ +#define ACPI_BUS_SPACE_IO IA64_BUS_SPACE_IO +#define ACPI_BUS_HANDLE 0 +#endif +#ifdef __amd64__ +#define ACPI_BUS_SPACE_IO AMD64_BUS_SPACE_IO +#define ACPI_BUS_HANDLE 0 +#endif + +ACPI_STATUS +AcpiOsReadPort ( + ACPI_IO_ADDRESS InPort, + void *Value, + UINT32 Width) +{ + switch (Width) { + case 8: + *(u_int8_t *)Value = bus_space_read_1(ACPI_BUS_SPACE_IO, ACPI_BUS_HANDLE, InPort); + break; + case 16: + *(u_int16_t *)Value = bus_space_read_2(ACPI_BUS_SPACE_IO, ACPI_BUS_HANDLE, InPort); + break; + case 32: + *(u_int32_t *)Value = bus_space_read_4(ACPI_BUS_SPACE_IO, ACPI_BUS_HANDLE, InPort); + break; + default: + /* debug trap goes here */ + break; + } + + return(AE_OK); +} + +ACPI_STATUS +AcpiOsWritePort ( + ACPI_IO_ADDRESS OutPort, + ACPI_INTEGER Value, + UINT32 Width) +{ + switch (Width) { + case 8: + bus_space_write_1(ACPI_BUS_SPACE_IO, ACPI_BUS_HANDLE, OutPort, Value); + break; + case 16: + bus_space_write_2(ACPI_BUS_SPACE_IO, ACPI_BUS_HANDLE, OutPort, Value); + break; + case 32: + bus_space_write_4(ACPI_BUS_SPACE_IO, ACPI_BUS_HANDLE, OutPort, Value); + break; + default: + /* debug trap goes here */ + break; + } + + return(AE_OK); +} + +ACPI_STATUS +AcpiOsReadPciConfiguration ( + ACPI_PCI_ID *PciId, + UINT32 Register, + void *Value, + UINT32 Width) +{ + u_int32_t byte_width = Width / 8; + u_int32_t val; + + if (!pci_cfgregopen()) + return(AE_NOT_EXIST); + + val = pci_cfgregread(PciId->Bus, PciId->Device, PciId->Function, Register, byte_width); + switch (Width) { + case 8: + *(u_int8_t *)Value = val & 0xff; + break; + case 16: + *(u_int16_t *)Value = val & 0xffff; + break; + case 32: + *(u_int32_t *)Value = val; + break; + default: + /* debug trap goes here */ + break; + } + + + return(AE_OK); +} + + +ACPI_STATUS +AcpiOsWritePciConfiguration ( + ACPI_PCI_ID *PciId, + UINT32 Register, + ACPI_INTEGER Value, + UINT32 Width) +{ + u_int32_t byte_width = Width / 8; + + if (!pci_cfgregopen()) + return(AE_NOT_EXIST); + + pci_cfgregwrite(PciId->Bus, PciId->Device, PciId->Function, Register, Value, byte_width); + + return(AE_OK); +} + +/* XXX should use acpivar.h but too many include dependencies */ +extern ACPI_STATUS acpi_EvaluateInteger(ACPI_HANDLE handle, char *path, int + *number); + +/* + * Depth-first recursive case for finding the bus, given the slot/function. + */ +static int +acpi_bus_number(ACPI_HANDLE root, ACPI_HANDLE curr, ACPI_PCI_ID *PciId) +{ + ACPI_HANDLE parent; + ACPI_OBJECT_TYPE type; + UINT32 adr; + int bus, slot, func, class, subclass, header; + + /* Try to get the _BBN object of the root, otherwise assume it is 0 */ + bus = 0; + if (root == curr) { + if (ACPI_FAILURE(acpi_EvaluateInteger(root, "_BBN", &bus)) && + bootverbose) + printf("acpi_bus_number: root bus has no _BBN, assuming 0\n"); + return (bus); + } + if (ACPI_FAILURE(AcpiGetParent(curr, &parent))) + return (bus); + + /* First, recurse up the tree until we find the host bus */ + bus = acpi_bus_number(root, parent, PciId); + + /* Validate parent bus device type */ + if (ACPI_FAILURE(AcpiGetType(parent, &type)) || type != ACPI_TYPE_DEVICE) { + printf("acpi_bus_number: not a device, type %d\n", type); + return (bus); + } + /* Get the parent's slot and function */ + if (ACPI_FAILURE(acpi_EvaluateInteger(parent, "_ADR", &adr))) { + printf("acpi_bus_number: can't get _ADR\n"); + return (bus); + } + slot = ACPI_HIWORD(adr); + func = ACPI_LOWORD(adr); + + /* Is this a PCI-PCI or Cardbus-PCI bridge? */ + class = pci_cfgregread(bus, slot, func, PCIR_CLASS, 1); + if (class != PCIC_BRIDGE) + return (bus); + subclass = pci_cfgregread(bus, slot, func, PCIR_SUBCLASS, 1); + /* Find the header type, masking off the multifunction bit */ + header = pci_cfgregread(bus, slot, func, PCIR_HEADERTYPE, 1) & 0x7f; + if (header == 1 && subclass == PCIS_BRIDGE_PCI) + bus = pci_cfgregread(bus, slot, func, PCIR_SECBUS_1, 1); + if (header == 2 && subclass == PCIS_BRIDGE_CARDBUS) + bus = pci_cfgregread(bus, slot, func, PCIR_SECBUS_2, 1); + return (bus); +} + +/* + * Find the bus number for a device + * + * rhandle: handle for the root bus + * chandle: handle for the device + * PciId: pointer to device slot and function, we fill out bus + */ +void +AcpiOsDerivePciId ( + ACPI_HANDLE rhandle, + ACPI_HANDLE chandle, + ACPI_PCI_ID **PciId) +{ + ACPI_HANDLE parent; + int bus; + + if (pci_cfgregopen() == 0) + panic("AcpiOsDerivePciId unable to initialize pci bus"); + + /* Try to read _BBN for bus number if we're at the root */ + bus = 0; + if (rhandle == chandle) { + if (ACPI_FAILURE(acpi_EvaluateInteger(rhandle, "_BBN", &bus)) && + bootverbose) + printf("AcpiOsDerivePciId: root bus has no _BBN, assuming 0\n"); + } + /* + * Get the parent handle and call the recursive case. It is not + * clear why we seem to be getting a chandle that points to a child + * of the desired slot/function but passing in the parent handle + * here works. + */ + if (ACPI_SUCCESS(AcpiGetParent(chandle, &parent))) + bus = acpi_bus_number(rhandle, parent, *PciId); + (*PciId)->Bus = bus; + if (bootverbose) { + printf("AcpiOsDerivePciId: bus %d dev %d func %d\n", + (*PciId)->Bus, (*PciId)->Device, (*PciId)->Function); + } +} diff --git a/sys/dev/acpica/Osd/OsdInterrupt.c b/sys/dev/acpica/Osd/OsdInterrupt.c new file mode 100644 index 0000000..d848145 --- /dev/null +++ b/sys/dev/acpica/Osd/OsdInterrupt.c @@ -0,0 +1,137 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * 6.5 : Interrupt handling + */ + +#include "acpi.h" + +#include <sys/bus.h> +#include <machine/resource.h> +#include <machine/bus.h> +#include <sys/rman.h> + +#include <dev/acpica/acpivar.h> + +#define _COMPONENT ACPI_OS_SERVICES +ACPI_MODULE_NAME("INTERRUPT") + +static void InterruptWrapper(void *arg); +static OSD_HANDLER InterruptHandler; + +/* + * XXX this does not correctly free resources in the case of partically successful + * attachment. + */ +ACPI_STATUS +AcpiOsInstallInterruptHandler(UINT32 InterruptNumber, OSD_HANDLER ServiceRoutine, void *Context) +{ + struct acpi_softc *sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if ((sc = devclass_get_softc(devclass_find("acpi"), 0)) == NULL) + panic("can't find ACPI device to register interrupt"); + if (sc->acpi_dev == NULL) + panic("acpi softc has invalid device"); + + if ((InterruptNumber < 0) || (InterruptNumber > 255)) + return_ACPI_STATUS(AE_BAD_PARAMETER); + if (ServiceRoutine == NULL) + return_ACPI_STATUS(AE_BAD_PARAMETER); + if (InterruptHandler != NULL && InterruptHandler != ServiceRoutine) { + device_printf(sc->acpi_dev, "can't register more than one ACPI interrupt\n"); + return_ACPI_STATUS(AE_BAD_PARAMETER); + } + InterruptHandler = ServiceRoutine; + + /* + * This isn't strictly true, as we ought to be able to handle > 1 interrupt. The ACPI + * spec doesn't call for this though. + */ + if (sc->acpi_irq != NULL) { + device_printf(sc->acpi_dev, "attempt to register more than one interrupt handler\n"); + return_ACPI_STATUS(AE_ALREADY_EXISTS); + } + sc->acpi_irq_rid = 0; + bus_set_resource(sc->acpi_dev, SYS_RES_IRQ, 0, InterruptNumber, 1); + if ((sc->acpi_irq = bus_alloc_resource(sc->acpi_dev, SYS_RES_IRQ, &sc->acpi_irq_rid, 0, ~0, 1, + RF_SHAREABLE | RF_ACTIVE)) == NULL) { + device_printf(sc->acpi_dev, "could not allocate SCI interrupt\n"); + return_ACPI_STATUS(AE_ALREADY_EXISTS); + } + if (bus_setup_intr(sc->acpi_dev, sc->acpi_irq, INTR_TYPE_MISC, (driver_intr_t *)InterruptWrapper, + Context, &sc->acpi_irq_handle)) { + device_printf(sc->acpi_dev, "could not set up SCI interrupt\n"); + return_ACPI_STATUS(AE_ALREADY_EXISTS); + } + + return_ACPI_STATUS(AE_OK); +} + +ACPI_STATUS +AcpiOsRemoveInterruptHandler (UINT32 InterruptNumber, OSD_HANDLER ServiceRoutine) +{ + struct acpi_softc *sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if ((InterruptNumber < 0) || (InterruptNumber > 255)) + return_ACPI_STATUS(AE_BAD_PARAMETER); + if (ServiceRoutine == NULL) + return_ACPI_STATUS(AE_BAD_PARAMETER); + + if ((sc = devclass_get_softc(devclass_find("acpi"), 0)) == NULL) + panic("can't find ACPI device to deregister interrupt"); + + if (sc->acpi_irq == NULL) + return_ACPI_STATUS(AE_NOT_EXIST); + + bus_teardown_intr(sc->acpi_dev, sc->acpi_irq, sc->acpi_irq_handle); + bus_release_resource(sc->acpi_dev, SYS_RES_IRQ, 0, sc->acpi_irq); + bus_delete_resource(sc->acpi_dev, SYS_RES_IRQ, 0); + + sc->acpi_irq = NULL; + + return_ACPI_STATUS(AE_OK); +} + +/* + * Interrupt handler wrapper. + */ +static void +InterruptWrapper(void *arg) +{ + ACPI_LOCK_DECL; + + ACPI_LOCK; + InterruptHandler(arg); + ACPI_UNLOCK; +} diff --git a/sys/dev/acpica/Osd/OsdMemory.c b/sys/dev/acpica/Osd/OsdMemory.c new file mode 100644 index 0000000..2bb13cb --- /dev/null +++ b/sys/dev/acpica/Osd/OsdMemory.c @@ -0,0 +1,162 @@ +/*- + * Copyright (c) 2000 Mitsaru Iwasaki + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * 6.2 : Memory Management + */ + +#include "acpi.h" + +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <vm/vm.h> +#include <vm/pmap.h> + +static MALLOC_DEFINE(M_ACPICA, "acpica", "ACPI CA memory pool"); + +void * +AcpiOsAllocate(ACPI_SIZE Size) +{ + return(malloc(Size, M_ACPICA, M_NOWAIT)); +} + +void +AcpiOsFree (void *Memory) +{ + free(Memory, M_ACPICA); +} + +ACPI_STATUS +AcpiOsMapMemory (ACPI_PHYSICAL_ADDRESS PhysicalAddress, ACPI_SIZE Length, void **LogicalAddress) +{ + *LogicalAddress = pmap_mapdev((vm_offset_t)PhysicalAddress, Length); + if (*LogicalAddress == NULL) + return(AE_BAD_ADDRESS); + return(AE_OK); +} + +void +AcpiOsUnmapMemory (void *LogicalAddress, ACPI_SIZE Length) +{ + pmap_unmapdev((vm_offset_t)LogicalAddress, Length); +} + +ACPI_STATUS +AcpiOsGetPhysicalAddress(void *LogicalAddress, ACPI_PHYSICAL_ADDRESS *PhysicalAddress) +{ + /* we can't necessarily do this, so cop out */ + return(AE_BAD_ADDRESS); +} + +/* + * There is no clean way to do this. We make the charitable assumption + * that callers will not pass garbage to us. + */ +BOOLEAN +AcpiOsReadable (void *Pointer, UINT32 Length) +{ + return(TRUE); +} + +BOOLEAN +AcpiOsWritable (void *Pointer, UINT32 Length) +{ + return(TRUE); +} + +ACPI_STATUS +AcpiOsReadMemory ( + ACPI_PHYSICAL_ADDRESS Address, + void *Value, + UINT32 Width) +{ + void *LogicalAddress; + + if (AcpiOsMapMemory(Address, Width / 8, &LogicalAddress) != AE_OK) { + return(AE_NOT_EXIST); + } + + switch (Width) { + case 8: + *(u_int8_t *)Value = (*(volatile u_int8_t *)LogicalAddress); + break; + case 16: + *(u_int16_t *)Value = (*(volatile u_int16_t *)LogicalAddress); + break; + case 32: + *(u_int32_t *)Value = (*(volatile u_int32_t *)LogicalAddress); + break; + case 64: + *(u_int64_t *)Value = (*(volatile u_int64_t *)LogicalAddress); + break; + default: + /* debug trap goes here */ + break; + } + + AcpiOsUnmapMemory(LogicalAddress, Width / 8); + + return(AE_OK); +} + +ACPI_STATUS +AcpiOsWriteMemory ( + ACPI_PHYSICAL_ADDRESS Address, + ACPI_INTEGER Value, + UINT32 Width) +{ + void *LogicalAddress; + + if (AcpiOsMapMemory(Address, Width / 8, &LogicalAddress) != AE_OK) { + return(AE_NOT_EXIST); + } + + switch (Width) { + case 8: + (*(volatile u_int8_t *)LogicalAddress) = Value & 0xff; + break; + case 16: + (*(volatile u_int16_t *)LogicalAddress) = Value & 0xffff; + break; + case 32: + (*(volatile u_int32_t *)LogicalAddress) = Value & 0xffffffff; + break; + case 64: + (*(volatile u_int64_t *)LogicalAddress) = Value; + break; + default: + /* debug trap goes here */ + break; + } + + AcpiOsUnmapMemory(LogicalAddress, Width / 8); + + return(AE_OK); +} diff --git a/sys/dev/acpica/Osd/OsdSchedule.c b/sys/dev/acpica/Osd/OsdSchedule.c new file mode 100644 index 0000000..ac29c1e --- /dev/null +++ b/sys/dev/acpica/Osd/OsdSchedule.c @@ -0,0 +1,282 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * 6.3 : Scheduling services + */ + +#include "acpi.h" + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/interrupt.h> +#include <sys/kernel.h> +#include <sys/kthread.h> +#include <sys/malloc.h> +#include <sys/proc.h> +#include <sys/taskqueue.h> +#include <machine/clock.h> + +#include <sys/bus.h> + +#include <dev/acpica/acpivar.h> + +#define _COMPONENT ACPI_OS_SERVICES +ACPI_MODULE_NAME("SCHEDULE") + +/* + * This is a little complicated due to the fact that we need to build and then + * free a 'struct task' for each task we enqueue. + */ + +MALLOC_DEFINE(M_ACPITASK, "acpitask", "ACPI deferred task"); + +static void AcpiOsExecuteQueue(void *arg, int pending); + +struct acpi_task { + struct task at_task; + OSD_EXECUTION_CALLBACK at_function; + void *at_context; +}; + +struct acpi_task_queue { + STAILQ_ENTRY(acpi_task_queue) at_q; + struct acpi_task *at; +}; + +#if __FreeBSD_version >= 500000 +/* + * Private task queue definition for ACPI + */ +TASKQUEUE_DECLARE(acpi); +static void *taskqueue_acpi_ih; + +static void +taskqueue_acpi_enqueue(void *context) +{ + swi_sched(taskqueue_acpi_ih, 0); +} + +static void +taskqueue_acpi_run(void *dummy) +{ + taskqueue_run(taskqueue_acpi); +} + +TASKQUEUE_DEFINE(acpi, taskqueue_acpi_enqueue, 0, + swi_add(NULL, "acpitaskq", taskqueue_acpi_run, NULL, + SWI_TQ, 0, &taskqueue_acpi_ih)); + +#ifdef ACPI_USE_THREADS +STAILQ_HEAD(, acpi_task_queue) acpi_task_queue; +static struct mtx acpi_task_mtx; + +static void +acpi_task_thread(void *arg) +{ + struct acpi_task_queue *atq; + OSD_EXECUTION_CALLBACK Function; + void *Context; + + for (;;) { + mtx_lock(&acpi_task_mtx); + if ((atq = STAILQ_FIRST(&acpi_task_queue)) == NULL) { + msleep(&acpi_task_queue, &acpi_task_mtx, PCATCH, "actask", 0); + mtx_unlock(&acpi_task_mtx); + continue; + } + + STAILQ_REMOVE_HEAD(&acpi_task_queue, at_q); + mtx_unlock(&acpi_task_mtx); + + Function = (OSD_EXECUTION_CALLBACK)atq->at->at_function; + Context = atq->at->at_context; + + mtx_lock(&Giant); + Function(Context); + + free(atq->at, M_ACPITASK); + free(atq, M_ACPITASK); + mtx_unlock(&Giant); + } + + kthread_exit(0); +} + +int +acpi_task_thread_init(void) +{ + int i, err; + struct proc *acpi_kthread_proc; + + err = 0; + STAILQ_INIT(&acpi_task_queue); + mtx_init(&acpi_task_mtx, "ACPI task", NULL, MTX_DEF); + + for (i = 0; i < ACPI_MAX_THREADS; i++) { + err = kthread_create(acpi_task_thread, NULL, &acpi_kthread_proc, + 0, 0, "acpi_task%d", i); + if (err != 0) { + printf("%s: kthread_create failed(%d)\n", __func__, err); + break; + } + } + return (err); +} +#endif +#endif + +ACPI_STATUS +AcpiOsQueueForExecution(UINT32 Priority, OSD_EXECUTION_CALLBACK Function, void *Context) +{ + struct acpi_task *at; + int pri; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (Function == NULL) + return_ACPI_STATUS(AE_BAD_PARAMETER); + + at = malloc(sizeof(*at), M_ACPITASK, M_NOWAIT); /* Interrupt Context */ + if (at == NULL) + return_ACPI_STATUS(AE_NO_MEMORY); + bzero(at, sizeof(*at)); + + at->at_function = Function; + at->at_context = Context; + switch (Priority) { + case OSD_PRIORITY_GPE: + pri = 4; + break; + case OSD_PRIORITY_HIGH: + pri = 3; + break; + case OSD_PRIORITY_MED: + pri = 2; + break; + case OSD_PRIORITY_LO: + pri = 1; + break; + default: + free(at, M_ACPITASK); + return_ACPI_STATUS(AE_BAD_PARAMETER); + } + TASK_INIT(&at->at_task, pri, AcpiOsExecuteQueue, at); + +#if __FreeBSD_version < 500000 + taskqueue_enqueue(taskqueue_swi, (struct task *)at); +#else + taskqueue_enqueue(taskqueue_acpi, (struct task *)at); +#endif + return_ACPI_STATUS(AE_OK); +} + +static void +AcpiOsExecuteQueue(void *arg, int pending) +{ + struct acpi_task *at; + struct acpi_task_queue *atq; + OSD_EXECUTION_CALLBACK Function; + void *Context; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + at = (struct acpi_task *)arg; + atq = NULL; + Function = NULL; + Context = NULL; + +#ifdef ACPI_USE_THREADS + atq = malloc(sizeof(*atq), M_ACPITASK, M_NOWAIT); + if (atq == NULL) { + printf("%s: no memory\n", __func__); + return; + } + + atq->at = at; + + mtx_lock(&acpi_task_mtx); + STAILQ_INSERT_TAIL(&acpi_task_queue, atq, at_q); + mtx_unlock(&acpi_task_mtx); + wakeup_one(&acpi_task_queue); +#else + Function = (OSD_EXECUTION_CALLBACK)at->at_function; + Context = at->at_context; + + Function(Context); + free(at, M_ACPITASK); +#endif + + return_VOID; +} + +/* + * We don't have any sleep granularity better than hz, so + * make do with that. + */ +void +AcpiOsSleep (UINT32 Seconds, UINT32 Milliseconds) +{ + int timo; + static int dummy; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + timo = (Seconds * hz) + Milliseconds * hz / 1000; + if (timo == 0) + timo = 1; + tsleep(&dummy, 0, "acpislp", timo); + return_VOID; +} + +void +AcpiOsStall (UINT32 Microseconds) +{ + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + DELAY(Microseconds); + return_VOID; +} + +UINT32 +AcpiOsGetThreadId (void) +{ + struct proc *p; + /* XXX do not add FUNCTION_TRACE here, results in recursive call */ + + p = curproc; +#if __FreeBSD_version < 500000 + if (p == NULL) + p = &proc0; +#endif + KASSERT(p != NULL, ("%s: curproc is NULL!", __func__)); + return(p->p_pid + 1); /* can't return 0 */ +} diff --git a/sys/dev/acpica/Osd/OsdStream.c b/sys/dev/acpica/Osd/OsdStream.c new file mode 100644 index 0000000..7c9acde --- /dev/null +++ b/sys/dev/acpica/Osd/OsdStream.c @@ -0,0 +1,51 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * 6.6 : Stream I/O + */ + +#include "acpi.h" + +void +AcpiOsPrintf (const char *Format, ...) +{ + va_list ap; + + va_start(ap, Format); + vprintf(Format, ap); + va_end(ap); +} + +void +AcpiOsVprintf (const char *Format, va_list Args) +{ + vprintf(Format, Args); +} + diff --git a/sys/dev/acpica/Osd/OsdSynch.c b/sys/dev/acpica/Osd/OsdSynch.c new file mode 100644 index 0000000..718e3c7 --- /dev/null +++ b/sys/dev/acpica/Osd/OsdSynch.c @@ -0,0 +1,340 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * 6.1 : Mutual Exclusion and Synchronisation + */ + +#include "acpi.h" + +#include "opt_acpi.h" +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/sysctl.h> +#if __FreeBSD_version >= 500000 +#include <sys/lock.h> +#include <sys/mutex.h> +#endif + +#define _COMPONENT ACPI_OS_SERVICES +ACPI_MODULE_NAME("SYNCH") + +static MALLOC_DEFINE(M_ACPISEM, "acpisem", "ACPI semaphore"); + +#if __FreeBSD_version < 500000 +# define AS_LOCK(as) s = splhigh() +# define AS_UNLOCK(as) splx(s) +# define AS_LOCK_DECL int s +# define msleep(a, b, c, d, e) tsleep(a, c, d, e) +#else +# define AS_LOCK(as) mtx_lock(&(as)->as_mtx) +# define AS_UNLOCK(as) mtx_unlock(&(as)->as_mtx) +# define AS_LOCK_DECL +#endif + +/* + * Simple counting semaphore implemented using a mutex. (Subsequently used + * in the OSI code to implement a mutex. Go figure.) + */ +struct acpi_semaphore { +#if __FreeBSD_version >= 500000 + struct mtx as_mtx; +#endif + UINT32 as_units; + UINT32 as_maxunits; + UINT32 as_pendings; + UINT32 as_resetting; + UINT32 as_timeouts; +}; + +#ifndef ACPI_NO_SEMAPHORES +#ifndef ACPI_SEMAPHORES_MAX_PENDING +#define ACPI_SEMAPHORES_MAX_PENDING 4 +#endif +static int acpi_semaphore_debug = 0; +TUNABLE_INT("debug.acpi_semaphore_debug", &acpi_semaphore_debug); +SYSCTL_INT(_debug, OID_AUTO, acpi_semaphore_debug, CTLFLAG_RW, + &acpi_semaphore_debug, 0, ""); +#endif + +ACPI_STATUS +AcpiOsCreateSemaphore(UINT32 MaxUnits, UINT32 InitialUnits, ACPI_HANDLE *OutHandle) +{ +#ifndef ACPI_NO_SEMAPHORES + struct acpi_semaphore *as; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (OutHandle == NULL) + return(AE_BAD_PARAMETER); + if (InitialUnits > MaxUnits) + return_ACPI_STATUS(AE_BAD_PARAMETER); + + if ((as = malloc(sizeof(*as), M_ACPISEM, M_NOWAIT)) == NULL) + return_ACPI_STATUS(AE_NO_MEMORY); + + bzero(as, sizeof(*as)); +#if __FreeBSD_version >= 500000 + mtx_init(&as->as_mtx, "ACPI semaphore", NULL, MTX_DEF); +#endif + as->as_units = InitialUnits; + as->as_maxunits = MaxUnits; + as->as_pendings = as->as_resetting = as->as_timeouts = 0; + + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "created semaphore %p max %d, initial %d\n", + as, InitialUnits, MaxUnits)); + + *OutHandle = (ACPI_HANDLE)as; + return_ACPI_STATUS(AE_OK); +#else + *OutHandle = (ACPI_HANDLE)OutHandle; + return(AE_OK); +#endif +} + +ACPI_STATUS +AcpiOsDeleteSemaphore (ACPI_HANDLE Handle) +{ +#ifndef ACPI_NO_SEMAPHORES + struct acpi_semaphore *as = (struct acpi_semaphore *)Handle; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "destroyed semaphore %p\n", as)); +#if __FreeBSD_version >= 500000 + mtx_destroy(&as->as_mtx); +#endif + free(Handle, M_ACPISEM); + return_ACPI_STATUS(AE_OK); +#else + return(AE_OK); +#endif +} + +/* + * This implementation has a bug, in that it has to stall for the entire + * timeout before it will return AE_TIME. A better implementation would + * use getmicrotime() to correctly adjust the timeout after being woken up. + */ +ACPI_STATUS +AcpiOsWaitSemaphore(ACPI_HANDLE Handle, UINT32 Units, UINT16 Timeout) +{ +#ifndef ACPI_NO_SEMAPHORES + struct acpi_semaphore *as = (struct acpi_semaphore *)Handle; + ACPI_STATUS result; + int rv, tmo; + struct timeval timeouttv, currenttv, timelefttv; + AS_LOCK_DECL; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (as == NULL) + return_ACPI_STATUS(AE_BAD_PARAMETER); + + if (cold) + return_ACPI_STATUS(AE_OK); + +#if 0 + if (as->as_units < Units && as->as_timeouts > 10) { + printf("%s: semaphore %p too many timeouts, resetting\n", __func__, as); + AS_LOCK(as); + as->as_units = as->as_maxunits; + if (as->as_pendings) + as->as_resetting = 1; + as->as_timeouts = 0; + wakeup(as); + AS_UNLOCK(as); + return_ACPI_STATUS(AE_TIME); + } + + if (as->as_resetting) { + return_ACPI_STATUS(AE_TIME); + } +#endif + + /* a timeout of ACPI_WAIT_FOREVER means "forever" */ + if (Timeout == ACPI_WAIT_FOREVER) { + tmo = 0; + timeouttv.tv_sec = ((0xffff/1000) + 1); /* cf. ACPI spec */ + timeouttv.tv_usec = 0; + } else { + /* compute timeout using microseconds per tick */ + tmo = (Timeout * 1000) / (1000000 / hz); + if (tmo <= 0) + tmo = 1; + timeouttv.tv_sec = Timeout / 1000; + timeouttv.tv_usec = (Timeout % 1000) * 1000; + } + + /* calculate timeout value in timeval */ + getmicrotime(¤ttv); + timevaladd(&timeouttv, ¤ttv); + + AS_LOCK(as); + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "get %d units from semaphore %p (has %d), timeout %d\n", + Units, as, as->as_units, Timeout)); + for (;;) { + if (as->as_maxunits == ACPI_NO_UNIT_LIMIT) { + result = AE_OK; + break; + } + if (as->as_units >= Units) { + as->as_units -= Units; + result = AE_OK; + break; + } + + /* limit number of pending treads */ + if (as->as_pendings >= ACPI_SEMAPHORES_MAX_PENDING) { + result = AE_TIME; + break; + } + + /* if timeout values of zero is specified, return immediately */ + if (Timeout == 0) { + result = AE_TIME; + break; + } + +#if __FreeBSD_version >= 500000 + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "semaphore blocked, calling msleep(%p, %p, %d, \"acsem\", %d)\n", + as, &as->as_mtx, PCATCH, tmo)); +#endif + + as->as_pendings++; + + if (acpi_semaphore_debug) { + printf("%s: Sleep %d, pending %d, semaphore %p, thread %d\n", + __func__, Timeout, as->as_pendings, as, AcpiOsGetThreadId()); + } + + rv = msleep(as, &as->as_mtx, PCATCH, "acsem", tmo); + + as->as_pendings--; + +#if 0 + if (as->as_resetting) { + /* semaphore reset, return immediately */ + if (as->as_pendings == 0) { + as->as_resetting = 0; + } + result = AE_TIME; + break; + } +#endif + + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "msleep(%d) returned %d\n", tmo, rv)); + if (rv == EWOULDBLOCK) { + result = AE_TIME; + break; + } + + /* check if we already awaited enough */ + timelefttv = timeouttv; + getmicrotime(¤ttv); + timevalsub(&timelefttv, ¤ttv); + if (timelefttv.tv_sec < 0) { + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, "await semaphore %p timeout\n", as)); + result = AE_TIME; + break; + } + + /* adjust timeout for the next sleep */ + tmo = (timelefttv.tv_sec * 1000000 + timelefttv.tv_usec) / (1000000 / hz); + if (tmo <= 0) + tmo = 1; + + if (acpi_semaphore_debug) { + printf("%s: Wakeup timeleft(%lu, %lu), tmo %u, semaphore %p, thread %d\n", + __func__, timelefttv.tv_sec, timelefttv.tv_usec, tmo, as, AcpiOsGetThreadId()); + } + } + + if (acpi_semaphore_debug) { + if (result == AE_TIME && Timeout > 0) { + printf("%s: Timeout %d, pending %d, semaphore %p\n", + __func__, Timeout, as->as_pendings, as); + } + if (result == AE_OK && (as->as_timeouts > 0 || as->as_pendings > 0)) { + printf("%s: Acquire %d, units %d, pending %d, semaphore %p, thread %d\n", + __func__, Units, as->as_units, as->as_pendings, as, AcpiOsGetThreadId()); + } + } + + if (result == AE_TIME) { + as->as_timeouts++; + } else { + as->as_timeouts = 0; + } + + AS_UNLOCK(as); + + return_ACPI_STATUS(result); +#else + return(AE_OK); +#endif +} + +ACPI_STATUS +AcpiOsSignalSemaphore(ACPI_HANDLE Handle, UINT32 Units) +{ +#ifndef ACPI_NO_SEMAPHORES + struct acpi_semaphore *as = (struct acpi_semaphore *)Handle; + AS_LOCK_DECL; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (as == NULL) + return_ACPI_STATUS(AE_BAD_PARAMETER); + + AS_LOCK(as); + ACPI_DEBUG_PRINT((ACPI_DB_MUTEX, + "return %d units to semaphore %p (has %d)\n", + Units, as, as->as_units)); + if (as->as_maxunits != ACPI_NO_UNIT_LIMIT) { + as->as_units += Units; + if (as->as_units > as->as_maxunits) + as->as_units = as->as_maxunits; + } + + if (acpi_semaphore_debug && (as->as_timeouts > 0 || as->as_pendings > 0)) { + printf("%s: Release %d, units %d, pending %d, semaphore %p, thread %d\n", + __func__, Units, as->as_units, as->as_pendings, as, AcpiOsGetThreadId()); + } + + wakeup(as); + AS_UNLOCK(as); + return_ACPI_STATUS(AE_OK); +#else + return(AE_OK); +#endif +} diff --git a/sys/dev/acpica/Osd/OsdTable.c b/sys/dev/acpica/Osd/OsdTable.c new file mode 100644 index 0000000..f24c5ea --- /dev/null +++ b/sys/dev/acpica/Osd/OsdTable.c @@ -0,0 +1,96 @@ +/*- + * Copyright (c) 2002 Mitsaru Iwasaki + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * ACPI Table interfaces + */ + +#include "acpi.h" + +#include <sys/kernel.h> +#include <sys/linker.h> + +#undef _COMPONENT +#define _COMPONENT ACPI_TABLES + +static char acpi_os_name[128]; + +ACPI_STATUS +AcpiOsPredefinedOverride ( + const ACPI_PREDEFINED_NAMES *InitVal, + ACPI_STRING *NewVal) +{ + if (InitVal == NULL || NewVal == NULL) + return(AE_BAD_PARAMETER); + + *NewVal = NULL; + if (strncmp(InitVal->Name, "_OS_", 4) == 0 && + getenv_string("hw.acpi.os_name", acpi_os_name, sizeof(acpi_os_name))) { + printf("ACPI: Overriding _OS definition with \"%s\"\n", acpi_os_name); + *NewVal = acpi_os_name; + } + + return(AE_OK); +} + +ACPI_STATUS +AcpiOsTableOverride ( + ACPI_TABLE_HEADER *ExistingTable, + ACPI_TABLE_HEADER **NewTable) +{ + caddr_t acpi_dsdt, p; + + if (ExistingTable == NULL || NewTable == NULL) + { + return(AE_BAD_PARAMETER); + } + + (*NewTable) = NULL; + + if (strncmp(ExistingTable->Signature, "DSDT", 4) != 0) + { + return(AE_OK); + } + + if ((acpi_dsdt = preload_search_by_type("acpi_dsdt")) == NULL) + { + return(AE_OK); + } + + if ((p = preload_search_info(acpi_dsdt, MODINFO_ADDR)) == NULL) + { + return(AE_OK); + } + + (*NewTable) = *(void **)p; + + printf("ACPI: DSDT was overridden.\n"); + + return(AE_OK); +} + diff --git a/sys/dev/acpica/acpi.c b/sys/dev/acpica/acpi.c new file mode 100644 index 0000000..c5f920e --- /dev/null +++ b/sys/dev/acpica/acpi.c @@ -0,0 +1,2191 @@ +/*- + * Copyright (c) 2000 Takanori Watanabe <takawata@jp.freebsd.org> + * Copyright (c) 2000 Mitsuru IWASAKI <iwasaki@jp.freebsd.org> + * Copyright (c) 2000, 2001 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/proc.h> +#include <sys/fcntl.h> +#include <sys/malloc.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/ioccom.h> +#include <sys/reboot.h> +#include <sys/sysctl.h> +#include <sys/ctype.h> +#include <sys/linker.h> +#include <sys/power.h> + +#include <machine/clock.h> +#include <machine/resource.h> + +#include <isa/isavar.h> + +#include "acpi.h" + +#include <dev/acpica/acpica_support.h> + +#include <dev/acpica/acpivar.h> +#include <dev/acpica/acpiio.h> + +MALLOC_DEFINE(M_ACPIDEV, "acpidev", "ACPI devices"); + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_BUS +ACPI_MODULE_NAME("ACPI") + +/* + * Character device + */ + +static d_open_t acpiopen; +static d_close_t acpiclose; +static d_ioctl_t acpiioctl; + +#define CDEV_MAJOR 152 +static struct cdevsw acpi_cdevsw = { + .d_open = acpiopen, + .d_close = acpiclose, + .d_ioctl = acpiioctl, + .d_name = "acpi", + .d_maj = CDEV_MAJOR, +}; + +static const char* sleep_state_names[] = { + "S0", "S1", "S2", "S3", "S4", "S5", "NONE"}; + +/* this has to be static, as the softc is gone when we need it */ +static int acpi_off_state = ACPI_STATE_S5; + +#if __FreeBSD_version >= 500000 +struct mtx acpi_mutex; +#endif + +static int acpi_modevent(struct module *mod, int event, void *junk); +static void acpi_identify(driver_t *driver, device_t parent); +static int acpi_probe(device_t dev); +static int acpi_attach(device_t dev); +static device_t acpi_add_child(device_t bus, int order, const char *name, int unit); +static int acpi_print_child(device_t bus, device_t child); +static int acpi_read_ivar(device_t dev, device_t child, int index, uintptr_t *result); +static int acpi_write_ivar(device_t dev, device_t child, int index, uintptr_t value); +static int acpi_set_resource(device_t dev, device_t child, int type, int rid, u_long start, + u_long count); +static int acpi_get_resource(device_t dev, device_t child, int type, int rid, u_long *startp, + u_long *countp); +static struct resource *acpi_alloc_resource(device_t bus, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags); +static int acpi_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r); +static u_int32_t acpi_isa_get_logicalid(device_t dev); +static u_int32_t acpi_isa_get_compatid(device_t dev); +static int acpi_isa_pnp_probe(device_t bus, device_t child, struct isa_pnp_id *ids); + +static void acpi_probe_children(device_t bus); +static ACPI_STATUS acpi_probe_child(ACPI_HANDLE handle, UINT32 level, void *context, void **status); + +static void acpi_shutdown_pre_sync(void *arg, int howto); +static void acpi_shutdown_final(void *arg, int howto); + +static void acpi_enable_fixed_events(struct acpi_softc *sc); + +static void acpi_system_eventhandler_sleep(void *arg, int state); +static void acpi_system_eventhandler_wakeup(void *arg, int state); +static int acpi_supported_sleep_state_sysctl(SYSCTL_HANDLER_ARGS); +static int acpi_sleep_state_sysctl(SYSCTL_HANDLER_ARGS); + +static int acpi_pm_func(u_long cmd, void *arg, ...); + +static device_method_t acpi_methods[] = { + /* Device interface */ + DEVMETHOD(device_identify, acpi_identify), + DEVMETHOD(device_probe, acpi_probe), + DEVMETHOD(device_attach, acpi_attach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + + /* Bus interface */ + DEVMETHOD(bus_add_child, acpi_add_child), + DEVMETHOD(bus_print_child, acpi_print_child), + DEVMETHOD(bus_read_ivar, acpi_read_ivar), + DEVMETHOD(bus_write_ivar, acpi_write_ivar), + DEVMETHOD(bus_set_resource, acpi_set_resource), + DEVMETHOD(bus_get_resource, acpi_get_resource), + DEVMETHOD(bus_alloc_resource, acpi_alloc_resource), + DEVMETHOD(bus_release_resource, acpi_release_resource), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + + /* ISA emulation */ + DEVMETHOD(isa_pnp_probe, acpi_isa_pnp_probe), + + {0, 0} +}; + +static driver_t acpi_driver = { + "acpi", + acpi_methods, + sizeof(struct acpi_softc), +}; + +static devclass_t acpi_devclass; +DRIVER_MODULE(acpi, nexus, acpi_driver, acpi_devclass, acpi_modevent, 0); +MODULE_VERSION(acpi, 100); + +SYSCTL_INT(_debug, OID_AUTO, acpi_debug_layer, CTLFLAG_RW, &AcpiDbgLayer, 0, ""); +SYSCTL_INT(_debug, OID_AUTO, acpi_debug_level, CTLFLAG_RW, &AcpiDbgLevel, 0, ""); +static int acpi_ca_version = ACPI_CA_VERSION; +SYSCTL_INT(_debug, OID_AUTO, acpi_ca_version, CTLFLAG_RD, &acpi_ca_version, 0, ""); + +/* + * ACPI can only be loaded as a module by the loader; activating it after + * system bootstrap time is not useful, and can be fatal to the system. + * It also cannot be unloaded, since the entire system bus heirarchy hangs off it. + */ +static int +acpi_modevent(struct module *mod, int event, void *junk) +{ + switch(event) { + case MOD_LOAD: + if (!cold) { + printf("The ACPI driver cannot be loaded after boot.\n"); + return(EPERM); + } + break; + case MOD_UNLOAD: + if (!cold && power_pm_get_type() == POWER_PM_TYPE_ACPI) + return(EBUSY); + break; + default: + break; + } + return(0); +} + +/* + * Detect ACPI, perform early initialisation + */ +static void +acpi_identify(driver_t *driver, device_t parent) +{ + device_t child; + int error; +#ifdef ACPI_DEBUGGER + char *debugpoint; +#endif + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (!cold) + return_VOID; + + /* + * Check that we haven't been disabled with a hint. + */ + if (!resource_int_value("acpi", 0, "disabled", &error) && + (error != 0)) + return_VOID; + + /* + * Make sure we're not being doubly invoked. + */ + if (device_find_child(parent, "acpi", 0) != NULL) + return_VOID; + +#if __FreeBSD_version >= 500000 + /* initialise the ACPI mutex */ + mtx_init(&acpi_mutex, "ACPI global lock", NULL, MTX_DEF); +#endif + + /* + * Start up the ACPI CA subsystem. + */ +#ifdef ACPI_DEBUGGER + debugpoint = getenv("debug.acpi.debugger"); + if (debugpoint) { + if (!strcmp(debugpoint, "init")) + acpi_EnterDebugger(); + freeenv(debugpoint); + } +#endif + if (ACPI_FAILURE(error = AcpiInitializeSubsystem())) { + printf("ACPI: initialisation failed: %s\n", AcpiFormatException(error)); + return_VOID; + } +#ifdef ACPI_DEBUGGER + debugpoint = getenv("debug.acpi.debugger"); + if (debugpoint) { + if (!strcmp(debugpoint, "tables")) + acpi_EnterDebugger(); + freeenv(debugpoint); + } +#endif + + if (ACPI_FAILURE(error = AcpiLoadTables())) { + printf("ACPI: table load failed: %s\n", AcpiFormatException(error)); + return_VOID; + } + + /* + * Attach the actual ACPI device. + */ + if ((child = BUS_ADD_CHILD(parent, 0, "acpi", 0)) == NULL) { + device_printf(parent, "ACPI: could not attach\n"); + return_VOID; + } +} + +/* + * Fetch some descriptive data from ACPI to put in our attach message + */ +static int +acpi_probe(device_t dev) +{ + ACPI_TABLE_HEADER th; + char buf[20]; + ACPI_STATUS status; + int error; + ACPI_LOCK_DECL; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (power_pm_get_type() != POWER_PM_TYPE_NONE && + power_pm_get_type() != POWER_PM_TYPE_ACPI) { + device_printf(dev, "Other PM system enabled.\n"); + return_VALUE(ENXIO); + } + + ACPI_LOCK; + + if (ACPI_FAILURE(status = AcpiGetTableHeader(ACPI_TABLE_XSDT, 1, &th))) { + device_printf(dev, "couldn't get XSDT header: %s\n", AcpiFormatException(status)); + error = ENXIO; + } else { + sprintf(buf, "%.6s %.8s", th.OemId, th.OemTableId); + device_set_desc_copy(dev, buf); + error = 0; + } + ACPI_UNLOCK; + return_VALUE(error); +} + +static int +acpi_attach(device_t dev) +{ + struct acpi_softc *sc; + ACPI_STATUS status; + int error; + UINT32 flags; + char *env; +#ifdef ACPI_DEBUGGER + char *debugpoint; +#endif + ACPI_LOCK_DECL; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_LOCK; + sc = device_get_softc(dev); + bzero(sc, sizeof(*sc)); + sc->acpi_dev = dev; + +#ifdef ACPI_DEBUGGER + debugpoint = getenv("debug.acpi.debugger"); + if (debugpoint) { + if (!strcmp(debugpoint, "spaces")) + acpi_EnterDebugger(); + freeenv(debugpoint); + } +#endif + + /* + * Install the default address space handlers. + */ + error = ENXIO; + if (ACPI_FAILURE(status = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT, + ACPI_ADR_SPACE_SYSTEM_MEMORY, + ACPI_DEFAULT_HANDLER, + NULL, NULL))) { + device_printf(dev, "could not initialise SystemMemory handler: %s\n", AcpiFormatException(status)); + goto out; + } + if (ACPI_FAILURE(status = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT, + ACPI_ADR_SPACE_SYSTEM_IO, + ACPI_DEFAULT_HANDLER, + NULL, NULL))) { + device_printf(dev, "could not initialise SystemIO handler: %s\n", AcpiFormatException(status)); + goto out; + } + if (ACPI_FAILURE(status = AcpiInstallAddressSpaceHandler(ACPI_ROOT_OBJECT, + ACPI_ADR_SPACE_PCI_CONFIG, + ACPI_DEFAULT_HANDLER, + NULL, NULL))) { + device_printf(dev, "could not initialise PciConfig handler: %s\n", AcpiFormatException(status)); + goto out; + } + + /* + * Bring ACPI fully online. + * + * Note that some systems (specifically, those with namespace evaluation issues + * that require the avoidance of parts of the namespace) must avoid running _INI + * and _STA on everything, as well as dodging the final object init pass. + * + * For these devices, we set ACPI_NO_DEVICE_INIT and ACPI_NO_OBJECT_INIT). + * + * XXX We should arrange for the object init pass after we have attached all our + * child devices, but on many systems it works here. + */ +#ifdef ACPI_DEBUGGER + debugpoint = getenv("debug.acpi.debugger"); + if (debugpoint) { + if (!strcmp(debugpoint, "enable")) + acpi_EnterDebugger(); + freeenv(debugpoint); + } +#endif + flags = 0; + if (testenv("debug.acpi.avoid")) + flags = ACPI_NO_DEVICE_INIT | ACPI_NO_OBJECT_INIT; + if (ACPI_FAILURE(status = AcpiEnableSubsystem(flags))) { + device_printf(dev, "could not enable ACPI: %s\n", AcpiFormatException(status)); + goto out; + } + + if (ACPI_FAILURE(status = AcpiInitializeObjects(flags))) { + device_printf(dev, "could not initialize ACPI objects: %s\n", AcpiFormatException(status)); + goto out; + } + + /* + * Setup our sysctl tree. + * + * XXX: This doesn't check to make sure that none of these fail. + */ + sysctl_ctx_init(&sc->acpi_sysctl_ctx); + sc->acpi_sysctl_tree = SYSCTL_ADD_NODE(&sc->acpi_sysctl_ctx, + SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, + device_get_name(dev), CTLFLAG_RD, 0, ""); + SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "supported_sleep_state", CTLTYPE_STRING | CTLFLAG_RD, + 0, 0, acpi_supported_sleep_state_sysctl, "A", ""); + SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "power_button_state", CTLTYPE_STRING | CTLFLAG_RW, + &sc->acpi_power_button_sx, 0, acpi_sleep_state_sysctl, "A", ""); + SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "sleep_button_state", CTLTYPE_STRING | CTLFLAG_RW, + &sc->acpi_sleep_button_sx, 0, acpi_sleep_state_sysctl, "A", ""); + SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "lid_switch_state", CTLTYPE_STRING | CTLFLAG_RW, + &sc->acpi_lid_switch_sx, 0, acpi_sleep_state_sysctl, "A", ""); + SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "standby_state", CTLTYPE_STRING | CTLFLAG_RW, + &sc->acpi_standby_sx, 0, acpi_sleep_state_sysctl, "A", ""); + SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "suspend_state", CTLTYPE_STRING | CTLFLAG_RW, + &sc->acpi_suspend_sx, 0, acpi_sleep_state_sysctl, "A", ""); + SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "sleep_delay", CTLFLAG_RD | CTLFLAG_RW, + &sc->acpi_sleep_delay, 0, "sleep delay"); + SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "s4bios", CTLFLAG_RD | CTLFLAG_RW, + &sc->acpi_s4bios, 0, "S4BIOS mode"); + SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "verbose", CTLFLAG_RD | CTLFLAG_RW, + &sc->acpi_verbose, 0, "verbose mode"); + SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "disable_on_poweroff", CTLFLAG_RD | CTLFLAG_RW, + &sc->acpi_disable_on_poweroff, 0, "ACPI subsystem disable on poweroff"); + sc->acpi_disable_on_poweroff = 1; + sc->acpi_sleep_delay = 0; + sc->acpi_s4bios = 1; + if (bootverbose) + sc->acpi_verbose = 1; + if ((env = getenv("hw.acpi.verbose")) && strcmp(env, "0")) { + sc->acpi_verbose = 1; + freeenv(env); + } + + /* + * Dispatch the default sleep state to devices. + * TBD: should be configured from userland policy manager. + */ + sc->acpi_power_button_sx = ACPI_POWER_BUTTON_DEFAULT_SX; + sc->acpi_sleep_button_sx = ACPI_SLEEP_BUTTON_DEFAULT_SX; + sc->acpi_lid_switch_sx = ACPI_LID_SWITCH_DEFAULT_SX; + sc->acpi_standby_sx = ACPI_STATE_S1; + sc->acpi_suspend_sx = ACPI_STATE_S3; + + acpi_enable_fixed_events(sc); + + /* + * Scan the namespace and attach/initialise children. + */ +#ifdef ACPI_DEBUGGER + debugpoint = getenv("debug.acpi.debugger"); + if (debugpoint) { + if (!strcmp(debugpoint, "probe")) + acpi_EnterDebugger(); + freeenv(debugpoint); + } +#endif + + /* + * Register our shutdown handlers + */ + EVENTHANDLER_REGISTER(shutdown_pre_sync, acpi_shutdown_pre_sync, sc, SHUTDOWN_PRI_LAST); + EVENTHANDLER_REGISTER(shutdown_final, acpi_shutdown_final, sc, SHUTDOWN_PRI_LAST); + + /* + * Register our acpi event handlers. + * XXX should be configurable eg. via userland policy manager. + */ + EVENTHANDLER_REGISTER(acpi_sleep_event, acpi_system_eventhandler_sleep, sc, ACPI_EVENT_PRI_LAST); + EVENTHANDLER_REGISTER(acpi_wakeup_event, acpi_system_eventhandler_wakeup, sc, ACPI_EVENT_PRI_LAST); + + /* + * Flag our initial states. + */ + sc->acpi_enabled = 1; + sc->acpi_sstate = ACPI_STATE_S0; + sc->acpi_sleep_disabled = 0; + + /* + * Create the control device + */ + sc->acpi_dev_t = make_dev(&acpi_cdevsw, 0, UID_ROOT, GID_WHEEL, 0644, + "acpi"); + sc->acpi_dev_t->si_drv1 = sc; + +#ifdef ACPI_DEBUGGER + debugpoint = getenv("debug.acpi.debugger"); + if (debugpoint) { + if (!strcmp(debugpoint, "running")) + acpi_EnterDebugger(); + freeenv(debugpoint); + } +#endif + +#ifdef ACPI_USE_THREADS + if ((error = acpi_task_thread_init())) { + goto out; + } +#endif + + if ((error = acpi_machdep_init(dev))) { + goto out; + } + + /* Register ACPI again to pass the correct argument of pm_func. */ + power_pm_register(POWER_PM_TYPE_ACPI, acpi_pm_func, sc); + + if (!acpi_disabled("bus")) + acpi_probe_children(dev); + + error = 0; + + out: + ACPI_UNLOCK; + return_VALUE(error); +} + +/* + * Handle a new device being added + */ +static device_t +acpi_add_child(device_t bus, int order, const char *name, int unit) +{ + struct acpi_device *ad; + device_t child; + + if ((ad = malloc(sizeof(*ad), M_ACPIDEV, M_NOWAIT)) == NULL) + return(NULL); + bzero(ad, sizeof(*ad)); + + resource_list_init(&ad->ad_rl); + + child = device_add_child_ordered(bus, order, name, unit); + if (child != NULL) + device_set_ivars(child, ad); + return(child); +} + +static int +acpi_print_child(device_t bus, device_t child) +{ + struct acpi_device *adev = device_get_ivars(child); + struct resource_list *rl = &adev->ad_rl; + int retval = 0; + + retval += bus_print_child_header(bus, child); + retval += resource_list_print_type(rl, "port", SYS_RES_IOPORT, "%#lx"); + retval += resource_list_print_type(rl, "iomem", SYS_RES_MEMORY, "%#lx"); + retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%ld"); + retval += resource_list_print_type(rl, "drq", SYS_RES_DRQ, "%ld"); + retval += bus_print_child_footer(bus, child); + + return(retval); +} + + +/* + * Handle per-device ivars + */ +static int +acpi_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) +{ + struct acpi_device *ad; + + if ((ad = device_get_ivars(child)) == NULL) { + printf("device has no ivars\n"); + return(ENOENT); + } + + switch(index) { + /* ACPI ivars */ + case ACPI_IVAR_HANDLE: + *(ACPI_HANDLE *)result = ad->ad_handle; + break; + case ACPI_IVAR_MAGIC: + *(int *)result = ad->ad_magic; + break; + case ACPI_IVAR_PRIVATE: + *(void **)result = ad->ad_private; + break; + + /* ISA compatibility */ + case ISA_IVAR_VENDORID: + case ISA_IVAR_SERIAL: + case ISA_IVAR_COMPATID: + *(int *)result = -1; + break; + + case ISA_IVAR_LOGICALID: + *(int *)result = acpi_isa_get_logicalid(child); + break; + + default: + return(ENOENT); + } + return(0); +} + +static int +acpi_write_ivar(device_t dev, device_t child, int index, uintptr_t value) +{ + struct acpi_device *ad; + + if ((ad = device_get_ivars(child)) == NULL) { + printf("device has no ivars\n"); + return(ENOENT); + } + + switch(index) { + /* ACPI ivars */ + case ACPI_IVAR_HANDLE: + ad->ad_handle = (ACPI_HANDLE)value; + break; + case ACPI_IVAR_MAGIC: + ad->ad_magic = (int )value; + break; + case ACPI_IVAR_PRIVATE: + ad->ad_private = (void *)value; + break; + + default: + panic("bad ivar write request (%d)", index); + return(ENOENT); + } + return(0); +} + +/* + * Handle child resource allocation/removal + */ +static int +acpi_set_resource(device_t dev, device_t child, int type, int rid, u_long start, u_long count) +{ + struct acpi_device *ad = device_get_ivars(child); + struct resource_list *rl = &ad->ad_rl; + + resource_list_add(rl, type, rid, start, start + count -1, count); + + return(0); +} + +static int +acpi_get_resource(device_t dev, device_t child, int type, int rid, u_long *startp, u_long *countp) +{ + struct acpi_device *ad = device_get_ivars(child); + struct resource_list *rl = &ad->ad_rl; + struct resource_list_entry *rle; + + rle = resource_list_find(rl, type, rid); + if (!rle) + return(ENOENT); + + if (startp) + *startp = rle->start; + if (countp) + *countp = rle->count; + + return(0); +} + +static struct resource * +acpi_alloc_resource(device_t bus, device_t child, int type, int *rid, + u_long start, u_long end, u_long count, u_int flags) +{ + struct acpi_device *ad = device_get_ivars(child); + struct resource_list *rl = &ad->ad_rl; + + return(resource_list_alloc(rl, bus, child, type, rid, start, end, count, flags)); +} + +static int +acpi_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r) +{ + struct acpi_device *ad = device_get_ivars(child); + struct resource_list *rl = &ad->ad_rl; + + return(resource_list_release(rl, bus, child, type, rid, r)); +} + +/* + * Handle ISA-like devices probing for a PnP ID to match. + */ +#define PNP_EISAID(s) \ + ((((s[0] - '@') & 0x1f) << 2) \ + | (((s[1] - '@') & 0x18) >> 3) \ + | (((s[1] - '@') & 0x07) << 13) \ + | (((s[2] - '@') & 0x1f) << 8) \ + | (PNP_HEXTONUM(s[4]) << 16) \ + | (PNP_HEXTONUM(s[3]) << 20) \ + | (PNP_HEXTONUM(s[6]) << 24) \ + | (PNP_HEXTONUM(s[5]) << 28)) + +static u_int32_t +acpi_isa_get_logicalid(device_t dev) +{ + ACPI_HANDLE h; + ACPI_DEVICE_INFO devinfo; + ACPI_STATUS error; + u_int32_t pnpid; + ACPI_LOCK_DECL; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + pnpid = 0; + ACPI_LOCK; + + /* fetch and validate the HID */ + if ((h = acpi_get_handle(dev)) == NULL) + goto out; + if (ACPI_FAILURE(error = AcpiGetObjectInfo(h, &devinfo))) + goto out; + if (!(devinfo.Valid & ACPI_VALID_HID)) + goto out; + + pnpid = PNP_EISAID(devinfo.HardwareId); +out: + ACPI_UNLOCK; + return_VALUE(pnpid); +} + +static u_int32_t +acpi_isa_get_compatid(device_t dev) +{ + ACPI_HANDLE h; + ACPI_DEVICE_INFO devinfo; + ACPI_STATUS error; + u_int32_t pnpid; + ACPI_LOCK_DECL; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + pnpid = 0; + ACPI_LOCK; + + /* fetch and validate the HID */ + if ((h = acpi_get_handle(dev)) == NULL) + goto out; + if (ACPI_FAILURE(error = AcpiGetObjectInfo(h, &devinfo))) + goto out; + if (ACPI_FAILURE(error = acpi_EvaluateInteger(h, "_CID", &pnpid))) + goto out; + +out: + ACPI_UNLOCK; + return_VALUE(pnpid); +} + + +static int +acpi_isa_pnp_probe(device_t bus, device_t child, struct isa_pnp_id *ids) +{ + int result; + u_int32_t lid, cid; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + /* + * ISA-style drivers attached to ACPI may persist and + * probe manually if we return ENOENT. We never want + * that to happen, so don't ever return it. + */ + result = ENXIO; + + /* scan the supplied IDs for a match */ + lid = acpi_isa_get_logicalid(child); + cid = acpi_isa_get_compatid(child); + while (ids && ids->ip_id) { + if (lid == ids->ip_id || cid == ids->ip_id) { + result = 0; + goto out; + } + ids++; + } + out: + return_VALUE(result); +} + +/* + * Scan relevant portions of the ACPI namespace and attach child devices. + * + * Note that we only expect to find devices in the \_PR_, \_TZ_, \_SI_ and \_SB_ scopes, + * and \_PR_ and \_TZ_ become obsolete in the ACPI 2.0 spec. + */ +static void +acpi_probe_children(device_t bus) +{ + ACPI_HANDLE parent; + static char *scopes[] = {"\\_PR_", "\\_TZ_", "\\_SI", "\\_SB_", NULL}; + int i; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_ASSERTLOCK; + + /* + * Create any static children by calling device identify methods. + */ + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "device identify routines\n")); + bus_generic_probe(bus); + + /* + * Scan the namespace and insert placeholders for all the devices that + * we find. + * + * Note that we use AcpiWalkNamespace rather than AcpiGetDevices because + * we want to create nodes for all devices, not just those that are currently + * present. (This assumes that we don't want to create/remove devices as they + * appear, which might be smarter.) + */ + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "namespace scan\n")); + for (i = 0; scopes[i] != NULL; i++) + if (ACPI_SUCCESS(AcpiGetHandle(ACPI_ROOT_OBJECT, scopes[i], &parent))) + AcpiWalkNamespace(ACPI_TYPE_ANY, parent, 100, acpi_probe_child, bus, NULL); + + /* + * Scan all of the child devices we have created and let them probe/attach. + */ + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "first bus_generic_attach\n")); + bus_generic_attach(bus); + + /* + * Some of these children may have attached others as part of their attach + * process (eg. the root PCI bus driver), so rescan. + */ + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "second bus_generic_attach\n")); + bus_generic_attach(bus); + + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "done attaching children\n")); + return_VOID; +} + +/* + * Evaluate a child device and determine whether we might attach a device to + * it. + */ +static ACPI_STATUS +acpi_probe_child(ACPI_HANDLE handle, UINT32 level, void *context, void **status) +{ + ACPI_OBJECT_TYPE type; + device_t child, bus = (device_t)context; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + /* + * Skip this device if we think we'll have trouble with it. + */ + if (acpi_avoid(handle)) + return_ACPI_STATUS(AE_OK); + + if (ACPI_SUCCESS(AcpiGetType(handle, &type))) { + switch(type) { + case ACPI_TYPE_DEVICE: + case ACPI_TYPE_PROCESSOR: + case ACPI_TYPE_THERMAL: + case ACPI_TYPE_POWER: + if (acpi_disabled("children")) + break; + /* + * Create a placeholder device for this node. Sort the placeholder + * so that the probe/attach passes will run breadth-first. + */ + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "scanning '%s'\n", acpi_name(handle))); + child = BUS_ADD_CHILD(bus, level * 10, NULL, -1); + if (child == NULL) + break; + acpi_set_handle(child, handle); + + /* + * Check that the device is present. If it's not present, + * leave it disabled (so that we have a device_t attached to + * the handle, but we don't probe it). + */ + if ((type == ACPI_TYPE_DEVICE) && (!acpi_DeviceIsPresent(child))) { + device_disable(child); + break; + } + + /* + * Get the device's resource settings and attach them. + * Note that if the device has _PRS but no _CRS, we need + * to decide when it's appropriate to try to configure the + * device. Ignore the return value here; it's OK for the + * device not to have any resources. + */ + acpi_parse_resources(child, handle, &acpi_res_parse_set); + + /* if we're debugging, probe/attach now rather than later */ + ACPI_DEBUG_EXEC(device_probe_and_attach(child)); + break; + } + } + return_ACPI_STATUS(AE_OK); +} + +static void +acpi_shutdown_pre_sync(void *arg, int howto) +{ + + struct acpi_softc *sc = arg; + + ACPI_ASSERTLOCK; + + /* + * Disable all ACPI events before soft off, otherwise the system + * will be turned on again on some laptops. + * + * XXX this should probably be restricted to masking some events just + * before powering down, since we may still need ACPI during the + * shutdown process. + */ + if (sc->acpi_disable_on_poweroff) + acpi_Disable(sc); +} + +static void +acpi_shutdown_final(void *arg, int howto) +{ + ACPI_STATUS status; + + ACPI_ASSERTLOCK; + + if (howto & RB_POWEROFF) { + printf("Power system off using ACPI...\n"); + if (ACPI_FAILURE(status = AcpiEnterSleepStatePrep(acpi_off_state))) { + printf("AcpiEnterSleepStatePrep failed - %s\n", + AcpiFormatException(status)); + return; + } + if (ACPI_FAILURE(status = AcpiEnterSleepState(acpi_off_state))) { + printf("ACPI power-off failed - %s\n", AcpiFormatException(status)); + } else { + DELAY(1000000); + printf("ACPI power-off failed - timeout\n"); + } + } else { + printf("Terminate ACPI\n"); + AcpiTerminate(); + } +} + +static void +acpi_enable_fixed_events(struct acpi_softc *sc) +{ + static int first_time = 1; +#define MSGFORMAT "%s button is handled as a fixed feature programming model.\n" + + ACPI_ASSERTLOCK; + + /* Enable and clear fixed events and install handlers. */ + if ((AcpiGbl_FADT != NULL) && (AcpiGbl_FADT->PwrButton == 0)) { + AcpiEnableEvent(ACPI_EVENT_POWER_BUTTON, ACPI_EVENT_FIXED, 0); + AcpiClearEvent(ACPI_EVENT_POWER_BUTTON, ACPI_EVENT_FIXED); + AcpiInstallFixedEventHandler(ACPI_EVENT_POWER_BUTTON, + acpi_eventhandler_power_button_for_sleep, sc); + if (first_time) { + device_printf(sc->acpi_dev, MSGFORMAT, "power"); + } + } + if ((AcpiGbl_FADT != NULL) && (AcpiGbl_FADT->SleepButton == 0)) { + AcpiEnableEvent(ACPI_EVENT_SLEEP_BUTTON, ACPI_EVENT_FIXED, 0); + AcpiClearEvent(ACPI_EVENT_SLEEP_BUTTON, ACPI_EVENT_FIXED); + AcpiInstallFixedEventHandler(ACPI_EVENT_SLEEP_BUTTON, + acpi_eventhandler_sleep_button_for_sleep, sc); + if (first_time) { + device_printf(sc->acpi_dev, MSGFORMAT, "sleep"); + } + } + + first_time = 0; +} + +/* + * Returns true if the device is actually present and should + * be attached to. This requires the present, enabled, UI-visible + * and diagnostics-passed bits to be set. + */ +BOOLEAN +acpi_DeviceIsPresent(device_t dev) +{ + ACPI_HANDLE h; + ACPI_DEVICE_INFO devinfo; + ACPI_STATUS error; + + ACPI_ASSERTLOCK; + + if ((h = acpi_get_handle(dev)) == NULL) + return(FALSE); + if (ACPI_FAILURE(error = AcpiGetObjectInfo(h, &devinfo))) + return(FALSE); + /* if no _STA method, must be present */ + if (!(devinfo.Valid & ACPI_VALID_STA)) + return(TRUE); + /* return true for 'present' and 'functioning' */ + if ((devinfo.CurrentStatus & 0x9) == 0x9) + return(TRUE); + return(FALSE); +} + +/* + * Returns true if the battery is actually present and inserted. + */ +BOOLEAN +acpi_BatteryIsPresent(device_t dev) +{ + ACPI_HANDLE h; + ACPI_DEVICE_INFO devinfo; + ACPI_STATUS error; + + ACPI_ASSERTLOCK; + + if ((h = acpi_get_handle(dev)) == NULL) + return(FALSE); + if (ACPI_FAILURE(error = AcpiGetObjectInfo(h, &devinfo))) + return(FALSE); + /* if no _STA method, must be present */ + if (!(devinfo.Valid & ACPI_VALID_STA)) + return(TRUE); + /* return true for 'present' and 'functioning' */ + if ((devinfo.CurrentStatus & 0x19) == 0x19) + return(TRUE); + return(FALSE); +} + +/* + * Match a HID string against a device + */ +BOOLEAN +acpi_MatchHid(device_t dev, char *hid) +{ + ACPI_HANDLE h; + ACPI_DEVICE_INFO devinfo; + ACPI_STATUS error; + int cid; + + ACPI_ASSERTLOCK; + + if (hid == NULL) + return(FALSE); + if ((h = acpi_get_handle(dev)) == NULL) + return(FALSE); + if (ACPI_FAILURE(error = AcpiGetObjectInfo(h, &devinfo))) + return(FALSE); + if ((devinfo.Valid & ACPI_VALID_HID) && !strcmp(hid, devinfo.HardwareId)) + return(TRUE); + if (ACPI_FAILURE(error = acpi_EvaluateInteger(h, "_CID", &cid))) + return(FALSE); + if (cid == PNP_EISAID(hid)) + return(TRUE); + return(FALSE); +} + +/* + * Return the handle of a named object within our scope, ie. that of (parent) + * or one if its parents. + */ +ACPI_STATUS +acpi_GetHandleInScope(ACPI_HANDLE parent, char *path, ACPI_HANDLE *result) +{ + ACPI_HANDLE r; + ACPI_STATUS status; + + ACPI_ASSERTLOCK; + + /* walk back up the tree to the root */ + for (;;) { + if (ACPI_SUCCESS(status = AcpiGetHandle(parent, path, &r))) { + *result = r; + return(AE_OK); + } + if (status != AE_NOT_FOUND) + return(AE_OK); + if (ACPI_FAILURE(AcpiGetParent(parent, &r))) + return(AE_NOT_FOUND); + parent = r; + } +} + +/* + * Allocate a buffer with a preset data size. + */ +ACPI_BUFFER * +acpi_AllocBuffer(int size) +{ + ACPI_BUFFER *buf; + + if ((buf = malloc(size + sizeof(*buf), M_ACPIDEV, M_NOWAIT)) == NULL) + return(NULL); + buf->Length = size; + buf->Pointer = (void *)(buf + 1); + return(buf); +} + +/* + * Evaluate a path that should return an integer. + */ +ACPI_STATUS +acpi_EvaluateInteger(ACPI_HANDLE handle, char *path, int *number) +{ + ACPI_STATUS error; + ACPI_BUFFER buf; + ACPI_OBJECT param; + + ACPI_ASSERTLOCK; + + if (handle == NULL) + handle = ACPI_ROOT_OBJECT; + + /* + * Assume that what we've been pointed at is an Integer object, or + * a method that will return an Integer. + */ + buf.Pointer = ¶m; + buf.Length = sizeof(param); + if (ACPI_SUCCESS(error = AcpiEvaluateObject(handle, path, NULL, &buf))) { + if (param.Type == ACPI_TYPE_INTEGER) { + *number = param.Integer.Value; + } else { + error = AE_TYPE; + } + } + + /* + * In some applications, a method that's expected to return an Integer + * may instead return a Buffer (probably to simplify some internal + * arithmetic). We'll try to fetch whatever it is, and if it's a Buffer, + * convert it into an Integer as best we can. + * + * This is a hack. + */ + if (error == AE_BUFFER_OVERFLOW) { + if ((buf.Pointer = AcpiOsAllocate(buf.Length)) == NULL) { + error = AE_NO_MEMORY; + } else { + if (ACPI_SUCCESS(error = AcpiEvaluateObject(handle, path, NULL, &buf))) { + error = acpi_ConvertBufferToInteger(&buf, number); + } + } + AcpiOsFree(buf.Pointer); + } + return(error); +} + +ACPI_STATUS +acpi_ConvertBufferToInteger(ACPI_BUFFER *bufp, int *number) +{ + ACPI_OBJECT *p; + int i; + + p = (ACPI_OBJECT *)bufp->Pointer; + if (p->Type == ACPI_TYPE_INTEGER) { + *number = p->Integer.Value; + return(AE_OK); + } + if (p->Type != ACPI_TYPE_BUFFER) + return(AE_TYPE); + if (p->Buffer.Length > sizeof(int)) + return(AE_BAD_DATA); + *number = 0; + for (i = 0; i < p->Buffer.Length; i++) + *number += (*(p->Buffer.Pointer + i) << (i * 8)); + return(AE_OK); +} + +/* + * Iterate over the elements of an a package object, calling the supplied + * function for each element. + * + * XXX possible enhancement might be to abort traversal on error. + */ +ACPI_STATUS +acpi_ForeachPackageObject(ACPI_OBJECT *pkg, void (* func)(ACPI_OBJECT *comp, void *arg), void *arg) +{ + ACPI_OBJECT *comp; + int i; + + if ((pkg == NULL) || (pkg->Type != ACPI_TYPE_PACKAGE)) + return(AE_BAD_PARAMETER); + + /* iterate over components */ + for (i = 0, comp = pkg->Package.Elements; i < pkg->Package.Count; i++, comp++) + func(comp, arg); + + return(AE_OK); +} + +/* + * Find the (index)th resource object in a set. + */ +ACPI_STATUS +acpi_FindIndexedResource(ACPI_BUFFER *buf, int index, ACPI_RESOURCE **resp) +{ + ACPI_RESOURCE *rp; + int i; + + rp = (ACPI_RESOURCE *)buf->Pointer; + i = index; + while (i-- > 0) { + /* range check */ + if (rp > (ACPI_RESOURCE *)((u_int8_t *)buf->Pointer + buf->Length)) + return(AE_BAD_PARAMETER); + /* check for terminator */ + if ((rp->Id == ACPI_RSTYPE_END_TAG) || + (rp->Length == 0)) + return(AE_NOT_FOUND); + rp = ACPI_RESOURCE_NEXT(rp); + } + if (resp != NULL) + *resp = rp; + return(AE_OK); +} + +/* + * Append an ACPI_RESOURCE to an ACPI_BUFFER. + * + * Given a pointer to an ACPI_RESOURCE structure, expand the ACPI_BUFFER + * provided to contain it. If the ACPI_BUFFER is empty, allocate a sensible + * backing block. If the ACPI_RESOURCE is NULL, return an empty set of + * resources. + */ +#define ACPI_INITIAL_RESOURCE_BUFFER_SIZE 512 + +ACPI_STATUS +acpi_AppendBufferResource(ACPI_BUFFER *buf, ACPI_RESOURCE *res) +{ + ACPI_RESOURCE *rp; + void *newp; + + /* + * Initialise the buffer if necessary. + */ + if (buf->Pointer == NULL) { + buf->Length = ACPI_INITIAL_RESOURCE_BUFFER_SIZE; + if ((buf->Pointer = AcpiOsAllocate(buf->Length)) == NULL) + return(AE_NO_MEMORY); + rp = (ACPI_RESOURCE *)buf->Pointer; + rp->Id = ACPI_RSTYPE_END_TAG; + rp->Length = 0; + } + if (res == NULL) + return(AE_OK); + + /* + * Scan the current buffer looking for the terminator. + * This will either find the terminator or hit the end + * of the buffer and return an error. + */ + rp = (ACPI_RESOURCE *)buf->Pointer; + for (;;) { + /* range check, don't go outside the buffer */ + if (rp >= (ACPI_RESOURCE *)((u_int8_t *)buf->Pointer + buf->Length)) + return(AE_BAD_PARAMETER); + if ((rp->Id == ACPI_RSTYPE_END_TAG) || + (rp->Length == 0)) { + break; + } + rp = ACPI_RESOURCE_NEXT(rp); + } + + /* + * Check the size of the buffer and expand if required. + * + * Required size is: + * size of existing resources before terminator + + * size of new resource and header + + * size of terminator. + * + * Note that this loop should really only run once, unless + * for some reason we are stuffing a *really* huge resource. + */ + while ((((u_int8_t *)rp - (u_int8_t *)buf->Pointer) + + res->Length + ACPI_RESOURCE_LENGTH_NO_DATA + + ACPI_RESOURCE_LENGTH) >= buf->Length) { + if ((newp = AcpiOsAllocate(buf->Length * 2)) == NULL) + return(AE_NO_MEMORY); + bcopy(buf->Pointer, newp, buf->Length); + rp = (ACPI_RESOURCE *)((u_int8_t *)newp + + ((u_int8_t *)rp - (u_int8_t *)buf->Pointer)); + AcpiOsFree(buf->Pointer); + buf->Pointer = newp; + buf->Length += buf->Length; + } + + /* + * Insert the new resource. + */ + bcopy(res, rp, res->Length + ACPI_RESOURCE_LENGTH_NO_DATA); + + /* + * And add the terminator. + */ + rp = ACPI_RESOURCE_NEXT(rp); + rp->Id = ACPI_RSTYPE_END_TAG; + rp->Length = 0; + + return(AE_OK); +} + +/* + * Set interrupt model. + */ +ACPI_STATUS +acpi_SetIntrModel(int model) +{ + ACPI_OBJECT_LIST ArgList; + ACPI_OBJECT Arg; + + Arg.Type = ACPI_TYPE_INTEGER; + Arg.Integer.Value = model; + ArgList.Count = 1; + ArgList.Pointer = &Arg; + return (AcpiEvaluateObject(ACPI_ROOT_OBJECT, "_PIC", &ArgList, NULL)); +} + +#define ACPI_MINIMUM_AWAKETIME 5 + +static void +acpi_sleep_enable(void *arg) +{ + ((struct acpi_softc *)arg)->acpi_sleep_disabled = 0; +} + +/* + * Set the system sleep state + * + * Currently we only support S1 and S5 + */ +ACPI_STATUS +acpi_SetSleepState(struct acpi_softc *sc, int state) +{ + ACPI_STATUS status = AE_OK; + UINT8 TypeA; + UINT8 TypeB; + + ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, state); + ACPI_ASSERTLOCK; + + if (sc->acpi_sstate != ACPI_STATE_S0) + return_ACPI_STATUS(AE_BAD_PARAMETER); /* avoid reentry */ + + if (sc->acpi_sleep_disabled) + return_ACPI_STATUS(AE_OK); + + switch (state) { + case ACPI_STATE_S0: /* XXX only for testing */ + if (ACPI_FAILURE(status = AcpiEnterSleepState((UINT8)state))) { + device_printf(sc->acpi_dev, "AcpiEnterSleepState failed - %s\n", AcpiFormatException(status)); + } + break; + + case ACPI_STATE_S1: + case ACPI_STATE_S2: + case ACPI_STATE_S3: + case ACPI_STATE_S4: + if (ACPI_FAILURE(status = AcpiGetSleepTypeData((UINT8)state, &TypeA, &TypeB))) { + device_printf(sc->acpi_dev, "AcpiGetSleepTypeData failed - %s\n", AcpiFormatException(status)); + break; + } + + sc->acpi_sstate = state; + sc->acpi_sleep_disabled = 1; + + /* + * Inform all devices that we are going to sleep. + */ + if (DEVICE_SUSPEND(root_bus) != 0) { + /* + * Re-wake the system. + * + * XXX note that a better two-pass approach with a 'veto' pass + * followed by a "real thing" pass would be better, but the + * current bus interface does not provide for this. + */ + DEVICE_RESUME(root_bus); + return_ACPI_STATUS(AE_ERROR); + } + + if (ACPI_FAILURE(status = AcpiEnterSleepStatePrep(state))) { + device_printf(sc->acpi_dev, "AcpiEnterSleepStatePrep failed - %s\n", + AcpiFormatException(status)); + break; + } + + if (sc->acpi_sleep_delay > 0) { + DELAY(sc->acpi_sleep_delay * 1000000); + } + + if (state != ACPI_STATE_S1) { + acpi_sleep_machdep(sc, state); + + /* AcpiEnterSleepState() maybe incompleted, unlock here if locked. */ + if (AcpiGbl_AcpiMutexInfo[ACPI_MTX_HARDWARE].OwnerId != ACPI_MUTEX_NOT_ACQUIRED) { + AcpiUtReleaseMutex(ACPI_MTX_HARDWARE); + } + + /* Re-enable ACPI hardware on wakeup from sleep state 4. */ + if (state == ACPI_STATE_S4) { + AcpiEnable(); + } + } else { + if (ACPI_FAILURE(status = AcpiEnterSleepState((UINT8)state))) { + device_printf(sc->acpi_dev, "AcpiEnterSleepState failed - %s\n", AcpiFormatException(status)); + break; + } + } + AcpiLeaveSleepState((UINT8)state); + DEVICE_RESUME(root_bus); + sc->acpi_sstate = ACPI_STATE_S0; + acpi_enable_fixed_events(sc); + break; + + case ACPI_STATE_S5: + /* + * Shut down cleanly and power off. This will call us back through the + * shutdown handlers. + */ + shutdown_nice(RB_POWEROFF); + break; + + default: + status = AE_BAD_PARAMETER; + break; + } + + if (sc->acpi_sleep_disabled) + timeout(acpi_sleep_enable, (caddr_t)sc, hz * ACPI_MINIMUM_AWAKETIME); + + return_ACPI_STATUS(status); +} + +/* + * Enable/Disable ACPI + */ +ACPI_STATUS +acpi_Enable(struct acpi_softc *sc) +{ + ACPI_STATUS status; + u_int32_t flags; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_ASSERTLOCK; + + flags = ACPI_NO_ADDRESS_SPACE_INIT | ACPI_NO_HARDWARE_INIT | + ACPI_NO_DEVICE_INIT | ACPI_NO_OBJECT_INIT; + if (!sc->acpi_enabled) { + status = AcpiEnableSubsystem(flags); + } else { + status = AE_OK; + } + if (status == AE_OK) + sc->acpi_enabled = 1; + return_ACPI_STATUS(status); +} + +ACPI_STATUS +acpi_Disable(struct acpi_softc *sc) +{ + ACPI_STATUS status; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + ACPI_ASSERTLOCK; + + if (sc->acpi_enabled) { + status = AcpiDisable(); + } else { + status = AE_OK; + } + if (status == AE_OK) + sc->acpi_enabled = 0; + return_ACPI_STATUS(status); +} + +/* + * ACPI Event Handlers + */ + +/* System Event Handlers (registered by EVENTHANDLER_REGISTER) */ + +static void +acpi_system_eventhandler_sleep(void *arg, int state) +{ + ACPI_LOCK_DECL; + ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, state); + + ACPI_LOCK; + if (state >= ACPI_STATE_S0 && state <= ACPI_S_STATES_MAX) + acpi_SetSleepState((struct acpi_softc *)arg, state); + ACPI_UNLOCK; + return_VOID; +} + +static void +acpi_system_eventhandler_wakeup(void *arg, int state) +{ + ACPI_LOCK_DECL; + ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, state); + + /* Well, what to do? :-) */ + + ACPI_LOCK; + ACPI_UNLOCK; + + return_VOID; +} + +/* + * ACPICA Event Handlers (FixedEvent, also called from button notify handler) + */ +UINT32 +acpi_eventhandler_power_button_for_sleep(void *context) +{ + struct acpi_softc *sc = (struct acpi_softc *)context; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + EVENTHANDLER_INVOKE(acpi_sleep_event, sc->acpi_power_button_sx); + + return_VALUE(ACPI_INTERRUPT_HANDLED); +} + +UINT32 +acpi_eventhandler_power_button_for_wakeup(void *context) +{ + struct acpi_softc *sc = (struct acpi_softc *)context; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + EVENTHANDLER_INVOKE(acpi_wakeup_event, sc->acpi_power_button_sx); + + return_VALUE(ACPI_INTERRUPT_HANDLED); +} + +UINT32 +acpi_eventhandler_sleep_button_for_sleep(void *context) +{ + struct acpi_softc *sc = (struct acpi_softc *)context; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + EVENTHANDLER_INVOKE(acpi_sleep_event, sc->acpi_sleep_button_sx); + + return_VALUE(ACPI_INTERRUPT_HANDLED); +} + +UINT32 +acpi_eventhandler_sleep_button_for_wakeup(void *context) +{ + struct acpi_softc *sc = (struct acpi_softc *)context; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + EVENTHANDLER_INVOKE(acpi_wakeup_event, sc->acpi_sleep_button_sx); + + return_VALUE(ACPI_INTERRUPT_HANDLED); +} + +/* + * XXX This is kinda ugly, and should not be here. + */ +struct acpi_staticbuf { + ACPI_BUFFER buffer; + char data[512]; +}; + +char * +acpi_name(ACPI_HANDLE handle) +{ + static struct acpi_staticbuf buf; + + ACPI_ASSERTLOCK; + + buf.buffer.Length = 512; + buf.buffer.Pointer = &buf.data[0]; + + if (ACPI_SUCCESS(AcpiGetName(handle, ACPI_FULL_PATHNAME, &buf.buffer))) + return(buf.buffer.Pointer); + return("(unknown path)"); +} + +/* + * Debugging/bug-avoidance. Avoid trying to fetch info on various + * parts of the namespace. + */ +int +acpi_avoid(ACPI_HANDLE handle) +{ + char *cp, *env, *np; + int len; + + np = acpi_name(handle); + if (*np == '\\') + np++; + if ((env = getenv("debug.acpi.avoid")) == NULL) + return(0); + + /* scan the avoid list checking for a match */ + cp = env; + for (;;) { + while ((*cp != 0) && isspace(*cp)) + cp++; + if (*cp == 0) + break; + len = 0; + while ((cp[len] != 0) && !isspace(cp[len])) + len++; + if (!strncmp(cp, np, len)) { + freeenv(env); + return(1); + } + cp += len; + } + freeenv(env); + return(0); +} + +/* + * Debugging/bug-avoidance. Disable ACPI subsystem components. + */ +int +acpi_disabled(char *subsys) +{ + char *cp, *env; + int len; + + if ((env = getenv("debug.acpi.disable")) == NULL) + return(0); + if (!strcmp(env, "all")) { + freeenv(env); + return(1); + } + + /* scan the disable list checking for a match */ + cp = env; + for (;;) { + while ((*cp != 0) && isspace(*cp)) + cp++; + if (*cp == 0) + break; + len = 0; + while ((cp[len] != 0) && !isspace(cp[len])) + len++; + if (!strncmp(cp, subsys, len)) { + freeenv(env); + return(1); + } + cp += len; + } + freeenv(env); + return(0); +} + +/* + * Device wake capability enable/disable. + */ +void +acpi_device_enable_wake_capability(ACPI_HANDLE h, int enable) +{ + ACPI_OBJECT_LIST ArgList; + ACPI_OBJECT Arg; + + /* + * TBD: All Power Resources referenced by elements 2 through N + * of the _PRW object are put into the ON state. + */ + + /* + * enable/disable device wake function. + */ + + ArgList.Count = 1; + ArgList.Pointer = &Arg; + + Arg.Type = ACPI_TYPE_INTEGER; + Arg.Integer.Value = enable; + + (void)AcpiEvaluateObject(h, "_PSW", &ArgList, NULL); +} + +void +acpi_device_enable_wake_event(ACPI_HANDLE h) +{ + struct acpi_softc *sc; + ACPI_STATUS status; + ACPI_BUFFER prw_buffer; + ACPI_OBJECT *res; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if ((sc = devclass_get_softc(acpi_devclass, 0)) == NULL) { + return; + } + + /* + * _PRW object is only required for devices that have the ability + * to wake the system from a system sleeping state. + */ + prw_buffer.Length = ACPI_ALLOCATE_BUFFER; + status = AcpiEvaluateObject(h, "_PRW", NULL, &prw_buffer); + if (ACPI_FAILURE(status)) { + return; + } + + res = (ACPI_OBJECT *)prw_buffer.Pointer; + if (res == NULL) { + return; + } + + if ((res->Type != ACPI_TYPE_PACKAGE) || (res->Package.Count < 2)) { + goto out; + } + + /* + * The element 1 of the _PRW object: + * The lowest power system sleeping state that can be entered + * while still providing wake functionality. + * The sleeping state being entered must be greater or equal to + * the power state declared in element 1 of the _PRW object. + */ + if (res->Package.Elements[1].Type != ACPI_TYPE_INTEGER) { + goto out; + } + + if (sc->acpi_sstate > res->Package.Elements[1].Integer.Value) { + goto out; + } + + /* + * The element 0 of the _PRW object: + */ + switch(res->Package.Elements[0].Type) { + case ACPI_TYPE_INTEGER: + /* + * If the data type of this package element is numeric, then this + * _PRW package element is the bit index in the GPEx_EN, in the + * GPE blocks described in the FADT, of the enable bit that is + * enabled for the wake event. + */ + + status = AcpiEnableEvent(res->Package.Elements[0].Integer.Value, + ACPI_EVENT_GPE, ACPI_EVENT_WAKE_ENABLE); + if (ACPI_FAILURE(status)) + printf("%s: EnableEvent Failed\n", __func__); + break; + + case ACPI_TYPE_PACKAGE: + /* XXX TBD */ + + /* + * If the data type of this package element is a package, then this + * _PRW package element is itself a package containing two + * elements. The first is an object reference to the GPE Block + * device that contains the GPE that will be triggered by the wake + * event. The second element is numeric and it contains the bit + * index in the GPEx_EN, in the GPE Block referenced by the + * first element in the package, of the enable bit that is enabled for + * the wake event. + * For example, if this field is a package then it is of the form: + * Package() {\_SB.PCI0.ISA.GPE, 2} + */ + + break; + + default: + break; + } + +out: + if (prw_buffer.Pointer != NULL) + AcpiOsFree(prw_buffer.Pointer); + return; +} + +/* + * Control interface. + * + * We multiplex ioctls for all participating ACPI devices here. Individual + * drivers wanting to be accessible via /dev/acpi should use the register/deregister + * interface to make their handlers visible. + */ +struct acpi_ioctl_hook +{ + TAILQ_ENTRY(acpi_ioctl_hook) link; + u_long cmd; + int (* fn)(u_long cmd, caddr_t addr, void *arg); + void *arg; +}; + +static TAILQ_HEAD(,acpi_ioctl_hook) acpi_ioctl_hooks; +static int acpi_ioctl_hooks_initted; + +/* + * Register an ioctl handler. + */ +int +acpi_register_ioctl(u_long cmd, int (* fn)(u_long cmd, caddr_t addr, void *arg), void *arg) +{ + struct acpi_ioctl_hook *hp; + + if ((hp = malloc(sizeof(*hp), M_ACPIDEV, M_NOWAIT)) == NULL) + return(ENOMEM); + hp->cmd = cmd; + hp->fn = fn; + hp->arg = arg; + if (acpi_ioctl_hooks_initted == 0) { + TAILQ_INIT(&acpi_ioctl_hooks); + acpi_ioctl_hooks_initted = 1; + } + TAILQ_INSERT_TAIL(&acpi_ioctl_hooks, hp, link); + return(0); +} + +/* + * Deregister an ioctl handler. + */ +void +acpi_deregister_ioctl(u_long cmd, int (* fn)(u_long cmd, caddr_t addr, void *arg)) +{ + struct acpi_ioctl_hook *hp; + + TAILQ_FOREACH(hp, &acpi_ioctl_hooks, link) + if ((hp->cmd == cmd) && (hp->fn == fn)) + break; + + if (hp != NULL) { + TAILQ_REMOVE(&acpi_ioctl_hooks, hp, link); + free(hp, M_ACPIDEV); + } +} + +static int +acpiopen(dev_t dev, int flag, int fmt, d_thread_t *td) +{ + return(0); +} + +static int +acpiclose(dev_t dev, int flag, int fmt, d_thread_t *td) +{ + return(0); +} + +static int +acpiioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, d_thread_t *td) +{ + struct acpi_softc *sc; + struct acpi_ioctl_hook *hp; + int error, xerror, state; + ACPI_LOCK_DECL; + + ACPI_LOCK; + + error = state = 0; + sc = dev->si_drv1; + + /* + * Scan the list of registered ioctls, looking for handlers. + */ + if (acpi_ioctl_hooks_initted) { + TAILQ_FOREACH(hp, &acpi_ioctl_hooks, link) { + if (hp->cmd == cmd) { + xerror = hp->fn(cmd, addr, hp->arg); + if (xerror != 0) + error = xerror; + goto out; + } + } + } + + /* + * Core ioctls are not permitted for non-writable user. + * Currently, other ioctls just fetch information. + * Not changing system behavior. + */ + if(!(flag & FWRITE)){ + return EPERM; + } + + /* + * Core system ioctls. + */ + switch (cmd) { + case ACPIIO_ENABLE: + if (ACPI_FAILURE(acpi_Enable(sc))) + error = ENXIO; + break; + + case ACPIIO_DISABLE: + if (ACPI_FAILURE(acpi_Disable(sc))) + error = ENXIO; + break; + + case ACPIIO_SETSLPSTATE: + if (!sc->acpi_enabled) { + error = ENXIO; + break; + } + state = *(int *)addr; + if (state >= ACPI_STATE_S0 && state <= ACPI_S_STATES_MAX) { + acpi_SetSleepState(sc, state); + } else { + error = EINVAL; + } + break; + + default: + if (error == 0) + error = EINVAL; + break; + } + +out: + ACPI_UNLOCK; + return(error); +} + +static int +acpi_supported_sleep_state_sysctl(SYSCTL_HANDLER_ARGS) +{ + char sleep_state[4]; + char buf[16]; + int error; + UINT8 state, TypeA, TypeB; + + buf[0] = '\0'; + for (state = ACPI_STATE_S1; state < ACPI_S_STATES_MAX+1; state++) { + if (ACPI_SUCCESS(AcpiGetSleepTypeData(state, &TypeA, &TypeB))) { + sprintf(sleep_state, "S%d ", state); + strcat(buf, sleep_state); + } + } + error = sysctl_handle_string(oidp, buf, sizeof(buf), req); + return(error); +} + +static int +acpi_sleep_state_sysctl(SYSCTL_HANDLER_ARGS) +{ + char sleep_state[10]; + int error; + u_int new_state, old_state; + + old_state = *(u_int *)oidp->oid_arg1; + if (old_state > ACPI_S_STATES_MAX+1) { + strcpy(sleep_state, "unknown"); + } else { + bzero(sleep_state, sizeof(sleep_state)); + strncpy(sleep_state, sleep_state_names[old_state], + sizeof(sleep_state_names[old_state])); + } + error = sysctl_handle_string(oidp, sleep_state, sizeof(sleep_state), req); + if (error == 0 && req->newptr != NULL) { + for (new_state = ACPI_STATE_S0; new_state <= ACPI_S_STATES_MAX+1; new_state++) { + if (strncmp(sleep_state, sleep_state_names[new_state], + sizeof(sleep_state)) == 0) + break; + } + if (new_state <= ACPI_S_STATES_MAX+1) { + if (new_state != old_state) { + *(u_int *)oidp->oid_arg1 = new_state; + } + } else { + error = EINVAL; + } + } + return(error); +} + +#ifdef ACPI_DEBUG +/* + * Support for parsing debug options from the kernel environment. + * + * Bits may be set in the AcpiDbgLayer and AcpiDbgLevel debug registers + * by specifying the names of the bits in the debug.acpi.layer and + * debug.acpi.level environment variables. Bits may be unset by + * prefixing the bit name with !. + */ +struct debugtag +{ + char *name; + UINT32 value; +}; + +static struct debugtag dbg_layer[] = { + {"ACPI_UTILITIES", ACPI_UTILITIES}, + {"ACPI_HARDWARE", ACPI_HARDWARE}, + {"ACPI_EVENTS", ACPI_EVENTS}, + {"ACPI_TABLES", ACPI_TABLES}, + {"ACPI_NAMESPACE", ACPI_NAMESPACE}, + {"ACPI_PARSER", ACPI_PARSER}, + {"ACPI_DISPATCHER", ACPI_DISPATCHER}, + {"ACPI_EXECUTER", ACPI_EXECUTER}, + {"ACPI_RESOURCES", ACPI_RESOURCES}, + {"ACPI_CA_DEBUGGER", ACPI_CA_DEBUGGER}, + {"ACPI_OS_SERVICES", ACPI_OS_SERVICES}, + {"ACPI_CA_DISASSEMBLER", ACPI_CA_DISASSEMBLER}, + + {"ACPI_BUS", ACPI_BUS}, + {"ACPI_SYSTEM", ACPI_SYSTEM}, + {"ACPI_POWER", ACPI_POWER}, + {"ACPI_EC", ACPI_EC}, + {"ACPI_AC_ADAPTER", ACPI_AC_ADAPTER}, + {"ACPI_BATTERY", ACPI_BATTERY}, + {"ACPI_BUTTON", ACPI_BUTTON}, + {"ACPI_PROCESSOR", ACPI_PROCESSOR}, + {"ACPI_THERMAL", ACPI_THERMAL}, + {"ACPI_FAN", ACPI_FAN}, + + {"ACPI_ALL_DRIVERS", ACPI_ALL_DRIVERS}, + {"ACPI_ALL_COMPONENTS", ACPI_ALL_COMPONENTS}, + {NULL, 0} +}; + +static struct debugtag dbg_level[] = { + {"ACPI_LV_ERROR", ACPI_LV_ERROR}, + {"ACPI_LV_WARN", ACPI_LV_WARN}, + {"ACPI_LV_INIT", ACPI_LV_INIT}, + {"ACPI_LV_DEBUG_OBJECT", ACPI_LV_DEBUG_OBJECT}, + {"ACPI_LV_INFO", ACPI_LV_INFO}, + {"ACPI_LV_ALL_EXCEPTIONS", ACPI_LV_ALL_EXCEPTIONS}, + + /* Trace verbosity level 1 [Standard Trace Level] */ + {"ACPI_LV_PARSE", ACPI_LV_PARSE}, + {"ACPI_LV_LOAD", ACPI_LV_LOAD}, + {"ACPI_LV_DISPATCH", ACPI_LV_DISPATCH}, + {"ACPI_LV_EXEC", ACPI_LV_EXEC}, + {"ACPI_LV_NAMES", ACPI_LV_NAMES}, + {"ACPI_LV_OPREGION", ACPI_LV_OPREGION}, + {"ACPI_LV_BFIELD", ACPI_LV_BFIELD}, + {"ACPI_LV_TABLES", ACPI_LV_TABLES}, + {"ACPI_LV_VALUES", ACPI_LV_VALUES}, + {"ACPI_LV_OBJECTS", ACPI_LV_OBJECTS}, + {"ACPI_LV_RESOURCES", ACPI_LV_RESOURCES}, + {"ACPI_LV_USER_REQUESTS", ACPI_LV_USER_REQUESTS}, + {"ACPI_LV_PACKAGE", ACPI_LV_PACKAGE}, + {"ACPI_LV_INIT_NAMES", ACPI_LV_INIT_NAMES}, + {"ACPI_LV_VERBOSITY1", ACPI_LV_VERBOSITY1}, + + /* Trace verbosity level 2 [Function tracing and memory allocation] */ + {"ACPI_LV_ALLOCATIONS", ACPI_LV_ALLOCATIONS}, + {"ACPI_LV_FUNCTIONS", ACPI_LV_FUNCTIONS}, + {"ACPI_LV_OPTIMIZATIONS", ACPI_LV_OPTIMIZATIONS}, + {"ACPI_LV_VERBOSITY2", ACPI_LV_VERBOSITY2}, + {"ACPI_LV_ALL", ACPI_LV_ALL}, + + /* Trace verbosity level 3 [Threading, I/O, and Interrupts] */ + {"ACPI_LV_MUTEX", ACPI_LV_MUTEX}, + {"ACPI_LV_THREADS", ACPI_LV_THREADS}, + {"ACPI_LV_IO", ACPI_LV_IO}, + {"ACPI_LV_INTERRUPTS", ACPI_LV_INTERRUPTS}, + {"ACPI_LV_VERBOSITY3", ACPI_LV_VERBOSITY3}, + + /* Exceptionally verbose output -- also used in the global "DebugLevel" */ + {"ACPI_LV_AML_DISASSEMBLE", ACPI_LV_AML_DISASSEMBLE}, + {"ACPI_LV_VERBOSE_INFO", ACPI_LV_VERBOSE_INFO}, + {"ACPI_LV_FULL_TABLES", ACPI_LV_FULL_TABLES}, + {"ACPI_LV_EVENTS", ACPI_LV_EVENTS}, + {"ACPI_LV_VERBOSE", ACPI_LV_VERBOSE}, + {NULL, 0} +}; + +static void +acpi_parse_debug(char *cp, struct debugtag *tag, UINT32 *flag) +{ + char *ep; + int i, l; + int set; + + while (*cp) { + if (isspace(*cp)) { + cp++; + continue; + } + ep = cp; + while (*ep && !isspace(*ep)) + ep++; + if (*cp == '!') { + set = 0; + cp++; + if (cp == ep) + continue; + } else { + set = 1; + } + l = ep - cp; + for (i = 0; tag[i].name != NULL; i++) { + if (!strncmp(cp, tag[i].name, l)) { + if (set) { + *flag |= tag[i].value; + } else { + *flag &= ~tag[i].value; + } + printf("ACPI_DEBUG: set '%s'\n", tag[i].name); + } + } + cp = ep; + } +} + +static void +acpi_set_debugging(void *junk) +{ + char *cp; + + if (!cold) + return; + + AcpiDbgLayer = 0; + AcpiDbgLevel = 0; + if ((cp = getenv("debug.acpi.layer")) != NULL) { + acpi_parse_debug(cp, &dbg_layer[0], &AcpiDbgLayer); + freeenv(cp); + } + if ((cp = getenv("debug.acpi.level")) != NULL) { + acpi_parse_debug(cp, &dbg_level[0], &AcpiDbgLevel); + freeenv(cp); + } + + printf("ACPI debug layer 0x%x debug level 0x%x\n", AcpiDbgLayer, AcpiDbgLevel); +} +SYSINIT(acpi_debugging, SI_SUB_TUNABLES, SI_ORDER_ANY, acpi_set_debugging, NULL); +#endif + +static int +acpi_pm_func(u_long cmd, void *arg, ...) +{ + int state, acpi_state; + int error; + struct acpi_softc *sc; + va_list ap; + + error = 0; + switch (cmd) { + case POWER_CMD_SUSPEND: + sc = (struct acpi_softc *)arg; + if (sc == NULL) { + error = EINVAL; + goto out; + } + + va_start(ap, arg); + state = va_arg(ap, int); + va_end(ap); + + switch (state) { + case POWER_SLEEP_STATE_STANDBY: + acpi_state = sc->acpi_standby_sx; + break; + case POWER_SLEEP_STATE_SUSPEND: + acpi_state = sc->acpi_suspend_sx; + break; + case POWER_SLEEP_STATE_HIBERNATE: + acpi_state = ACPI_STATE_S4; + break; + default: + error = EINVAL; + goto out; + } + + acpi_SetSleepState(sc, acpi_state); + break; + + default: + error = EINVAL; + goto out; + } + +out: + return (error); +} + +static void +acpi_pm_register(void *arg) +{ + int error; + + if (!cold) + return; + + if (!resource_int_value("acpi", 0, "disabled", &error) && + (error != 0)) + return; + + power_pm_register(POWER_PM_TYPE_ACPI, acpi_pm_func, NULL); +} + +SYSINIT(power, SI_SUB_KLD, SI_ORDER_ANY, acpi_pm_register, 0); + diff --git a/sys/dev/acpica/acpi_acad.c b/sys/dev/acpica/acpi_acad.c new file mode 100644 index 0000000..09bb149 --- /dev/null +++ b/sys/dev/acpica/acpi_acad.c @@ -0,0 +1,282 @@ +/*- + * Copyright (c) 2000 Takanori Watanabe + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/bus.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> +#include <sys/ioccom.h> +#include <sys/malloc.h> +#include <sys/conf.h> +#include <sys/power.h> + +#include "acpi.h" +#include <dev/acpica/acpivar.h> +#include <dev/acpica/acpiio.h> + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_AC_ADAPTER +ACPI_MODULE_NAME("AC_ADAPTER") + +#define ACPI_DEVICE_CHECK_PNP 0x00 +#define ACPI_DEVICE_CHECK_EXISTENCE 0x01 +#define ACPI_POWERSOURCE_STAT_CHANGE 0x80 + +static void acpi_acad_get_status(void * ); +static void acpi_acad_notify_handler(ACPI_HANDLE , UINT32 ,void *); +static int acpi_acad_probe(device_t); +static int acpi_acad_attach(device_t); +static int acpi_acad_ioctl(u_long, caddr_t, void *); +static int acpi_acad_sysctl(SYSCTL_HANDLER_ARGS); +static void acpi_acad_init_acline(void *arg); + +struct acpi_acad_softc { + int status; + + int initializing; +}; + +static void +acpi_acad_get_status(void *context) +{ + int newstatus; + device_t dev = context; + struct acpi_acad_softc *sc = device_get_softc(dev); + ACPI_HANDLE h = acpi_get_handle(dev); + + if (ACPI_FAILURE(acpi_EvaluateInteger(h, "_PSR", &newstatus))) { + sc->status = -1; + return; + } + + if (sc->status != newstatus) { + sc->status = newstatus; + /* set system power profile based on AC adapter status */ + power_profile_set_state(sc->status ? POWER_PROFILE_PERFORMANCE : POWER_PROFILE_ECONOMY); + ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), + "%s Line\n",(sc->status) ? "On" : "Off"); + } +} + +static void +acpi_acad_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) +{ + device_t dev = context; + + ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), + "Notify %d\n", notify); + + switch (notify) { + case ACPI_DEVICE_CHECK_PNP: + case ACPI_DEVICE_CHECK_EXISTENCE: + case ACPI_POWERSOURCE_STAT_CHANGE: + /*Temporally. It is better to notify policy manager*/ + AcpiOsQueueForExecution(OSD_PRIORITY_LO, + acpi_acad_get_status,context); + break; + default: + break; + } +} + +static int +acpi_acad_probe(device_t dev) +{ + + if ((acpi_get_type(dev) == ACPI_TYPE_DEVICE) && + acpi_MatchHid(dev, "ACPI0003")) { + + /* + * Set device description + */ + device_set_desc(dev, "AC adapter"); + return(0); + } + return(ENXIO); +} + +static int +acpi_acad_attach(device_t dev) +{ + int error; + struct acpi_acad_softc *sc; + struct acpi_softc *acpi_sc; + + ACPI_HANDLE handle = acpi_get_handle(dev); + AcpiInstallNotifyHandler(handle, + ACPI_DEVICE_NOTIFY, + acpi_acad_notify_handler, dev); + /*Installing system notify is not so good*/ + AcpiInstallNotifyHandler(handle, + ACPI_SYSTEM_NOTIFY, + acpi_acad_notify_handler, dev); + + if ((sc = device_get_softc(dev)) == NULL) { + return (ENXIO); + } + if ((error = acpi_register_ioctl(ACPIIO_ACAD_GET_STATUS, + acpi_acad_ioctl, dev)) != 0) { + return (error); + } + + if (device_get_unit(dev) == 0) { + acpi_sc = acpi_device_get_parent_softc(dev); + SYSCTL_ADD_PROC(&acpi_sc->acpi_sysctl_ctx, + SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), + OID_AUTO, "acline", CTLTYPE_INT | CTLFLAG_RD, + &sc->status, 0, acpi_acad_sysctl, "I", ""); + } + + /* Get initial status after whole system is up. */ + sc->status = -1; + sc->initializing = 0; + + AcpiOsQueueForExecution(OSD_PRIORITY_LO, acpi_acad_init_acline, dev); + + return(0); +} + +static device_method_t acpi_acad_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_acad_probe), + DEVMETHOD(device_attach, acpi_acad_attach), + + {0, 0} +}; + +static driver_t acpi_acad_driver = { + "acpi_acad", + acpi_acad_methods, + sizeof(struct acpi_acad_softc), +}; + +static devclass_t acpi_acad_devclass; +DRIVER_MODULE(acpi_acad,acpi,acpi_acad_driver,acpi_acad_devclass,0,0); + +static int +acpi_acad_ioctl(u_long cmd, caddr_t addr, void *arg) +{ + device_t dev; + struct acpi_acad_softc *sc; + + dev = (device_t)arg; + if ((sc = device_get_softc(dev)) == NULL) { + return(ENXIO); + } + + /* + * No security check required: information retrieval only. If + * new functions are added here, a check might be required. + */ + + switch (cmd) { + case ACPIIO_ACAD_GET_STATUS: + acpi_acad_get_status(dev); + *(int *)addr = sc->status; + break; + } + + return(0); +} + +static int +acpi_acad_sysctl(SYSCTL_HANDLER_ARGS) +{ + int val; + int error; + + if (acpi_acad_get_acline(&val)) { + return (ENXIO); + } + + val = *(u_int *)oidp->oid_arg1; + error = sysctl_handle_int(oidp, &val, 0, req); + return (error); +} + +static void +acpi_acad_init_acline(void *arg) +{ + int retry; + int status; + device_t dev = (device_t)arg; + struct acpi_acad_softc *sc = device_get_softc(dev); +#define ACPI_ACAD_RETRY_MAX 6 + + if (sc->initializing) { + return; + } + + sc->initializing = 1; + + ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), + "acline initialization start\n"); + + status = 0; + for (retry = 0; retry < ACPI_ACAD_RETRY_MAX; retry++, AcpiOsSleep(10, 0)) { + acpi_acad_get_status(dev); + if (status != sc->status) + break; + } + + ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), + "acline initialization done, tried %d times\n", retry+1); + + sc->initializing = 0; +} + +/* + * Public interfaces. + */ + +int +acpi_acad_get_acline(int *status) +{ + device_t dev; + struct acpi_acad_softc *sc; + + if ((dev = devclass_get_device(acpi_acad_devclass, 0)) == NULL) { + return (ENXIO); + } + + if ((sc = device_get_softc(dev)) == NULL) { + return (ENXIO); + } + + acpi_acad_get_status(dev); + *status = sc->status; + + return (0); +} + diff --git a/sys/dev/acpica/acpi_battery.c b/sys/dev/acpica/acpi_battery.c new file mode 100644 index 0000000..e26aee1 --- /dev/null +++ b/sys/dev/acpica/acpi_battery.c @@ -0,0 +1,266 @@ +/*- + * Copyright (c) 2000 Mitsuru IWASAKI <iwasaki@jp.freebsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_acpi.h" /* XXX trim includes */ +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/proc.h> +#include <sys/malloc.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/ioccom.h> +#include <sys/reboot.h> +#include <sys/sysctl.h> +#include <sys/ctype.h> + +#include <machine/clock.h> + +#include <machine/resource.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> +#include <dev/acpica/acpiio.h> + +MALLOC_DEFINE(M_ACPIBATT, "acpibatt", "ACPI generic battery data"); + +/* + * ACPI Battery Abstruction Layer. + */ + +struct acpi_batteries { + TAILQ_ENTRY(acpi_batteries) link; + struct acpi_battdesc battdesc; +}; + +static TAILQ_HEAD(,acpi_batteries) acpi_batteries; +static int acpi_batteries_initted = 0; +static int acpi_batteries_units = 0; +static int acpi_battery_info_expire = 5; +static struct acpi_battinfo acpi_battery_battinfo; + +int +acpi_battery_get_units(void) +{ + + return (acpi_batteries_units); +} + +int +acpi_battery_get_battdesc(int logical_unit, struct acpi_battdesc *battdesc) +{ + int i; + struct acpi_batteries *bp; + + if (logical_unit < 0 || logical_unit >= acpi_batteries_units) { + return (ENXIO); + } + + i = 0; + TAILQ_FOREACH(bp, &acpi_batteries, link) { + if (logical_unit == i) { + battdesc->type = bp->battdesc.type; + battdesc->phys_unit = bp->battdesc.phys_unit; + return (0); + } + i++; + } + + return (ENXIO); +} + +int +acpi_battery_get_battinfo(int unit, struct acpi_battinfo *battinfo) +{ + int error; + struct acpi_battdesc battdesc; + + error = 0; + if (unit == -1) { + error = acpi_cmbat_get_battinfo(-1, battinfo); + goto out; + } else { + if ((error = acpi_battery_get_battdesc(unit, &battdesc)) != 0) { + goto out; + } + switch (battdesc.type) { + case ACPI_BATT_TYPE_CMBAT: + error = acpi_cmbat_get_battinfo(battdesc.phys_unit, + battinfo); + break; + default: + error = ENXIO; + break; + } + } +out: + return (error); +} + +int +acpi_battery_get_info_expire(void) +{ + + return (acpi_battery_info_expire); +} + +static int +acpi_battery_ioctl(u_long cmd, caddr_t addr, void *arg) +{ + int error; + int logical_unit; + union acpi_battery_ioctl_arg *ioctl_arg; + + ioctl_arg = (union acpi_battery_ioctl_arg *)addr; + error = 0; + + /* + * No security check required: information retrieval only. If + * new functions are added here, a check might be required. + */ + + switch (cmd) { + case ACPIIO_BATT_GET_UNITS: + *(int *)addr = acpi_battery_get_units(); + break; + + case ACPIIO_BATT_GET_BATTDESC: + logical_unit = ioctl_arg->unit; + error = acpi_battery_get_battdesc(logical_unit, &ioctl_arg->battdesc); + break; + + case ACPIIO_BATT_GET_BATTINFO: + logical_unit = ioctl_arg->unit; + error = acpi_battery_get_battinfo(logical_unit, + &ioctl_arg->battinfo); + break; + + default: + error = EINVAL; + break; + } + + return (error); +} + +static int +acpi_battery_sysctl(SYSCTL_HANDLER_ARGS) +{ + int val; + int error; + + acpi_battery_get_battinfo(-1, &acpi_battery_battinfo); + val = *(u_int *)oidp->oid_arg1; + error = sysctl_handle_int(oidp, &val, 0, req); + return (error); +} + +static int +acpi_battery_init(void) +{ + device_t dev; + struct acpi_softc *sc; + int error; + + if ((dev = devclass_get_device(devclass_find("acpi"), 0)) == NULL) { + return (ENXIO); + } + if ((sc = device_get_softc(dev)) == NULL) { + return (ENXIO); + } + + error = 0; + + TAILQ_INIT(&acpi_batteries); + acpi_batteries_initted = 1; + + if ((error = acpi_register_ioctl(ACPIIO_BATT_GET_UNITS, + acpi_battery_ioctl, NULL)) != 0) { + return (error); + } + if ((error = acpi_register_ioctl(ACPIIO_BATT_GET_BATTDESC, + acpi_battery_ioctl, NULL)) != 0) { + return (error); + } + if ((error = acpi_register_ioctl(ACPIIO_BATT_GET_BATTINFO, + acpi_battery_ioctl, NULL)) != 0) { + return (error); + } + + sysctl_ctx_init(&sc->acpi_battery_sysctl_ctx); + sc->acpi_battery_sysctl_tree = SYSCTL_ADD_NODE(&sc->acpi_battery_sysctl_ctx, + SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "battery", CTLFLAG_RD, 0, ""); + SYSCTL_ADD_PROC(&sc->acpi_battery_sysctl_ctx, + SYSCTL_CHILDREN(sc->acpi_battery_sysctl_tree), + OID_AUTO, "life", CTLTYPE_INT | CTLFLAG_RD, + &acpi_battery_battinfo.cap, 0, acpi_battery_sysctl, "I", ""); + SYSCTL_ADD_PROC(&sc->acpi_battery_sysctl_ctx, + SYSCTL_CHILDREN(sc->acpi_battery_sysctl_tree), + OID_AUTO, "time", CTLTYPE_INT | CTLFLAG_RD, + &acpi_battery_battinfo.min, 0, acpi_battery_sysctl, "I", ""); + SYSCTL_ADD_PROC(&sc->acpi_battery_sysctl_ctx, + SYSCTL_CHILDREN(sc->acpi_battery_sysctl_tree), + OID_AUTO, "state", CTLTYPE_INT | CTLFLAG_RD, + &acpi_battery_battinfo.state, 0, acpi_battery_sysctl, "I", ""); + SYSCTL_ADD_INT(&sc->acpi_battery_sysctl_ctx, + SYSCTL_CHILDREN(sc->acpi_battery_sysctl_tree), + OID_AUTO, "units", CTLFLAG_RD, &acpi_batteries_units, 0, ""); + SYSCTL_ADD_INT(&sc->acpi_battery_sysctl_ctx, + SYSCTL_CHILDREN(sc->acpi_battery_sysctl_tree), + OID_AUTO, "info_expire", CTLFLAG_RD | CTLFLAG_RW, + &acpi_battery_info_expire, 0, ""); + + return (error); +} + +int +acpi_battery_register(int type, int phys_unit) +{ + int error; + struct acpi_batteries *bp; + + error = 0; + if ((bp = malloc(sizeof(*bp), M_ACPIBATT, M_NOWAIT)) == NULL) { + return(ENOMEM); + } + + bp->battdesc.type = type; + bp->battdesc.phys_unit = phys_unit; + if (acpi_batteries_initted == 0) { + if ((error = acpi_battery_init()) != 0) { + free(bp, M_ACPIBATT); + return(error); + } + } + + TAILQ_INSERT_TAIL(&acpi_batteries, bp, link); + acpi_batteries_units++; + + return(0); +} diff --git a/sys/dev/acpica/acpi_button.c b/sys/dev/acpica/acpi_button.c new file mode 100644 index 0000000..a4bebc2 --- /dev/null +++ b/sys/dev/acpica/acpi_button.c @@ -0,0 +1,224 @@ +/*- + * Copyright (c) 2000 Mitsaru IWASAKI <iwasaki@jp.freebsd.org> + * Copyright (c) 2000 Michael Smith <msmith@freebsd.org> + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/bus.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_BUTTON +ACPI_MODULE_NAME("BUTTON") + +struct acpi_button_softc { + device_t button_dev; + ACPI_HANDLE button_handle; +#define ACPI_POWER_BUTTON 0 +#define ACPI_SLEEP_BUTTON 1 + boolean_t button_type; /* Power or Sleep Button */ +}; + +static int acpi_button_probe(device_t dev); +static int acpi_button_attach(device_t dev); +static int acpi_button_suspend(device_t dev); +static int acpi_button_resume(device_t dev); +static void acpi_button_notify_handler(ACPI_HANDLE h,UINT32 notify, void *context); +static void acpi_button_notify_pressed_for_sleep(void *arg); +static void acpi_button_notify_pressed_for_wakeup(void *arg); + +static device_method_t acpi_button_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_button_probe), + DEVMETHOD(device_attach, acpi_button_attach), + DEVMETHOD(device_suspend, acpi_button_suspend), + DEVMETHOD(device_resume, acpi_button_resume), + + {0, 0} +}; + +static driver_t acpi_button_driver = { + "acpi_button", + acpi_button_methods, + sizeof(struct acpi_button_softc), +}; + +static devclass_t acpi_button_devclass; +DRIVER_MODULE(acpi_button, acpi, acpi_button_driver, acpi_button_devclass, 0, 0); + +static int +acpi_button_probe(device_t dev) +{ + struct acpi_button_softc *sc; + + sc = device_get_softc(dev); + if (acpi_get_type(dev) == ACPI_TYPE_DEVICE) { + if (!acpi_disabled("button")) { + if (acpi_MatchHid(dev, "PNP0C0C")) { + device_set_desc(dev, "Power Button"); + sc->button_type = ACPI_POWER_BUTTON; + return(0); + } + if (acpi_MatchHid(dev, "PNP0C0E")) { + device_set_desc(dev, "Sleep Button"); + sc->button_type = ACPI_SLEEP_BUTTON; + return(0); + } + } + } + return(ENXIO); +} + +static int +acpi_button_attach(device_t dev) +{ + struct acpi_button_softc *sc; + ACPI_STATUS status; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = device_get_softc(dev); + sc->button_dev = dev; + sc->button_handle = acpi_get_handle(dev); + + if (ACPI_FAILURE(status = AcpiInstallNotifyHandler(sc->button_handle, ACPI_DEVICE_NOTIFY, + acpi_button_notify_handler, sc))) { + device_printf(sc->button_dev, "couldn't install Notify handler - %s\n", AcpiFormatException(status)); + return_VALUE(ENXIO); + } + acpi_device_enable_wake_capability(sc->button_handle, 1); + return_VALUE(0); +} + +static int +acpi_button_suspend(device_t dev) +{ + struct acpi_button_softc *sc; + + sc = device_get_softc(dev); + acpi_device_enable_wake_event(sc->button_handle); + return (0); +} + +static int +acpi_button_resume(device_t dev) +{ + + return (0); +} + +static void +acpi_button_notify_pressed_for_sleep(void *arg) +{ + struct acpi_button_softc *sc; + struct acpi_softc *acpi_sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = (struct acpi_button_softc *)arg; + acpi_sc = acpi_device_get_parent_softc(sc->button_dev); + if (acpi_sc == NULL) { + return_VOID; + } + + switch (sc->button_type) { + case ACPI_POWER_BUTTON: + ACPI_VPRINT(sc->button_dev, acpi_sc, "power button pressed\n"); + acpi_eventhandler_power_button_for_sleep((void *)acpi_sc); + break; + case ACPI_SLEEP_BUTTON: + ACPI_VPRINT(sc->button_dev, acpi_sc, "sleep button pressed\n"); + acpi_eventhandler_sleep_button_for_sleep((void *)acpi_sc); + break; + default: + break; /* unknown button type */ + } + return_VOID; +} + +static void +acpi_button_notify_pressed_for_wakeup(void *arg) +{ + struct acpi_button_softc *sc; + struct acpi_softc *acpi_sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = (struct acpi_button_softc *)arg; + acpi_sc = acpi_device_get_parent_softc(sc->button_dev); + if (acpi_sc == NULL) { + return_VOID; + } + + switch (sc->button_type) { + case ACPI_POWER_BUTTON: + ACPI_VPRINT(sc->button_dev, acpi_sc, "wakeup by power button\n"); + acpi_eventhandler_power_button_for_wakeup((void *)acpi_sc); + break; + case ACPI_SLEEP_BUTTON: + ACPI_VPRINT(sc->button_dev, acpi_sc, "wakeup by sleep button\n"); + acpi_eventhandler_sleep_button_for_wakeup((void *)acpi_sc); + break; + default: + break; /* unknown button type */ + } + return_VOID; +} + +/* XXX maybe not here */ +#define ACPI_NOTIFY_BUTTON_PRESSED_FOR_SLEEP 0x80 +#define ACPI_NOTIFY_BUTTON_PRESSED_FOR_WAKEUP 0x02 + +static void +acpi_button_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) +{ + struct acpi_button_softc *sc = (struct acpi_button_softc *)context; + + ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); + + switch (notify) { + case ACPI_NOTIFY_BUTTON_PRESSED_FOR_SLEEP: + AcpiOsQueueForExecution(OSD_PRIORITY_LO, acpi_button_notify_pressed_for_sleep, sc); + break; + case ACPI_NOTIFY_BUTTON_PRESSED_FOR_WAKEUP: + AcpiOsQueueForExecution(OSD_PRIORITY_LO, acpi_button_notify_pressed_for_wakeup, sc); + break; + default: + break; /* unknown notification value */ + } + return_VOID; +} + + diff --git a/sys/dev/acpica/acpi_cmbat.c b/sys/dev/acpica/acpi_cmbat.c new file mode 100644 index 0000000..1cfd584 --- /dev/null +++ b/sys/dev/acpica/acpi_cmbat.c @@ -0,0 +1,715 @@ +/*- + * Copyright (c) 2000 Munehiro Matsuda + * Copyright (c) 2000 Takanori Watanabe + * Copyright (c) 2000 Mitsuru IWASAKI <iwasaki@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/ioccom.h> +#include <sys/conf.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> +#include <sys/malloc.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> +#include <dev/acpica/acpiio.h> + +MALLOC_DEFINE(M_ACPICMBAT, "acpicmbat", "ACPI control method battery data"); + +#define CMBAT_POLLRATE (60 * hz) + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_BATTERY +ACPI_MODULE_NAME("BATTERY") + +#define ACPI_BATTERY_BST_CHANGE 0x80 +#define ACPI_BATTERY_BIF_CHANGE 0x81 + +#define PKG_GETINT(res, tmp, idx, dest, label) do { \ + tmp = &res->Package.Elements[idx]; \ + if (tmp == NULL) { \ + ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), \ + "%s: PKG_GETINT error, idx = %d\n.", __func__, idx); \ + goto label; \ + } \ + if (tmp->Type != ACPI_TYPE_INTEGER) \ + goto label; \ + dest = tmp->Integer.Value; \ +} while (0) + +#define PKG_GETSTR(res, tmp, idx, dest, size, label) do { \ + size_t length; \ + length = size; \ + tmp = &res->Package.Elements[idx]; \ + if (tmp == NULL) { \ + ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), \ + "%s: PKG_GETSTR error, idx = %d\n.", __func__, idx); \ + goto label; \ + } \ + bzero(dest, sizeof(dest)); \ + switch (tmp->Type) { \ + case ACPI_TYPE_STRING: \ + if (tmp->String.Length < length) { \ + length = tmp->String.Length; \ + } \ + strncpy(dest, tmp->String.Pointer, length); \ + break; \ + case ACPI_TYPE_BUFFER: \ + if (tmp->Buffer.Length < length) { \ + length = tmp->Buffer.Length; \ + } \ + strncpy(dest, tmp->Buffer.Pointer, length); \ + break; \ + default: \ + goto label; \ + } \ + dest[sizeof(dest)-1] = '\0'; \ +} while (0) + +struct acpi_cmbat_softc { + device_t dev; + + struct acpi_bif bif; + struct acpi_bst bst; + struct timespec bif_lastupdated; + struct timespec bst_lastupdated; + int bif_updating; + int bst_updating; + + int present; + int cap; + int min; + int full_charge_time; + int initializing; +}; + +static struct timespec acpi_cmbat_info_lastupdated; + +/* XXX: devclass_get_maxunit() don't give us the current allocated units... */ +static int acpi_cmbat_units = 0; + +static int acpi_cmbat_info_expired(struct timespec *); +static void acpi_cmbat_info_updated(struct timespec *); +static void acpi_cmbat_get_bst(void *); +static void acpi_cmbat_get_bif(void *); +static void acpi_cmbat_notify_handler(ACPI_HANDLE, UINT32, void *); +static int acpi_cmbat_probe(device_t); +static int acpi_cmbat_attach(device_t); +static int acpi_cmbat_resume(device_t); +static int acpi_cmbat_ioctl(u_long, caddr_t, void *); +static int acpi_cmbat_is_bst_valid(struct acpi_bst*); +static int acpi_cmbat_is_bif_valid(struct acpi_bif*); +static int acpi_cmbat_get_total_battinfo(struct acpi_battinfo *); +static void acpi_cmbat_init_battery(void *); + +static __inline int +acpi_cmbat_info_expired(struct timespec *lastupdated) +{ + struct timespec curtime; + + if (lastupdated == NULL) { + return (1); + } + + if (!timespecisset(lastupdated)) { + return (1); + } + + getnanotime(&curtime); + timespecsub(&curtime, lastupdated); + return ((curtime.tv_sec < 0 || curtime.tv_sec > acpi_battery_get_info_expire())); +} + + +static __inline void +acpi_cmbat_info_updated(struct timespec *lastupdated) +{ + + if (lastupdated != NULL) { + getnanotime(lastupdated); + } +} + +static void +acpi_cmbat_get_bst(void *context) +{ + device_t dev; + struct acpi_cmbat_softc *sc; + ACPI_STATUS as; + ACPI_OBJECT *res, *tmp; + ACPI_HANDLE h; + ACPI_BUFFER bst_buffer; + + dev = context; + sc = device_get_softc(dev); + h = acpi_get_handle(dev); + bst_buffer.Pointer = NULL; + + if (!acpi_cmbat_info_expired(&sc->bst_lastupdated)) { + return; + } + + if (sc->bst_updating) { + return; + } + sc->bst_updating = 1; + + bst_buffer.Length = ACPI_ALLOCATE_BUFFER; + if (ACPI_FAILURE(as = AcpiEvaluateObject(h, "_BST", NULL, &bst_buffer))) { + ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), + "error fetching current battery status -- %s\n", + AcpiFormatException(as)); + goto end; + } + + res = (ACPI_OBJECT *)bst_buffer.Pointer; + + if ((res == NULL) || (res->Type != ACPI_TYPE_PACKAGE) || (res->Package.Count != 4)) { + ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), + "battery status corrupted\n"); + goto end; + } + + PKG_GETINT(res, tmp, 0, sc->bst.state, end); + PKG_GETINT(res, tmp, 1, sc->bst.rate, end); + PKG_GETINT(res, tmp, 2, sc->bst.cap, end); + PKG_GETINT(res, tmp, 3, sc->bst.volt, end); + acpi_cmbat_info_updated(&sc->bst_lastupdated); +end: + if (bst_buffer.Pointer != NULL) + AcpiOsFree(bst_buffer.Pointer); + sc->bst_updating = 0; +} + +static void +acpi_cmbat_get_bif(void *context) +{ + device_t dev; + struct acpi_cmbat_softc *sc; + ACPI_STATUS as; + ACPI_OBJECT *res, *tmp; + ACPI_HANDLE h; + ACPI_BUFFER bif_buffer; + + dev = context; + sc = device_get_softc(dev); + h = acpi_get_handle(dev); + bif_buffer.Pointer = NULL; + + if (!acpi_cmbat_info_expired(&sc->bif_lastupdated)) { + return; + } + + if (sc->bif_updating) { + return; + } + sc->bif_updating = 1; + + bif_buffer.Length = ACPI_ALLOCATE_BUFFER; + if (ACPI_FAILURE(as = AcpiEvaluateObject(h, "_BIF", NULL, &bif_buffer))) { + ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), + "error fetching current battery info -- %s\n", + AcpiFormatException(as)); + goto end; + } + + res = (ACPI_OBJECT *)bif_buffer.Pointer; + + if ((res == NULL) || (res->Type != ACPI_TYPE_PACKAGE) || (res->Package.Count != 13)) { + ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), + "battery info corrupted\n"); + goto end; + } + + PKG_GETINT(res, tmp, 0, sc->bif.unit, end); + PKG_GETINT(res, tmp, 1, sc->bif.dcap, end); + PKG_GETINT(res, tmp, 2, sc->bif.lfcap, end); + PKG_GETINT(res, tmp, 3, sc->bif.btech, end); + PKG_GETINT(res, tmp, 4, sc->bif.dvol, end); + PKG_GETINT(res, tmp, 5, sc->bif.wcap, end); + PKG_GETINT(res, tmp, 6, sc->bif.lcap, end); + PKG_GETINT(res, tmp, 7, sc->bif.gra1, end); + PKG_GETINT(res, tmp, 8, sc->bif.gra2, end); + PKG_GETSTR(res, tmp, 9, sc->bif.model, ACPI_CMBAT_MAXSTRLEN, end); + PKG_GETSTR(res, tmp, 10, sc->bif.serial, ACPI_CMBAT_MAXSTRLEN, end); + PKG_GETSTR(res, tmp, 11, sc->bif.type, ACPI_CMBAT_MAXSTRLEN, end); + PKG_GETSTR(res, tmp, 12, sc->bif.oeminfo, ACPI_CMBAT_MAXSTRLEN, end); + acpi_cmbat_info_updated(&sc->bif_lastupdated); +end: + if (bif_buffer.Pointer != NULL) + AcpiOsFree(bif_buffer.Pointer); + sc->bif_updating = 0; +} + +static void +acpi_cmbat_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) +{ + device_t dev; + struct acpi_cmbat_softc *sc; + + dev = (device_t)context; + if ((sc = device_get_softc(dev)) == NULL) { + return; + } + + switch (notify) { + case ACPI_BATTERY_BST_CHANGE: + timespecclear(&sc->bst_lastupdated); + break; + case ACPI_BATTERY_BIF_CHANGE: + timespecclear(&sc->bif_lastupdated); + AcpiOsQueueForExecution(OSD_PRIORITY_LO, acpi_cmbat_get_bif, dev); + break; + default: + break; + } +} + +static int +acpi_cmbat_probe(device_t dev) +{ + + if ((acpi_get_type(dev) == ACPI_TYPE_DEVICE) && + !acpi_disabled("cmbat") && + acpi_MatchHid(dev, "PNP0C0A")) { + /* + * Set device description. + */ + device_set_desc(dev, "Control method Battery"); + return (0); + } + return (ENXIO); +} + +static int +acpi_cmbat_attach(device_t dev) +{ + int error; + ACPI_HANDLE handle; + struct acpi_cmbat_softc *sc; + + if ((sc = device_get_softc(dev)) == NULL) { + return (ENXIO); + } + + handle = acpi_get_handle(dev); + + AcpiInstallNotifyHandler(handle, ACPI_DEVICE_NOTIFY, + acpi_cmbat_notify_handler, dev); + + sc->bif_updating = sc->bst_updating = 0; + sc->dev = dev; + + timespecclear(&sc->bif_lastupdated); + timespecclear(&sc->bst_lastupdated); + + if (acpi_cmbat_units == 0) { + if ((error = acpi_register_ioctl(ACPIIO_CMBAT_GET_BIF, + acpi_cmbat_ioctl, NULL)) != 0) { + return (error); + } + if ((error = acpi_register_ioctl(ACPIIO_CMBAT_GET_BST, + acpi_cmbat_ioctl, NULL)) != 0) { + return (error); + } + } + + if ((error = acpi_battery_register(ACPI_BATT_TYPE_CMBAT, + acpi_cmbat_units)) != 0) { + return (error); + } + + acpi_cmbat_units++; + timespecclear(&acpi_cmbat_info_lastupdated); + sc->initializing = 0; + + AcpiOsQueueForExecution(OSD_PRIORITY_LO, acpi_cmbat_init_battery, dev); + return (0); +} + +static int +acpi_cmbat_resume(device_t dev) +{ + + AcpiOsQueueForExecution(OSD_PRIORITY_LO, acpi_cmbat_init_battery, dev); + return (0); +} + +static device_method_t acpi_cmbat_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_cmbat_probe), + DEVMETHOD(device_attach, acpi_cmbat_attach), + DEVMETHOD(device_resume, acpi_cmbat_resume), + + {0, 0} +}; + +static driver_t acpi_cmbat_driver = { + "acpi_cmbat", + acpi_cmbat_methods, + sizeof(struct acpi_cmbat_softc), +}; + +static devclass_t acpi_cmbat_devclass; +DRIVER_MODULE(acpi_cmbat, acpi, acpi_cmbat_driver, acpi_cmbat_devclass, 0, 0); + +static int +acpi_cmbat_ioctl(u_long cmd, caddr_t addr, void *arg) +{ + device_t dev; + union acpi_battery_ioctl_arg *ioctl_arg; + struct acpi_cmbat_softc *sc; + struct acpi_bif *bifp; + struct acpi_bst *bstp; + + ioctl_arg = (union acpi_battery_ioctl_arg *)addr; + if ((dev = devclass_get_device(acpi_cmbat_devclass, + ioctl_arg->unit)) == NULL) { + return (ENXIO); + } + + if ((sc = device_get_softc(dev)) == NULL) { + return (ENXIO); + } + + /* + * No security check required: information retrieval only. If + * new functions are added here, a check might be required. + */ + + switch (cmd) { + case ACPIIO_CMBAT_GET_BIF: + acpi_cmbat_get_bif(dev); + bifp = &ioctl_arg->bif; + bifp->unit = sc->bif.unit; + bifp->dcap = sc->bif.dcap; + bifp->lfcap = sc->bif.lfcap; + bifp->btech = sc->bif.btech; + bifp->dvol = sc->bif.dvol; + bifp->wcap = sc->bif.wcap; + bifp->lcap = sc->bif.lcap; + bifp->gra1 = sc->bif.gra1; + bifp->gra2 = sc->bif.gra2; + strncpy(bifp->model, sc->bif.model, sizeof(sc->bif.model)); + strncpy(bifp->serial, sc->bif.serial, sizeof(sc->bif.serial)); + strncpy(bifp->type, sc->bif.type, sizeof(sc->bif.type)); + strncpy(bifp->oeminfo, sc->bif.oeminfo, sizeof(sc->bif.oeminfo)); + break; + + case ACPIIO_CMBAT_GET_BST: + bstp = &ioctl_arg->bst; + if (acpi_BatteryIsPresent(dev)) { + acpi_cmbat_get_bst(dev); + bstp->state = sc->bst.state; + bstp->rate = sc->bst.rate; + bstp->cap = sc->bst.cap; + bstp->volt = sc->bst.volt; + } else + bstp->state = ACPI_BATT_STAT_NOT_PRESENT; + break; + } + + return (0); +} + +static __inline int +acpi_cmbat_is_bst_valid(struct acpi_bst *bst) +{ + if (bst->state >= ACPI_BATT_STAT_MAX || + bst->cap == 0xffffffff || + bst->volt == 0xffffffff) { + return (0); + } + + return (1); +} + +static __inline int +acpi_cmbat_is_bif_valid(struct acpi_bif *bif) +{ + if (bif->lfcap == 0) { + return (0); + } + + return (1); +} + +static int +acpi_cmbat_get_total_battinfo(struct acpi_battinfo *battinfo) +{ + int i; + int error; + int batt_stat; + int valid_rate, valid_units; + int cap, min; + int total_cap, total_min, total_full; + device_t dev; + struct acpi_cmbat_softc *sc; + static int bat_units = 0; + static struct acpi_cmbat_softc **bat = NULL; + + cap = min = -1; + batt_stat = ACPI_BATT_STAT_NOT_PRESENT; + error = 0; + + /* Allocate array of softc pointers */ + if (bat_units != acpi_cmbat_units) { + if (bat != NULL) { + free(bat, M_ACPICMBAT); + bat = NULL; + } + bat_units = 0; + } + if (bat == NULL) { + bat_units = acpi_cmbat_units; + bat = malloc(sizeof(struct acpi_cmbat_softc *) * bat_units, + M_ACPICMBAT, M_NOWAIT); + if (bat == NULL) { + error = ENOMEM; + goto out; + } + + /* Collect softc pointers */ + for (i = 0; i < acpi_cmbat_units; i++) { + if ((dev = devclass_get_device(acpi_cmbat_devclass, i)) == NULL) { + error = ENXIO; + goto out; + } + + if ((sc = device_get_softc(dev)) == NULL) { + error = ENXIO; + goto out; + } + + bat[i] = sc; + } + } + + /* Get battery status, valid rate and valid units */ + batt_stat = valid_rate = valid_units = 0; + for (i = 0; i < acpi_cmbat_units; i++) { + bat[i]->present = acpi_BatteryIsPresent(bat[i]->dev); + if (!bat[i]->present) + continue; + + acpi_cmbat_get_bst(bat[i]->dev); + + /* If battey not installed, we get strange values */ + if (!acpi_cmbat_is_bst_valid(&(bat[i]->bst)) || + !acpi_cmbat_is_bif_valid(&(bat[i]->bif))) { + bat[i]->present = 0; + continue; + } + + valid_units++; + + bat[i]->cap = 100 * bat[i]->bst.cap / bat[i]->bif.lfcap; + + batt_stat |= bat[i]->bst.state; + + if (bat[i]->bst.rate > 0) { + /* + * XXX Hack to calculate total battery time. + * Systems with 2 or more battries, they may get used + * one by one, thus bst.rate is set only to the one + * in use. For remaining batteries bst.rate = 0, which + * makes it impossible to calculate remaining time. + * Some other systems may need sum of bst.rate in + * dis-charging state. + * There for we sum up the bst.rate that is valid + * (in dis-charging state), and use the sum to + * calcutate remaining batteries' time. + */ + if (bat[i]->bst.state & ACPI_BATT_STAT_DISCHARG) { + valid_rate += bat[i]->bst.rate; + } + } + } + + /* Calculate total battery capacity and time */ + total_cap = total_min = total_full = 0; + for (i = 0; i < acpi_cmbat_units; i++) { + if (!bat[i]->present) { + continue; + } + + if (valid_rate > 0) { + /* Use the sum of bst.rate */ + bat[i]->min = 60 * bat[i]->bst.cap / valid_rate; + } else if (bat[i]->full_charge_time > 0) { + bat[i]->min = (bat[i]->full_charge_time * bat[i]->cap) / 100; + } else { + /* Couldn't find valid rate and full battery time */ + bat[i]->min = 0; + } + total_min += bat[i]->min; + total_cap += bat[i]->cap; + total_full += bat[i]->full_charge_time; + } + + /* Battery life */ + if (valid_units == 0) { + cap = -1; + batt_stat = ACPI_BATT_STAT_NOT_PRESENT; + } else { + cap = total_cap / valid_units; + } + + /* Battery time */ + if (valid_units == 0) { + min = -1; + } else if (valid_rate == 0 || (batt_stat & ACPI_BATT_STAT_CHARGING)) { + if (total_full == 0) { + min = -1; + } else { + min = (total_full * cap) / 100; + } + } else { + min = total_min; + } + + acpi_cmbat_info_updated(&acpi_cmbat_info_lastupdated); +out: + battinfo->cap = cap; + battinfo->min = min; + battinfo->state = batt_stat; + + return (error); +} + +static void +acpi_cmbat_init_battery(void *arg) +{ + int retry; + device_t dev = (device_t)arg; + struct acpi_cmbat_softc *sc = device_get_softc(dev); +#define ACPI_CMBAT_RETRY_MAX 6 + + if (sc->initializing) { + return; + } + + sc->initializing = 1; + + ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), + "battery initialization start\n"); + + for (retry = 0; retry < ACPI_CMBAT_RETRY_MAX; retry++, AcpiOsSleep(10, 0)) { + sc->present = acpi_BatteryIsPresent(dev); + if (!sc->present) { + continue; + } + + timespecclear(&sc->bst_lastupdated); + timespecclear(&sc->bif_lastupdated); + + acpi_cmbat_get_bst(dev); + + if (!acpi_cmbat_is_bst_valid(&sc->bst)) { + continue; + } + + acpi_cmbat_get_bif(dev); + + if (!acpi_cmbat_is_bif_valid(&sc->bif)) { + continue; + } + + break; + } + + if (retry == ACPI_CMBAT_RETRY_MAX) + ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), + "battery initialization failed, giving up\n"); + else + ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), + "battery initialization done, tried %d times\n", + retry+1); + + sc->initializing = 0; +} + +/* + * Public interfaces. + */ + +int +acpi_cmbat_get_battinfo(int unit, struct acpi_battinfo *battinfo) +{ + int error; + device_t dev; + struct acpi_cmbat_softc *sc; + + if (unit == -1) { + return (acpi_cmbat_get_total_battinfo(battinfo)); + } + + if (acpi_cmbat_info_expired(&acpi_cmbat_info_lastupdated)) { + error = acpi_cmbat_get_total_battinfo(battinfo); + if (error) { + goto out; + } + } + + error = 0; + if (unit >= acpi_cmbat_units) { + error = ENXIO; + goto out; + } + + if ((dev = devclass_get_device(acpi_cmbat_devclass, unit)) == NULL) { + error = ENXIO; + goto out; + } + + if ((sc = device_get_softc(dev)) == NULL) { + error = ENXIO; + goto out; + } + + if (!sc->present) { + battinfo->cap = -1; + battinfo->min = -1; + battinfo->state = ACPI_BATT_STAT_NOT_PRESENT; + } else { + battinfo->cap = sc->cap; + battinfo->min = sc->min; + battinfo->state = sc->bst.state; + } +out: + return (error); +} + diff --git a/sys/dev/acpica/acpi_cpu.c b/sys/dev/acpica/acpi_cpu.c new file mode 100644 index 0000000..b5b04fd --- /dev/null +++ b/sys/dev/acpica/acpi_cpu.c @@ -0,0 +1,415 @@ +/*- + * Copyright (c) 2001 Michael Smith + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/power.h> + +#include <machine/bus_pio.h> +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> + +/* + * Support for ACPI Processor devices. + * + * Note that this only provides ACPI 1.0 support (with the exception of the + * PSTATE_CNT field). 2.0 support will involve implementing _PTC, _PCT, + * _PSS and _PPC. + */ + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_PROCESSOR +ACPI_MODULE_NAME("PROCESSOR") + +struct acpi_cpu_softc { + device_t cpu_dev; + ACPI_HANDLE cpu_handle; + + u_int32_t cpu_id; + + /* CPU throttling control register */ + struct resource *cpu_p_blk; +#define CPU_GET_P_CNT(sc) (bus_space_read_4(rman_get_bustag((sc)->cpu_p_blk), \ + rman_get_bushandle((sc)->cpu_p_blk), \ + 0)) +#define CPU_SET_P_CNT(sc, val) (bus_space_write_4(rman_get_bustag((sc)->cpu_p_blk), \ + rman_get_bushandle((sc)->cpu_p_blk), \ + 0, (val))) +#define CPU_P_CNT_THT_EN (1<<4) +}; + +/* + * Speeds are stored in counts, from 1 - CPU_MAX_SPEED, and + * reported to the user in tenths of a percent. + */ +static u_int32_t cpu_duty_offset; +static u_int32_t cpu_duty_width; +#define CPU_MAX_SPEED (1 << cpu_duty_width) +#define CPU_SPEED_PERCENT(x) ((1000 * (x)) / CPU_MAX_SPEED) +#define CPU_SPEED_PRINTABLE(x) (CPU_SPEED_PERCENT(x) / 10),(CPU_SPEED_PERCENT(x) % 10) + +static u_int32_t cpu_smi_cmd; /* should be a generic way to do this */ +static u_int8_t cpu_pstate_cnt; + +static u_int32_t cpu_current_state; +static u_int32_t cpu_performance_state; +static u_int32_t cpu_economy_state; +static u_int32_t cpu_max_state; + +static device_t *cpu_devices; +static int cpu_ndevices; + +static struct sysctl_ctx_list acpi_cpu_sysctl_ctx; +static struct sysctl_oid *acpi_cpu_sysctl_tree; + +static int acpi_cpu_probe(device_t dev); +static int acpi_cpu_attach(device_t dev); +static void acpi_cpu_init_throttling(void *arg); +static void acpi_cpu_set_speed(u_int32_t speed); +static void acpi_cpu_power_profile(void *arg); +static int acpi_cpu_speed_sysctl(SYSCTL_HANDLER_ARGS); + +static device_method_t acpi_cpu_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_cpu_probe), + DEVMETHOD(device_attach, acpi_cpu_attach), + + {0, 0} +}; + +static driver_t acpi_cpu_driver = { + "acpi_cpu", + acpi_cpu_methods, + sizeof(struct acpi_cpu_softc), +}; + +static devclass_t acpi_cpu_devclass; +DRIVER_MODULE(acpi_cpu, acpi, acpi_cpu_driver, acpi_cpu_devclass, 0, 0); + +static int +acpi_cpu_probe(device_t dev) +{ + if (!acpi_disabled("cpu") && + (acpi_get_type(dev) == ACPI_TYPE_PROCESSOR)) { + device_set_desc(dev, "CPU"); /* XXX get more verbose description? */ + return(0); + } + return(ENXIO); +} + +static int +acpi_cpu_attach(device_t dev) +{ + struct acpi_cpu_softc *sc; + struct acpi_softc *acpi_sc; + ACPI_OBJECT processor; + ACPI_BUFFER buf; + ACPI_STATUS status; + u_int32_t p_blk; + u_int32_t p_blk_length; + u_int32_t duty_end; + int rid; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + ACPI_ASSERTLOCK; + + sc = device_get_softc(dev); + sc->cpu_dev = dev; + sc->cpu_handle = acpi_get_handle(dev); + + /* + * Get global parameters from the FADT. + */ + if (device_get_unit(sc->cpu_dev) == 0) { + cpu_duty_offset = AcpiGbl_FADT->DutyOffset; + cpu_duty_width = AcpiGbl_FADT->DutyWidth; + cpu_smi_cmd = AcpiGbl_FADT->SmiCmd; + cpu_pstate_cnt = AcpiGbl_FADT->PstateCnt; + + /* validate the offset/width */ + if (cpu_duty_width > 0) { + duty_end = cpu_duty_offset + cpu_duty_width - 1; + /* check that it fits */ + if (duty_end > 31) { + printf("acpi_cpu: CLK_VAL field overflows P_CNT register\n"); + cpu_duty_width = 0; + } + /* check for overlap with the THT_EN bit */ + if ((cpu_duty_offset <= 4) && (duty_end >= 4)) { + printf("acpi_cpu: CLK_VAL field overlaps THT_EN bit\n"); + cpu_duty_width = 0; + } + } + + /* + * Start the throttling process once the probe phase completes, if we think that + * it's going to be useful. If the duty width value is zero, there are no significant + * bits in the register and thus no throttled states. + */ + if (cpu_duty_width > 0) { + AcpiOsQueueForExecution(OSD_PRIORITY_LO, acpi_cpu_init_throttling, NULL); + + acpi_sc = acpi_device_get_parent_softc(dev); + sysctl_ctx_init(&acpi_cpu_sysctl_ctx); + acpi_cpu_sysctl_tree = SYSCTL_ADD_NODE(&acpi_cpu_sysctl_ctx, + SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), + OID_AUTO, "cpu", CTLFLAG_RD, 0, ""); + + SYSCTL_ADD_INT(&acpi_cpu_sysctl_ctx, SYSCTL_CHILDREN(acpi_cpu_sysctl_tree), + OID_AUTO, "max_speed", CTLFLAG_RD, + &cpu_max_state, 0, "maximum CPU speed"); + SYSCTL_ADD_INT(&acpi_cpu_sysctl_ctx, SYSCTL_CHILDREN(acpi_cpu_sysctl_tree), + OID_AUTO, "current_speed", CTLFLAG_RD, + &cpu_current_state, 0, "current CPU speed"); + SYSCTL_ADD_PROC(&acpi_cpu_sysctl_ctx, SYSCTL_CHILDREN(acpi_cpu_sysctl_tree), + OID_AUTO, "performance_speed", CTLTYPE_INT | CTLFLAG_RW, + &cpu_performance_state, 0, acpi_cpu_speed_sysctl, "I", ""); + SYSCTL_ADD_PROC(&acpi_cpu_sysctl_ctx, SYSCTL_CHILDREN(acpi_cpu_sysctl_tree), + OID_AUTO, "economy_speed", CTLTYPE_INT | CTLFLAG_RW, + &cpu_economy_state, 0, acpi_cpu_speed_sysctl, "I", ""); + } + } + + /* + * Get the processor object. + */ + buf.Pointer = &processor; + buf.Length = sizeof(processor); + if (ACPI_FAILURE(status = AcpiEvaluateObject(sc->cpu_handle, NULL, NULL, &buf))) { + device_printf(sc->cpu_dev, "couldn't get Processor object - %s\n", AcpiFormatException(status)); + return_VALUE(ENXIO); + } + if (processor.Type != ACPI_TYPE_PROCESSOR) { + device_printf(sc->cpu_dev, "Processor object has bad type %d\n", processor.Type); + return_VALUE(ENXIO); + } + sc->cpu_id = processor.Processor.ProcId; + + /* + * If it looks like we support throttling, find this CPU's P_BLK. + * + * Note that some systems seem to duplicate the P_BLK pointer across + * multiple CPUs, so not getting the resource is not fatal. + * + * XXX should support _PTC here as well, once we work out how to parse it. + * + * XXX is it valid to assume that the P_BLK must be 6 bytes long? + */ + if (cpu_duty_width > 0) { + p_blk = processor.Processor.PblkAddress; + p_blk_length = processor.Processor.PblkLength; + + /* allocate bus space if possible */ + if ((p_blk > 0) && (p_blk_length == 6)) { + rid = 0; + bus_set_resource(sc->cpu_dev, SYS_RES_IOPORT, rid, p_blk, p_blk_length); + sc->cpu_p_blk = bus_alloc_resource(sc->cpu_dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, + RF_ACTIVE); + + ACPI_DEBUG_PRINT((ACPI_DB_IO, "acpi_cpu%d: throttling with P_BLK at 0x%x/%d%s\n", + device_get_unit(sc->cpu_dev), p_blk, p_blk_length, + sc->cpu_p_blk ? "" : " (shadowed)")); + } + } + return_VALUE(0); +} + +/* + * Call this *after* all CPUs have been attached. + * + * Takes the ACPI lock to avoid fighting anyone over the SMI command + * port. Could probably lock less code. + */ +static void +acpi_cpu_init_throttling(void *arg) +{ + int cpu_temp_speed; + ACPI_LOCK_DECL; + + ACPI_LOCK; + + /* get set of CPU devices */ + devclass_get_devices(acpi_cpu_devclass, &cpu_devices, &cpu_ndevices); + + /* initialise throttling states */ + cpu_max_state = CPU_MAX_SPEED; + cpu_performance_state = cpu_max_state; + cpu_economy_state = cpu_performance_state / 2; + if (cpu_economy_state == 0) /* 0 is 'reserved' */ + cpu_economy_state++; + if (TUNABLE_INT_FETCH("hw.acpi.cpu.performance_speed", + &cpu_temp_speed) && cpu_temp_speed > 0 && + cpu_temp_speed <= cpu_max_state) + cpu_performance_state = cpu_temp_speed; + if (TUNABLE_INT_FETCH("hw.acpi.cpu.economy_speed", + &cpu_temp_speed) && cpu_temp_speed > 0 && + cpu_temp_speed <= cpu_max_state) + cpu_economy_state = cpu_temp_speed; + + /* register performance profile change handler */ + EVENTHANDLER_REGISTER(power_profile_change, acpi_cpu_power_profile, NULL, 0); + + /* if ACPI 2.0+, signal platform that we are taking over throttling */ + if (cpu_pstate_cnt != 0) { + /* XXX should be a generic interface for this */ + AcpiOsWritePort(cpu_smi_cmd, cpu_pstate_cnt, 8); + } + + ACPI_UNLOCK; + + /* set initial speed */ + acpi_cpu_power_profile(NULL); + + printf("acpi_cpu: throttling enabled, %d steps (100%% to %d.%d%%), " + "currently %d.%d%%\n", CPU_MAX_SPEED, CPU_SPEED_PRINTABLE(1), + CPU_SPEED_PRINTABLE(cpu_current_state)); +} + +/* + * Set CPUs to the new state. + * + * Must be called with the ACPI lock held. + */ +static void +acpi_cpu_set_speed(u_int32_t speed) +{ + struct acpi_cpu_softc *sc; + int i; + u_int32_t p_cnt, clk_val; + + ACPI_ASSERTLOCK; + + /* iterate over processors */ + for (i = 0; i < cpu_ndevices; i++) { + sc = device_get_softc(cpu_devices[i]); + if (sc->cpu_p_blk == NULL) + continue; + + /* get the current P_CNT value and disable throttling */ + p_cnt = CPU_GET_P_CNT(sc); + p_cnt &= ~CPU_P_CNT_THT_EN; + CPU_SET_P_CNT(sc, p_cnt); + + /* if we're at maximum speed, that's all */ + if (speed < CPU_MAX_SPEED) { + + /* mask the old CLK_VAL off and or-in the new value */ + clk_val = CPU_MAX_SPEED << cpu_duty_offset; + p_cnt &= ~clk_val; + p_cnt |= (speed << cpu_duty_offset); + + /* write the new P_CNT value and then enable throttling */ + CPU_SET_P_CNT(sc, p_cnt); + p_cnt |= CPU_P_CNT_THT_EN; + CPU_SET_P_CNT(sc, p_cnt); + } + ACPI_VPRINT(sc->cpu_dev, acpi_device_get_parent_softc(sc->cpu_dev), + "set speed to %d.%d%%\n", CPU_SPEED_PRINTABLE(speed)); + } + cpu_current_state = speed; +} + +/* + * Power profile change hook. + * + * Uses the ACPI lock to avoid reentrancy. + */ +static void +acpi_cpu_power_profile(void *arg) +{ + int state; + u_int32_t new; + ACPI_LOCK_DECL; + + state = power_profile_get_state(); + if (state != POWER_PROFILE_PERFORMANCE && + state != POWER_PROFILE_ECONOMY) { + return; + } + + ACPI_LOCK; + + switch (state) { + case POWER_PROFILE_PERFORMANCE: + new = cpu_performance_state; + break; + case POWER_PROFILE_ECONOMY: + new = cpu_economy_state; + break; + default: + new = cpu_current_state; + break; + } + + if (cpu_current_state != new) + acpi_cpu_set_speed(new); + + ACPI_UNLOCK; +} + +/* + * Handle changes in the performance/ecomony CPU settings. + * + * Does not need the ACPI lock (although setting *argp should + * probably be atomic). + */ +static int +acpi_cpu_speed_sysctl(SYSCTL_HANDLER_ARGS) +{ + u_int32_t *argp; + u_int32_t arg; + int error; + + argp = (u_int32_t *)oidp->oid_arg1; + arg = *argp; + error = sysctl_handle_int(oidp, &arg, 0, req); + + /* error or no new value */ + if ((error != 0) || (req->newptr == NULL)) + return(error); + + /* range check */ + if ((arg < 1) || (arg > cpu_max_state)) + return(EINVAL); + + /* set new value and possibly switch */ + *argp = arg; + acpi_cpu_power_profile(NULL); + + return(0); +} diff --git a/sys/dev/acpica/acpi_ec.c b/sys/dev/acpica/acpi_ec.c new file mode 100644 index 0000000..680d3f4 --- /dev/null +++ b/sys/dev/acpica/acpi_ec.c @@ -0,0 +1,848 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +/****************************************************************************** + * + * 1. Copyright Notice + * + * Some or all of this work - Copyright (c) 1999, Intel Corp. All rights + * reserved. + * + * 2. License + * + * 2.1. This is your license from Intel Corp. under its intellectual property + * rights. You may have additional license terms from the party that provided + * you this software, covering your right to use that party's intellectual + * property rights. + * + * 2.2. Intel grants, free of charge, to any person ("Licensee") obtaining a + * copy of the source code appearing in this file ("Covered Code") an + * irrevocable, perpetual, worldwide license under Intel's copyrights in the + * base code distributed originally by Intel ("Original Intel Code") to copy, + * make derivatives, distribute, use and display any portion of the Covered + * Code in any form, with the right to sublicense such rights; and + * + * 2.3. Intel grants Licensee a non-exclusive and non-transferable patent + * license (with the right to sublicense), under only those claims of Intel + * patents that are infringed by the Original Intel Code, to make, use, sell, + * offer to sell, and import the Covered Code and derivative works thereof + * solely to the minimum extent necessary to exercise the above copyright + * license, and in no event shall the patent license extend to any additions + * to or modifications of the Original Intel Code. No other license or right + * is granted directly or by implication, estoppel or otherwise; + * + * The above copyright and patent license is granted only if the following + * conditions are met: + * + * 3. Conditions + * + * 3.1. Redistribution of Source with Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification with rights to further distribute source must include + * the above Copyright Notice, the above License, this list of Conditions, + * and the following Disclaimer and Export Compliance provision. In addition, + * Licensee must cause all Covered Code to which Licensee contributes to + * contain a file documenting the changes Licensee made to create that Covered + * Code and the date of any change. Licensee must include in that file the + * documentation of any changes made by any predecessor Licensee. Licensee + * must include a prominent statement that the modification is derived, + * directly or indirectly, from Original Intel Code. + * + * 3.2. Redistribution of Source with no Rights to Further Distribute Source. + * Redistribution of source code of any substantial portion of the Covered + * Code or modification without rights to further distribute source must + * include the following Disclaimer and Export Compliance provision in the + * documentation and/or other materials provided with distribution. In + * addition, Licensee may not authorize further sublicense of source of any + * portion of the Covered Code, and must include terms to the effect that the + * license from Licensee to its licensee is limited to the intellectual + * property embodied in the software Licensee provides to its licensee, and + * not to intellectual property embodied in modifications its licensee may + * make. + * + * 3.3. Redistribution of Executable. Redistribution in executable form of any + * substantial portion of the Covered Code or modification must reproduce the + * above Copyright Notice, and the following Disclaimer and Export Compliance + * provision in the documentation and/or other materials provided with the + * distribution. + * + * 3.4. Intel retains all right, title, and interest in and to the Original + * Intel Code. + * + * 3.5. Neither the name Intel nor any other trademark owned or controlled by + * Intel shall be used in advertising or otherwise to promote the sale, use or + * other dealings in products derived from or relating to the Covered Code + * without prior written authorization from Intel. + * + * 4. Disclaimer and Export Compliance + * + * 4.1. INTEL MAKES NO WARRANTY OF ANY KIND REGARDING ANY SOFTWARE PROVIDED + * HERE. ANY SOFTWARE ORIGINATING FROM INTEL OR DERIVED FROM INTEL SOFTWARE + * IS PROVIDED "AS IS," AND INTEL WILL NOT PROVIDE ANY SUPPORT, ASSISTANCE, + * INSTALLATION, TRAINING OR OTHER SERVICES. INTEL WILL NOT PROVIDE ANY + * UPDATES, ENHANCEMENTS OR EXTENSIONS. INTEL SPECIFICALLY DISCLAIMS ANY + * IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGEMENT AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * 4.2. IN NO EVENT SHALL INTEL HAVE ANY LIABILITY TO LICENSEE, ITS LICENSEES + * OR ANY OTHER THIRD PARTY, FOR ANY LOST PROFITS, LOST DATA, LOSS OF USE OR + * COSTS OF PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, OR FOR ANY INDIRECT, + * SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THIS AGREEMENT, UNDER ANY + * CAUSE OF ACTION OR THEORY OF LIABILITY, AND IRRESPECTIVE OF WHETHER INTEL + * HAS ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES. THESE LIMITATIONS + * SHALL APPLY NOTWITHSTANDING THE FAILURE OF THE ESSENTIAL PURPOSE OF ANY + * LIMITED REMEDY. + * + * 4.3. Licensee shall not export, either directly or indirectly, any of this + * software or system incorporating such software without first obtaining any + * required license or other approval from the U. S. Department of Commerce or + * any other agency or department of the United States Government. In the + * event Licensee exports any such software from the United States or + * re-exports any such software from a foreign destination, Licensee shall + * ensure that the distribution and export/re-export of the software is in + * compliance with all laws, regulations, orders, or other restrictions of the + * U.S. Export Administration Regulations. Licensee agrees that neither it nor + * any of its subsidiaries will export/re-export any technical data, process, + * software, or service, directly or indirectly, to any country for which the + * United States government or any agency thereof requires an export license, + * other governmental approval, or letter of assurance, without first obtaining + * such license, approval or letter. + * + *****************************************************************************/ + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/bus.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_EC +ACPI_MODULE_NAME("EC") + +/* + * EC_COMMAND: + * ----------- + */ +typedef UINT8 EC_COMMAND; + +#define EC_COMMAND_UNKNOWN ((EC_COMMAND) 0x00) +#define EC_COMMAND_READ ((EC_COMMAND) 0x80) +#define EC_COMMAND_WRITE ((EC_COMMAND) 0x81) +#define EC_COMMAND_BURST_ENABLE ((EC_COMMAND) 0x82) +#define EC_COMMAND_BURST_DISABLE ((EC_COMMAND) 0x83) +#define EC_COMMAND_QUERY ((EC_COMMAND) 0x84) + +/* + * EC_STATUS: + * ---------- + * The encoding of the EC status register is illustrated below. + * Note that a set bit (1) indicates the property is TRUE + * (e.g. if bit 0 is set then the output buffer is full). + * +-+-+-+-+-+-+-+-+ + * |7|6|5|4|3|2|1|0| + * +-+-+-+-+-+-+-+-+ + * | | | | | | | | + * | | | | | | | +- Output Buffer Full? + * | | | | | | +--- Input Buffer Full? + * | | | | | +----- <reserved> + * | | | | +------- Data Register is Command Byte? + * | | | +--------- Burst Mode Enabled? + * | | +----------- SCI Event? + * | +------------- SMI Event? + * +--------------- <Reserved> + * + */ +typedef UINT8 EC_STATUS; + +#define EC_FLAG_OUTPUT_BUFFER ((EC_STATUS) 0x01) +#define EC_FLAG_INPUT_BUFFER ((EC_STATUS) 0x02) +#define EC_FLAG_BURST_MODE ((EC_STATUS) 0x10) +#define EC_FLAG_SCI ((EC_STATUS) 0x20) + +/* + * EC_EVENT: + * --------- + */ +typedef UINT8 EC_EVENT; + +#define EC_EVENT_UNKNOWN ((EC_EVENT) 0x00) +#define EC_EVENT_OUTPUT_BUFFER_FULL ((EC_EVENT) 0x01) +#define EC_EVENT_INPUT_BUFFER_EMPTY ((EC_EVENT) 0x02) +#define EC_EVENT_SCI ((EC_EVENT) 0x20) + +/* + * Register access primitives + */ +#define EC_GET_DATA(sc) \ + bus_space_read_1((sc)->ec_data_tag, (sc)->ec_data_handle, 0) + +#define EC_SET_DATA(sc, v) \ + bus_space_write_1((sc)->ec_data_tag, (sc)->ec_data_handle, 0, (v)) + +#define EC_GET_CSR(sc) \ + bus_space_read_1((sc)->ec_csr_tag, (sc)->ec_csr_handle, 0) + +#define EC_SET_CSR(sc, v) \ + bus_space_write_1((sc)->ec_csr_tag, (sc)->ec_csr_handle, 0, (v)) + +/* + * Driver softc. + */ +struct acpi_ec_softc { + device_t ec_dev; + ACPI_HANDLE ec_handle; + UINT32 ec_gpebit; + + int ec_data_rid; + struct resource *ec_data_res; + bus_space_tag_t ec_data_tag; + bus_space_handle_t ec_data_handle; + + int ec_csr_rid; + struct resource *ec_csr_res; + bus_space_tag_t ec_csr_tag; + bus_space_handle_t ec_csr_handle; + + int ec_locked; + int ec_lockhandle; + int ec_pendquery; + int ec_csrvalue; +}; + +static int acpi_ec_event_driven = 0; +TUNABLE_INT("hw.acpi.ec.event_driven", &acpi_ec_event_driven); + +#define EC_LOCK_TIMEOUT 1000 /* 1ms */ + +static __inline ACPI_STATUS +EcLock(struct acpi_ec_softc *sc) +{ + ACPI_STATUS status; + + /* XXX ACPI_WAIT_FOREVER is probably a bad idea, what is a better time? */ + if (ACPI_SUCCESS(status = AcpiAcquireGlobalLock(ACPI_WAIT_FOREVER, &sc->ec_lockhandle))) + (sc)->ec_locked = 1; + + return(status); +} + +static __inline void +EcUnlock(struct acpi_ec_softc *sc) +{ + (sc)->ec_locked = 0; + AcpiReleaseGlobalLock(sc->ec_lockhandle); +} + +static __inline int +EcIsLocked(struct acpi_ec_softc *sc) +{ + return((sc)->ec_locked != 0); +} + +typedef struct +{ + EC_COMMAND Command; + UINT8 Address; + UINT8 Data; +} EC_REQUEST; + +static void EcGpeHandler(void *Context); +static ACPI_STATUS EcSpaceSetup(ACPI_HANDLE Region, UINT32 Function, + void *Context, void **return_Context); +static ACPI_STATUS EcSpaceHandler(UINT32 Function, ACPI_PHYSICAL_ADDRESS Address, UINT32 width, ACPI_INTEGER *Value, + void *Context, void *RegionContext); + +static ACPI_STATUS EcWaitEvent(struct acpi_ec_softc *sc, EC_EVENT Event); +static ACPI_STATUS EcQuery(struct acpi_ec_softc *sc, UINT8 *Data); +static ACPI_STATUS EcTransaction(struct acpi_ec_softc *sc, EC_REQUEST *EcRequest); +static ACPI_STATUS EcRead(struct acpi_ec_softc *sc, UINT8 Address, UINT8 *Data); +static ACPI_STATUS EcWrite(struct acpi_ec_softc *sc, UINT8 Address, UINT8 *Data); + +static void acpi_ec_identify(driver_t driver, device_t bus); +static int acpi_ec_probe(device_t dev); +static int acpi_ec_attach(device_t dev); + +static device_method_t acpi_ec_methods[] = { + /* Device interface */ + DEVMETHOD(device_identify, acpi_ec_identify), + DEVMETHOD(device_probe, acpi_ec_probe), + DEVMETHOD(device_attach, acpi_ec_attach), + + {0, 0} +}; + +static driver_t acpi_ec_driver = { + "acpi_ec", + acpi_ec_methods, + sizeof(struct acpi_ec_softc), +}; + +static devclass_t acpi_ec_devclass; +DRIVER_MODULE(acpi_ec, acpi, acpi_ec_driver, acpi_ec_devclass, 0, 0); + +/* + * Look for an ECDT table and if we find one, set up a default EC + * space handler to catch possible attempts to access EC space before + * we have a real driver instance in place. + * We're not really an identify routine, but because we get called + * before most other things, this works out OK. + */ +static void +acpi_ec_identify(driver_t driver, device_t bus) +{ + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + /* XXX implement - need an ACPI 2.0 system to test this */ + + return_VOID; +} + +/* + * We could setup resources in the probe routine in order to have them printed + * when the device is attached. + */ +static int +acpi_ec_probe(device_t dev) +{ + + if ((acpi_get_type(dev) == ACPI_TYPE_DEVICE) && + !acpi_disabled("ec") && + acpi_MatchHid(dev, "PNP0C09")) { + + /* + * Set device description + */ + device_set_desc(dev, "embedded controller"); + + return(0); + } + return(ENXIO); +} + +static int +acpi_ec_attach(device_t dev) +{ + struct acpi_ec_softc *sc; + ACPI_STATUS Status; + int errval = 0; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + /* + * Fetch/initialise softc + */ + sc = device_get_softc(dev); + bzero(sc, sizeof(*sc)); + sc->ec_dev = dev; + sc->ec_handle = acpi_get_handle(dev); + + /* + * Attach bus resources + */ + sc->ec_data_rid = 0; + if ((sc->ec_data_res = bus_alloc_resource(sc->ec_dev, SYS_RES_IOPORT, &sc->ec_data_rid, + 0, ~0, 1, RF_ACTIVE)) == NULL) { + device_printf(dev, "can't allocate data port\n"); + errval = ENXIO; + goto out; + } + sc->ec_data_tag = rman_get_bustag(sc->ec_data_res); + sc->ec_data_handle = rman_get_bushandle(sc->ec_data_res); + + sc->ec_csr_rid = 1; + if ((sc->ec_csr_res = bus_alloc_resource(sc->ec_dev, SYS_RES_IOPORT, &sc->ec_csr_rid, + 0, ~0, 1, RF_ACTIVE)) == NULL) { + device_printf(dev, "can't allocate command/status port\n"); + errval = ENXIO; + goto out; + } + sc->ec_csr_tag = rman_get_bustag(sc->ec_csr_res); + sc->ec_csr_handle = rman_get_bushandle(sc->ec_csr_res); + + /* + * Install GPE handler + * + * Evaluate the _GPE method to find the GPE bit used by the EC to signal + * status (SCI). + */ + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "attaching GPE\n")); + if (ACPI_FAILURE(Status = acpi_EvaluateInteger(sc->ec_handle, "_GPE", &sc->ec_gpebit))) { + device_printf(dev, "can't evaluate _GPE - %s\n", AcpiFormatException(Status)); + errval =ENXIO; + goto out; + } + + /* + * Install a handler for this EC's GPE bit. Note that EC SCIs are + * treated as both edge- and level-triggered interrupts; in other words + * we clear the status bit immediately after getting an EC-SCI, then + * again after we're done processing the event. This guarantees that + * events we cause while performing a transaction (e.g. IBE/OBF) get + * cleared before re-enabling the GPE. + */ + if (ACPI_FAILURE(Status = AcpiInstallGpeHandler(sc->ec_gpebit, + ACPI_EVENT_LEVEL_TRIGGERED | + ACPI_EVENT_EDGE_TRIGGERED, + EcGpeHandler, sc))) { + device_printf(dev, "can't install GPE handler for %s - %s\n", + acpi_name(sc->ec_handle), AcpiFormatException(Status)); + errval = ENXIO; + goto out; + } + + /* + * Install address space handler + */ + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "attaching address space handler\n")); + if (ACPI_FAILURE(Status = AcpiInstallAddressSpaceHandler(sc->ec_handle, + ACPI_ADR_SPACE_EC, + EcSpaceHandler, + EcSpaceSetup, + sc))) { + device_printf(dev, "can't install address space handler for %s - %s\n", + acpi_name(sc->ec_handle), AcpiFormatException(Status)); + panic("very suck"); + errval = ENXIO; + goto out; + } + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "attach complete\n")); + return_VALUE(0); + out: + if(sc->ec_csr_res) + bus_release_resource(sc->ec_dev, SYS_RES_IOPORT, sc->ec_csr_rid, + sc->ec_csr_res); + if(sc->ec_data_res) + bus_release_resource(sc->ec_dev, SYS_RES_IOPORT, sc->ec_data_rid, + sc->ec_data_res); + return_VALUE(errval); +} + +static void +EcGpeQueryHandler(void *Context) +{ + struct acpi_ec_softc *sc = (struct acpi_ec_softc *)Context; + UINT8 Data; + ACPI_STATUS Status; + char qxx[5]; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + for (;;) { + + /* + * Check EC_SCI. + * + * Bail out if the EC_SCI bit of the status register is not set. + * Note that this function should only be called when + * this bit is set (polling is used to detect IBE/OBF events). + * + * It is safe to do this without locking the controller, as it's + * OK to call EcQuery when there's no data ready; in the worst + * case we should just find nothing waiting for us and bail. + */ + if (!(EC_GET_CSR(sc) & EC_EVENT_SCI)) + break; + + /* + * Find out why the EC is signalling us + */ + Status = EcQuery(sc, &Data); + + /* + * If we failed to get anything from the EC, give up + */ + if (ACPI_FAILURE(Status)) { + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "GPE query failed - %s\n", AcpiFormatException(Status)); + break; + } + + /* + * Evaluate _Qxx to respond to the controller. + */ + sprintf(qxx, "_Q%02x", Data); + strupr(qxx); + Status = AcpiEvaluateObject(sc->ec_handle, qxx, NULL, NULL); + /* + * Ignore spurious query requests. + */ + if (ACPI_FAILURE(Status) && (Data != 0 || Status != AE_NOT_FOUND)) { + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "evaluation of GPE query method %s failed - %s\n", + qxx, AcpiFormatException(Status)); + } + } + /* I know I request Level trigger cleanup */ + if (ACPI_FAILURE(AcpiClearEvent(sc->ec_gpebit, ACPI_EVENT_GPE))) + printf("EcGpeQueryHandler:ClearEvent Failed\n"); + if (ACPI_FAILURE(AcpiEnableEvent(sc->ec_gpebit, ACPI_EVENT_GPE, 0))) + printf("EcGpeQueryHandler:EnableEvent Failed\n"); + return_VOID; +} + +/* + * Handle a GPE sent to us. + */ +static void +EcGpeHandler(void *Context) +{ + struct acpi_ec_softc *sc = Context; + int csrvalue; + + /* + * If EC is locked, the intr must process EcRead/Write wait only. + * Query request must be pending. + */ + if (EcIsLocked(sc)){ + csrvalue = EC_GET_CSR(sc); + if (csrvalue & EC_EVENT_SCI) + sc->ec_pendquery = 1; + if ((csrvalue & EC_FLAG_OUTPUT_BUFFER) + || !(csrvalue & EC_FLAG_INPUT_BUFFER)) { + sc->ec_csrvalue = csrvalue; + wakeup((void *)&sc->ec_csrvalue); + } + }else{ + /* Queue GpeQuery Handler */ + if (ACPI_FAILURE(AcpiOsQueueForExecution(OSD_PRIORITY_HIGH, + EcGpeQueryHandler,Context))) { + printf("QueryHandler Queuing Failed\n"); + } + } + return; +} + +static ACPI_STATUS +EcSpaceSetup(ACPI_HANDLE Region, UINT32 Function, void *Context, void **RegionContext) +{ + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + /* + * Just pass the context through, there's nothing to do here. + */ + *RegionContext = Context; + + return_ACPI_STATUS(AE_OK); +} + +static ACPI_STATUS +EcSpaceHandler(UINT32 Function, ACPI_PHYSICAL_ADDRESS Address, UINT32 width, ACPI_INTEGER *Value, + void *Context, void *RegionContext) +{ + struct acpi_ec_softc *sc = (struct acpi_ec_softc *)Context; + ACPI_STATUS Status = AE_OK; + EC_REQUEST EcRequest; + int i; + + ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, (UINT32)Address); + + if ((Address > 0xFF) || (width % 8 != 0) || (Value == NULL) || (Context == NULL)) + return_ACPI_STATUS(AE_BAD_PARAMETER); + + switch (Function) { + case ACPI_READ: + EcRequest.Command = EC_COMMAND_READ; + EcRequest.Address = Address; + (*Value) = 0; + break; + + case ACPI_WRITE: + EcRequest.Command = EC_COMMAND_WRITE; + EcRequest.Address = Address; + break; + + default: + device_printf(sc->ec_dev, "invalid Address Space function %d\n", Function); + return_ACPI_STATUS(AE_BAD_PARAMETER); + } + + /* + * Perform the transaction. + */ + for (i = 0; i < width; i += 8) { + if (Function == ACPI_READ) + EcRequest.Data = 0; + else + EcRequest.Data = (UINT8)((*Value) >> i); + if (ACPI_FAILURE(Status = EcTransaction(sc, &EcRequest))) + break; + (*Value) |= (ACPI_INTEGER)EcRequest.Data << i; + if (++EcRequest.Address == 0) + return_ACPI_STATUS(AE_BAD_PARAMETER); + } + return_ACPI_STATUS(Status); +} + +/* + * Wait for an event interrupt for a specific condition. + */ +static ACPI_STATUS +EcWaitEventIntr(struct acpi_ec_softc *sc, EC_EVENT Event) +{ + EC_STATUS EcStatus; + int i; + + ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, (UINT32)Event); + + /* XXX this should test whether interrupts are available some other way */ + if (cold || acpi_ec_event_driven) + return_ACPI_STATUS(EcWaitEvent(sc, Event)); + + if (!EcIsLocked(sc)) + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "EcWaitEventIntr called without EC lock!\n"); + + EcStatus = EC_GET_CSR(sc); + + /* XXX waiting too long? */ + for(i = 0; i < 10; i++){ + /* + * Check EC status against the desired event. + */ + if ((Event == EC_EVENT_OUTPUT_BUFFER_FULL) && + (EcStatus & EC_FLAG_OUTPUT_BUFFER)) + return_ACPI_STATUS(AE_OK); + + if ((Event == EC_EVENT_INPUT_BUFFER_EMPTY) && + !(EcStatus & EC_FLAG_INPUT_BUFFER)) + return_ACPI_STATUS(AE_OK); + + sc->ec_csrvalue = 0; + if (ACPI_MSLEEP(&sc->ec_csrvalue, &acpi_mutex, PZERO, "EcWait", 1) != EWOULDBLOCK){ + EcStatus = sc->ec_csrvalue; + }else{ + EcStatus = EC_GET_CSR(sc); + } + } + return_ACPI_STATUS(AE_ERROR); +} + +static ACPI_STATUS +EcWaitEvent(struct acpi_ec_softc *sc, EC_EVENT Event) +{ + EC_STATUS EcStatus; + UINT32 i = 0; + + if (!EcIsLocked(sc)) + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "EcWaitEvent called without EC lock!\n"); + + /* + * Stall 1us: + * ---------- + * Stall for 1 microsecond before reading the status register + * for the first time. This allows the EC to set the IBF/OBF + * bit to its proper state. + * + * XXX it is not clear why we read the CSR twice. + */ + AcpiOsStall(1); + EcStatus = EC_GET_CSR(sc); + + /* + * Wait For Event: + * --------------- + * Poll the EC status register to detect completion of the last + * command. Wait up to 10ms (in 10us chunks) for this to occur. + */ + for (i = 0; i < 1000; i++) { + EcStatus = EC_GET_CSR(sc); + + if ((Event == EC_EVENT_OUTPUT_BUFFER_FULL) && + (EcStatus & EC_FLAG_OUTPUT_BUFFER)) + return(AE_OK); + + if ((Event == EC_EVENT_INPUT_BUFFER_EMPTY) && + !(EcStatus & EC_FLAG_INPUT_BUFFER)) + return(AE_OK); + + AcpiOsStall(10); + } + + return(AE_ERROR); +} + +static ACPI_STATUS +EcQuery(struct acpi_ec_softc *sc, UINT8 *Data) +{ + ACPI_STATUS Status; + + if (ACPI_FAILURE(Status = EcLock(sc))) + return(Status); + + EC_SET_CSR(sc, EC_COMMAND_QUERY); + if (ACPI_SUCCESS(Status = EcWaitEvent(sc, EC_EVENT_OUTPUT_BUFFER_FULL))) + *Data = EC_GET_DATA(sc); + + EcUnlock(sc); + + if (ACPI_FAILURE(Status)) + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "timeout waiting for EC to respond to EC_COMMAND_QUERY\n"); + return(Status); +} + +static ACPI_STATUS +EcTransaction(struct acpi_ec_softc *sc, EC_REQUEST *EcRequest) +{ + ACPI_STATUS Status; + + /* + * Lock the EC + */ + if (ACPI_FAILURE(Status = EcLock(sc))) + return(Status); + + /* + * Perform the transaction. + */ + switch (EcRequest->Command) { + case EC_COMMAND_READ: + Status = EcRead(sc, EcRequest->Address, &(EcRequest->Data)); + break; + + case EC_COMMAND_WRITE: + Status = EcWrite(sc, EcRequest->Address, &(EcRequest->Data)); + break; + + default: + Status = AE_SUPPORT; + break; + } + + /* + * Unlock the EC + */ + EcUnlock(sc); + + /* + * Clear & Re-Enable the EC GPE: + * ----------------------------- + * 'Consume' any EC GPE events that we generated while performing + * the transaction (e.g. IBF/OBF). Clearing the GPE here shouldn't + * have an adverse affect on outstanding EC-SCI's, as the source + * (EC-SCI) will still be high and thus should trigger the GPE + * immediately after we re-enabling it. + */ + if (sc->ec_pendquery){ + if (ACPI_FAILURE(AcpiOsQueueForExecution(OSD_PRIORITY_HIGH, + EcGpeQueryHandler, sc))) + printf("Pend Query Queuing Failed\n"); + sc->ec_pendquery = 0; + } + + if (ACPI_FAILURE(AcpiClearEvent(sc->ec_gpebit, ACPI_EVENT_GPE))) + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "EcRequest: Unable to clear the EC GPE.\n"); + if (ACPI_FAILURE(AcpiEnableEvent(sc->ec_gpebit, ACPI_EVENT_GPE, 0))) + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "EcRequest: Unable to re-enable the EC GPE.\n"); + + return(Status); +} + + +static ACPI_STATUS +EcRead(struct acpi_ec_softc *sc, UINT8 Address, UINT8 *Data) +{ + ACPI_STATUS Status; + + if (!EcIsLocked(sc)) + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "EcRead called without EC lock!\n"); + + /*EcBurstEnable(EmbeddedController);*/ + + EC_SET_CSR(sc, EC_COMMAND_READ); + if (ACPI_FAILURE(Status = EcWaitEventIntr(sc, EC_EVENT_INPUT_BUFFER_EMPTY))) { + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "EcRead: Failed waiting for EC to process read command.\n"); + return(Status); + } + + EC_SET_DATA(sc, Address); + if (ACPI_FAILURE(Status = EcWaitEventIntr(sc, EC_EVENT_OUTPUT_BUFFER_FULL))) { + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "EcRead: Failed waiting for EC to send data.\n"); + return(Status); + } + + (*Data) = EC_GET_DATA(sc); + + /*EcBurstDisable(EmbeddedController);*/ + + return(AE_OK); +} + +static ACPI_STATUS +EcWrite(struct acpi_ec_softc *sc, UINT8 Address, UINT8 *Data) +{ + ACPI_STATUS Status; + + if (!EcIsLocked(sc)) + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "EcWrite called without EC lock!\n"); + + /*EcBurstEnable(EmbeddedController);*/ + + EC_SET_CSR(sc, EC_COMMAND_WRITE); + if (ACPI_FAILURE(Status = EcWaitEventIntr(sc, EC_EVENT_INPUT_BUFFER_EMPTY))) { + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "EcWrite: Failed waiting for EC to process write command.\n"); + return(Status); + } + + EC_SET_DATA(sc, Address); + if (ACPI_FAILURE(Status = EcWaitEventIntr(sc, EC_EVENT_INPUT_BUFFER_EMPTY))) { + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "EcRead: Failed waiting for EC to process address.\n"); + return(Status); + } + + EC_SET_DATA(sc, *Data); + if (ACPI_FAILURE(Status = EcWaitEventIntr(sc, EC_EVENT_INPUT_BUFFER_EMPTY))) { + ACPI_VPRINT(sc->ec_dev, acpi_device_get_parent_softc(sc->ec_dev), + "EcWrite: Failed waiting for EC to process data.\n"); + return(Status); + } + + /*EcBurstDisable(EmbeddedController);*/ + + return(AE_OK); +} diff --git a/sys/dev/acpica/acpi_lid.c b/sys/dev/acpica/acpi_lid.c new file mode 100644 index 0000000..5a4b8cc --- /dev/null +++ b/sys/dev/acpica/acpi_lid.c @@ -0,0 +1,181 @@ +/*- + * Copyright (c) 2000 Takanori Watanabe <takawata@jp.freebsd.org> + * Copyright (c) 2000 Mitsuru IWASAKI <iwasaki@jp.freebsd.org> + * Copyright (c) 2000 Michael Smith <msmith@freebd.org> + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/bus.h> +#include <sys/proc.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_BUTTON +ACPI_MODULE_NAME("LID") + +struct acpi_lid_softc { + device_t lid_dev; + ACPI_HANDLE lid_handle; + int lid_status; /* open or closed */ +}; + +static int acpi_lid_probe(device_t dev); +static int acpi_lid_attach(device_t dev); +static int acpi_lid_suspend(device_t dev); +static int acpi_lid_resume(device_t dev); +static void acpi_lid_notify_status_changed(void *arg); +static void acpi_lid_notify_handler(ACPI_HANDLE h,UINT32 notify, void *context); + +static device_method_t acpi_lid_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_lid_probe), + DEVMETHOD(device_attach, acpi_lid_attach), + DEVMETHOD(device_suspend, acpi_lid_suspend), + DEVMETHOD(device_resume, acpi_lid_resume), + + {0, 0} +}; + +static driver_t acpi_lid_driver = { + "acpi_lid", + acpi_lid_methods, + sizeof(struct acpi_lid_softc), +}; + +static devclass_t acpi_lid_devclass; +DRIVER_MODULE(acpi_lid, acpi, acpi_lid_driver, acpi_lid_devclass, 0, 0); + +static int +acpi_lid_probe(device_t dev) +{ + if ((acpi_get_type(dev) == ACPI_TYPE_DEVICE) && + !acpi_disabled("lid") && + acpi_MatchHid(dev, "PNP0C0D")) { + device_set_desc(dev, "Control Method Lid Switch"); + return(0); + } + return(ENXIO); +} + +static int +acpi_lid_attach(device_t dev) +{ + struct acpi_lid_softc *sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = device_get_softc(dev); + sc->lid_dev = dev; + sc->lid_handle = acpi_get_handle(dev); + + /* + * Install notification handler + */ + AcpiInstallNotifyHandler(sc->lid_handle, ACPI_DEVICE_NOTIFY, acpi_lid_notify_handler, sc); + acpi_device_enable_wake_capability(sc->lid_handle, 1); + return_VALUE(0); +} + +static int +acpi_lid_suspend(device_t dev) +{ + struct acpi_lid_softc *sc; + + sc = device_get_softc(dev); + acpi_device_enable_wake_event(sc->lid_handle); + return (0); +} + +static int +acpi_lid_resume(device_t dev) +{ + return (0); +} + +static void +acpi_lid_notify_status_changed(void *arg) +{ + struct acpi_lid_softc *sc; + struct acpi_softc *acpi_sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = (struct acpi_lid_softc *)arg; + + /* + * Evaluate _LID and check the return value, update lid status. + * Zero: The lid is closed + * Non-zero: The lid is open + */ + if (ACPI_FAILURE(acpi_EvaluateInteger(sc->lid_handle, "_LID", &sc->lid_status))) + return_VOID; + + acpi_sc = acpi_device_get_parent_softc(sc->lid_dev); + if (acpi_sc == NULL) { + return_VOID; + } + + ACPI_VPRINT(sc->lid_dev, acpi_sc, + "Lid %s\n", sc->lid_status ? "opened" : "closed"); + + if (sc->lid_status == 0) { + EVENTHANDLER_INVOKE(acpi_sleep_event, acpi_sc->acpi_lid_switch_sx); + } else { + EVENTHANDLER_INVOKE(acpi_wakeup_event, acpi_sc->acpi_lid_switch_sx); + } + + return_VOID; +} + +/* XXX maybe not here */ +#define ACPI_NOTIFY_STATUS_CHANGED 0x80 + +static void +acpi_lid_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) +{ + struct acpi_lid_softc *sc = (struct acpi_lid_softc *)context; + + ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); + + switch (notify) { + case ACPI_NOTIFY_STATUS_CHANGED: + AcpiOsQueueForExecution(OSD_PRIORITY_LO, acpi_lid_notify_status_changed, sc); + break; + default: + break; /* unknown notification value */ + } + return_VOID; +} + diff --git a/sys/dev/acpica/acpi_pci.c b/sys/dev/acpica/acpi_pci.c new file mode 100644 index 0000000..2448b62 --- /dev/null +++ b/sys/dev/acpica/acpi_pci.c @@ -0,0 +1,235 @@ +/* + * Copyright (c) 1997, Stefan Esser <se@freebsd.org> + * Copyright (c) 2000, Michael Smith <msmith@freebsd.org> + * Copyright (c) 2000, BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice unmodified, this list of conditions, and the following + * disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * $FreeBSD$ + * + */ + +#include "opt_bus.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> + +#include <sys/pciio.h> +#include <dev/pci/pcireg.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/pci_private.h> + +#include "pcib_if.h" +#include "pci_if.h" + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_BUS +ACPI_MODULE_NAME("PCI") + +struct acpi_pci_devinfo { + struct pci_devinfo ap_dinfo; + ACPI_HANDLE ap_handle; +}; + +static int acpi_pci_probe(device_t dev); +static int acpi_pci_attach(device_t dev); +static int acpi_pci_read_ivar(device_t dev, device_t child, int which, + uintptr_t *result); +#if 0 +static int acpi_pci_set_powerstate_method(device_t dev, device_t child, + int state); +static int acpi_pci_get_powerstate_method(device_t dev, device_t child); +#endif +static ACPI_STATUS acpi_pci_save_handle(ACPI_HANDLE handle, UINT32 level, + void *context, void **status); + +static device_method_t acpi_pci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_pci_probe), + DEVMETHOD(device_attach, acpi_pci_attach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, bus_generic_resume), + + /* Bus interface */ + DEVMETHOD(bus_print_child, pci_print_child), + DEVMETHOD(bus_probe_nomatch, pci_probe_nomatch), + DEVMETHOD(bus_read_ivar, acpi_pci_read_ivar), + DEVMETHOD(bus_write_ivar, pci_write_ivar), + DEVMETHOD(bus_driver_added, bus_generic_driver_added), + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + + DEVMETHOD(bus_get_resource_list,pci_get_resource_list), + DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), + DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), + DEVMETHOD(bus_delete_resource, pci_delete_resource), + DEVMETHOD(bus_alloc_resource, pci_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_child_pnpinfo_str, pci_child_pnpinfo_str_method), + DEVMETHOD(bus_child_location_str, pci_child_location_str_method), + + /* PCI interface */ + DEVMETHOD(pci_read_config, pci_read_config_method), + DEVMETHOD(pci_write_config, pci_write_config_method), + DEVMETHOD(pci_enable_busmaster, pci_enable_busmaster_method), + DEVMETHOD(pci_disable_busmaster, pci_disable_busmaster_method), + DEVMETHOD(pci_enable_io, pci_enable_io_method), + DEVMETHOD(pci_disable_io, pci_disable_io_method), + /* XXX: We should override these two. */ + DEVMETHOD(pci_get_powerstate, pci_get_powerstate_method), + DEVMETHOD(pci_set_powerstate, pci_set_powerstate_method), + + { 0, 0 } +}; + +static driver_t acpi_pci_driver = { + "pci", + acpi_pci_methods, + 0, /* no softc */ +}; + +DRIVER_MODULE(acpi_pci, pcib, acpi_pci_driver, pci_devclass, 0, 0); +MODULE_VERSION(acpi_pci, 1); +MODULE_DEPEND(acpi_pci, pci, 1, 1, 1); + +static int +acpi_pci_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) +{ + struct acpi_pci_devinfo *dinfo; + + switch (which) { + case ACPI_IVAR_HANDLE: + dinfo = device_get_ivars(child); + *result = (uintptr_t)dinfo->ap_handle; + return(0); + } + return(pci_read_ivar(dev, child, which, result)); +} + +#if 0 +/* + * PCI power manangement + */ +static int +acpi_pci_set_powerstate_method(device_t dev, device_t child, int state) +{ + /* XXX: TODO */ + return (ENXIO); +} + +static int +acpi_pci_get_powerstate_method(device_t dev, device_t child) +{ + /* XXX: TODO */ + return (ENXIO); +} +#endif + +static ACPI_STATUS +acpi_pci_save_handle(ACPI_HANDLE handle, UINT32 level, void *context, + void **status) +{ + struct acpi_pci_devinfo *dinfo; + device_t *devlist; + int devcount, i, func, slot; + UINT32 address; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (ACPI_FAILURE(acpi_EvaluateInteger(handle, "_ADR", &address))) + return_ACPI_STATUS(AE_OK); + slot = address >> 16; + func = address & 0xffff; + if (device_get_children((device_t)context, &devlist, &devcount) != 0) + return_ACPI_STATUS(AE_OK); + for (i = 0; i < devcount; i++) { + dinfo = device_get_ivars(devlist[i]); + if (dinfo->ap_dinfo.cfg.func == func && + dinfo->ap_dinfo.cfg.slot == slot) { + dinfo->ap_handle = handle; + free(devlist, M_TEMP); + return_ACPI_STATUS(AE_OK); + } + } + free(devlist, M_TEMP); + return_ACPI_STATUS(AE_OK); +} + +static int +acpi_pci_probe(device_t dev) +{ + + if (pcib_get_bus(dev) < 0) + return (ENXIO); + + device_set_desc(dev, "ACPI PCI bus"); + + if (acpi_get_handle(dev) == NULL) + return (ENXIO); + return (0); +} + +static int +acpi_pci_attach(device_t dev) +{ + int busno; + + /* + * Since there can be multiple independantly numbered PCI + * busses on some large alpha systems, we can't use the unit + * number to decide what bus we are probing. We ask the parent + * pcib what our bus number is. + */ + busno = pcib_get_bus(dev); + if (bootverbose) + device_printf(dev, "physical bus=%d\n", busno); + + /* + * First, PCI devices are added as in the normal PCI bus driver. + * Afterwards, the ACPI namespace under the bridge driver is + * walked to save ACPI handles to all the devices that appear in + * the ACPI namespace as immediate descendants of the bridge. + * + * XXX: Sometimes PCI devices show up in the ACPI namespace that + * pci_add_children() doesn't find. We currently just ignore + * these devices. + */ + pci_add_children(dev, busno, sizeof(struct acpi_pci_devinfo)); + (void) AcpiWalkNamespace(ACPI_TYPE_DEVICE, acpi_get_handle(dev), 1, + acpi_pci_save_handle, dev, NULL); + + return (bus_generic_attach(dev)); +} diff --git a/sys/dev/acpica/acpi_pci_link.c b/sys/dev/acpica/acpi_pci_link.c new file mode 100644 index 0000000..7027cc7 --- /dev/null +++ b/sys/dev/acpica/acpi_pci_link.c @@ -0,0 +1,1077 @@ +/*- + * Copyright (c) 2002 Mitsuru IWASAKI <iwasaki@jp.freebsd.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/bus.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> +#include <dev/acpica/acpi_pcibvar.h> + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_BUS +ACPI_MODULE_NAME("PCI_LINK") + +#define MAX_POSSIBLE_INTERRUPTS 16 +#define MAX_ISA_INTERRUPTS 16 +#define MAX_ACPI_INTERRUPTS 255 + +struct acpi_pci_link_entry { + TAILQ_ENTRY(acpi_pci_link_entry) links; + ACPI_HANDLE handle; + UINT8 current_irq; + UINT8 initial_irq; + ACPI_RESOURCE possible_resources; + UINT8 number_of_interrupts; + UINT8 interrupts[MAX_POSSIBLE_INTERRUPTS]; + + UINT8 sorted_irq[MAX_POSSIBLE_INTERRUPTS]; + int references; + int priority; +}; + +TAILQ_HEAD(acpi_pci_link_entries, acpi_pci_link_entry); +static struct acpi_pci_link_entries acpi_pci_link_entries; + +struct acpi_prt_entry { + TAILQ_ENTRY(acpi_prt_entry) links; + device_t pcidev; + int busno; + ACPI_PCI_ROUTING_TABLE prt; + struct acpi_pci_link_entry *pci_link; +}; + +TAILQ_HEAD(acpi_prt_entries, acpi_prt_entry); +static struct acpi_prt_entries acpi_prt_entries; + +static int irq_penalty[MAX_ACPI_INTERRUPTS]; + +#define ACPI_STA_PRESENT 0x00000001 +#define ACPI_STA_ENABLE 0x00000002 +#define ACPI_STA_SHOWINUI 0x00000004 +#define ACPI_STA_FUNCTIONAL 0x00000008 + +/* + * PCI link object management + */ + +static void +acpi_pci_link_entry_dump(struct acpi_prt_entry *entry) +{ + UINT8 i; + ACPI_RESOURCE_IRQ *Irq; + + if (entry == NULL || entry->pci_link == NULL) { + return; + } + + printf("%s irq %3d: ", acpi_name(entry->pci_link->handle), + entry->pci_link->current_irq); + + printf("["); + for (i = 0; i < entry->pci_link->number_of_interrupts; i++) { + printf("%3d", entry->pci_link->interrupts[i]); + } + printf("] "); + + Irq = NULL; + switch (entry->pci_link->possible_resources.Id) { + case ACPI_RSTYPE_IRQ: + Irq = &entry->pci_link->possible_resources.Data.Irq; + + switch (Irq->ActiveHighLow) { + case ACPI_ACTIVE_HIGH: + printf("high,"); + break; + + case ACPI_ACTIVE_LOW: + printf("low,"); + break; + + default: + printf("unkown,"); + break; + } + + switch (Irq->EdgeLevel) { + case ACPI_EDGE_SENSITIVE: + printf("edge,"); + break; + + case ACPI_LEVEL_SENSITIVE: + printf("level,"); + break; + + default: + printf("unkown,"); + break; + } + + switch (Irq->SharedExclusive) { + case ACPI_EXCLUSIVE: + printf("exclusive"); + break; + + case ACPI_SHARED: + printf("sharable"); + break; + + default: + printf("unkown"); + break; + } + + break; + + case ACPI_RSTYPE_EXT_IRQ: + /* TBD */ + break; + } + + printf(" %d.%d.%d", entry->busno, + (int)((entry->prt.Address & 0xffff0000) >> 16), + (int)entry->prt.Pin); + + printf("\n"); +} + +static ACPI_STATUS +acpi_pci_link_get_object_status(ACPI_HANDLE handle, UINT32 *sta) +{ + ACPI_DEVICE_INFO devinfo; + ACPI_STATUS error; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (handle == NULL || sta == NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "invalid argument\n")); + return_ACPI_STATUS (AE_BAD_PARAMETER); + } + + error = AcpiGetObjectInfo(handle, &devinfo); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "couldn't get object info %s - %s\n", + acpi_name(handle), AcpiFormatException(error))); + return_ACPI_STATUS (error); + } + + if ((devinfo.Valid & ACPI_VALID_HID) == 0 || + strcmp(devinfo.HardwareId, "PNP0C0F") != 0) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "invalid hardware ID - %s\n", + acpi_name(handle))); + return_ACPI_STATUS (AE_TYPE); + } + + if (devinfo.Valid & ACPI_VALID_STA) { + *sta = devinfo.CurrentStatus; + } else { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, "invalid status - %s\n", + acpi_name(handle))); + *sta = 0; + } + + return_ACPI_STATUS (AE_OK); +} + +static ACPI_STATUS +acpi_pci_link_get_irq_resources(ACPI_RESOURCE *resources, + UINT8 *number_of_interrupts, UINT8 interrupts[]) +{ + UINT8 count; + UINT8 i; + UINT32 NumberOfInterrupts; + UINT32 *Interrupts; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (resources == NULL || number_of_interrupts == NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "invalid argument\n")); + return_ACPI_STATUS (AE_BAD_PARAMETER); + } + + *number_of_interrupts = 0; + NumberOfInterrupts = 0; + Interrupts = NULL; + + if (resources->Id == ACPI_RSTYPE_START_DPF) + resources = ACPI_NEXT_RESOURCE(resources); + + if (resources->Id != ACPI_RSTYPE_IRQ && + resources->Id != ACPI_RSTYPE_EXT_IRQ) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Resource is not an IRQ entry - %d\n", resources->Id)); + return_ACPI_STATUS (AE_TYPE); + } + + switch (resources->Id) { + case ACPI_RSTYPE_IRQ: + NumberOfInterrupts = resources->Data.Irq.NumberOfInterrupts; + Interrupts = resources->Data.Irq.Interrupts; + break; + + case ACPI_RSTYPE_EXT_IRQ: + NumberOfInterrupts = resources->Data.ExtendedIrq.NumberOfInterrupts; + Interrupts = resources->Data.ExtendedIrq.Interrupts; + break; + } + + if (NumberOfInterrupts == 0) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Blank IRQ resource\n")); + return_ACPI_STATUS (AE_NULL_ENTRY); + } + + count = 0; + for (i = 0; i < NumberOfInterrupts; i++) { + if (i >= MAX_POSSIBLE_INTERRUPTS) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, "too many IRQs %d\n", i)); + break; + } + + if (Interrupts[i] == NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, "Invalid IRQ %d\n", + Interrupts[i])); + continue; + } + interrupts[count] = Interrupts[i]; + count++; + } + *number_of_interrupts = count; + + return_ACPI_STATUS (AE_OK); +} + +static ACPI_STATUS +acpi_pci_link_get_current_irq(struct acpi_pci_link_entry *link, UINT8 *irq) +{ + ACPI_STATUS error; + ACPI_BUFFER buf; + ACPI_RESOURCE *resources; + UINT8 number_of_interrupts; + UINT8 interrupts[MAX_POSSIBLE_INTERRUPTS];; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (link == NULL || irq == NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, "invalid argument\n")); + return_ACPI_STATUS (AE_BAD_PARAMETER); + } + + *irq = 0; + buf.Pointer = NULL; + buf.Length = ACPI_ALLOCATE_BUFFER; + error = AcpiGetCurrentResources(link->handle, &buf); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "couldn't get PCI interrupt link device _CRS %s - %s\n", + acpi_name(link->handle), AcpiFormatException(error))); + return_ACPI_STATUS (error); + } + if (buf.Pointer == NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "couldn't allocate memory - %s\n", + acpi_name(link->handle))); + return_ACPI_STATUS (AE_NO_MEMORY); + } + + resources = (ACPI_RESOURCE *) buf.Pointer; + + number_of_interrupts = 0; + bzero(interrupts, sizeof(interrupts)); + error = acpi_pci_link_get_irq_resources(resources, + &number_of_interrupts, interrupts); + AcpiOsFree(buf.Pointer); + + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "couldn't get current IRQ from PCI interrupt link %s - %s\n", + acpi_name(link->handle), AcpiFormatException(error))); + return_ACPI_STATUS (error); + } + + if (number_of_interrupts == 0) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "PCI interrupt link device _CRS data is corrupted - %s\n", + acpi_name(link->handle))); + return_ACPI_STATUS (AE_NULL_ENTRY); + } + + *irq = interrupts[0]; + + return_ACPI_STATUS (AE_OK); +} + +static ACPI_STATUS +acpi_pci_link_add_link(ACPI_HANDLE handle, struct acpi_prt_entry *entry) +{ + ACPI_STATUS error; + ACPI_BUFFER buf; + ACPI_RESOURCE *resources; + struct acpi_pci_link_entry *link; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + entry->pci_link = NULL; + TAILQ_FOREACH(link, &acpi_pci_link_entries, links) { + if (link->handle == handle) { + entry->pci_link = link; + link->references++; + return_ACPI_STATUS (AE_OK); + } + } + + link = AcpiOsAllocate(sizeof(struct acpi_pci_link_entry)); + if (link == NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "couldn't allocate memory - %s\n", acpi_name(handle))); + return_ACPI_STATUS (AE_NO_MEMORY); + } + + buf.Pointer = NULL; + buf.Length = ACPI_ALLOCATE_BUFFER; + + bzero(link, sizeof(struct acpi_pci_link_entry)); + + link->handle = handle; + + error = acpi_pci_link_get_current_irq(link, &link->current_irq); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "couldn't get current IRQ from PCI interrupt link %s - %s\n", + acpi_name(handle), AcpiFormatException(error))); + } + + link->initial_irq = link->current_irq; + + error = AcpiGetPossibleResources(handle, &buf); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "couldn't get PCI interrupt link device _PRS data %s - %s\n", + acpi_name(handle), AcpiFormatException(error))); + goto out; + } + + if (buf.Pointer == NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "_PRS nuffer is empty - %s\n", acpi_name(handle))); + error = AE_NO_MEMORY; + goto out; + } + + resources = (ACPI_RESOURCE *) buf.Pointer; + bcopy(resources, &link->possible_resources, + sizeof(link->possible_resources)); + + error = acpi_pci_link_get_irq_resources(resources, + &link->number_of_interrupts, link->interrupts); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "couldn't get possible IRQs from PCI interrupt link %s - %s\n", + acpi_name(handle), AcpiFormatException(error))); + goto out; + } + + if (link->number_of_interrupts == 0) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "PCI interrupt link device _PRS data is corrupted - %s\n", + acpi_name(handle))); + error = AE_NULL_ENTRY; + goto out; + } + + link->references++; + + TAILQ_INSERT_TAIL(&acpi_pci_link_entries, link, links); + entry->pci_link = link; + + error = AE_OK; +out: + if (buf.Pointer != NULL) { + AcpiOsFree(buf.Pointer); + } + + if (error != AE_OK && link != NULL) { + AcpiOsFree(link); + } + + return_ACPI_STATUS (error); +} + +static ACPI_STATUS +acpi_pci_link_add_prt(device_t pcidev, ACPI_PCI_ROUTING_TABLE *prt, int busno) +{ + ACPI_HANDLE handle; + ACPI_STATUS error; + UINT32 sta; + struct acpi_prt_entry *entry; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if ((prt == NULL) || (prt->Source == NULL) || (prt->Source[0] == '\0')) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "couldn't handle this routing table - hardwired\n")); + return_ACPI_STATUS (AE_BAD_PARAMETER); + } + + error = AcpiGetHandle(acpi_get_handle(pcidev), prt->Source, &handle); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "couldn't get acpi handle - %s\n", + AcpiFormatException(error))); + return_ACPI_STATUS (error); + } + + error = acpi_pci_link_get_object_status(handle, &sta); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "couldn't get object status %s - %s\n", + acpi_name(handle), AcpiFormatException(error))); + return_ACPI_STATUS (error); + } + + if (!(sta & (ACPI_STA_PRESENT | ACPI_STA_FUNCTIONAL))) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "PCI interrupt link is not functional - %s\n", + acpi_name(handle))); + return_ACPI_STATUS (AE_ERROR); + } + + TAILQ_FOREACH(entry, &acpi_prt_entries, links) { + if (entry->busno == busno && + entry->prt.Address == prt->Address && + entry->prt.Pin == prt->Pin) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "PCI interrupt link entry already exists - %s\n", + acpi_name(handle))); + return_ACPI_STATUS (AE_ALREADY_EXISTS); + } + } + + entry = AcpiOsAllocate(sizeof(struct acpi_prt_entry)); + if (entry == NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "couldn't allocate memory - %s\n", acpi_name(handle))); + return_ACPI_STATUS (AE_NO_MEMORY); + } + + bzero(entry, sizeof(struct acpi_prt_entry)); + + entry->pcidev = pcidev; + entry->busno = busno; + bcopy(prt, &entry->prt, sizeof(entry->prt)); + + error = acpi_pci_link_add_link(handle, entry); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "couldn't add prt entry to pci link %s - %s\n", + acpi_name(handle), AcpiFormatException(error))); + goto out; + } + + TAILQ_INSERT_TAIL(&acpi_prt_entries, entry, links); + error = AE_OK; + +out: + if (error != AE_OK && entry != NULL) { + AcpiOsFree(entry); + } + + return_ACPI_STATUS (error); +} + +static int +acpi_pci_link_is_valid_irq(struct acpi_pci_link_entry *link, UINT8 irq) +{ + UINT8 i; + + if (irq == 0) { + return (0); + } + + for (i = 0; i < link->number_of_interrupts; i++) { + if (link->interrupts[i] == irq) { + return (1); + } + } + + /* allow initial IRQ as valid one. */ + if (link->initial_irq == irq) { + return (1); + } + + return (0); +} + +static ACPI_STATUS +acpi_pci_link_set_irq(struct acpi_pci_link_entry *link, UINT8 irq) +{ + ACPI_STATUS error; + ACPI_RESOURCE resbuf; + ACPI_BUFFER crsbuf; + UINT32 sta; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (!acpi_pci_link_is_valid_irq(link, irq)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "couldn't set invalid IRQ %d - %s\n", irq, + acpi_name(link->handle))); + return_ACPI_STATUS (AE_BAD_PARAMETER); + } + + error = acpi_pci_link_get_current_irq(link, &link->current_irq); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "couldn't get current IRQ from PCI interrupt link %s - %s\n", + acpi_name(link->handle), AcpiFormatException(error))); + } + + if (link->current_irq == irq) { + return_ACPI_STATUS (AE_OK); + } + + bzero(&resbuf, sizeof(resbuf)); + crsbuf.Pointer = NULL; + resbuf.Id = ACPI_RSTYPE_IRQ; + resbuf.Length = ACPI_SIZEOF_RESOURCE(ACPI_RESOURCE_IRQ); + + if (link->possible_resources.Id != ACPI_RSTYPE_IRQ && + link->possible_resources.Id != ACPI_RSTYPE_EXT_IRQ) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "Resource is not an IRQ entry %s - %d\n", + acpi_name(link->handle), link->possible_resources.Id)); + return_ACPI_STATUS (AE_TYPE); + } + + switch (link->possible_resources.Id) { + case ACPI_RSTYPE_IRQ: + /* structure copy other fields */ + resbuf.Data.Irq = link->possible_resources.Data.Irq; + break; + + case ACPI_RSTYPE_EXT_IRQ: + /* XXX */ + resbuf.Data.Irq.EdgeLevel = ACPI_LEVEL_SENSITIVE; + resbuf.Data.Irq.ActiveHighLow = ACPI_ACTIVE_LOW; + resbuf.Data.Irq.SharedExclusive = ACPI_SHARED; + break; + } + + resbuf.Data.Irq.NumberOfInterrupts = 1; + resbuf.Data.Irq.Interrupts[0] = irq; + + error = acpi_AppendBufferResource(&crsbuf, &resbuf); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "couldn't setup buffer by acpi_AppendBufferResource - %s\n", + acpi_name(link->handle))); + return_ACPI_STATUS (error); + } + + if (crsbuf.Pointer == NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_ERROR, + "buffer setup by acpi_AppendBufferResource is corrupted - %s\n", + acpi_name(link->handle))); + return_ACPI_STATUS (AE_NO_MEMORY); + } + + error = AcpiSetCurrentResources(link->handle, &crsbuf); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "couldn't set PCI interrupt link device _SRS %s - %s\n", + acpi_name(link->handle), AcpiFormatException(error))); + return_ACPI_STATUS (error); + } + + AcpiOsFree(crsbuf.Pointer); + link->current_irq = 0; + + error = acpi_pci_link_get_object_status(link->handle, &sta); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "couldn't get object status %s - %s\n", + acpi_name(link->handle), AcpiFormatException(error))); + return_ACPI_STATUS (error); + } + + if (!(sta & ACPI_STA_ENABLE)) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "PCI interrupt link is disabled - %s\n", + acpi_name(link->handle))); + return_ACPI_STATUS (AE_ERROR); + } + + error = acpi_pci_link_get_current_irq(link, &link->current_irq); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "couldn't get current IRQ from PCI interrupt link %s - %s\n", + acpi_name(link->handle), AcpiFormatException(error))); + return_ACPI_STATUS (error); + } + + if (link->current_irq == irq) { + error = AE_OK; + } else { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "couldn't set IRQ %d to PCI interrupt link %d - %s\n", + irq, link->current_irq, acpi_name(link->handle))); + + link->current_irq = 0; + error = AE_ERROR; + } + + return_ACPI_STATUS (error); +} + +/* + * Auto arbitration for boot-disabled devices + */ + +static void +acpi_pci_link_bootdisabled_dump(void) + +{ + int i; + int irq; + struct acpi_pci_link_entry *link; + + TAILQ_FOREACH(link, &acpi_pci_link_entries, links) { + /* boot-disabled link only. */ + if (link->current_irq != 0) { + continue; + } + + printf("%s:\n", acpi_name(link->handle)); + printf(" interrupts: "); + for (i = 0; i < link->number_of_interrupts; i++) { + irq = link->sorted_irq[i]; + printf("%6d", irq); + } + printf("\n"); + printf(" penalty: "); + for (i = 0; i < link->number_of_interrupts; i++) { + irq = link->sorted_irq[i]; + printf("%6d", irq_penalty[irq]); + } + printf("\n"); + printf(" references: %d\n", link->references); + printf(" priority: %d\n", link->priority); + } +} + +static void +acpi_pci_link_init_irq_penalty(void) +{ + int irq; + + bzero(irq_penalty, sizeof(irq_penalty)); + for (irq = 0; irq < MAX_ISA_INTERRUPTS; irq++) { + /* 0, 1, 2, 8: timer, keyboard, cascade */ + if (irq == 0 || irq == 1 || irq == 2 || irq == 8) { + irq_penalty[irq] = 100000; + continue; + } + + /* 13, 14, 15: npx, ATA controllers */ + if (irq == 13 || irq == 14 || irq == 15) { + irq_penalty[irq] = 10000; + continue; + } + + /* 3,4,6,7,12: typicially used by legacy hardware */ + if (irq == 3 || irq == 4 || irq == 6 || irq == 7 || irq == 12) { + irq_penalty[irq] = 1000; + continue; + } + } +} + +static int +acpi_pci_link_is_irq_exclusive(ACPI_RESOURCE *res) +{ + if (res == NULL) { + return (0); + } + + if (res->Id != ACPI_RSTYPE_IRQ && + res->Id != ACPI_RSTYPE_EXT_IRQ) { + return (0); + } + + if (res->Id == ACPI_RSTYPE_IRQ && + res->Data.Irq.SharedExclusive == ACPI_EXCLUSIVE) { + return (1); + } + + if (res->Id == ACPI_RSTYPE_EXT_IRQ && + res->Data.ExtendedIrq.SharedExclusive == ACPI_EXCLUSIVE) { + return (1); + } + + return (0); +} + +static void +acpi_pci_link_update_irq_penalty(device_t dev, int busno) +{ + int i; + int irq; + int rid; + struct resource *res; + struct acpi_prt_entry *entry; + struct acpi_pci_link_entry *link; + + TAILQ_FOREACH(entry, &acpi_prt_entries, links) { + if (entry->busno != busno) { + continue; + } + + link = entry->pci_link; + if (link == NULL) { + continue; /* impossible... */ + } + + if (link->current_irq != 0) { + /* not boot-disabled link, we will use this IRQ. */ + irq_penalty[link->current_irq] += 100; + continue; + } + + /* boot-disabled link */ + for (i = 0; i < link->number_of_interrupts; i++) { + /* give 10 for each possible IRQs. */ + irq = link->interrupts[i]; + irq_penalty[irq] += 10; + + /* higher penalty if exclusive. */ + if (acpi_pci_link_is_irq_exclusive(&link->possible_resources)) { + irq_penalty[irq] += 100; + } + + /* XXX try to get this IRQ in non-sharable mode. */ + rid = 0; + res = bus_alloc_resource(dev, SYS_RES_IRQ, + &rid, irq, irq, 1, 0); + if (res != NULL) { + bus_release_resource(dev, SYS_RES_IRQ, + rid, res); + } else { + /* this is in use, give 100. */ + irq_penalty[irq] += 100; + } + } + + /* initialize `sorted' possible IRQs. */ + bcopy(link->interrupts, link->sorted_irq, + sizeof(link->sorted_irq)); + } +} + +static void +acpi_pci_link_set_bootdisabled_priority(void) +{ + int sum_penalty; + int i; + int irq; + struct acpi_pci_link_entry *link, *link_pri; + TAILQ_HEAD(, acpi_pci_link_entry) sorted_list; + + if (bootverbose) { + printf("---- before setting priority for links ------------\n"); + acpi_pci_link_bootdisabled_dump(); + } + + /* reset priority for all links. */ + TAILQ_FOREACH(link, &acpi_pci_link_entries, links) { + link->priority = 0; + } + + TAILQ_FOREACH(link, &acpi_pci_link_entries, links) { + /* not boot-disabled link, give no chance to be arbitrated. */ + if (link->current_irq != 0) { + link->priority = 0; + continue; + } + + /* + * Calculate the priority for each boot-disabled links. + * o IRQ penalty indicates difficulty to use. + * o #references for devices indicates importance of the link. + * o #interrupts indicates flexibility of the link. + */ + sum_penalty = 0; + for (i = 0; i < link->number_of_interrupts; i++) { + irq = link->interrupts[i]; + sum_penalty += irq_penalty[irq]; + } + + link->priority = (sum_penalty * link->references) / link->number_of_interrupts; + } + + /* + * Sort PCI links based on the priority. + * XXX Any other better ways rather than using work list? + */ + TAILQ_INIT(&sorted_list); + while (!TAILQ_EMPTY(&acpi_pci_link_entries)) { + link = TAILQ_FIRST(&acpi_pci_link_entries); + /* find an entry which has the highest priority. */ + TAILQ_FOREACH(link_pri, &acpi_pci_link_entries, links) { + if (link->priority < link_pri->priority) { + link = link_pri; + } + } + /* move to work list. */ + TAILQ_REMOVE(&acpi_pci_link_entries, link, links); + TAILQ_INSERT_TAIL(&sorted_list, link, links); + } + + while (!TAILQ_EMPTY(&sorted_list)) { + /* move them back to the list, one by one... */ + link = TAILQ_FIRST(&sorted_list); + TAILQ_REMOVE(&sorted_list, link, links); + TAILQ_INSERT_TAIL(&acpi_pci_link_entries, link, links); + } +} + +static void +acpi_pci_link_fixup_bootdisabled_link(void) +{ + int i, j; + int irq1, irq2; + struct acpi_pci_link_entry *link; + ACPI_STATUS error; + + if (bootverbose) { + printf("---- before fixup boot-disabled links -------------\n"); + acpi_pci_link_bootdisabled_dump(); + } + + TAILQ_FOREACH(link, &acpi_pci_link_entries, links) { + /* ignore non boot-disabled links. */ + if (link->current_irq != 0) { + continue; + } + + /* sort IRQs based on their penalty descending. */ + for (i = 0; i < link->number_of_interrupts; i++) { + irq1 = link->sorted_irq[i]; + for (j = i + 1; j < link->number_of_interrupts; j++) { + irq2 = link->sorted_irq[j]; + if (irq_penalty[irq1] < irq_penalty[irq2]) { + continue; + } + link->sorted_irq[i] = irq2; + link->sorted_irq[j] = irq1; + irq1 = irq2; + } + } + + /* try with lower penalty IRQ. */ + for (i = 0; i < link->number_of_interrupts - 1; i++) { + irq1 = link->sorted_irq[i]; + error = acpi_pci_link_set_irq(link, irq1); + if (error == AE_OK) { + /* OK, we use this. give another penalty. */ + irq_penalty[irq1] += 100 * link->references; + break; + } + /* NG, try next IRQ... */ + } + } + + if (bootverbose) { + printf("---- after fixup boot-disabled links --------------\n"); + acpi_pci_link_bootdisabled_dump(); + } +} + +/* + * Public interface + */ + +int +acpi_pci_link_config(device_t dev, ACPI_BUFFER *prtbuf, int busno) +{ + struct acpi_prt_entry *entry; + ACPI_PCI_ROUTING_TABLE *prt; + u_int8_t *prtp; + ACPI_STATUS error; + static int first_time =1; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (acpi_disabled("pci_link")) { + return (0); + } + + if (first_time) { + TAILQ_INIT(&acpi_prt_entries); + TAILQ_INIT(&acpi_pci_link_entries); + acpi_pci_link_init_irq_penalty(); + first_time = 0; + } + + if (prtbuf == NULL) { + return (-1); + } + + prtp = prtbuf->Pointer; + if (prtp == NULL) { /* didn't get routing table */ + return (-1); + } + + /* scan the PCI Routing Table */ + for (;;) { + prt = (ACPI_PCI_ROUTING_TABLE *)prtp; + + if (prt->Length == 0) /* end of table */ + break; + + error = acpi_pci_link_add_prt(dev, prt, busno); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "couldn't add PCI interrupt link entry - %s\n", + AcpiFormatException(error))); + } + + /* skip to next entry */ + prtp += prt->Length; + } + + if (bootverbose) { + printf("---- initial configuration ------------------------\n"); + TAILQ_FOREACH(entry, &acpi_prt_entries, links) { + if (entry->busno != busno) { + continue; + } + + acpi_pci_link_entry_dump(entry); + } + } + + /* manual configuration. */ + TAILQ_FOREACH(entry, &acpi_prt_entries, links) { + UINT8 irq; + char *irqstr, *op; + char prthint[32]; + + if (entry->busno != busno) { + continue; + } + + snprintf(prthint, sizeof(prthint), + "hw.acpi.pci.link.%d.%d.%d.irq", entry->busno, + (int)((entry->prt.Address & 0xffff0000) >> 16), + (int)entry->prt.Pin); + + irqstr = getenv(prthint); + if (irqstr == NULL) { + continue; + } + + irq = strtoul(irqstr, &op, 0); + if (*op != '\0') { + continue; + } + + if (acpi_pci_link_is_valid_irq(entry->pci_link, irq)) { + error = acpi_pci_link_set_irq(entry->pci_link, irq); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "couldn't set IRQ to PCI interrupt link entry %s - %s\n", + acpi_name(entry->pci_link->handle), + AcpiFormatException(error))); + } + continue; + } + + /* + * Do auto arbitration for this device's PCI link + * if hint value 0 is specified. + */ + if (irq == 0) { + entry->pci_link->current_irq = 0; + } + } + + /* auto arbitration */ + acpi_pci_link_update_irq_penalty(dev, busno); + acpi_pci_link_set_bootdisabled_priority(); + acpi_pci_link_fixup_bootdisabled_link(); + + if (bootverbose) { + printf("---- arbitrated configuration ---------------------\n"); + TAILQ_FOREACH(entry, &acpi_prt_entries, links) { + if (entry->busno != busno) { + continue; + } + + acpi_pci_link_entry_dump(entry); + } + } + + return (0); +} + +int +acpi_pci_link_resume(device_t dev, ACPI_BUFFER *prtbuf, int busno) +{ + struct acpi_prt_entry *entry; + ACPI_STATUS error; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (acpi_disabled("pci_link")) { + return (0); + } + + TAILQ_FOREACH(entry, &acpi_prt_entries, links) { + if (entry->pcidev != dev) { + continue; + } + + error = acpi_pci_link_set_irq(entry->pci_link, + entry->pci_link->current_irq); + if (ACPI_FAILURE(error)) { + ACPI_DEBUG_PRINT((ACPI_DB_WARN, + "couldn't set IRQ to PCI interrupt link entry %s - %s\n", + acpi_name(entry->pci_link->handle), + AcpiFormatException(error))); + } + } + + return (0); +} + diff --git a/sys/dev/acpica/acpi_pcib.c b/sys/dev/acpica/acpi_pcib.c new file mode 100644 index 0000000..3677966 --- /dev/null +++ b/sys/dev/acpica/acpi_pcib.c @@ -0,0 +1,338 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/malloc.h> +#include <sys/kernel.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> +#include <dev/acpica/acpi_pcibvar.h> + +#include <machine/pci_cfgreg.h> +#include <pci/pcivar.h> +#include <pci/pcib_private.h> +#include "pcib_if.h" + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_BUS +ACPI_MODULE_NAME("PCI") + +int +acpi_pcib_attach(device_t dev, ACPI_BUFFER *prt, int busno) +{ + device_t child; + ACPI_STATUS status; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + /* + * Don't attach if we're not really there. + * + * XXX: This isn't entirely correct since we may be a PCI bus + * on a hot-plug docking station, etc. + */ + if (!acpi_DeviceIsPresent(dev)) + return_VALUE(ENXIO); + + /* + * Get the PCI interrupt routing table for this bus. + */ + prt->Length = ACPI_ALLOCATE_BUFFER; + status = AcpiGetIrqRoutingTable(acpi_get_handle(dev), prt); + if (ACPI_FAILURE(status)) + /* This is not an error, but it may reduce functionality. */ + device_printf(dev, + "could not get PCI interrupt routing table for %s - %s\n", + acpi_name(acpi_get_handle(dev)), AcpiFormatException(status)); + + /* + * Attach the PCI bus proper. + */ + if ((child = device_add_child(dev, "pci", busno)) == NULL) { + device_printf(device_get_parent(dev), "couldn't attach pci bus\n"); + return_VALUE(ENXIO); + } + + /* + * Now go scan the bus. + */ + acpi_pci_link_config(dev, prt, busno); + return_VALUE(bus_generic_attach(dev)); +} + +int +acpi_pcib_resume(device_t dev, ACPI_BUFFER *prt, int busno) +{ + acpi_pci_link_resume(dev, prt, busno); + return (bus_generic_resume(dev)); +} + +/* + * Route an interrupt for a child of the bridge. + * + * XXX clean up error messages + * + * XXX this function is somewhat bulky + */ +int +acpi_pcib_route_interrupt(device_t pcib, device_t dev, int pin, + ACPI_BUFFER *prtbuf) +{ + ACPI_PCI_ROUTING_TABLE *prt; + ACPI_HANDLE lnkdev; + ACPI_BUFFER crsbuf, prsbuf; + ACPI_RESOURCE *crsres, *prsres, resbuf; + ACPI_DEVICE_INFO devinfo; + ACPI_STATUS status; + u_int8_t *prtp; + int interrupt; + int i; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + crsbuf.Pointer = NULL; + prsbuf.Pointer = NULL; + interrupt = 255; + + /* ACPI numbers pins 0-3, not 1-4 like the BIOS */ + pin--; + + prtp = prtbuf->Pointer; + if (prtp == NULL) /* didn't get routing table */ + goto out; + + /* scan the table looking for this device */ + for (;;) { + prt = (ACPI_PCI_ROUTING_TABLE *)prtp; + + if (prt->Length == 0) /* end of table */ + goto out; + + /* + * Compare the slot number (high word of Address) and pin number + * (note that ACPI uses 0 for INTA) to check for a match. + * + * Note that the low word of the Address field (function number) + * is required by the specification to be 0xffff. We don't risk + * checking it here. + */ + if ((((prt->Address & 0xffff0000) >> 16) == pci_get_slot(dev)) && + (prt->Pin == pin)) { + if (bootverbose) + device_printf(pcib, "matched entry for %d.%d.INT%c (source %s)\n", + pci_get_bus(dev), pci_get_slot(dev), 'A' + pin, prt->Source); + break; + } + + /* skip to next entry */ + prtp += prt->Length; + } + + /* + * If source is empty/NULL, the source index is the global IRQ number. + */ + if ((prt->Source == NULL) || (prt->Source[0] == '\0')) { + if (bootverbose) + device_printf(pcib, "device is hardwired to IRQ %d\n", + prt->SourceIndex); + interrupt = prt->SourceIndex; + goto out; + } + + /* + * We have to find the source device (PCI interrupt link device) + */ + if (ACPI_FAILURE(AcpiGetHandle(ACPI_ROOT_OBJECT, prt->Source, &lnkdev))) { + device_printf(pcib, "couldn't find PCI interrupt link device %s\n", + prt->Source); + goto out; + } + + /* + * Verify that this is a PCI link device, and that it's present. + */ + if (ACPI_FAILURE(AcpiGetObjectInfo(lnkdev, &devinfo))) { + device_printf(pcib, "couldn't validate PCI interrupt link device %s\n", + prt->Source); + goto out; + } + if (!(devinfo.Valid & ACPI_VALID_HID) || strcmp("PNP0C0F", devinfo.HardwareId)) { + device_printf(pcib, "PCI interrupt link device %s has wrong _HID (%s)\n", + prt->Source, devinfo.HardwareId); + goto out; + } + if (devinfo.Valid & ACPI_VALID_STA && (devinfo.CurrentStatus & 0x9) != 0x9) { + device_printf(pcib, "PCI interrupt link device %s not present\n", + prt->Source); + goto out; + } + + /* + * Get the current and possible resources for the interrupt link device. + */ + crsbuf.Length = ACPI_ALLOCATE_BUFFER; + if (ACPI_FAILURE(status = AcpiGetCurrentResources(lnkdev, &crsbuf))) { + device_printf(pcib, "couldn't get PCI interrupt link device _CRS data - %s\n", + AcpiFormatException(status)); + goto out; /* this is fatal */ + } + prsbuf.Length = ACPI_ALLOCATE_BUFFER; + if (ACPI_FAILURE(status = AcpiGetPossibleResources(lnkdev, &prsbuf))) { + device_printf(pcib, "couldn't get PCI interrupt link device _PRS data - %s\n", + AcpiFormatException(status)); + /* this is not fatal, since it may be hardwired */ + } + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "got %ld bytes for %s._CRS\n", + (long)crsbuf.Length, acpi_name(lnkdev))); + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "got %ld bytes for %s._PRS\n", + (long)prsbuf.Length, acpi_name(lnkdev))); + + /* + * The interrupt may already be routed, so check _CRS first. We don't check the + * 'decoding' bit in the _STA result, since there's nothing in the spec that + * mandates it be set, however some BIOS' will set it if the decode is active. + * + * The Source Index points to the particular resource entry we're interested in. + */ + if (ACPI_FAILURE(acpi_FindIndexedResource(&crsbuf, prt->SourceIndex, &crsres))) { + device_printf(pcib, "_CRS buffer corrupt, cannot route interrupt\n"); + goto out; + } + + /* type-check the resource we've got */ + if (crsres->Id != ACPI_RSTYPE_IRQ) { /* XXX ACPI_RSTYPE_EXT_IRQ */ + device_printf(pcib, "_CRS resource entry has unsupported type %d\n", + crsres->Id); + goto out; + } + + /* if there's more than one interrupt, we are confused */ + if (crsres->Data.Irq.NumberOfInterrupts > 1) { + device_printf(pcib, "device has too many interrupts (%d)\n", + crsres->Data.Irq.NumberOfInterrupts); + goto out; + } + + /* + * If there's only one interrupt, and it's not zero, then we're already routed. + * + * Note that we could also check the 'decoding' bit in _STA, but can't depend on + * it since it's not part of the spec. + * + * XXX check ASL examples to see if this is an acceptable set of tests + */ + if ((crsres->Data.Irq.NumberOfInterrupts == 1) && (crsres->Data.Irq.Interrupts[0] != 0)) { + device_printf(pcib, "slot %d INT%c is routed to irq %d\n", + pci_get_slot(dev), 'A' + pin, crsres->Data.Irq.Interrupts[0]); + interrupt = crsres->Data.Irq.Interrupts[0]; + goto out; + } + + /* + * There isn't an interrupt, so we have to look at _PRS to get one. + * Get the set of allowed interrupts from the _PRS resource indexed by SourceIndex. + */ + if (prsbuf.Pointer == NULL) { + device_printf(pcib, "device has no routed interrupt and no _PRS on PCI interrupt link device\n"); + goto out; + } + if (ACPI_FAILURE(acpi_FindIndexedResource(&prsbuf, prt->SourceIndex, &prsres))) { + device_printf(pcib, "_PRS buffer corrupt, cannot route interrupt\n"); + goto out; + } + + /* type-check the resource we've got */ + if (prsres->Id != ACPI_RSTYPE_IRQ) { /* XXX ACPI_RSTYPE_EXT_IRQ */ + device_printf(pcib, "_PRS resource entry has unsupported type %d\n", + prsres->Id); + goto out; + } + + /* there has to be at least one interrupt available */ + if (prsres->Data.Irq.NumberOfInterrupts < 1) { + device_printf(pcib, "device has no interrupts\n"); + goto out; + } + + /* + * Pick an interrupt to use. Note that a more scientific approach than just + * taking the first one available would be desirable. + * + * The PCI BIOS $PIR table offers "preferred PCI interrupts", but ACPI doesn't + * seem to offer a similar mechanism, so picking a "good" interrupt here is a + * difficult task. + * + * Build a resource buffer and pass it to AcpiSetCurrentResources to route the + * new interrupt. + */ + device_printf(pcib, "possible interrupts:"); + for (i = 0; i < prsres->Data.Irq.NumberOfInterrupts; i++) + printf(" %d", prsres->Data.Irq.Interrupts[i]); + printf("\n"); + + if (crsbuf.Pointer != NULL) /* should never happen */ + AcpiOsFree(crsbuf.Pointer); + crsbuf.Pointer = NULL; + resbuf.Id = ACPI_RSTYPE_IRQ; + resbuf.Length = ACPI_SIZEOF_RESOURCE(ACPI_RESOURCE_IRQ); + resbuf.Data.Irq = prsres->Data.Irq; /* structure copy other fields */ + resbuf.Data.Irq.NumberOfInterrupts = 1; + resbuf.Data.Irq.Interrupts[0] = prsres->Data.Irq.Interrupts[0]; /* just take first... */ + if (ACPI_FAILURE(status = acpi_AppendBufferResource(&crsbuf, &resbuf))) { + device_printf(pcib, "couldn't route interrupt %d via %s, interrupt resource build failed - %s\n", + prsres->Data.Irq.Interrupts[0], acpi_name(lnkdev), AcpiFormatException(status)); + goto out; + } + if (ACPI_FAILURE(status = AcpiSetCurrentResources(lnkdev, &crsbuf))) { + device_printf(pcib, "couldn't route interrupt %d via %s - %s\n", + prsres->Data.Irq.Interrupts[0], acpi_name(lnkdev), AcpiFormatException(status)); + goto out; + } + + /* successful, return the interrupt we just routed */ + device_printf(pcib, "slot %d INT%c routed to irq %d via %s\n", + pci_get_slot(dev), 'A' + pin, prsres->Data.Irq.Interrupts[0], + acpi_name(lnkdev)); + interrupt = prsres->Data.Irq.Interrupts[0]; + + out: + if (crsbuf.Pointer != NULL) + AcpiOsFree(crsbuf.Pointer); + if (prsbuf.Pointer != NULL) + AcpiOsFree(prsbuf.Pointer); + + /* XXX APIC_IO interrupt mapping? */ + return_VALUE(interrupt); +} + diff --git a/sys/dev/acpica/acpi_pcib_acpi.c b/sys/dev/acpica/acpi_pcib_acpi.c new file mode 100644 index 0000000..7fca8be --- /dev/null +++ b/sys/dev/acpica/acpi_pcib_acpi.c @@ -0,0 +1,291 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/malloc.h> +#include <sys/kernel.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> + +#include <machine/pci_cfgreg.h> +#include <pci/pcivar.h> +#include <pci/pcib_private.h> +#include "pcib_if.h" + +#include <dev/acpica/acpi_pcibvar.h> + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_BUS +ACPI_MODULE_NAME("PCI_ACPI") + +struct acpi_hpcib_softc { + device_t ap_dev; + ACPI_HANDLE ap_handle; + + int ap_segment; /* analagous to Alpha 'hose' */ + int ap_bus; /* bios-assigned bus number */ + + ACPI_BUFFER ap_prt; /* interrupt routing table */ +}; + + +static int acpi_pcib_acpi_probe(device_t bus); +static int acpi_pcib_acpi_attach(device_t bus); +static int acpi_pcib_acpi_resume(device_t bus); +static int acpi_pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result); +static int acpi_pcib_write_ivar(device_t dev, device_t child, int which, uintptr_t value); +static u_int32_t acpi_pcib_read_config(device_t dev, int bus, int slot, int func, int reg, int bytes); +static void acpi_pcib_write_config(device_t dev, int bus, int slot, int func, int reg, + u_int32_t data, int bytes); +static int acpi_pcib_acpi_route_interrupt(device_t pcib, + device_t dev, int pin); + +static device_method_t acpi_pcib_acpi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_pcib_acpi_probe), + DEVMETHOD(device_attach, acpi_pcib_acpi_attach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, acpi_pcib_acpi_resume), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_read_ivar, acpi_pcib_read_ivar), + DEVMETHOD(bus_write_ivar, acpi_pcib_write_ivar), + DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + + /* pcib interface */ + DEVMETHOD(pcib_maxslots, pcib_maxslots), + DEVMETHOD(pcib_read_config, acpi_pcib_read_config), + DEVMETHOD(pcib_write_config, acpi_pcib_write_config), + DEVMETHOD(pcib_route_interrupt, acpi_pcib_acpi_route_interrupt), + + {0, 0} +}; + +static driver_t acpi_pcib_acpi_driver = { + "pcib", + acpi_pcib_acpi_methods, + sizeof(struct acpi_hpcib_softc), +}; + +DRIVER_MODULE(acpi_pcib, acpi, acpi_pcib_acpi_driver, pcib_devclass, 0, 0); + +static int +acpi_pcib_acpi_probe(device_t dev) +{ + + if ((acpi_get_type(dev) == ACPI_TYPE_DEVICE) && + !acpi_disabled("pci") && + acpi_MatchHid(dev, "PNP0A03")) { + + if (!pci_cfgregopen()) + return(ENXIO); + + /* + * Set device description + */ + device_set_desc(dev, "ACPI Host-PCI bridge"); + return(0); + } + return(ENXIO); +} + +static int +acpi_pcib_acpi_attach(device_t dev) +{ + struct acpi_hpcib_softc *sc; + ACPI_STATUS status; + uint addr, slot, func, busok; + uint8_t busno; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + sc = device_get_softc(dev); + sc->ap_dev = dev; + sc->ap_handle = acpi_get_handle(dev); + + /* + * Get our base bus number by evaluating _BBN. + * If this doesn't work, we assume we're bus number 0. + * + * XXX note that it may also not exist in the case where we are + * meant to use a private configuration space mechanism for this bus, + * so we should dig out our resources and check to see if we have + * anything like that. How do we do this? + * XXX If we have the requisite information, and if we don't think the + * default PCI configuration space handlers can deal with this bus, + * we should attach our own handler. + * XXX invoke _REG on this for the PCI config space address space? + * XXX It seems many BIOS's with multiple Host-PCI bridges do not set + * _BBN correctly. They set _BBN to zero for all bridges. Thus, + * if _BBN is zero and pcib0 already exists, we try to read our + * bus number from the configuration registers at address _ADR. + */ + status = acpi_EvaluateInteger(sc->ap_handle, "_BBN", &sc->ap_bus); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) { + device_printf(dev, "could not evaluate _BBN - %s\n", + AcpiFormatException(status)); + return_VALUE(ENXIO); + } else { + /* if it's not found, assume 0 */ + sc->ap_bus = 0; + } + } + + /* + * If the bus is zero and pcib0 already exists, read the bus number + * via PCI config space. + */ + busok = 1; + if (sc->ap_bus == 0 && devclass_get_device(pcib_devclass, 0) != dev) { + busok = 0; + status = acpi_EvaluateInteger(sc->ap_handle, "_ADR", &addr); + if (ACPI_FAILURE(status)) { + if (status != AE_NOT_FOUND) { + device_printf(dev, "could not evaluate _ADR - %s\n", + AcpiFormatException(status)); + return_VALUE(ENXIO); + } else + device_printf(dev, "could not determine config space address\n"); + } else { + /* XXX: We assume bus 0. */ + slot = addr >> 16; + func = addr & 0xffff; + if (bootverbose) + device_printf(dev, "reading config registers from 0:%d:%d\n", + slot, func); + if (host_pcib_get_busno(pci_cfgregread, 0, slot, func, &busno) == 0) + device_printf(dev, "could not read bus number from config space\n"); + else { + sc->ap_bus = busno; + busok = 1; + } + } + } + + /* + * If nothing else worked, hope that ACPI at least lays out the + * host-PCI bridges in order and that as a result our unit number + * is actually our bus number. There are several reasons this + * might not be true. + */ + if (busok == 0) { + sc->ap_bus = device_get_unit(dev); + device_printf(dev, "trying bus number %d\n", sc->ap_bus); + } + + /* + * Get our segment number by evaluating _SEG + * It's OK for this to not exist. + */ + if (ACPI_FAILURE(status = acpi_EvaluateInteger(sc->ap_handle, "_SEG", &sc->ap_segment))) { + if (status != AE_NOT_FOUND) { + device_printf(dev, "could not evaluate _SEG - %s\n", AcpiFormatException(status)); + return_VALUE(ENXIO); + } + /* if it's not found, assume 0 */ + sc->ap_segment = 0; + } + + return (acpi_pcib_attach(dev, &sc->ap_prt, sc->ap_bus)); +} + +static int +acpi_pcib_acpi_resume(device_t dev) +{ + struct acpi_hpcib_softc *sc = device_get_softc(dev); + + return (acpi_pcib_resume(dev, &sc->ap_prt, sc->ap_bus)); +} + +/* + * Support for standard PCI bridge ivars. + */ +static int +acpi_pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) +{ + struct acpi_hpcib_softc *sc = device_get_softc(dev); + + switch (which) { + case PCIB_IVAR_BUS: + *result = sc->ap_bus; + return(0); + case ACPI_IVAR_HANDLE: + *result = (uintptr_t)sc->ap_handle; + return(0); + } + return(ENOENT); +} + +static int +acpi_pcib_write_ivar(device_t dev, device_t child, int which, uintptr_t value) +{ + struct acpi_hpcib_softc *sc = device_get_softc(dev); + + switch (which) { + case PCIB_IVAR_BUS: + sc->ap_bus = value; + return(0); + } + return(ENOENT); +} + +static u_int32_t +acpi_pcib_read_config(device_t dev, int bus, int slot, int func, int reg, int bytes) +{ + return(pci_cfgregread(bus, slot, func, reg, bytes)); +} + +static void +acpi_pcib_write_config(device_t dev, int bus, int slot, int func, int reg, u_int32_t data, int bytes) +{ + pci_cfgregwrite(bus, slot, func, reg, data, bytes); +} + +static int +acpi_pcib_acpi_route_interrupt(device_t pcib, device_t dev, int pin) +{ + struct acpi_hpcib_softc *sc; + + /* find the bridge softc */ + sc = device_get_softc(pcib); + return (acpi_pcib_route_interrupt(pcib, dev, pin, &sc->ap_prt)); +} diff --git a/sys/dev/acpica/acpi_pcib_pci.c b/sys/dev/acpica/acpi_pcib_pci.c new file mode 100644 index 0000000..5909e65 --- /dev/null +++ b/sys/dev/acpica/acpi_pcib_pci.c @@ -0,0 +1,166 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#include "opt_acpi.h" + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/malloc.h> +#include <sys/kernel.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> +#include <dev/acpica/acpi_pcibvar.h> + +#include <machine/pci_cfgreg.h> +#include <pci/pcivar.h> +#include <pci/pcireg.h> +#include <pci/pcib_private.h> + +#include "pcib_if.h" + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_BUS +ACPI_MODULE_NAME("PCI_PCI") + +struct acpi_pcib_softc { + struct pcib_softc ap_pcibsc; + ACPI_HANDLE ap_handle; + ACPI_BUFFER ap_prt; /* interrupt routing table */ +}; + +struct acpi_pcib_lookup_info { + UINT32 address; + ACPI_HANDLE handle; +}; + +static int acpi_pcib_pci_probe(device_t bus); +static int acpi_pcib_pci_attach(device_t bus); +static int acpi_pcib_pci_resume(device_t bus); +static int acpi_pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result); +static int acpi_pcib_pci_route_interrupt(device_t pcib, + device_t dev, int pin); + +static device_method_t acpi_pcib_pci_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_pcib_pci_probe), + DEVMETHOD(device_attach, acpi_pcib_pci_attach), + DEVMETHOD(device_shutdown, bus_generic_shutdown), + DEVMETHOD(device_suspend, bus_generic_suspend), + DEVMETHOD(device_resume, acpi_pcib_pci_resume), + + /* Bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + DEVMETHOD(bus_read_ivar, acpi_pcib_read_ivar), + DEVMETHOD(bus_write_ivar, pcib_write_ivar), + DEVMETHOD(bus_alloc_resource, pcib_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_release_resource), + DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), + DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + + /* pcib interface */ + DEVMETHOD(pcib_maxslots, pcib_maxslots), + DEVMETHOD(pcib_read_config, pcib_read_config), + DEVMETHOD(pcib_write_config, pcib_write_config), + DEVMETHOD(pcib_route_interrupt, acpi_pcib_pci_route_interrupt), + + {0, 0} +}; + +static driver_t acpi_pcib_pci_driver = { + "pcib", + acpi_pcib_pci_methods, + sizeof(struct acpi_pcib_softc), +}; + +DRIVER_MODULE(acpi_pcib, pci, acpi_pcib_pci_driver, pcib_devclass, 0, 0); + +static int +acpi_pcib_pci_probe(device_t dev) +{ + + if ((pci_get_class(dev) != PCIC_BRIDGE) || + (pci_get_subclass(dev) != PCIS_BRIDGE_PCI) || + acpi_disabled("pci")) + return (ENXIO); + if (acpi_get_handle(dev) == NULL) + return (ENXIO); + if (!pci_cfgregopen()) + return (ENXIO); + + device_set_desc(dev, "ACPI PCI-PCI bridge"); + return (-1000); +} + +static int +acpi_pcib_pci_attach(device_t dev) +{ + struct acpi_pcib_softc *sc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + pcib_attach_common(dev); + sc = device_get_softc(dev); + sc->ap_handle = acpi_get_handle(dev); + return (acpi_pcib_attach(dev, &sc->ap_prt, sc->ap_pcibsc.secbus)); +} + +static int +acpi_pcib_pci_resume(device_t dev) +{ + struct acpi_pcib_softc *sc = device_get_softc(dev); + + return (acpi_pcib_resume(dev, &sc->ap_prt, sc->ap_pcibsc.secbus)); +} + +static int +acpi_pcib_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) +{ + struct acpi_pcib_softc *sc = device_get_softc(dev); + + switch (which) { + case ACPI_IVAR_HANDLE: + *result = (uintptr_t)sc->ap_handle; + return(0); + } + return(pcib_read_ivar(dev, child, which, result)); +} + +static int +acpi_pcib_pci_route_interrupt(device_t pcib, device_t dev, int pin) +{ + struct acpi_pcib_softc *sc; + + sc = device_get_softc(pcib); + return (acpi_pcib_route_interrupt(pcib, dev, pin, &sc->ap_prt)); +} diff --git a/sys/dev/acpica/acpi_pcibvar.h b/sys/dev/acpica/acpi_pcibvar.h new file mode 100644 index 0000000..a7a5f20 --- /dev/null +++ b/sys/dev/acpica/acpi_pcibvar.h @@ -0,0 +1,41 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _ACPI_PCIBVAR_H_ +#define _ACPI_PCIBVAR_H_ + +int acpi_pcib_attach(device_t bus, ACPI_BUFFER *prt, int busno); +int acpi_pcib_route_interrupt(device_t pcib, device_t dev, int pin, + ACPI_BUFFER *ptrbuf); +int acpi_pcib_resume(device_t bus, ACPI_BUFFER *prt, int busno); + +int acpi_pci_link_config(device_t pcib, ACPI_BUFFER *prt, int busno); +int acpi_pci_link_resume(device_t pcib, ACPI_BUFFER *prt, int busno); + +#endif diff --git a/sys/dev/acpica/acpi_powerres.c b/sys/dev/acpica/acpi_powerres.c new file mode 100644 index 0000000..fc42695 --- /dev/null +++ b/sys/dev/acpica/acpi_powerres.c @@ -0,0 +1,654 @@ +/*- + * Copyright (c) 2001 Michael Smith + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_acpi.h" /* XXX trim includes */ +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/proc.h> +#include <sys/malloc.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/ioccom.h> +#include <sys/reboot.h> +#include <sys/sysctl.h> +#include <sys/systm.h> +#include <sys/ctype.h> + +#include <machine/clock.h> + +#include <machine/resource.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> +#include <dev/acpica/acpiio.h> + +/* + * ACPI power resource management. + * + * Power resource behaviour is slightly complicated by the fact that + * a single power resource may provide power for more than one device. + * Thus, we must track the device(s) being powered by a given power + * resource, and only deactivate it when there are no powered devices. + * + * Note that this only manages resources for known devices. There is an + * ugly case where we may turn of power to a device which is in use because + * we don't know that it depends on a given resource. We should perhaps + * try to be smarter about this, but a more complete solution would involve + * scanning all of the ACPI namespace to find devices we're not currently + * aware of, and this raises questions about whether they should be left + * on, turned off, etc. + * + * XXX locking + */ + +MALLOC_DEFINE(M_ACPIPWR, "acpipwr", "ACPI power resources"); + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_POWER +ACPI_MODULE_NAME("POWERRES") + +/* return values from _STA on a power resource */ +#define ACPI_PWR_OFF 0 +#define ACPI_PWR_ON 1 + +/* + * A relationship between a power resource and a consumer. + */ +struct acpi_powerreference { + struct acpi_powerconsumer *ar_consumer; + struct acpi_powerresource *ar_resource; + TAILQ_ENTRY(acpi_powerreference) ar_rlink; /* link on resource list */ + TAILQ_ENTRY(acpi_powerreference) ar_clink; /* link on consumer */ +}; + +/* + * A power-managed device. + */ +struct acpi_powerconsumer { + ACPI_HANDLE ac_consumer; /* device which is powered */ + int ac_state; + TAILQ_ENTRY(acpi_powerconsumer) ac_link; + TAILQ_HEAD(,acpi_powerreference) ac_references; +}; + +/* + * A power resource. + */ +struct acpi_powerresource { + TAILQ_ENTRY(acpi_powerresource) ap_link; + TAILQ_HEAD(,acpi_powerreference) ap_references; + ACPI_HANDLE ap_resource; /* the resource's handle */ + ACPI_INTEGER ap_systemlevel; + ACPI_INTEGER ap_order; +}; + +static TAILQ_HEAD(acpi_powerresource_list, acpi_powerresource) acpi_powerresources; +static TAILQ_HEAD(acpi_powerconsumer_list, acpi_powerconsumer) acpi_powerconsumers; + +static ACPI_STATUS acpi_pwr_register_consumer(ACPI_HANDLE consumer); +static ACPI_STATUS acpi_pwr_deregister_consumer(ACPI_HANDLE consumer); +static ACPI_STATUS acpi_pwr_register_resource(ACPI_HANDLE res); +static ACPI_STATUS acpi_pwr_deregister_resource(ACPI_HANDLE res); +static void acpi_pwr_reference_resource(ACPI_OBJECT *obj, void *arg); +static ACPI_STATUS acpi_pwr_switch_power(void); +static struct acpi_powerresource *acpi_pwr_find_resource(ACPI_HANDLE res); +static struct acpi_powerconsumer *acpi_pwr_find_consumer(ACPI_HANDLE consumer); + +/* + * Initialise our lists. + */ +static void +acpi_pwr_init(void *junk) +{ + TAILQ_INIT(&acpi_powerresources); + TAILQ_INIT(&acpi_powerconsumers); +} +SYSINIT(acpi_powerresource, SI_SUB_TUNABLES, SI_ORDER_ANY, acpi_pwr_init, NULL); + +/* + * Register a power resource. + * + * It's OK to call this if we already know about the resource. + */ +static ACPI_STATUS +acpi_pwr_register_resource(ACPI_HANDLE res) +{ + ACPI_STATUS status; + ACPI_BUFFER buf; + ACPI_OBJECT *obj; + struct acpi_powerresource *rp, *srp; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + rp = NULL; + buf.Pointer = NULL; + + /* look to see if we know about this resource */ + if (acpi_pwr_find_resource(res) != NULL) + return_ACPI_STATUS(AE_OK); /* already know about it */ + + /* allocate a new resource */ + if ((rp = malloc(sizeof(*rp), M_ACPIPWR, M_NOWAIT | M_ZERO)) == NULL) { + status = AE_NO_MEMORY; + goto out; + } + TAILQ_INIT(&rp->ap_references); + rp->ap_resource = res; + + /* get the Power Resource object */ + buf.Length = ACPI_ALLOCATE_BUFFER; + if (ACPI_FAILURE(status = AcpiEvaluateObject(res, NULL, NULL, &buf))) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "no power resource object\n")); + goto out; + } + obj = buf.Pointer; + if (obj->Type != ACPI_TYPE_POWER) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "questionable power resource object %s\n", acpi_name(res))); + status = AE_TYPE; + goto out; + } + rp->ap_systemlevel = obj->PowerResource.SystemLevel; + rp->ap_order = obj->PowerResource.ResourceOrder; + + /* sort the resource into the list */ + status = AE_OK; + srp = TAILQ_FIRST(&acpi_powerresources); + if ((srp == NULL) || (rp->ap_order < srp->ap_order)) { + TAILQ_INSERT_HEAD(&acpi_powerresources, rp, ap_link); + goto done; + } + TAILQ_FOREACH(srp, &acpi_powerresources, ap_link) + if (rp->ap_order < srp->ap_order) { + TAILQ_INSERT_BEFORE(srp, rp, ap_link); + goto done; + } + TAILQ_INSERT_TAIL(&acpi_powerresources, rp, ap_link); + + done: + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "registered power resource %s\n", acpi_name(res))); + out: + if (buf.Pointer != NULL) + AcpiOsFree(buf.Pointer); + if (ACPI_FAILURE(status) && (rp != NULL)) + free(rp, M_ACPIPWR); + return_ACPI_STATUS(status); +} + +/* + * Deregister a power resource. + */ +static ACPI_STATUS +acpi_pwr_deregister_resource(ACPI_HANDLE res) +{ + struct acpi_powerresource *rp; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + rp = NULL; + + /* find the resource */ + if ((rp = acpi_pwr_find_resource(res)) == NULL) + return_ACPI_STATUS(AE_BAD_PARAMETER); + + /* check that there are no consumers referencing this resource */ + if (TAILQ_FIRST(&rp->ap_references) != NULL) + return_ACPI_STATUS(AE_BAD_PARAMETER); + + /* pull it off the list and free it */ + TAILQ_REMOVE(&acpi_powerresources, rp, ap_link); + free(rp, M_ACPIPWR); + + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "deregistered power resource %s\n", acpi_name(res))); + + return_ACPI_STATUS(AE_OK); +} + +/* + * Register a power consumer. + * + * It's OK to call this if we already know about the consumer. + */ +static ACPI_STATUS +acpi_pwr_register_consumer(ACPI_HANDLE consumer) +{ + struct acpi_powerconsumer *pc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + /* check to see whether we know about this consumer already */ + if ((pc = acpi_pwr_find_consumer(consumer)) != NULL) + return_ACPI_STATUS(AE_OK); + + /* allocate a new power consumer */ + if ((pc = malloc(sizeof(*pc), M_ACPIPWR, M_NOWAIT)) == NULL) + return_ACPI_STATUS(AE_NO_MEMORY); + TAILQ_INSERT_HEAD(&acpi_powerconsumers, pc, ac_link); + TAILQ_INIT(&pc->ac_references); + pc->ac_consumer = consumer; + + pc->ac_state = ACPI_STATE_UNKNOWN; /* XXX we should try to find its current state */ + + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "registered power consumer %s\n", acpi_name(consumer))); + + return_ACPI_STATUS(AE_OK); +} + +/* + * Deregister a power consumer. + * + * This should only be done once the consumer has been powered off. + * (XXX is this correct? Check once implemented) + */ +static ACPI_STATUS +acpi_pwr_deregister_consumer(ACPI_HANDLE consumer) +{ + struct acpi_powerconsumer *pc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + /* find the consumer */ + if ((pc = acpi_pwr_find_consumer(consumer)) == NULL) + return_ACPI_STATUS(AE_BAD_PARAMETER); + + /* make sure the consumer's not referencing anything right now */ + if (TAILQ_FIRST(&pc->ac_references) != NULL) + return_ACPI_STATUS(AE_BAD_PARAMETER); + + /* pull the consumer off the list and free it */ + TAILQ_REMOVE(&acpi_powerconsumers, pc, ac_link); + + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "deregistered power consumer %s\n", acpi_name(consumer))); + + return_ACPI_STATUS(AE_OK); +} + +/* + * Set a power consumer to a particular power state. + */ +ACPI_STATUS +acpi_pwr_switch_consumer(ACPI_HANDLE consumer, int state) +{ + struct acpi_powerconsumer *pc; + struct acpi_powerreference *pr; + ACPI_HANDLE method_handle, reslist_handle, pr0_handle; + ACPI_BUFFER reslist_buffer; + ACPI_OBJECT *reslist_object; + ACPI_STATUS status; + char *method_name, *reslist_name; + int res_changed; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + /* find the consumer */ + if ((pc = acpi_pwr_find_consumer(consumer)) == NULL) { + if (ACPI_FAILURE(status = acpi_pwr_register_consumer(consumer))) + return_ACPI_STATUS(status); + if ((pc = acpi_pwr_find_consumer(consumer)) == NULL) { + return_ACPI_STATUS(AE_ERROR); /* something very wrong */ + } + } + + /* check for valid transitions */ + if ((pc->ac_state == ACPI_STATE_D3) && (state != ACPI_STATE_D0)) + return_ACPI_STATUS(AE_BAD_PARAMETER); /* can only go to D0 from D3 */ + + /* find transition mechanism(s) */ + switch(state) { + case ACPI_STATE_D0: + method_name = "_PS0"; + reslist_name = "_PR0"; + break; + case ACPI_STATE_D1: + method_name = "_PS1"; + reslist_name = "_PR1"; + break; + case ACPI_STATE_D2: + method_name = "_PS2"; + reslist_name = "_PR2"; + break; + case ACPI_STATE_D3: + method_name = "_PS3"; + reslist_name = "_PR3"; + break; + default: + return_ACPI_STATUS(AE_BAD_PARAMETER); + } + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "setup to switch %s D%d -> D%d\n", + acpi_name(consumer), pc->ac_state, state)); + + /* + * Verify that this state is supported, ie. one of method or + * reslist must be present. We need to do this before we go + * dereferencing resources (since we might be trying to go to + * a state we don't support). + * + * Note that if any states are supported, the device has to + * support D0 and D3. It's never an error to try to go to + * D0. + */ + reslist_buffer.Pointer = NULL; + reslist_object = NULL; + if (ACPI_FAILURE(AcpiGetHandle(consumer, method_name, &method_handle))) + method_handle = NULL; + if (ACPI_FAILURE(AcpiGetHandle(consumer, reslist_name, &reslist_handle))) + reslist_handle = NULL; + if ((reslist_handle == NULL) && (method_handle == NULL)) { + if (state == ACPI_STATE_D0) { + pc->ac_state = ACPI_STATE_D0; + return_ACPI_STATUS(AE_OK); + } + if (state != ACPI_STATE_D3) { + goto bad; + } + + /* turn off the resources listed in _PR0 to go to D3. */ + if (ACPI_FAILURE(AcpiGetHandle(consumer, "_PR0", &pr0_handle))) { + goto bad; + } + reslist_buffer.Length = ACPI_ALLOCATE_BUFFER; + if (ACPI_FAILURE(status = AcpiEvaluateObject(pr0_handle, NULL, NULL, &reslist_buffer))) { + goto bad; + } + reslist_object = (ACPI_OBJECT *)reslist_buffer.Pointer; + if ((reslist_object->Type != ACPI_TYPE_PACKAGE) || + (reslist_object->Package.Count == 0)) { + goto bad; + } + AcpiOsFree(reslist_buffer.Pointer); + reslist_buffer.Pointer = NULL; + reslist_object = NULL; + } + + /* + * Check that we can actually fetch the list of power resources + */ + if (reslist_handle != NULL) { + reslist_buffer.Length = ACPI_ALLOCATE_BUFFER; + if (ACPI_FAILURE(status = AcpiEvaluateObject(reslist_handle, NULL, NULL, &reslist_buffer))) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "can't evaluate resource list %s\n", + acpi_name(reslist_handle))); + goto out; + } + reslist_object = (ACPI_OBJECT *)reslist_buffer.Pointer; + if (reslist_object->Type != ACPI_TYPE_PACKAGE) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "resource list is not ACPI_TYPE_PACKAGE (%d)\n", + reslist_object->Type)); + status = AE_TYPE; + goto out; + } + } + + /* + * Now we are ready to switch, so kill off any current power resource references. + */ + res_changed = 0; + while((pr = TAILQ_FIRST(&pc->ac_references)) != NULL) { + res_changed = 1; + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "removing reference to %s\n", acpi_name(pr->ar_resource->ap_resource))); + TAILQ_REMOVE(&pr->ar_resource->ap_references, pr, ar_rlink); + TAILQ_REMOVE(&pc->ac_references, pr, ar_clink); + free(pr, M_ACPIPWR); + } + + /* + * Add new power resource references, if we have any. Traverse the + * package that we got from evaluating reslist_handle, and look up each + * of the resources that are referenced. + */ + if (reslist_object != NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "referencing %d new resources\n", + reslist_object->Package.Count)); + acpi_ForeachPackageObject(reslist_object, acpi_pwr_reference_resource, pc); + res_changed = 1; + } + + /* + * If we changed anything in the resource list, we need to run a switch + * pass now. + */ + if (ACPI_FAILURE(status = acpi_pwr_switch_power())) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "failed to correctly switch resources to move %s to D%d\n", + acpi_name(consumer), state)); + goto out; /* XXX is this appropriate? Should we return to previous state? */ + } + + /* invoke power state switch method (if present) */ + if (method_handle != NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "invoking state transition method %s\n", + acpi_name(method_handle))); + if (ACPI_FAILURE(status = AcpiEvaluateObject(method_handle, NULL, NULL, NULL))) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "failed to set state - %s\n", + AcpiFormatException(status))); + pc->ac_state = ACPI_STATE_UNKNOWN; + goto out; /* XXX Should we return to previous state? */ + } + } + + /* transition was successful */ + pc->ac_state = state; + return_ACPI_STATUS(AE_OK); + + bad: + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "attempt to set unsupported state D%d\n", + state)); + status = AE_BAD_PARAMETER; + + out: + if (reslist_buffer.Pointer != NULL) + AcpiOsFree(reslist_buffer.Pointer); + return_ACPI_STATUS(status); +} + +/* + * Called to create a reference between a power consumer and a power resource + * identified in the object. + */ +static void +acpi_pwr_reference_resource(ACPI_OBJECT *obj, void *arg) +{ + struct acpi_powerconsumer *pc = (struct acpi_powerconsumer *)arg; + struct acpi_powerreference *pr; + struct acpi_powerresource *rp; + ACPI_HANDLE res; + ACPI_STATUS status; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + /* check the object type */ + switch (obj->Type) { + case ACPI_TYPE_ANY: + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "building reference from %s to %s\n", + acpi_name(pc->ac_consumer), acpi_name(obj->Reference.Handle))); + + res = obj->Reference.Handle; + break; + + case ACPI_TYPE_STRING: + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "building reference from %s to %s\n", + acpi_name(pc->ac_consumer), obj->String.Pointer)); + + /* get the handle of the resource */ + if (ACPI_FAILURE(status = AcpiGetHandle(NULL, obj->String.Pointer, &res))) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "couldn't find power resource %s\n", + obj->String.Pointer)); + return_VOID; + } + break; + + default: + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "don't know how to create a power reference to object type %d\n", + obj->Type)); + return_VOID; + } + + /* create/look up the resource */ + if (ACPI_FAILURE(status = acpi_pwr_register_resource(res))) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "couldn't register power resource %s - %s\n", + obj->String.Pointer, AcpiFormatException(status))); + return_VOID; + } + if ((rp = acpi_pwr_find_resource(res)) == NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "power resource list corrupted\n")); + return_VOID; + } + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "found power resource %s\n", acpi_name(rp->ap_resource))); + + /* create a reference between the consumer and resource */ + if ((pr = malloc(sizeof(*pr), M_ACPIPWR, M_NOWAIT | M_ZERO)) == NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "couldn't allocate memory for a power consumer reference\n")); + return_VOID; + } + pr->ar_consumer = pc; + pr->ar_resource = rp; + TAILQ_INSERT_TAIL(&pc->ac_references, pr, ar_clink); + TAILQ_INSERT_TAIL(&rp->ap_references, pr, ar_rlink); + + return_VOID; +} + + +/* + * Switch power resources to conform to the desired state. + * + * Consumers may have modified the power resource list in an arbitrary + * fashion; we sweep it in sequence order. + */ +static ACPI_STATUS +acpi_pwr_switch_power(void) +{ + struct acpi_powerresource *rp; + ACPI_STATUS status; + int cur; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + /* + * Sweep the list forwards turning things on. + */ + TAILQ_FOREACH(rp, &acpi_powerresources, ap_link) { + if (TAILQ_FIRST(&rp->ap_references) == NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "%s has no references, not turning on\n", + acpi_name(rp->ap_resource))); + continue; + } + + /* we could cache this if we trusted it not to change under us */ + if (ACPI_FAILURE(status = acpi_EvaluateInteger(rp->ap_resource, "_STA", &cur))) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "can't get status of %s - %d\n", + acpi_name(rp->ap_resource), status)); + continue; /* XXX is this correct? Always switch if in doubt? */ + } + + /* + * Switch if required. Note that we ignore the result of the switch + * effort; we don't know what to do if it fails, so checking wouldn't + * help much. + */ + if (cur != ACPI_PWR_ON) { + if (ACPI_FAILURE(status = AcpiEvaluateObject(rp->ap_resource, "_ON", NULL, NULL))) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "failed to switch %s on - %s\n", + acpi_name(rp->ap_resource), AcpiFormatException(status))); + } else { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "switched %s on\n", acpi_name(rp->ap_resource))); + } + } else { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "%s is already on\n", acpi_name(rp->ap_resource))); + } + } + + /* + * Sweep the list backwards turning things off. + */ + TAILQ_FOREACH_REVERSE(rp, &acpi_powerresources, acpi_powerresource_list, ap_link) { + if (TAILQ_FIRST(&rp->ap_references) != NULL) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "%s has references, not turning off\n", + acpi_name(rp->ap_resource))); + continue; + } + + /* we could cache this if we trusted it not to change under us */ + if (ACPI_FAILURE(status = acpi_EvaluateInteger(rp->ap_resource, "_STA", &cur))) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "can't get status of %s - %d\n", + acpi_name(rp->ap_resource), status)); + continue; /* XXX is this correct? Always switch if in doubt? */ + } + + /* + * Switch if required. Note that we ignore the result of the switch + * effort; we don't know what to do if it fails, so checking wouldn't + * help much. + */ + if (cur != ACPI_PWR_OFF) { + if (ACPI_FAILURE(status = AcpiEvaluateObject(rp->ap_resource, "_OFF", NULL, NULL))) { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "failed to switch %s off - %s\n", + acpi_name(rp->ap_resource), AcpiFormatException(status))); + } else { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "switched %s off\n", acpi_name(rp->ap_resource))); + } + } else { + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "%s is already off\n", acpi_name(rp->ap_resource))); + } + } + return_ACPI_STATUS(AE_OK); +} + +/* + * Find a power resource's control structure. + */ +static struct acpi_powerresource * +acpi_pwr_find_resource(ACPI_HANDLE res) +{ + struct acpi_powerresource *rp; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + TAILQ_FOREACH(rp, &acpi_powerresources, ap_link) + if (rp->ap_resource == res) + break; + return_PTR(rp); +} + +/* + * Find a power consumer's control structure. + */ +static struct acpi_powerconsumer * +acpi_pwr_find_consumer(ACPI_HANDLE consumer) +{ + struct acpi_powerconsumer *pc; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + TAILQ_FOREACH(pc, &acpi_powerconsumers, ac_link) + if (pc->ac_consumer == consumer) + break; + return_PTR(pc); +} + diff --git a/sys/dev/acpica/acpi_resource.c b/sys/dev/acpica/acpi_resource.c new file mode 100644 index 0000000..9dc0e3c --- /dev/null +++ b/sys/dev/acpica/acpi_resource.c @@ -0,0 +1,592 @@ +/*- + * Copyright (c) 2000 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/bus.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_BUS +ACPI_MODULE_NAME("RESOURCE") + +/* + * Fetch a device's resources and associate them with the device. + * + * Note that it might be nice to also locate ACPI-specific resource items, such + * as GPE bits. + * + * We really need to split the resource-fetching code out from the + * resource-parsing code, since we may want to use the parsing + * code for _PRS someday. + */ +ACPI_STATUS +acpi_parse_resources(device_t dev, ACPI_HANDLE handle, struct acpi_parse_resource_set *set) +{ + ACPI_BUFFER buf; + ACPI_RESOURCE *res; + char *curr, *last; + ACPI_STATUS status; + void *context; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + /* + * Special-case some devices that abuse _PRS/_CRS to mean + * something other than "I consume this resource". + * + * XXX do we really need this? It's only relevant once + * we start always-allocating these resources, and even + * then, the only special-cased device is likely to be + * the PCI interrupt link. + */ + + /* + * Fetch the device's current resources. + */ + buf.Length = ACPI_ALLOCATE_BUFFER; + if (ACPI_FAILURE((status = AcpiGetCurrentResources(handle, &buf)))) { + if (status != AE_NOT_FOUND) + printf("can't fetch resources for %s - %s\n", + acpi_name(handle), AcpiFormatException(status)); + return_ACPI_STATUS(status); + } + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "%s - got %ld bytes of resources\n", + acpi_name(handle), (long)buf.Length)); + set->set_init(dev, &context); + + /* + * Iterate through the resources + */ + curr = buf.Pointer; + last = (char *)buf.Pointer + buf.Length; + while (curr < last) { + res = (ACPI_RESOURCE *)curr; + curr += res->Length; + + /* + * Handle the individual resource types + */ + switch(res->Id) { + case ACPI_RSTYPE_END_TAG: + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "EndTag\n")); + curr = last; + break; + + case ACPI_RSTYPE_FIXED_IO: + if (res->Data.FixedIo.RangeLength <= 0) + break; + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "FixedIo 0x%x/%d\n", + res->Data.FixedIo.BaseAddress, + res->Data.FixedIo.RangeLength)); + set->set_ioport(dev, context, + res->Data.FixedIo.BaseAddress, + res->Data.FixedIo.RangeLength); + break; + + case ACPI_RSTYPE_IO: + if (res->Data.Io.RangeLength <= 0) + break; + if (res->Data.Io.MinBaseAddress == res->Data.Io.MaxBaseAddress) { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Io 0x%x/%d\n", + res->Data.Io.MinBaseAddress, + res->Data.Io.RangeLength)); + set->set_ioport(dev, context, + res->Data.Io.MinBaseAddress, + res->Data.Io.RangeLength); + } else { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Io 0x%x-0x%x/%d\n", + res->Data.Io.MinBaseAddress, + res->Data.Io.MaxBaseAddress, + res->Data.Io.RangeLength)); + set->set_iorange(dev, context, + res->Data.Io.MinBaseAddress, + res->Data.Io.MaxBaseAddress, + res->Data.Io.RangeLength, res->Data.Io.Alignment); + } + break; + + case ACPI_RSTYPE_FIXED_MEM32: + if (res->Data.FixedMemory32.RangeLength <= 0) + break; + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "FixedMemory32 0x%x/%d\n", + res->Data.FixedMemory32.RangeBaseAddress, + res->Data.FixedMemory32.RangeLength)); + set->set_memory(dev, context, res->Data.FixedMemory32.RangeBaseAddress, + res->Data.FixedMemory32.RangeLength); + break; + + case ACPI_RSTYPE_MEM32: + if (res->Data.Memory32.RangeLength <= 0) + break; + if (res->Data.Memory32.MinBaseAddress == res->Data.Memory32.MaxBaseAddress) { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Memory32 0x%x/%d\n", + res->Data.Memory32.MinBaseAddress, + res->Data.Memory32.RangeLength)); + set->set_memory(dev, context, + res->Data.Memory32.MinBaseAddress, + res->Data.Memory32.RangeLength); + } else { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Memory32 0x%x-0x%x/%d\n", + res->Data.Memory32.MinBaseAddress, + res->Data.Memory32.MaxBaseAddress, + res->Data.Memory32.RangeLength)); + set->set_memoryrange(dev, context, + res->Data.Memory32.MinBaseAddress, + res->Data.Memory32.MaxBaseAddress, + res->Data.Memory32.RangeLength, + res->Data.Memory32.Alignment); + } + break; + + case ACPI_RSTYPE_MEM24: + if (res->Data.Memory24.RangeLength <= 0) + break; + if (res->Data.Memory24.MinBaseAddress == res->Data.Memory24.MaxBaseAddress) { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Memory24 0x%x/%d\n", + res->Data.Memory24.MinBaseAddress, + res->Data.Memory24.RangeLength)); + set->set_memory(dev, context, res->Data.Memory24.MinBaseAddress, + res->Data.Memory24.RangeLength); + } else { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Memory24 0x%x-0x%x/%d\n", + res->Data.Memory24.MinBaseAddress, + res->Data.Memory24.MaxBaseAddress, + res->Data.Memory24.RangeLength)); + set->set_memoryrange(dev, context, + res->Data.Memory24.MinBaseAddress, + res->Data.Memory24.MaxBaseAddress, + res->Data.Memory24.RangeLength, + res->Data.Memory24.Alignment); + } + break; + + case ACPI_RSTYPE_IRQ: + /* + * from 1.0b 6.4.2 + * "This structure is repeated for each separate interrupt + * required" + */ + set->set_irq(dev, context, res->Data.Irq.Interrupts, + res->Data.Irq.NumberOfInterrupts); + break; + + case ACPI_RSTYPE_DMA: + /* + * from 1.0b 6.4.3 + * "This structure is repeated for each separate dma channel + * required" + */ + + set->set_drq(dev, context, res->Data.Dma.Channels, + res->Data.Dma.NumberOfChannels); + break; + + case ACPI_RSTYPE_START_DPF: + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "start dependant functions\n")); + set->set_start_dependant(dev, context, + res->Data.StartDpf.CompatibilityPriority); + break; + + case ACPI_RSTYPE_END_DPF: + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "end dependant functions\n")); + set->set_end_dependant(dev, context); + break; + + case ACPI_RSTYPE_ADDRESS32: + if (res->Data.Address32.AddressLength <= 0) + break; + if (res->Data.Address32.ProducerConsumer != ACPI_CONSUMER) { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "ignored Address32 %s producer\n", + (res->Data.Address32.ResourceType == ACPI_IO_RANGE) ? + "IO" : "Memory")); + break; + } + if (res->Data.Address32.ResourceType != ACPI_MEMORY_RANGE && + res->Data.Address32.ResourceType != ACPI_IO_RANGE) { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, + "ignored Address32 for non-memory, non-I/O\n")); + break; + } + + if ((res->Data.Address32.MinAddressFixed == ACPI_ADDRESS_FIXED) && + (res->Data.Address32.MaxAddressFixed == ACPI_ADDRESS_FIXED)) { + if (res->Data.Address32.ResourceType == ACPI_MEMORY_RANGE) { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Address32/Memory 0x%x/%d\n", + res->Data.Address32.MinAddressRange, + res->Data.Address32.AddressLength)); + set->set_memory(dev, context, + res->Data.Address32.MinAddressRange, + res->Data.Address32.AddressLength); + } else { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Address32/IO 0x%x/%d\n", + res->Data.Address32.MinAddressRange, + res->Data.Address32.AddressLength)); + set->set_ioport(dev, context, + res->Data.Address32.MinAddressRange, + res->Data.Address32.AddressLength); + } + } else { + if (res->Data.Address32.ResourceType == ACPI_MEMORY_RANGE) { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Address32/Memory 0x%x-0x%x/%d\n", + res->Data.Address32.MinAddressRange, + res->Data.Address32.MaxAddressRange, + res->Data.Address32.AddressLength)); + set->set_memoryrange(dev, context, + res->Data.Address32.MinAddressRange, + res->Data.Address32.MaxAddressRange, + res->Data.Address32.AddressLength, + res->Data.Address32.Granularity); + } else { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Address32/IO 0x%x-0x%x/%d\n", + res->Data.Address32.MinAddressRange, + res->Data.Address32.MaxAddressRange, + res->Data.Address32.AddressLength)); + set->set_iorange(dev, context, + res->Data.Address32.MinAddressRange, + res->Data.Address32.MaxAddressRange, + res->Data.Address32.AddressLength, + res->Data.Address32.Granularity); + } + } + break; + + case ACPI_RSTYPE_ADDRESS16: + if (res->Data.Address16.AddressLength <= 0) + break; + if (res->Data.Address16.ProducerConsumer != ACPI_CONSUMER) { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "ignored Address16 %s producer\n", + (res->Data.Address16.ResourceType == ACPI_IO_RANGE) ? + "IO" : "Memory")); + break; + } + if (res->Data.Address16.ResourceType != ACPI_MEMORY_RANGE && + res->Data.Address16.ResourceType != ACPI_IO_RANGE) { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, + "ignored Address16 for non-memory, non-I/O\n")); + break; + } + + if ((res->Data.Address16.MinAddressFixed == ACPI_ADDRESS_FIXED) && + (res->Data.Address16.MaxAddressFixed == ACPI_ADDRESS_FIXED)) { + if (res->Data.Address16.ResourceType == ACPI_MEMORY_RANGE) { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Address16/Memory 0x%x/%d\n", + res->Data.Address16.MinAddressRange, + res->Data.Address16.AddressLength)); + set->set_memory(dev, context, + res->Data.Address16.MinAddressRange, + res->Data.Address16.AddressLength); + } else { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Address16/IO 0x%x/%d\n", + res->Data.Address16.MinAddressRange, + res->Data.Address16.AddressLength)); + set->set_ioport(dev, context, + res->Data.Address16.MinAddressRange, + res->Data.Address16.AddressLength); + } + } else { + if (res->Data.Address16.ResourceType == ACPI_MEMORY_RANGE) { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Address16/Memory 0x%x-0x%x/%d\n", + res->Data.Address16.MinAddressRange, + res->Data.Address16.MaxAddressRange, + res->Data.Address16.AddressLength)); + set->set_memoryrange(dev, context, + res->Data.Address16.MinAddressRange, + res->Data.Address16.MaxAddressRange, + res->Data.Address16.AddressLength, + res->Data.Address16.Granularity); + } else { + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Address16/IO 0x%x-0x%x/%d\n", + res->Data.Address16.MinAddressRange, + res->Data.Address16.MaxAddressRange, + res->Data.Address16.AddressLength)); + set->set_iorange(dev, context, + res->Data.Address16.MinAddressRange, + res->Data.Address16.MaxAddressRange, + res->Data.Address16.AddressLength, + res->Data.Address16.Granularity); + } + } + break; + + case ACPI_RSTYPE_ADDRESS64: + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "unimplemented Address64 resource\n")); + break; + + case ACPI_RSTYPE_EXT_IRQ: + /* XXX special handling? */ + set->set_irq(dev, context,res->Data.ExtendedIrq.Interrupts, + res->Data.ExtendedIrq.NumberOfInterrupts); + break; + + case ACPI_RSTYPE_VENDOR: + ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "unimplemented VendorSpecific resource\n")); + break; + default: + break; + } + } + AcpiOsFree(buf.Pointer); + set->set_done(dev, context); + return_ACPI_STATUS(AE_OK); +} + +/* + * Resource-set vectors used to attach _CRS-derived resources + * to an ACPI device. + */ +static void acpi_res_set_init(device_t dev, void **context); +static void acpi_res_set_done(device_t dev, void *context); +static void acpi_res_set_ioport(device_t dev, void *context, u_int32_t base, u_int32_t length); +static void acpi_res_set_iorange(device_t dev, void *context, u_int32_t low, u_int32_t high, + u_int32_t length, u_int32_t align); +static void acpi_res_set_memory(device_t dev, void *context, u_int32_t base, u_int32_t length); +static void acpi_res_set_memoryrange(device_t dev, void *context, u_int32_t low, u_int32_t high, + u_int32_t length, u_int32_t align); +static void acpi_res_set_irq(device_t dev, void *context, u_int32_t *irq, + int count); +static void acpi_res_set_drq(device_t dev, void *context, u_int32_t *drq, + int count); +static void acpi_res_set_start_dependant(device_t dev, void *context, int preference); +static void acpi_res_set_end_dependant(device_t dev, void *context); + +struct acpi_parse_resource_set acpi_res_parse_set = { + acpi_res_set_init, + acpi_res_set_done, + acpi_res_set_ioport, + acpi_res_set_iorange, + acpi_res_set_memory, + acpi_res_set_memoryrange, + acpi_res_set_irq, + acpi_res_set_drq, + acpi_res_set_start_dependant, + acpi_res_set_end_dependant +}; + +struct acpi_res_context { + int ar_nio; + int ar_nmem; + int ar_nirq; + int ar_ndrq; +}; + +static void +acpi_res_set_init(device_t dev, void **context) +{ + struct acpi_res_context *cp; + + if ((cp = AcpiOsAllocate(sizeof(*cp))) != NULL) { + bzero(cp, sizeof(*cp)); + *context = cp; + } +} + +static void +acpi_res_set_done(device_t dev, void *context) +{ + struct acpi_res_context *cp = (struct acpi_res_context *)context; + + if (cp == NULL) + return; + AcpiOsFree(cp); +} + +static void +acpi_res_set_ioport(device_t dev, void *context, u_int32_t base, u_int32_t length) +{ + struct acpi_res_context *cp = (struct acpi_res_context *)context; + + if (cp == NULL) + return; + bus_set_resource(dev, SYS_RES_IOPORT, cp->ar_nio++, base, length); +} + +static void +acpi_res_set_iorange(device_t dev, void *context, u_int32_t low, u_int32_t high, u_int32_t length, u_int32_t align) +{ + struct acpi_res_context *cp = (struct acpi_res_context *)context; + + if (cp == NULL) + return; + device_printf(dev, "I/O range not supported\n"); +} + +static void +acpi_res_set_memory(device_t dev, void *context, u_int32_t base, u_int32_t length) +{ + struct acpi_res_context *cp = (struct acpi_res_context *)context; + + if (cp == NULL) + return; + + bus_set_resource(dev, SYS_RES_MEMORY, cp->ar_nmem++, base, length); +} + +static void +acpi_res_set_memoryrange(device_t dev, void *context, u_int32_t low, u_int32_t high, u_int32_t length, u_int32_t align) +{ + struct acpi_res_context *cp = (struct acpi_res_context *)context; + + if (cp == NULL) + return; + device_printf(dev, "memory range not supported\n"); +} + +static void +acpi_res_set_irq(device_t dev, void *context, u_int32_t *irq, int count) +{ + struct acpi_res_context *cp = (struct acpi_res_context *)context; + + if (cp == NULL) + return; + if (irq == NULL) + return; + + /*This implements no resource relocation.*/ + if(count != 1) + return; + + bus_set_resource(dev, SYS_RES_IRQ, cp->ar_nirq++, *irq, 1); +} + +static void +acpi_res_set_drq(device_t dev, void *context, u_int32_t *drq, int count) +{ + struct acpi_res_context *cp = (struct acpi_res_context *)context; + + if (cp == NULL) + return; + if (drq == NULL) + return; + + /*This implements no resource relocation.*/ + if(count != 1) + return; + + bus_set_resource(dev, SYS_RES_DRQ, cp->ar_ndrq++, *drq, 1); +} + +static void +acpi_res_set_start_dependant(device_t dev, void *context, int preference) +{ + struct acpi_res_context *cp = (struct acpi_res_context *)context; + + if (cp == NULL) + return; + device_printf(dev, "dependant functions not supported\n"); +} + +static void +acpi_res_set_end_dependant(device_t dev, void *context) +{ + struct acpi_res_context *cp = (struct acpi_res_context *)context; + + if (cp == NULL) + return; +} + +/* + * Resource-owning placeholders. + * + * This code "owns" system resource objects that aren't + * otherwise useful to devices, and which shouldn't be + * considered "free". + * + * Note that some systems claim *all* of the physical address space + * with a PNP0C01 device, so we cannot correctly "own" system memory + * here (must be done in the SMAP handler on x86 systems, for + * example). + */ + +static int acpi_sysresource_probe(device_t dev); +static int acpi_sysresource_attach(device_t dev); + +static device_method_t acpi_sysresource_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_sysresource_probe), + DEVMETHOD(device_attach, acpi_sysresource_attach), + + {0, 0} +}; + +static driver_t acpi_sysresource_driver = { + "acpi_sysresource", + acpi_sysresource_methods, + 0, +}; + +static devclass_t acpi_sysresource_devclass; +DRIVER_MODULE(acpi_sysresource, acpi, acpi_sysresource_driver, acpi_sysresource_devclass, 0, 0); + +static int +acpi_sysresource_probe(device_t dev) +{ + if (acpi_disabled("sysresource")) + return(ENXIO); + if (acpi_MatchHid(dev, "PNP0C02")) { + device_set_desc(dev, "system resource"); + } else { + return(ENXIO); + } + device_quiet(dev); + return(-100); +} + +static int +acpi_sysresource_attach(device_t dev) +{ + struct resource *res; + int i, rid; + + /* + * Suck up all the resources that might have been assigned to us. + * Note that it's impossible to tell the difference between a + * resource that someone else has claimed, and one that doesn't + * exist. + */ + for (i = 0; i < 100; i++) { + rid = i; + res = bus_alloc_resource(dev, SYS_RES_IOPORT, &rid, 0, ~0, 1, 0); + rid = i; + res = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid, 0, ~0, 1, 0); + rid = i; + res = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1, RF_SHAREABLE); + } + return(0); +} diff --git a/sys/dev/acpica/acpi_thermal.c b/sys/dev/acpica/acpi_thermal.c new file mode 100644 index 0000000..7806180 --- /dev/null +++ b/sys/dev/acpica/acpi_thermal.c @@ -0,0 +1,828 @@ +/*- + * Copyright (c) 2000, 2001 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/kthread.h> +#include <sys/bus.h> +#include <sys/proc.h> +#include <sys/reboot.h> +#include <sys/sysctl.h> +#include <sys/unistd.h> +#include <sys/power.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_THERMAL +ACPI_MODULE_NAME("THERMAL") + +#define TZ_ZEROC 2732 +#define TZ_KELVTOC(x) (((x) - TZ_ZEROC) / 10), (((x) - TZ_ZEROC) % 10) + +#define TZ_NOTIFY_TEMPERATURE 0x80 +#define TZ_NOTIFY_DEVICES 0x81 +#define TZ_NOTIFY_LEVELS 0x82 + +#define TZ_POLLRATE 30 /* every 30 seconds by default */ + +#define TZ_NUMLEVELS 10 /* defined by ACPI spec */ +struct acpi_tz_zone { + int ac[TZ_NUMLEVELS]; + ACPI_BUFFER al[TZ_NUMLEVELS]; + int crt; + int hot; + ACPI_BUFFER psl; + int psv; + int tc1; + int tc2; + int tsp; + int tzp; +}; + + +struct acpi_tz_softc { + device_t tz_dev; /* device handle */ + ACPI_HANDLE tz_handle; /* thermal zone handle */ + int tz_temperature; /* current temperature */ + int tz_active; /* current active cooling */ +#define TZ_ACTIVE_NONE -1 + int tz_requested; /* user-requested minimum active cooling */ + int tz_thflags; /* current temperature-related flags */ +#define TZ_THFLAG_NONE 0 +#define TZ_THFLAG_PSV (1<<0) +#define TZ_THFLAG_HOT (1<<2) +#define TZ_THFLAG_CRT (1<<3) + int tz_flags; +#define TZ_FLAG_NO_SCP (1<<0) /* no _SCP method */ +#define TZ_FLAG_GETPROFILE (1<<1) /* fetch power_profile in timeout */ + struct timespec tz_cooling_started; /* current cooling starting time */ + + struct sysctl_ctx_list tz_sysctl_ctx; /* sysctl tree */ + struct sysctl_oid *tz_sysctl_tree; + + struct acpi_tz_zone tz_zone; /* thermal zone parameters */ + int tz_tmp_updating; +}; + +static int acpi_tz_probe(device_t dev); +static int acpi_tz_attach(device_t dev); +static int acpi_tz_establish(struct acpi_tz_softc *sc); +static void acpi_tz_monitor(struct acpi_tz_softc *sc); +static void acpi_tz_all_off(struct acpi_tz_softc *sc); +static void acpi_tz_switch_cooler_off(ACPI_OBJECT *obj, void *arg); +static void acpi_tz_switch_cooler_on(ACPI_OBJECT *obj, void *arg); +static void acpi_tz_getparam(struct acpi_tz_softc *sc, char *node, int *data); +static void acpi_tz_sanity(struct acpi_tz_softc *sc, int *val, char *what); +static int acpi_tz_active_sysctl(SYSCTL_HANDLER_ARGS); +static void acpi_tz_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context); +static void acpi_tz_timeout(struct acpi_tz_softc *sc); +static void acpi_tz_power_profile(void *arg); + +static void acpi_tz_thread(void *arg); +static struct proc *acpi_tz_proc; + +static device_method_t acpi_tz_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_tz_probe), + DEVMETHOD(device_attach, acpi_tz_attach), + + {0, 0} +}; + +static driver_t acpi_tz_driver = { + "acpi_tz", + acpi_tz_methods, + sizeof(struct acpi_tz_softc), +}; + +static devclass_t acpi_tz_devclass; +DRIVER_MODULE(acpi_tz, acpi, acpi_tz_driver, acpi_tz_devclass, 0, 0); + +static struct sysctl_ctx_list acpi_tz_sysctl_ctx; +static struct sysctl_oid *acpi_tz_sysctl_tree; + +static int acpi_tz_min_runtime = 0;/* minimum cooling run time */ +static int acpi_tz_polling_rate = TZ_POLLRATE; + +/* + * Match an ACPI thermal zone. + */ +static int +acpi_tz_probe(device_t dev) +{ + int result; + ACPI_LOCK_DECL; + + ACPI_LOCK; + + /* no FUNCTION_TRACE - too noisy */ + + if ((acpi_get_type(dev) == ACPI_TYPE_THERMAL) && + !acpi_disabled("thermal")) { + device_set_desc(dev, "thermal zone"); + result = -10; + } else { + result = ENXIO; + } + ACPI_UNLOCK; + return(result); +} + +/* + * Attach to an ACPI thermal zone. + */ +static int +acpi_tz_attach(device_t dev) +{ + struct acpi_tz_softc *sc; + struct acpi_softc *acpi_sc; + int error; + char oidname[8]; + ACPI_LOCK_DECL; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + ACPI_LOCK; + + sc = device_get_softc(dev); + sc->tz_dev = dev; + sc->tz_handle = acpi_get_handle(dev); + sc->tz_requested = TZ_ACTIVE_NONE; + sc->tz_tmp_updating = 0; + + /* + * Parse the current state of the thermal zone and build control + * structures. + */ + if ((error = acpi_tz_establish(sc)) != 0) + goto out; + + /* + * Register for any Notify events sent to this zone. + */ + AcpiInstallNotifyHandler(sc->tz_handle, ACPI_DEVICE_NOTIFY, + acpi_tz_notify_handler, sc); + + /* + * Create our sysctl nodes. + * + * XXX we need a mechanism for adding nodes under ACPI. + */ + if (device_get_unit(dev) == 0) { + acpi_sc = acpi_device_get_parent_softc(dev); + sysctl_ctx_init(&acpi_tz_sysctl_ctx); + acpi_tz_sysctl_tree = SYSCTL_ADD_NODE(&acpi_tz_sysctl_ctx, + SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), + OID_AUTO, "thermal", CTLFLAG_RD, 0, ""); + SYSCTL_ADD_INT(&acpi_tz_sysctl_ctx, + SYSCTL_CHILDREN(acpi_tz_sysctl_tree), + OID_AUTO, "min_runtime", CTLFLAG_RD | CTLFLAG_RW, + &acpi_tz_min_runtime, 0, "minimum cooling run time in sec"); + SYSCTL_ADD_INT(&acpi_tz_sysctl_ctx, + SYSCTL_CHILDREN(acpi_tz_sysctl_tree), + OID_AUTO, "polling_rate", CTLFLAG_RD | CTLFLAG_RW, + &acpi_tz_polling_rate, 0, "monitor polling rate"); + } + sysctl_ctx_init(&sc->tz_sysctl_ctx); + sprintf(oidname, "tz%d", device_get_unit(dev)); + sc->tz_sysctl_tree = SYSCTL_ADD_NODE(&sc->tz_sysctl_ctx, + SYSCTL_CHILDREN(acpi_tz_sysctl_tree), OID_AUTO, + oidname, CTLFLAG_RD, 0, ""); + SYSCTL_ADD_INT(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), + OID_AUTO, "temperature", CTLFLAG_RD, + &sc->tz_temperature, 0, "current thermal zone temperature"); + SYSCTL_ADD_PROC(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), + OID_AUTO, "active", CTLTYPE_INT | CTLFLAG_RW, + sc, 0, acpi_tz_active_sysctl, "I", ""); + + SYSCTL_ADD_INT(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), + OID_AUTO, "thermal_flags", CTLFLAG_RD, + &sc->tz_thflags, 0, "thermal zone flags"); + SYSCTL_ADD_INT(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), + OID_AUTO, "_PSV", CTLFLAG_RD, + &sc->tz_zone.psv, 0, ""); + SYSCTL_ADD_INT(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), + OID_AUTO, "_HOT", CTLFLAG_RD, + &sc->tz_zone.hot, 0, ""); + SYSCTL_ADD_INT(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), + OID_AUTO, "_CRT", CTLFLAG_RD, + &sc->tz_zone.crt, 0, ""); + SYSCTL_ADD_OPAQUE(&sc->tz_sysctl_ctx, SYSCTL_CHILDREN(sc->tz_sysctl_tree), + OID_AUTO, "_ACx", CTLFLAG_RD, &sc->tz_zone.ac, + sizeof(sc->tz_zone.ac), "I", ""); + + + /* + * Register our power profile event handler, and flag it for a manual + * invocation by our timeout. We defer it like this so that the rest + * of the subsystem has time to come up. + */ + EVENTHANDLER_REGISTER(power_profile_change, acpi_tz_power_profile, sc, 0); + sc->tz_flags |= TZ_FLAG_GETPROFILE; + + /* + * Don't bother evaluating/printing the temperature at this point; + * on many systems it'll be bogus until the EC is running. + */ + + /* + * Create our thread; we only need one, it will service all of the + * thermal zones. + */ + if (acpi_tz_proc == NULL) { + error = kthread_create(acpi_tz_thread, NULL, &acpi_tz_proc, + RFHIGHPID, 0, "acpi_thermal"); + if (error != 0) { + device_printf(sc->tz_dev, "could not create thread - %d", error); + goto out; + } + } + + out: + ACPI_UNLOCK; + + return_VALUE(error); +} + +/* + * Parse the current state of this thermal zone and set up to use it. + * + * Note that we may have previous state, which will have to be discarded. + */ +static int +acpi_tz_establish(struct acpi_tz_softc *sc) +{ + ACPI_OBJECT *obj; + int i; + char nbuf[8]; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + ACPI_ASSERTLOCK; + + /* + * Power everything off and erase any existing state. + */ + acpi_tz_all_off(sc); + for (i = 0; i < TZ_NUMLEVELS; i++) + if (sc->tz_zone.al[i].Pointer != NULL) + AcpiOsFree(sc->tz_zone.al[i].Pointer); + if (sc->tz_zone.psl.Pointer != NULL) + AcpiOsFree(sc->tz_zone.psl.Pointer); + bzero(&sc->tz_zone, sizeof(sc->tz_zone)); + + /* + * Evaluate thermal zone parameters. + */ + for (i = 0; i < TZ_NUMLEVELS; i++) { + sprintf(nbuf, "_AC%d", i); + acpi_tz_getparam(sc, nbuf, &sc->tz_zone.ac[i]); + sprintf(nbuf, "_AL%d", i); + sc->tz_zone.al[i].Length = ACPI_ALLOCATE_BUFFER; + sc->tz_zone.al[i].Pointer = NULL; + AcpiEvaluateObject(sc->tz_handle, nbuf, NULL, &sc->tz_zone.al[i]); + obj = (ACPI_OBJECT *)sc->tz_zone.al[i].Pointer; + if (obj != NULL) { + /* should be a package containing a list of power objects */ + if (obj->Type != ACPI_TYPE_PACKAGE) { + device_printf(sc->tz_dev, "%s has unknown object type %d, rejecting\n", + nbuf, obj->Type); + return_VALUE(ENXIO); + } + } + } + acpi_tz_getparam(sc, "_CRT", &sc->tz_zone.crt); + acpi_tz_getparam(sc, "_HOT", &sc->tz_zone.hot); + sc->tz_zone.psl.Length = ACPI_ALLOCATE_BUFFER; + sc->tz_zone.psl.Pointer = NULL; + AcpiEvaluateObject(sc->tz_handle, "_PSL", NULL, &sc->tz_zone.psl); + acpi_tz_getparam(sc, "_PSV", &sc->tz_zone.psv); + acpi_tz_getparam(sc, "_TC1", &sc->tz_zone.tc1); + acpi_tz_getparam(sc, "_TC2", &sc->tz_zone.tc2); + acpi_tz_getparam(sc, "_TSP", &sc->tz_zone.tsp); + acpi_tz_getparam(sc, "_TZP", &sc->tz_zone.tzp); + + /* + * Sanity-check the values we've been given. + * + * XXX what do we do about systems that give us the same value for + * more than one of these setpoints? + */ + acpi_tz_sanity(sc, &sc->tz_zone.crt, "_CRT"); + acpi_tz_sanity(sc, &sc->tz_zone.hot, "_HOT"); + acpi_tz_sanity(sc, &sc->tz_zone.psv, "_PSV"); + for (i = 0; i < TZ_NUMLEVELS; i++) + acpi_tz_sanity(sc, &sc->tz_zone.ac[i], "_ACx"); + + /* + * Power off everything that we've just been given. + */ + acpi_tz_all_off(sc); + + return_VALUE(0); +} + +static char *aclevel_string[] = { + "NONE", "_AC0", "_AC1", "_AC2", "_AC3", "_AC4", + "_AC5", "_AC6", "_AC7", "_AC8", "_AC9" }; + +static __inline const char * +acpi_tz_aclevel_string(int active) +{ + if (active < -1 || active >= TZ_NUMLEVELS) { + return (aclevel_string[0]); + } + + return (aclevel_string[active+1]); +} + +/* + * Evaluate the condition of a thermal zone, take appropriate actions. + */ +static void +acpi_tz_monitor(struct acpi_tz_softc *sc) +{ + int temp; + int i; + int newactive, newflags; + struct timespec curtime; + ACPI_STATUS status; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + ACPI_ASSERTLOCK; + + if (sc->tz_tmp_updating) { + goto out; + } + sc->tz_tmp_updating = 1; + + /* + * Get the current temperature. + */ + if (ACPI_FAILURE(status = acpi_EvaluateInteger(sc->tz_handle, "_TMP", &temp))) { + ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), + "error fetching current temperature -- %s\n", + AcpiFormatException(status)); + /* XXX disable zone? go to max cooling? */ + goto out; + } + + ACPI_DEBUG_PRINT((ACPI_DB_VALUES, "got %d.%dC\n", TZ_KELVTOC(temp))); + sc->tz_temperature = temp; + + /* + * Work out what we ought to be doing right now. + * + * Note that the _ACx levels sort from hot to cold. + */ + newactive = TZ_ACTIVE_NONE; + for (i = TZ_NUMLEVELS - 1; i >= 0; i--) { + if ((sc->tz_zone.ac[i] != -1) && (temp >= sc->tz_zone.ac[i])) { + newactive = i; + if (sc->tz_active != newactive) { + ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), + "_AC%d: temperature %d.%d >= setpoint %d.%d\n", i, + TZ_KELVTOC(temp), TZ_KELVTOC(sc->tz_zone.ac[i])); + getnanotime(&sc->tz_cooling_started); + } + } + } + + /* + * We are going to get _ACx level down (colder side), but give a guaranteed + * minimum cooling run time if requested. + */ + if (acpi_tz_min_runtime > 0 && sc->tz_active != TZ_ACTIVE_NONE && + (newactive == TZ_ACTIVE_NONE || newactive > sc->tz_active)) { + getnanotime(&curtime); + timespecsub(&curtime, &sc->tz_cooling_started); + if (curtime.tv_sec < acpi_tz_min_runtime) { + newactive = sc->tz_active; + } + } + + /* handle user override of active mode */ + if (sc->tz_requested > newactive) + newactive = sc->tz_requested; + + /* update temperature-related flags */ + newflags = TZ_THFLAG_NONE; + if ((sc->tz_zone.psv != -1) && (temp >= sc->tz_zone.psv)) + newflags |= TZ_THFLAG_PSV; + if ((sc->tz_zone.hot != -1) && (temp >= sc->tz_zone.hot)) + newflags |= TZ_THFLAG_HOT; + if ((sc->tz_zone.crt != -1) && (temp >= sc->tz_zone.crt)) + newflags |= TZ_THFLAG_CRT; + + /* + * If the active cooling state has changed, we have to switch things. + */ + if (newactive != sc->tz_active) { + + /* turn off the cooling devices that are on, if any are */ + if (sc->tz_active != TZ_ACTIVE_NONE) + acpi_ForeachPackageObject((ACPI_OBJECT *)sc->tz_zone.al[sc->tz_active].Pointer, + acpi_tz_switch_cooler_off, sc); + + /* turn on cooling devices that are required, if any are */ + if (newactive != TZ_ACTIVE_NONE) + acpi_ForeachPackageObject((ACPI_OBJECT *)sc->tz_zone.al[newactive].Pointer, + acpi_tz_switch_cooler_on, sc); + ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), + "switched from %s to %s: %d.%dC\n", + acpi_tz_aclevel_string(sc->tz_active), + acpi_tz_aclevel_string(newactive), TZ_KELVTOC(temp)); + sc->tz_active = newactive; + } + + /* + * XXX (de)activate any passive cooling that may be required. + */ + + /* + * If we have just become _HOT or _CRT, warn the user. + * + * We should actually shut down at this point, but it's not clear + * that some systems don't actually map _CRT to the same value as _AC0. + */ + if ((newflags & (TZ_THFLAG_HOT | TZ_THFLAG_CRT)) && + !(sc->tz_thflags & (TZ_THFLAG_HOT | TZ_THFLAG_CRT))) { + device_printf(sc->tz_dev, "WARNING - current temperature (%d.%dC) exceeds system limits\n", + TZ_KELVTOC(sc->tz_temperature)); + /* shutdown_nice(RB_POWEROFF);*/ + } + sc->tz_thflags = newflags; + +out: + sc->tz_tmp_updating = 0; + return_VOID; +} + +/* + * Turn off all the cooling devices. + */ +static void +acpi_tz_all_off(struct acpi_tz_softc *sc) +{ + int i; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + ACPI_ASSERTLOCK; + + /* + * Scan all the _ALx objects, and turn them all off. + */ + for (i = 0; i < TZ_NUMLEVELS; i++) { + if (sc->tz_zone.al[i].Pointer == NULL) + continue; + acpi_ForeachPackageObject((ACPI_OBJECT *)sc->tz_zone.al[i].Pointer, + acpi_tz_switch_cooler_off, sc); + } + + /* + * XXX revert any passive-cooling options. + */ + + sc->tz_active = TZ_ACTIVE_NONE; + sc->tz_thflags = TZ_THFLAG_NONE; + return_VOID; +} + +/* + * Given an object, verify that it's a reference to a device of some sort, + * and try to switch it off. + */ +static void +acpi_tz_switch_cooler_off(ACPI_OBJECT *obj, void *arg) +{ + ACPI_HANDLE cooler; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + ACPI_ASSERTLOCK; + + switch(obj->Type) { + case ACPI_TYPE_ANY: + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "called to turn %s off\n", acpi_name(obj->Reference.Handle))); + + acpi_pwr_switch_consumer(obj->Reference.Handle, ACPI_STATE_D3); + break; + + case ACPI_TYPE_STRING: + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "called to turn %s off\n", obj->String.Pointer)); + + /* + * Find the handle for the device and turn it off. + * The String object here seems to contain a fully-qualified path, so we + * don't have to search for it in our parents. + * + * XXX This may not always be the case. + */ + if (ACPI_SUCCESS(AcpiGetHandle(NULL, obj->String.Pointer, &cooler))) + acpi_pwr_switch_consumer(cooler, ACPI_STATE_D3); + break; + + default: + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "called to handle unsupported object type %d\n", + obj->Type)); + break; + } + return_VOID; +} + +/* + * Given an object, verify that it's a reference to a device of some sort, + * and try to switch it on. + * + * XXX replication of off/on function code is bad, mmmkay? + */ +static void +acpi_tz_switch_cooler_on(ACPI_OBJECT *obj, void *arg) +{ + struct acpi_tz_softc *sc = (struct acpi_tz_softc *)arg; + ACPI_HANDLE cooler; + ACPI_STATUS status; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + ACPI_ASSERTLOCK; + + switch(obj->Type) { + case ACPI_TYPE_ANY: + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "called to turn %s on\n", acpi_name(obj->Reference.Handle))); + + if (ACPI_FAILURE(status = acpi_pwr_switch_consumer(obj->Reference.Handle, ACPI_STATE_D0))) { + ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), + "failed to activate %s - %s\n", acpi_name(obj->Reference.Handle), + AcpiFormatException(status)); + } + break; + + case ACPI_TYPE_STRING: + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "called to turn %s on\n", obj->String.Pointer)); + + /* + * Find the handle for the device and turn it off. + * The String object here seems to contain a fully-qualified path, so we + * don't have to search for it in our parents. + * + * XXX This may not always be the case. + */ + if (ACPI_SUCCESS(AcpiGetHandle(NULL, obj->String.Pointer, &cooler))) { + if (ACPI_FAILURE(status = acpi_pwr_switch_consumer(cooler, ACPI_STATE_D0))) { + ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), + "failed to activate %s - %s\n", + obj->String.Pointer, AcpiFormatException(status)); + } + } else { + ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), + "couldn't find %s\n", obj->String.Pointer); + } + break; + + default: + ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "called to handle unsupported object type %d\n", + obj->Type)); + break; + } + return_VOID; +} + +/* + * Read/debug-print a parameter, default it to -1. + */ +static void +acpi_tz_getparam(struct acpi_tz_softc *sc, char *node, int *data) +{ + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + ACPI_ASSERTLOCK; + + if (ACPI_FAILURE(acpi_EvaluateInteger(sc->tz_handle, node, data))) { + *data = -1; + } else { + ACPI_DEBUG_PRINT((ACPI_DB_VALUES, "%s.%s = %d\n", acpi_name(sc->tz_handle), + node, *data)); + } + return_VOID; +} + +/* + * Sanity-check a temperature value. Assume that setpoints + * should be between 0C and 150C. + */ +static void +acpi_tz_sanity(struct acpi_tz_softc *sc, int *val, char *what) +{ + if ((*val != -1) && ((*val < TZ_ZEROC) || (*val > (TZ_ZEROC + 1500)))) { + device_printf(sc->tz_dev, "%s value is absurd, ignored (%d.%dC)\n", + what, TZ_KELVTOC(*val)); + *val = -1; + } +} + +/* + * Respond to a sysctl on the active state node. + */ +static int +acpi_tz_active_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct acpi_tz_softc *sc; + int active; + int error; + ACPI_LOCK_DECL; + + ACPI_LOCK; + + sc = (struct acpi_tz_softc *)oidp->oid_arg1; + active = sc->tz_active; + error = sysctl_handle_int(oidp, &active, 0, req); + + /* error or no new value */ + if ((error != 0) || (req->newptr == NULL)) + goto out; + + /* range check */ + if ((active < -1) || (active >= TZ_NUMLEVELS)) { + error = EINVAL; + goto out; + } + + /* set new preferred level and re-switch */ + sc->tz_requested = active; + acpi_tz_monitor(sc); + + out: + ACPI_UNLOCK; + return(error); +} + +/* + * Respond to a Notify event sent to the zone. + */ +static void +acpi_tz_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) +{ + struct acpi_tz_softc *sc = (struct acpi_tz_softc *)context; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + ACPI_ASSERTLOCK; + + switch(notify) { + case TZ_NOTIFY_TEMPERATURE: + /* temperature change occurred */ + AcpiOsQueueForExecution(OSD_PRIORITY_HIGH, (OSD_EXECUTION_CALLBACK)acpi_tz_monitor, sc); + break; + case TZ_NOTIFY_DEVICES: + case TZ_NOTIFY_LEVELS: + /* zone devices/setpoints changed */ + AcpiOsQueueForExecution(OSD_PRIORITY_HIGH, (OSD_EXECUTION_CALLBACK)acpi_tz_establish, sc); + break; + default: + ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), + "unknown Notify event 0x%x\n", notify); + break; + } + return_VOID; +} + +/* + * Poll the thermal zone. + */ +static void +acpi_tz_timeout(struct acpi_tz_softc *sc) +{ + + /* do we need to get the power profile settings? */ + if (sc->tz_flags & TZ_FLAG_GETPROFILE) { + acpi_tz_power_profile((void *)sc); + sc->tz_flags &= ~TZ_FLAG_GETPROFILE; + } + + ACPI_ASSERTLOCK; + + /* check the current temperature and take action based on it */ + acpi_tz_monitor(sc); + + /* XXX passive cooling actions? */ +} + +/* + * System power profile may have changed; fetch and notify the + * thermal zone accordingly. + * + * Since this can be called from an arbitrary eventhandler, it needs + * to get the ACPI lock itself. + */ +static void +acpi_tz_power_profile(void *arg) +{ + ACPI_OBJECT_LIST args; + ACPI_OBJECT obj; + ACPI_STATUS status; + struct acpi_tz_softc *sc = (struct acpi_tz_softc *)arg; + int state; + ACPI_LOCK_DECL; + + state = power_profile_get_state(); + if (state != POWER_PROFILE_PERFORMANCE && + state != POWER_PROFILE_ECONOMY) { + return; + } + + ACPI_LOCK; + + /* check that we haven't decided there's no _SCP method */ + if (!(sc->tz_flags & TZ_FLAG_NO_SCP)) { + + /* call _SCP to set the new profile */ + obj.Type = ACPI_TYPE_INTEGER; + obj.Integer.Value = (state == POWER_PROFILE_PERFORMANCE) ? 0 : 1; + args.Count = 1; + args.Pointer = &obj; + if (ACPI_FAILURE(status = AcpiEvaluateObject(sc->tz_handle, "_SCP", &args, NULL))) { + if (status != AE_NOT_FOUND) + ACPI_VPRINT(sc->tz_dev, acpi_device_get_parent_softc(sc->tz_dev), + "can't evaluate %s._SCP - %s\n", acpi_name(sc->tz_handle), + AcpiFormatException(status)); + sc->tz_flags |= TZ_FLAG_NO_SCP; + } else { + /* we have to re-evaluate the entire zone now */ + AcpiOsQueueForExecution(OSD_PRIORITY_HIGH, (OSD_EXECUTION_CALLBACK)acpi_tz_establish, sc); + } + } + ACPI_UNLOCK; +} + +/* + * Thermal zone monitor thread. + */ +static void +acpi_tz_thread(void *arg) +{ + device_t *devs; + int devcount, i; + ACPI_LOCK_DECL; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + + devs = NULL; + devcount = 0; + + for (;;) { + tsleep(&acpi_tz_proc, PZERO, "nothing", hz * acpi_tz_polling_rate); + +#if __FreeBSD_version >= 500000 + mtx_lock(&Giant); +#endif + + if (devcount == 0) + devclass_get_devices(acpi_tz_devclass, &devs, &devcount); + + ACPI_LOCK; + for (i = 0; i < devcount; i++) + acpi_tz_timeout(device_get_softc(devs[i])); + ACPI_UNLOCK; + +#if __FreeBSD_version >= 500000 + mtx_unlock(&Giant); +#endif + } +} diff --git a/sys/dev/acpica/acpi_timer.c b/sys/dev/acpica/acpi_timer.c new file mode 100644 index 0000000..67e308a --- /dev/null +++ b/sys/dev/acpica/acpi_timer.c @@ -0,0 +1,389 @@ +/*- + * Copyright (c) 2000, 2001 Michael Smith + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ +#include "opt_acpi.h" +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/sysctl.h> +#if __FreeBSD_version >= 500000 +#include <sys/timetc.h> +#else +#include <sys/time.h> +#endif + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include "acpi.h" + +#include <dev/acpica/acpivar.h> +#include <pci/pcivar.h> + +/* + * A timecounter based on the free-running ACPI timer. + * + * Based on the i386-only mp_clock.c by <phk@FreeBSD.ORG>. + */ + +/* + * Hooks for the ACPI CA debugging infrastructure + */ +#define _COMPONENT ACPI_SYSTEM +ACPI_MODULE_NAME("TIMER") + +static device_t acpi_timer_dev; +struct resource *acpi_timer_reg; + +static u_int acpi_timer_frequency = 14318182/4; + +static void acpi_timer_identify(driver_t *driver, device_t parent); +static int acpi_timer_probe(device_t dev); +static int acpi_timer_attach(device_t dev); +static unsigned acpi_timer_get_timecount(struct timecounter *tc); +static unsigned acpi_timer_get_timecount_safe(struct timecounter *tc); +static int acpi_timer_sysctl_freq(SYSCTL_HANDLER_ARGS); +static void acpi_timer_test(void); + +static u_int32_t read_counter(void); +static int test_counter(void); + +/* + * Driver hung off ACPI. + */ +static device_method_t acpi_timer_methods[] = { + DEVMETHOD(device_identify, acpi_timer_identify), + DEVMETHOD(device_probe, acpi_timer_probe), + DEVMETHOD(device_attach, acpi_timer_attach), + + {0, 0} +}; + +static driver_t acpi_timer_driver = { + "acpi_timer", + acpi_timer_methods, + 0, +}; + +static devclass_t acpi_timer_devclass; +DRIVER_MODULE(acpi_timer, acpi, acpi_timer_driver, acpi_timer_devclass, 0, 0); + +/* + * Timecounter. + */ +static struct timecounter acpi_timer_timecounter = { + acpi_timer_get_timecount_safe, + 0, + 0xffffff, + 0, + "ACPI" +}; + + +static u_int32_t +read_counter() +{ + bus_space_handle_t bsh; + bus_space_tag_t bst; + u_int32_t tv; + + bsh = rman_get_bushandle(acpi_timer_reg); + bst = rman_get_bustag(acpi_timer_reg); + tv = bus_space_read_4(bst, bsh, 0); + bus_space_barrier(bst, bsh, 0, 4, BUS_SPACE_BARRIER_READ); + return (tv); +} + +#define N 2000 +static int +test_counter() +{ + int min, max, n, delta; + unsigned last, this; + + min = 10000000; + max = 0; + last = read_counter(); + for (n = 0; n < N; n++) { + this = read_counter(); + delta = (this - last) & 0xffffff; + if (delta > max) + max = delta; + else if (delta < min) + min = delta; + last = this; + } + if (max - min > 2) + n = 0; + else if (min < 0 || max == 0) + n = 0; + else + n = 1; + if (bootverbose) + printf("ACPI timer looks %s min = %d, max = %d, width = %d\n", + n ? "GOOD" : "BAD ", + min, max, max - min); + return (n); +} + +/* + * Locate the ACPI timer using the FADT, set up and allocate the I/O resources + * we will be using. + */ +static void +acpi_timer_identify(driver_t *driver, device_t parent) +{ + device_t dev; + char desc[40]; + u_long rlen, rstart; + int i, j, rid, rtype; + + ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + + if (acpi_disabled("timer")) + return_VOID; + + if (AcpiGbl_FADT == NULL) + return_VOID; + + if ((dev = BUS_ADD_CHILD(parent, 0, "acpi_timer", 0)) == NULL) { + device_printf(parent, "could not add acpi_timer0\n"); + return_VOID; + } + acpi_timer_dev = dev; + + rid = 0; + rlen = AcpiGbl_FADT->PmTmLen; + rtype = (AcpiGbl_FADT->XPmTmrBlk.AddressSpaceId) + ? SYS_RES_IOPORT : SYS_RES_MEMORY; + rstart = AcpiGbl_FADT->XPmTmrBlk.Address; + bus_set_resource(dev, rtype, rid, rstart, rlen); + acpi_timer_reg = bus_alloc_resource(dev, rtype, &rid, 0, ~0, 1, RF_ACTIVE); + if (acpi_timer_reg == NULL) { + device_printf(dev, "couldn't allocate I/O resource (%s 0x%lx)\n", + (rtype == SYS_RES_IOPORT) ? "port" : "mem", rstart); + return_VOID; + } + if (testenv("debug.acpi.timer_test")) + acpi_timer_test(); + + acpi_timer_timecounter.tc_frequency = acpi_timer_frequency; + j = 0; + for(i = 0; i < 10; i++) + j += test_counter(); + if (j == 10) { + acpi_timer_timecounter.tc_name = "ACPI-fast"; + acpi_timer_timecounter.tc_get_timecount = acpi_timer_get_timecount; + } else { + acpi_timer_timecounter.tc_name = "ACPI-safe"; + acpi_timer_timecounter.tc_get_timecount = acpi_timer_get_timecount_safe; + } + tc_init(&acpi_timer_timecounter); + + sprintf(desc, "%d-bit timer at 3.579545MHz", (AcpiGbl_FADT->TmrValExt) + ? 32 : 24); + device_set_desc_copy(dev, desc); + + return_VOID; +} + +static int +acpi_timer_probe(device_t dev) +{ + if (dev == acpi_timer_dev) + return(0); + return(ENXIO); +} + +static int +acpi_timer_attach(device_t dev) +{ + return(0); +} + +/* + * Fetch current time value from reliable hardware. + */ +static unsigned +acpi_timer_get_timecount(struct timecounter *tc) +{ + return (read_counter()); +} + +/* + * Fetch current time value from hardware that may not correctly + * latch the counter. + */ +static unsigned +acpi_timer_get_timecount_safe(struct timecounter *tc) +{ + unsigned u1, u2, u3; + + u2 = read_counter(); + u3 = read_counter(); + do { + u1 = u2; + u2 = u3; + u3 = read_counter(); + } while (u1 > u2 || u2 > u3 || (u3 - u1) > 15); + return (u2); +} + +/* + * Timecounter freqency adjustment interface. + */ +static int +acpi_timer_sysctl_freq(SYSCTL_HANDLER_ARGS) +{ + int error; + u_int freq; + + if (acpi_timer_timecounter.tc_frequency == 0) + return (EOPNOTSUPP); + freq = acpi_timer_frequency; + error = sysctl_handle_int(oidp, &freq, sizeof(freq), req); + if (error == 0 && req->newptr != NULL) { + acpi_timer_frequency = freq; + acpi_timer_timecounter.tc_frequency = acpi_timer_frequency; + } + return (error); +} + +SYSCTL_PROC(_machdep, OID_AUTO, acpi_timer_freq, CTLTYPE_INT | CTLFLAG_RW, + 0, sizeof(u_int), acpi_timer_sysctl_freq, "I", ""); + +/* + * Test harness for verifying ACPI timer behaviour. + * Boot with debug.acpi.timer_test set to invoke this. + */ +static void +acpi_timer_test(void) +{ + u_int32_t u1, u2, u3; + + u1 = read_counter(); + u2 = read_counter(); + u3 = read_counter(); + + device_printf(acpi_timer_dev, "timer test in progress, reboot to quit.\n"); + for (;;) { + /* + * The failure case is where u3 > u1, but u2 does not fall between the two, + * ie. it contains garbage. + */ + if (u3 > u1) { + if ((u2 < u1) || (u2 > u3)) + device_printf(acpi_timer_dev, "timer is not monotonic: 0x%08x,0x%08x,0x%08x\n", + u1, u2, u3); + } + u1 = u2; + u2 = u3; + u3 = read_counter(); + } +} + +/* + * Chipset workaround driver hung off PCI. + * + * Some ACPI timers are known or believed to suffer from implementation + * problems which can lead to erroneous values being read from the timer. + * + * Since we can't trust unknown chipsets, we default to a timer-read + * routine which compensates for the most common problem (as detailed + * in the excerpt from the Intel PIIX4 datasheet below). + * + * When we detect a known-functional chipset, we disable the workaround + * to improve speed. + * + * ] 20. ACPI Timer Errata + * ] + * ] Problem: The power management timer may return improper result when + * ] read. Although the timer value settles properly after incrementing, + * ] while incrementing there is a 3nS window every 69.8nS where the + * ] timer value is indeterminate (a 4.2% chance that the data will be + * ] incorrect when read). As a result, the ACPI free running count up + * ] timer specification is violated due to erroneous reads. Implication: + * ] System hangs due to the "inaccuracy" of the timer when used by + * ] software for time critical events and delays. + * ] + * ] Workaround: Read the register twice and compare. + * ] Status: This will not be fixed in the PIIX4 or PIIX4E, it is fixed + * ] in the PIIX4M. + * + * The counter is in other words not latched to the PCI bus clock when + * read. Notice the workaround isn't: We need to read until we have + * three monotonic samples and then use the middle one, otherwise we are + * not protected against the fact that the bits can be wrong in two + * directions. If we only cared about monosity two reads would be enough. + */ + +#if 0 +static int acpi_timer_pci_probe(device_t dev); + +static device_method_t acpi_timer_pci_methods[] = { + DEVMETHOD(device_probe, acpi_timer_pci_probe), + {0, 0} +}; + +static driver_t acpi_timer_pci_driver = { + "acpi_timer_pci", + acpi_timer_pci_methods, + 0, +}; + +devclass_t acpi_timer_pci_devclass; +DRIVER_MODULE(acpi_timer_pci, pci, acpi_timer_pci_driver, acpi_timer_pci_devclass, 0, 0); + +/* + * Look at PCI devices going past; if we detect one we know contains + * a functional ACPI timer device, enable the faster timecounter read + * routine. + */ +static int +acpi_timer_pci_probe(device_t dev) +{ + int vendor, device, revid; + + vendor = pci_get_vendor(dev); + device = pci_get_device(dev); + revid = pci_get_revid(dev); + + if (((vendor == 0x8086) && (device == 0x7113) && (revid >= 0x03)) || /* PIIX4M */ + ((vendor == 0x8086) && (device == 0x719b)) || /* i440MX */ + 0) { + + acpi_timer_timecounter.tc_get_timecount = acpi_timer_get_timecount; + acpi_timer_timecounter.tc_name = "ACPI-fast"; + if (bootverbose) + device_printf(acpi_timer_dev, "functional ACPI timer detected, enabling fast timecount interface\n"); + } + + return(ENXIO); /* we never match anything */ +} +#endif diff --git a/sys/dev/acpica/acpica_support.c b/sys/dev/acpica/acpica_support.c new file mode 100644 index 0000000..7dfde3b --- /dev/null +++ b/sys/dev/acpica/acpica_support.c @@ -0,0 +1,106 @@ +/*- + * Copyright (c) 2001 Mitsuru IWASAKI + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "acpi.h" +#include <machine/cpufunc.h> +#include <dev/acpica/acpica_support.h> + +ACPI_MODULE_NAME("SUPPORT") + +/* + * Implement support code temporary here until officially merged into + * Intel ACPI CA release. + */ + +#undef _COMPONENT +#define _COMPONENT ACPI_HARDWARE + +/****************************************************************************** + * + * FUNCTION: AcpiEnterSleepStateS4Bios + * + * PARAMETERS: None + * + * RETURN: Status + * + * DESCRIPTION: Enter a system sleep state S4BIOS + * + ******************************************************************************/ + +ACPI_STATUS +AcpiEnterSleepStateS4Bios ( + void) +{ + ACPI_OBJECT_LIST ArgList; + ACPI_OBJECT Arg; + UINT32 Value; + + + ACPI_FUNCTION_TRACE ("AcpiEnterSleepStateS4Bios"); + + /* run the _PTS and _GTS methods */ + + ACPI_MEMSET(&ArgList, 0, sizeof(ArgList)); + ArgList.Count = 1; + ArgList.Pointer = &Arg; + + ACPI_MEMSET(&Arg, 0, sizeof(Arg)); + Arg.Type = ACPI_TYPE_INTEGER; + Arg.Integer.Value = ACPI_STATE_S4; + + AcpiEvaluateObject (NULL, "\\_PTS", &ArgList, NULL); + AcpiEvaluateObject (NULL, "\\_GTS", &ArgList, NULL); + + /* clear wake status */ + + AcpiSetRegister (ACPI_BITREG_WAKE_STATUS, 1, ACPI_MTX_LOCK); + + ACPI_DISABLE_IRQS (); + + AcpiHwDisableNonWakeupGpes(); + + /* flush caches */ + + ACPI_FLUSH_CPU_CACHE (); + + /* write the value to command port and wait until we enter sleep state */ + do + { + AcpiOsStall(1000000); + AcpiOsWritePort (AcpiGbl_FADT->SmiCmd, AcpiGbl_FADT->S4BiosReq, 8); + AcpiGetRegister (ACPI_BITREG_WAKE_STATUS, &Value, ACPI_MTX_LOCK); + } + while (!Value); + + AcpiHwEnableNonWakeupGpes(); + + ACPI_ENABLE_IRQS (); + + return_ACPI_STATUS (AE_OK); +} + diff --git a/sys/dev/acpica/acpica_support.h b/sys/dev/acpica/acpica_support.h new file mode 100644 index 0000000..359d0d0 --- /dev/null +++ b/sys/dev/acpica/acpica_support.h @@ -0,0 +1,38 @@ +/*- + * Copyright (c) 2001 Mitsuru IWASAKI + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "acpi.h" + +ACPI_STATUS +AcpiEnterSleepStateS4Bios ( + void); + +ACPI_STATUS +AcpiSetDsdtTablePtr( + ACPI_TABLE_HEADER *Ptr); + diff --git a/sys/dev/acpica/acpiio.h b/sys/dev/acpica/acpiio.h new file mode 100644 index 0000000..002233b --- /dev/null +++ b/sys/dev/acpica/acpiio.h @@ -0,0 +1,106 @@ +/*- + * Copyright (c) 1999 Takanori Watanabe <takawata@shidahara1.planet.sci.kobe-u.ac.jp> + * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* + * Core ACPI subsystem ioctls + */ +#define ACPIIO_ENABLE _IO('P', 1) +#define ACPIIO_DISABLE _IO('P', 2) +#define ACPIIO_SETSLPSTATE _IOW('P', 3, int) + +struct acpi_battdesc { + int type; /* battery type: e.g. CMBAT */ + int phys_unit; /* physical unit of devclass */ +}; + +#define ACPI_BATT_TYPE_CMBAT 0x0000 +#define ACPI_BATT_TYPE_SMBAT 0x0001 + +struct acpi_battinfo { + int cap; /* percent */ + int min; /* remianing time */ + int state; /* battery state */ +}; + +#define ACPI_CMBAT_MAXSTRLEN 32 +struct acpi_bif { + u_int32_t unit; /* 0 for mWh, 1 for mAh */ + u_int32_t dcap; /* Design Capacity */ + u_int32_t lfcap; /* Last Full capacity */ + u_int32_t btech; /* Battery Technorogy */ + u_int32_t dvol; /* Design voltage (mV) */ + u_int32_t wcap; /* WARN capacity */ + u_int32_t lcap; /* Low capacity */ + u_int32_t gra1; /* Granulity 1(Warn to Low) */ + u_int32_t gra2; /* Granulity 2(Full to Warn) */ + char model[ACPI_CMBAT_MAXSTRLEN]; /* model identifier */ + char serial[ACPI_CMBAT_MAXSTRLEN]; /* Serial number */ + char type[ACPI_CMBAT_MAXSTRLEN]; /* Type */ + char oeminfo[ACPI_CMBAT_MAXSTRLEN]; /* OEM infomation */ +}; + +struct acpi_bst { + u_int32_t state; /* Battery State */ + u_int32_t rate; /* Present Rate */ + u_int32_t cap; /* Remaining Capacity */ + u_int32_t volt; /* Present Voltage */ +}; + +#define ACPI_BATT_STAT_DISCHARG 0x0001 +#define ACPI_BATT_STAT_CHARGING 0x0002 +#define ACPI_BATT_STAT_CRITICAL 0x0004 +#define ACPI_BATT_STAT_NOT_PRESENT 0x0007 +#define ACPI_BATT_STAT_MAX 0x0007 + +union acpi_battery_ioctl_arg { + int unit; /* argument: logical unit (-1 = overall) */ + + struct acpi_battdesc battdesc; + struct acpi_battinfo battinfo; + + struct acpi_bif bif; /* for acpi_cmbat */ + struct acpi_bst bst; /* for acpi_cmbat */ +}; + +/* Common battery ioctl */ +#define ACPIIO_BATT_GET_UNITS _IOR('B', 0x01, int) +#define ACPIIO_BATT_GET_TYPE _IOR('B', 0x02, union acpi_battery_ioctl_arg) +#define ACPIIO_BATT_GET_BATTINFO _IOWR('B', 0x03, union acpi_battery_ioctl_arg) +#define ACPIIO_BATT_GET_BATTDESC _IOWR('B', 0x04, union acpi_battery_ioctl_arg) + +/* Cotrol Method battery ioctl */ +#define ACPIIO_CMBAT_GET_BIF _IOWR('B', 0x10, union acpi_battery_ioctl_arg) +#define ACPIIO_CMBAT_GET_BST _IOWR('B', 0x11, union acpi_battery_ioctl_arg) + +#define ACPIIO_ACAD_GET_STATUS _IOR('A', 1, int) + +#ifdef _KERNEL +extern int acpi_register_ioctl(u_long cmd, int (* fn)(u_long cmd, caddr_t addr, void *arg), void *arg); +extern void acpi_deregister_ioctl(u_long cmd, int (* fn)(u_long cmd, caddr_t addr, void *arg)); +#endif diff --git a/sys/dev/acpica/acpivar.h b/sys/dev/acpica/acpivar.h new file mode 100644 index 0000000..cd254ac --- /dev/null +++ b/sys/dev/acpica/acpivar.h @@ -0,0 +1,411 @@ +/*- + * Copyright (c) 2000 Mitsuru IWASAKI <iwasaki@jp.freebsd.org> + * Copyright (c) 2000 Michael Smith <msmith@freebsd.org> + * Copyright (c) 2000 BSDi + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include "bus_if.h" +#include <sys/eventhandler.h> +#include <sys/sysctl.h> +#if __FreeBSD_version >= 500000 +#include <sys/lock.h> +#include <sys/mutex.h> +#endif + +#include <machine/bus.h> +#include <machine/resource.h> + +#if __FreeBSD_version < 500000 +typedef vm_offset_t vm_paddr_t; +#endif + +struct acpi_softc { + device_t acpi_dev; + dev_t acpi_dev_t; + + struct resource *acpi_irq; + int acpi_irq_rid; + void *acpi_irq_handle; + + int acpi_enabled; + int acpi_sstate; + int acpi_sleep_disabled; + + struct sysctl_ctx_list acpi_sysctl_ctx; + struct sysctl_oid *acpi_sysctl_tree; +#define ACPI_POWER_BUTTON_DEFAULT_SX ACPI_STATE_S5; +#define ACPI_SLEEP_BUTTON_DEFAULT_SX ACPI_STATE_S1; +#define ACPI_LID_SWITCH_DEFAULT_SX ACPI_STATE_S1; + int acpi_power_button_sx; + int acpi_sleep_button_sx; + int acpi_lid_switch_sx; + + int acpi_standby_sx; + int acpi_suspend_sx; + + int acpi_sleep_delay; + int acpi_s4bios; + int acpi_disable_on_poweroff; + + int acpi_verbose; + + bus_dma_tag_t acpi_waketag; + bus_dmamap_t acpi_wakemap; + vm_offset_t acpi_wakeaddr; + vm_paddr_t acpi_wakephys; + + struct sysctl_ctx_list acpi_battery_sysctl_ctx; + struct sysctl_oid *acpi_battery_sysctl_tree; +}; + +struct acpi_device { + /* ACPI ivars */ + ACPI_HANDLE ad_handle; + int ad_magic; + void *ad_private; + + /* resources */ + struct resource_list ad_rl; + +}; + +#if __FreeBSD_version < 500000 +/* + * In 4.x, ACPI is protected by splhigh(). + */ +# define ACPI_LOCK s = splhigh() +# define ACPI_UNLOCK splx(s) +# define ACPI_ASSERTLOCK +# define ACPI_MSLEEP(a, b, c, d, e) tsleep(a, c, d, e) +# define ACPI_LOCK_DECL int s +# define kthread_create(a, b, c, d, e, f) kthread_create(a, b, c, f) +# define tc_init(a) init_timecounter(a) +#elif 0 +/* + * The ACPI subsystem lives under a single mutex. You *must* + * acquire this mutex before calling any of the acpi_ or Acpi* functions. + */ +extern struct mtx acpi_mutex; +# define ACPI_LOCK mtx_lock(&acpi_mutex) +# define ACPI_UNLOCK mtx_unlock(&acpi_mutex) +# define ACPI_ASSERTLOCK mtx_assert(&acpi_mutex, MA_OWNED) +# define ACPI_MSLEEP(a, b, c, d, e) msleep(a, b, c, d, e) +# define ACPI_LOCK_DECL +#else +# define ACPI_LOCK +# define ACPI_UNLOCK +# define ACPI_ASSERTLOCK +# define ACPI_MSLEEP(a, b, c, d, e) tsleep(a, c, d, e) +# define ACPI_LOCK_DECL +#endif + +/* + * ACPI CA does not define layers for non-ACPI CA drivers. + * We define some here within the range provided. + */ +#define ACPI_BUS 0x00010000 +#define ACPI_SYSTEM 0x00020000 +#define ACPI_POWER 0x00040000 +#define ACPI_EC 0x00080000 +#define ACPI_AC_ADAPTER 0x00100000 +#define ACPI_BATTERY 0x00110000 +#define ACPI_BUTTON 0x00120000 +#define ACPI_PROCESSOR 0x00140000 +#define ACPI_THERMAL 0x00180000 +#define ACPI_FAN 0x00200000 + +/* + * Constants for different interrupt models used with acpi_SetIntrModel(). + */ +#define ACPI_INTR_PIC 0 +#define ACPI_INTR_APIC 1 +#define ACPI_INTR_SAPIC 2 + +/* XXX this is no longer referenced anywhere, remove? */ +#if 0 +/* + * This is a cheap and nasty way to get around the horrid counted list + * argument format that AcpiEvalateObject uses. + */ +#define ACPI_OBJECTLIST_MAX 16 +struct acpi_object_list { + UINT32 count; + ACPI_OBJECT *pointer[ACPI_OBJECTLIST_MAX]; + ACPI_OBJECT object[ACPI_OBJECTLIST_MAX]; +}; + +static __inline struct acpi_object_list * +acpi_AllocObjectList(int nobj) +{ + struct acpi_object_list *l; + int i; + + if (nobj > ACPI_OBJECTLIST_MAX) + return(NULL); + if ((l = AcpiOsAllocate(sizeof(*l))) == NULL) + return(NULL); + bzero(l, sizeof(*l)); + for (i = 0; i < ACPI_OBJECTLIST_MAX; i++) + l->pointer[i] = &l->object[i]; + l->count = nobj; + return(l); +} +#endif /* unused */ + +/* + * Note that the low ivar values are reserved to provide + * interface compatibility with ISA drivers which can also + * attach to ACPI. + */ +#define ACPI_IVAR_HANDLE 0x100 +#define ACPI_IVAR_MAGIC 0x101 +#define ACPI_IVAR_PRIVATE 0x102 + +static __inline ACPI_HANDLE +acpi_get_handle(device_t dev) +{ + uintptr_t up; + ACPI_HANDLE h; + + if (BUS_READ_IVAR(device_get_parent(dev), dev, ACPI_IVAR_HANDLE, &up)) + return(NULL); + h = (ACPI_HANDLE)up; + return(h); +} + +static __inline int +acpi_set_handle(device_t dev, ACPI_HANDLE h) +{ + uintptr_t up; + + up = (uintptr_t)h; + return(BUS_WRITE_IVAR(device_get_parent(dev), dev, ACPI_IVAR_HANDLE, up)); +} + +static __inline int +acpi_get_magic(device_t dev) +{ + uintptr_t up; + int m; + + if (BUS_READ_IVAR(device_get_parent(dev), dev, ACPI_IVAR_MAGIC, &up)) + return(0); + m = (int)up; + return(m); +} + +static __inline int +acpi_set_magic(device_t dev, int m) +{ + uintptr_t up; + + up = (uintptr_t)m; + return(BUS_WRITE_IVAR(device_get_parent(dev), dev, ACPI_IVAR_MAGIC, up)); +} + +static __inline void * +acpi_get_private(device_t dev) +{ + uintptr_t up; + void *p; + + if (BUS_READ_IVAR(device_get_parent(dev), dev, ACPI_IVAR_PRIVATE, &up)) + return(NULL); + p = (void *)up; + return(p); +} + +static __inline int +acpi_set_private(device_t dev, void *p) +{ + uintptr_t up; + + up = (uintptr_t)p; + return(BUS_WRITE_IVAR(device_get_parent(dev), dev, ACPI_IVAR_PRIVATE, up)); +} + +static __inline ACPI_OBJECT_TYPE +acpi_get_type(device_t dev) +{ + ACPI_HANDLE h; + ACPI_OBJECT_TYPE t; + + if ((h = acpi_get_handle(dev)) == NULL) + return(ACPI_TYPE_NOT_FOUND); + if (AcpiGetType(h, &t) != AE_OK) + return(ACPI_TYPE_NOT_FOUND); + return(t); +} + +#ifdef ACPI_DEBUGGER +extern void acpi_EnterDebugger(void); +#endif + +#ifdef ACPI_DEBUG +#include <sys/cons.h> +#define STEP(x) do {printf x, printf("\n"); cngetc();} while (0) +#else +#define STEP(x) +#endif + +#define ACPI_VPRINT(dev, acpi_sc, x...) do { \ + if (acpi_get_verbose(acpi_sc)) \ + device_printf(dev, x); \ +} while (0) + +#define ACPI_DEVINFO_PRESENT(x) (((x) & 0x9) == 9) +extern BOOLEAN acpi_DeviceIsPresent(device_t dev); +extern BOOLEAN acpi_BatteryIsPresent(device_t dev); +extern BOOLEAN acpi_MatchHid(device_t dev, char *hid); +extern ACPI_STATUS acpi_GetHandleInScope(ACPI_HANDLE parent, char *path, ACPI_HANDLE *result); +extern ACPI_BUFFER *acpi_AllocBuffer(int size); +extern ACPI_STATUS acpi_EvaluateInteger(ACPI_HANDLE handle, char *path, int *number); +extern ACPI_STATUS acpi_ConvertBufferToInteger(ACPI_BUFFER *bufp, int *number); +extern ACPI_STATUS acpi_ForeachPackageObject(ACPI_OBJECT *obj, + void (* func)(ACPI_OBJECT *comp, void *arg), + void *arg); +extern ACPI_STATUS acpi_FindIndexedResource(ACPI_BUFFER *buf, int index, ACPI_RESOURCE **resp); +extern ACPI_STATUS acpi_AppendBufferResource(ACPI_BUFFER *buf, ACPI_RESOURCE *res); +extern ACPI_STATUS acpi_SetIntrModel(int model); + +extern ACPI_STATUS acpi_SetSleepState(struct acpi_softc *sc, int state); +extern ACPI_STATUS acpi_Enable(struct acpi_softc *sc); +extern ACPI_STATUS acpi_Disable(struct acpi_softc *sc); + +struct acpi_parse_resource_set { + void (* set_init)(device_t dev, void **context); + void (* set_done)(device_t dev, void *context); + void (* set_ioport)(device_t dev, void *context, u_int32_t base, u_int32_t length); + void (* set_iorange)(device_t dev, void *context, u_int32_t low, u_int32_t high, + u_int32_t length, u_int32_t align); + void (* set_memory)(device_t dev, void *context, u_int32_t base, u_int32_t length); + void (* set_memoryrange)(device_t dev, void *context, u_int32_t low, u_int32_t high, + u_int32_t length, u_int32_t align); + void (* set_irq)(device_t dev, void *context, u_int32_t *irq, int cout); + void (* set_drq)(device_t dev, void *context, u_int32_t *drq, int count); + void (* set_start_dependant)(device_t dev, void *context, int preference); + void (* set_end_dependant)(device_t dev, void *context); +}; + +extern struct acpi_parse_resource_set acpi_res_parse_set; +extern ACPI_STATUS acpi_parse_resources(device_t dev, ACPI_HANDLE handle, + struct acpi_parse_resource_set *set); +/* XXX until Intel fix this in their headers, based on NEXT_RESOURCE */ +#define ACPI_RESOURCE_NEXT(Res) (ACPI_RESOURCE *)((UINT8 *) Res + Res->Length) + +/* + * ACPI event handling + */ +extern UINT32 acpi_eventhandler_power_button_for_sleep(void *context); +extern UINT32 acpi_eventhandler_power_button_for_wakeup(void *context); +extern UINT32 acpi_eventhandler_sleep_button_for_sleep(void *context); +extern UINT32 acpi_eventhandler_sleep_button_for_wakeup(void *context); + +#define ACPI_EVENT_PRI_FIRST 0 +#define ACPI_EVENT_PRI_DEFAULT 10000 +#define ACPI_EVENT_PRI_LAST 20000 + +typedef void (*acpi_event_handler_t)(void *, int); + +EVENTHANDLER_DECLARE(acpi_sleep_event, acpi_event_handler_t); +EVENTHANDLER_DECLARE(acpi_wakeup_event, acpi_event_handler_t); + +/* + * Device power control. + */ +extern ACPI_STATUS acpi_pwr_switch_consumer(ACPI_HANDLE consumer, int state); + +/* + * Misc. + */ +static __inline struct acpi_softc * +acpi_device_get_parent_softc(device_t child) +{ + device_t parent; + + parent = device_get_parent(child); + if (parent == NULL) { + return(NULL); + } + return(device_get_softc(parent)); +} + +static __inline int +acpi_get_verbose(struct acpi_softc *sc) +{ + if (sc) + return(sc->acpi_verbose); + return(0); +} + +extern char *acpi_name(ACPI_HANDLE handle); +extern int acpi_avoid(ACPI_HANDLE handle); +extern int acpi_disabled(char *subsys); + +extern void acpi_device_enable_wake_capability(ACPI_HANDLE h, int enable); +extern void acpi_device_enable_wake_event(ACPI_HANDLE h); + +extern int acpi_machdep_init(device_t dev); +extern void acpi_install_wakeup_handler(struct acpi_softc *sc); +extern int acpi_sleep_machdep(struct acpi_softc *sc, int state); + +/* + * Battery Abstraction. + */ +struct acpi_battinfo; +struct acpi_battdesc; + +extern int acpi_battery_register(int, int); +extern int acpi_battery_get_battinfo(int, struct acpi_battinfo *); +extern int acpi_battery_get_units(void); +extern int acpi_battery_get_info_expire(void); +extern int acpi_battery_get_battdesc(int, struct acpi_battdesc *); + +extern int acpi_cmbat_get_battinfo(int, struct acpi_battinfo *); + +/* + * AC adapter interface. + */ + +extern int acpi_acad_get_acline(int *); + +#if __FreeBSD_version >= 500000 +#ifndef ACPI_MAX_THREADS +#define ACPI_MAX_THREADS 3 +#endif +#if ACPI_MAX_THREADS > 0 +#define ACPI_USE_THREADS +#endif +#endif + +#ifdef ACPI_USE_THREADS +/* + * ACPI task kernel thread initialization. + */ +extern int acpi_task_thread_init(void); +#endif + |