diff options
Diffstat (limited to 'sys/amd64/acpica')
| -rw-r--r-- | sys/amd64/acpica/Makefile | 33 | ||||
| -rw-r--r-- | sys/amd64/acpica/OsdEnvironment.c | 71 | ||||
| -rw-r--r-- | sys/amd64/acpica/acpi_machdep.c | 139 | ||||
| -rw-r--r-- | sys/amd64/acpica/acpi_switch.S | 190 | ||||
| -rw-r--r-- | sys/amd64/acpica/acpi_wakecode.S | 286 | ||||
| -rw-r--r-- | sys/amd64/acpica/acpi_wakeup.c | 420 | ||||
| -rwxr-xr-x | sys/amd64/acpica/genwakecode.sh | 6 | ||||
| -rwxr-xr-x | sys/amd64/acpica/genwakedata.sh | 9 | ||||
| -rw-r--r-- | sys/amd64/acpica/madt.c | 786 |
9 files changed, 1940 insertions, 0 deletions
diff --git a/sys/amd64/acpica/Makefile b/sys/amd64/acpica/Makefile new file mode 100644 index 000000000000..743728051e97 --- /dev/null +++ b/sys/amd64/acpica/Makefile @@ -0,0 +1,33 @@ +# $FreeBSD$ + +# Correct path for kernel builds +# Don't rely on the kernel's .depend file +.ifdef MAKESRCPATH +.PATH: ${MAKESRCPATH} +DEPENDFILE= +.else +MAKESRCPATH= ${.CURDIR} +CLEANFILES= acpi_wakecode.h acpi_wakedata.h acpi_wakecode.bin acpi_wakecode.o +.endif +.if ${CC} == "icc" +CFLAGS+= -restrict +NOSTDINC= -X +.else +NOSTDINC= -nostdinc +.endif +CFLAGS+= ${NOSTDINC} -include opt_global.h -I. -I${MAKESRCPATH}/../.. + +all: acpi_wakecode.h acpi_wakedata.h + +acpi_wakecode.o: acpi_wakecode.S assym.s + +acpi_wakecode.bin: acpi_wakecode.o + objcopy -S -O binary acpi_wakecode.o acpi_wakecode.bin + +acpi_wakecode.h: acpi_wakecode.bin + sh ${MAKESRCPATH}/genwakecode.sh > acpi_wakecode.h + +acpi_wakedata.h: acpi_wakecode.bin + sh ${MAKESRCPATH}/genwakedata.sh > acpi_wakedata.h + +.include <bsd.prog.mk> diff --git a/sys/amd64/acpica/OsdEnvironment.c b/sys/amd64/acpica/OsdEnvironment.c new file mode 100644 index 000000000000..6928f2167870 --- /dev/null +++ b/sys/amd64/acpica/OsdEnvironment.c @@ -0,0 +1,71 @@ +/*- + * 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +/* + * 6.1 : Environmental support + */ +#include <sys/types.h> +#include <sys/bus.h> +#include <sys/linker_set.h> +#include <sys/sysctl.h> + +#include <contrib/dev/acpica/acpi.h> +#include <contrib/dev/acpica/actables.h> + +static u_long amd64_acpi_root; + +SYSCTL_ULONG(_machdep, OID_AUTO, acpi_root, CTLFLAG_RD, &amd64_acpi_root, 0, + "The physical address of the RSDP"); + +ACPI_STATUS +AcpiOsInitialize(void) +{ + return(0); +} + +ACPI_STATUS +AcpiOsTerminate(void) +{ + return(0); +} + +ACPI_PHYSICAL_ADDRESS +AcpiOsGetRootPointer(void) +{ + u_long ptr; + + if (amd64_acpi_root == 0 && + (resource_long_value("acpi", 0, "rsdp", (long *)&ptr) == 0 || + AcpiFindRootPointer((ACPI_NATIVE_UINT *)&ptr) == AE_OK) && + ptr != 0) + amd64_acpi_root = ptr; + + return (amd64_acpi_root); +} diff --git a/sys/amd64/acpica/acpi_machdep.c b/sys/amd64/acpica/acpi_machdep.c new file mode 100644 index 000000000000..c69f14ad8bba --- /dev/null +++ b/sys/amd64/acpica/acpi_machdep.c @@ -0,0 +1,139 @@ +/*- + * 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/sysctl.h> + +#include <contrib/dev/acpica/acpi.h> +#include <dev/acpica/acpivar.h> + +#include <machine/nexusvar.h> + +SYSCTL_DECL(_debug_acpi); + +int acpi_resume_beep; +TUNABLE_INT("debug.acpi.resume_beep", &acpi_resume_beep); +SYSCTL_INT(_debug_acpi, OID_AUTO, resume_beep, CTLFLAG_RW, &acpi_resume_beep, + 0, "Beep the PC speaker when resuming"); + +int acpi_reset_video; +TUNABLE_INT("hw.acpi.reset_video", &acpi_reset_video); + +static int intr_model = ACPI_INTR_PIC; +static struct apm_clone_data acpi_clone; + +int +acpi_machdep_init(device_t dev) +{ + struct acpi_softc *sc; + + sc = devclass_get_softc(devclass_find("acpi"), 0); + + /* Create a fake clone for /dev/acpi. */ + STAILQ_INIT(&sc->apm_cdevs); + acpi_clone.cdev = sc->acpi_dev_t; + acpi_clone.acpi_sc = sc; + ACPI_LOCK(acpi); + STAILQ_INSERT_TAIL(&sc->apm_cdevs, &acpi_clone, entries); + ACPI_UNLOCK(acpi); + sc->acpi_clone = &acpi_clone; + acpi_install_wakeup_handler(sc); + + if (intr_model != ACPI_INTR_PIC) + acpi_SetIntrModel(intr_model); + + SYSCTL_ADD_UINT(&sc->acpi_sysctl_ctx, + SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, + "reset_video", CTLFLAG_RW, &acpi_reset_video, 0, + "Call the VESA reset BIOS vector on the resume path"); + + return (0); +} + +void +acpi_SetDefaultIntrModel(int model) +{ + + intr_model = model; +} + +int +acpi_machdep_quirks(int *quirks) +{ + return (0); +} + +void +acpi_cpu_c1() +{ + __asm __volatile("sti; hlt"); +} + +/* + * ACPI nexus(4) driver. + */ +static int +nexus_acpi_probe(device_t dev) +{ + int error; + + error = acpi_identify(); + if (error) + return (error); + + return (BUS_PROBE_DEFAULT); +} + +static int +nexus_acpi_attach(device_t dev) +{ + + nexus_init_resources(); + bus_generic_probe(dev); + if (BUS_ADD_CHILD(dev, 10, "acpi", 0) == NULL) + panic("failed to add acpi0 device"); + + return (bus_generic_attach(dev)); +} + +static device_method_t nexus_acpi_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, nexus_acpi_probe), + DEVMETHOD(device_attach, nexus_acpi_attach), + + { 0, 0 } +}; + +DEFINE_CLASS_1(nexus, nexus_acpi_driver, nexus_acpi_methods, 1, nexus_driver); +static devclass_t nexus_devclass; + +DRIVER_MODULE(nexus_acpi, root, nexus_acpi_driver, nexus_devclass, 0, 0); diff --git a/sys/amd64/acpica/acpi_switch.S b/sys/amd64/acpica/acpi_switch.S new file mode 100644 index 000000000000..892dd11c47a5 --- /dev/null +++ b/sys/amd64/acpica/acpi_switch.S @@ -0,0 +1,190 @@ +/*- + * Copyright (c) 2001 Takanori Watanabe <takawata@jp.freebsd.org> + * Copyright (c) 2001 Mitsuru IWASAKI <iwasaki@jp.freebsd.org> + * Copyright (c) 2008-2009 Jung-uk Kim <jkim@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 <machine/asmacros.h> +#include <machine/specialreg.h> + +#include "acpi_wakedata.h" +#include "assym.s" + +#define WAKEUP_DECL(member) \ + .set WAKEUP_ ## member, wakeup_ ## member - wakeup_ctx + + WAKEUP_DECL(xpcb) + WAKEUP_DECL(gdt) + WAKEUP_DECL(efer) + WAKEUP_DECL(pat) + WAKEUP_DECL(star) + WAKEUP_DECL(lstar) + WAKEUP_DECL(cstar) + WAKEUP_DECL(sfmask) + WAKEUP_DECL(cpu) + +#define WAKEUP_CTX(member) WAKEUP_ ## member (%rdi) +#define WAKEUP_PCB(member) PCB_ ## member(%r11) +#define WAKEUP_XPCB(member) XPCB_ ## member(%r11) + +ENTRY(acpi_restorecpu) + /* Switch to KPML4phys. */ + movq %rsi, %rax + movq %rax, %cr3 + + /* Restore GDT. */ + lgdt WAKEUP_CTX(gdt) + jmp 1f +1: + + /* Fetch PCB. */ + movq WAKEUP_CTX(xpcb), %r11 + + /* Force kernel segment registers. */ + movl $KDSEL, %eax + movw %ax, %ds + movw %ax, %es + movw %ax, %ss + movl $KUF32SEL, %eax + movw %ax, %fs + movl $KUG32SEL, %eax + movw %ax, %gs + + movl $MSR_FSBASE, %ecx + movl WAKEUP_PCB(FSBASE), %eax + movl 4 + WAKEUP_PCB(FSBASE), %edx + wrmsr + movl $MSR_GSBASE, %ecx + movl WAKEUP_PCB(GSBASE), %eax + movl 4 + WAKEUP_PCB(GSBASE), %edx + wrmsr + movl $MSR_KGSBASE, %ecx + movl WAKEUP_XPCB(KGSBASE), %eax + movl 4 + WAKEUP_XPCB(KGSBASE), %edx + wrmsr + + /* Restore EFER. */ + movl $MSR_EFER, %ecx + movl WAKEUP_CTX(efer), %eax + wrmsr + + /* Restore PAT. */ + movl $MSR_PAT, %ecx + movl WAKEUP_CTX(pat), %eax + movl 4 + WAKEUP_CTX(pat), %edx + wrmsr + + /* Restore fast syscall stuff. */ + movl $MSR_STAR, %ecx + movl WAKEUP_CTX(star), %eax + movl 4 + WAKEUP_CTX(star), %edx + wrmsr + movl $MSR_LSTAR, %ecx + movl WAKEUP_CTX(lstar), %eax + movl 4 + WAKEUP_CTX(lstar), %edx + wrmsr + movl $MSR_CSTAR, %ecx + movl WAKEUP_CTX(cstar), %eax + movl 4 + WAKEUP_CTX(cstar), %edx + wrmsr + movl $MSR_SF_MASK, %ecx + movl WAKEUP_CTX(sfmask), %eax + wrmsr + + /* Restore CR0, CR2 and CR4. */ + movq WAKEUP_XPCB(CR0), %rax + movq %rax, %cr0 + movq WAKEUP_XPCB(CR2), %rax + movq %rax, %cr2 + movq WAKEUP_XPCB(CR4), %rax + movq %rax, %cr4 + + /* Restore descriptor tables. */ + lidt WAKEUP_XPCB(IDT) + lldt WAKEUP_XPCB(LDT) + +#define SDT_SYSTSS 9 +#define SDT_SYSBSY 11 + + /* Clear "task busy" bit and reload TR. */ + movq PCPU(TSS), %rax + andb $(~SDT_SYSBSY | SDT_SYSTSS), 5(%rax) + movw WAKEUP_XPCB(TR), %ax + ltr %ax + +#undef SDT_SYSTSS +#undef SDT_SYSBSY + + /* Restore other callee saved registers. */ + movq WAKEUP_PCB(R15), %r15 + movq WAKEUP_PCB(R14), %r14 + movq WAKEUP_PCB(R13), %r13 + movq WAKEUP_PCB(R12), %r12 + movq WAKEUP_PCB(RBP), %rbp + movq WAKEUP_PCB(RSP), %rsp + movq WAKEUP_PCB(RBX), %rbx + + /* Restore debug registers. */ + movq WAKEUP_PCB(DR0), %rax + movq %rax, %dr0 + movq WAKEUP_PCB(DR1), %rax + movq %rax, %dr1 + movq WAKEUP_PCB(DR2), %rax + movq %rax, %dr2 + movq WAKEUP_PCB(DR3), %rax + movq %rax, %dr3 + movq WAKEUP_PCB(DR6), %rax + movq %rax, %dr6 + movq WAKEUP_PCB(DR7), %rax + movq %rax, %dr7 + + /* Restore return address. */ + movq WAKEUP_PCB(RIP), %rax + movq %rax, (%rsp) + + /* Indicate the CPU is resumed. */ + xorl %eax, %eax + movl %eax, WAKEUP_CTX(cpu) + + ret +END(acpi_restorecpu) + +ENTRY(acpi_savecpu) + /* Fetch XPCB and save CPU context. */ + movq %rdi, %r10 + call savectx2 + movq %r10, %r11 + + /* Patch caller's return address and stack pointer. */ + movq (%rsp), %rax + movq %rax, WAKEUP_PCB(RIP) + movq %rsp, %rax + movq %rax, WAKEUP_PCB(RSP) + + movl $1, %eax + ret +END(acpi_savecpu) diff --git a/sys/amd64/acpica/acpi_wakecode.S b/sys/amd64/acpica/acpi_wakecode.S new file mode 100644 index 000000000000..4e82f53196af --- /dev/null +++ b/sys/amd64/acpica/acpi_wakecode.S @@ -0,0 +1,286 @@ +/*- + * Copyright (c) 2001 Takanori Watanabe <takawata@jp.freebsd.org> + * Copyright (c) 2001 Mitsuru IWASAKI <iwasaki@jp.freebsd.org> + * Copyright (c) 2003 Peter Wemm + * Copyright (c) 2008-2009 Jung-uk Kim <jkim@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$ + */ + +#define LOCORE + +#include <machine/asmacros.h> +#include <machine/specialreg.h> + +#include "assym.s" + +/* + * Resume entry point for real mode. + * + * If XFirmwareWakingVector is zero and FirmwareWakingVector is non-zero + * in FACS, the BIOS enters here in real mode after POST with CS set to + * (FirmwareWakingVector >> 4) and IP set to (FirmwareWakingVector & 0xf). + * Depending on the previous sleep state, we may need to initialize more + * of the system (i.e., S3 suspend-to-RAM vs. S4 suspend-to-disk). + * + * Note: If XFirmwareWakingVector is non-zero, it should disable address + * translation/paging and interrupts, load all segment registers with + * a flat 4 GB address space, and set EFLAGS.IF to zero. Currently + * this mode is not supported by this code. + */ + + .data /* So we can modify it */ + + ALIGN_TEXT +wakeup_start: + .code16 + /* + * Set up segment registers for real mode, a small stack for + * any calls we make, and clear any flags. + */ + cli /* make sure no interrupts */ + cld + mov %cs, %ax /* copy %cs to %ds. Remember these */ + mov %ax, %ds /* are offsets rather than selectors */ + mov %ax, %ss + movw $PAGE_SIZE - 8, %sp + xorw %ax, %ax + pushw %ax + popfw + + /* To debug resume hangs, beep the speaker if the user requested. */ + testb $~0, resume_beep - wakeup_start + jz 1f + movb $0, resume_beep - wakeup_start + movb $0xc0, %al + outb %al, $0x42 + movb $0x04, %al + outb %al, $0x42 + inb $0x61, %al + orb $0x3, %al + outb %al, $0x61 +1: + + /* Re-initialize video BIOS if the reset_video tunable is set. */ + testb $~0, reset_video - wakeup_start + jz 1f + movb $0, reset_video - wakeup_start + lcall $0xc000, $3 + + /* Re-start in case the previous BIOS call clobbers them. */ + jmp wakeup_start +1: + + /* + * Find relocation base and patch the gdt descript and ljmp targets + */ + xorl %ebx, %ebx + mov %cs, %bx + sall $4, %ebx /* %ebx is now our relocation base */ + + /* + * Load the descriptor table pointer. We'll need it when running + * in 16-bit protected mode. + */ + lgdtl bootgdtdesc - wakeup_start + + /* Enable protected mode */ + movl $CR0_PE, %eax + mov %eax, %cr0 + + /* + * Now execute a far jump to turn on protected mode. This + * causes the segment registers to turn into selectors and causes + * %cs to be loaded from the gdt. + * + * The following instruction is: + * ljmpl $bootcode32 - bootgdt, $wakeup_32 - wakeup_start + * but gas cannot assemble that. And besides, we patch the targets + * in early startup and its a little clearer what we are patching. + */ +wakeup_sw32: + .byte 0x66 /* size override to 32 bits */ + .byte 0xea /* opcode for far jump */ + .long wakeup_32 - wakeup_start /* offset in segment */ + .word bootcode32 - bootgdt /* index in gdt for 32 bit code */ + + /* + * At this point, we are running in 32 bit legacy protected mode. + */ + .code32 +wakeup_32: + + mov $bootdata32 - bootgdt, %eax + mov %ax, %ds + + /* Turn on the PAE and PSE bits for when paging is enabled */ + mov %cr4, %eax + orl $(CR4_PAE | CR4_PSE), %eax + mov %eax, %cr4 + + /* + * Enable EFER.LME so that we get long mode when all the prereqs are + * in place. In this case, it turns on when CR0_PG is finally enabled. + * Pick up a few other EFER bits that we'll use need we're here. + */ + movl $MSR_EFER, %ecx + rdmsr + orl $EFER_LME | EFER_SCE, %eax + wrmsr + + /* + * Point to the embedded page tables for startup. Note that this + * only gets accessed after we're actually in 64 bit mode, however + * we can only set the bottom 32 bits of %cr3 in this state. This + * means we are required to use a temporary page table that is below + * the 4GB limit. %ebx is still our relocation base. We could just + * subtract 3 * PAGE_SIZE, but that would be too easy. + */ + leal wakeup_pagetables - wakeup_start(%ebx), %eax + movl (%eax), %eax + mov %eax, %cr3 + + /* + * Finally, switch to long bit mode by enabling paging. We have + * to be very careful here because all the segmentation disappears + * out from underneath us. The spec says we can depend on the + * subsequent pipelined branch to execute, but *only if* everthing + * is still identity mapped. If any mappings change, the pipeline + * will flush. + */ + mov %cr0, %eax + orl $CR0_PG, %eax + mov %eax, %cr0 + + /* + * At this point paging is enabled, and we are in "compatability" mode. + * We do another far jump to reload %cs with the 64 bit selector. + * %cr3 points to a 4-level page table page. + * We cannot yet jump all the way to the kernel because we can only + * specify a 32 bit linear address. So, yet another trampoline. + * + * The following instruction is: + * ljmp $bootcode64 - bootgdt, $wakeup_64 - wakeup_start + * but gas cannot assemble that. And besides, we patch the targets + * in early startup and its a little clearer what we are patching. + */ +wakeup_sw64: + .byte 0xea /* opcode for far jump */ + .long wakeup_64 - wakeup_start /* offset in segment */ + .word bootcode64 - bootgdt /* index in gdt for 64 bit code */ + + /* + * Yeehar! We're running in 64-bit mode! We can mostly ignore our + * segment registers, and get on with it. + * Note that we are running at the correct virtual address, but with + * a 1:1 1GB mirrored mapping over entire address space. We had better + * switch to a real %cr3 promptly so that we can get to the direct map + * space. Remember that jmp is relative and that we've been relocated, + * so use an indirect jump. + */ + ALIGN_TEXT + .code64 +wakeup_64: + mov $bootdata64 - bootgdt, %eax + mov %ax, %ds + + /* Restore arguments and return. */ + movq wakeup_ctx - wakeup_start(%rbx), %rdi + movq wakeup_kpml4 - wakeup_start(%rbx), %rsi + movq wakeup_retaddr - wakeup_start(%rbx), %rax + jmp *%rax + + .data + +resume_beep: + .byte 0 +reset_video: + .byte 0 + + ALIGN_DATA +bootgdt: + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + .long 0x00000000 + +bootcode64: + .long 0x0000ffff + .long 0x00af9b00 + +bootdata64: + .long 0x0000ffff + .long 0x00af9300 + +bootcode32: + .long 0x0000ffff + .long 0x00cf9b00 + +bootdata32: + .long 0x0000ffff + .long 0x00cf9300 +bootgdtend: + +wakeup_pagetables: + .long 0 + +bootgdtdesc: + .word bootgdtend - bootgdt /* Length */ + .long bootgdt - wakeup_start /* Offset plus %ds << 4 */ + + ALIGN_DATA +wakeup_retaddr: + .quad 0 +wakeup_kpml4: + .quad 0 + +wakeup_ctx: + .quad 0 +wakeup_xpcb: + .quad 0 +wakeup_gdt: + .word 0 + .quad 0 + + ALIGN_DATA +wakeup_efer: + .quad 0 +wakeup_pat: + .quad 0 +wakeup_star: + .quad 0 +wakeup_lstar: + .quad 0 +wakeup_cstar: + .quad 0 +wakeup_sfmask: + .quad 0 +wakeup_cpu: + .long 0 +dummy: diff --git a/sys/amd64/acpica/acpi_wakeup.c b/sys/amd64/acpica/acpi_wakeup.c new file mode 100644 index 000000000000..2f9d8a01b295 --- /dev/null +++ b/sys/amd64/acpica/acpi_wakeup.c @@ -0,0 +1,420 @@ +/*- + * Copyright (c) 2001 Takanori Watanabe <takawata@jp.freebsd.org> + * Copyright (c) 2001 Mitsuru IWASAKI <iwasaki@jp.freebsd.org> + * Copyright (c) 2003 Peter Wemm + * Copyright (c) 2008-2009 Jung-uk Kim <jkim@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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/memrange.h> +#include <sys/smp.h> +#include <sys/types.h> + +#include <vm/vm.h> +#include <vm/pmap.h> + +#include <machine/intr_machdep.h> +#include <machine/pcb.h> +#include <machine/pmap.h> +#include <machine/specialreg.h> +#include <machine/vmparam.h> + +#ifdef SMP +#include <machine/apicreg.h> +#include <machine/smp.h> +#endif + +#include <contrib/dev/acpica/acpi.h> +#include <dev/acpica/acpivar.h> + +#include "acpi_wakecode.h" +#include "acpi_wakedata.h" + +/* Make sure the code is less than a page and leave room for the stack. */ +CTASSERT(sizeof(wakecode) < PAGE_SIZE - 1024); + +#ifndef _SYS_CDEFS_H_ +#error this file needs sys/cdefs.h as a prerequisite +#endif + +extern int acpi_resume_beep; +extern int acpi_reset_video; + +#ifdef SMP +extern struct xpcb *stopxpcbs; +#else +static struct xpcb *stopxpcbs; +#endif + +int acpi_restorecpu(struct xpcb *, vm_offset_t); +int acpi_savecpu(struct xpcb *); + +static void acpi_alloc_wakeup_handler(void); +static void acpi_stop_beep(void *); + +#ifdef SMP +static int acpi_wakeup_ap(struct acpi_softc *, int); +static void acpi_wakeup_cpus(struct acpi_softc *, cpumask_t); +#endif + +#define WAKECODE_VADDR(sc) ((sc)->acpi_wakeaddr + (3 * PAGE_SIZE)) +#define WAKECODE_PADDR(sc) ((sc)->acpi_wakephys + (3 * PAGE_SIZE)) +#define WAKECODE_FIXUP(offset, type, val) do { \ + type *addr; \ + addr = (type *)(WAKECODE_VADDR(sc) + offset); \ + *addr = val; \ +} while (0) + +/* Turn off bits 1&2 of the PIT, stopping the beep. */ +static void +acpi_stop_beep(void *arg) +{ + outb(0x61, inb(0x61) & ~0x3); +} + +#ifdef SMP +static int +acpi_wakeup_ap(struct acpi_softc *sc, int cpu) +{ + int vector = (WAKECODE_PADDR(sc) >> 12) & 0xff; + int apic_id = cpu_apic_ids[cpu]; + int ms; + + WAKECODE_FIXUP(wakeup_xpcb, struct xpcb *, &stopxpcbs[cpu]); + WAKECODE_FIXUP(wakeup_gdt, uint16_t, stopxpcbs[cpu].xpcb_gdt.rd_limit); + WAKECODE_FIXUP(wakeup_gdt + 2, uint64_t, + stopxpcbs[cpu].xpcb_gdt.rd_base); + WAKECODE_FIXUP(wakeup_cpu, int, cpu); + + /* do an INIT IPI: assert RESET */ + lapic_ipi_raw(APIC_DEST_DESTFLD | APIC_TRIGMOD_EDGE | + APIC_LEVEL_ASSERT | APIC_DESTMODE_PHY | APIC_DELMODE_INIT, apic_id); + + /* wait for pending status end */ + lapic_ipi_wait(-1); + + /* do an INIT IPI: deassert RESET */ + lapic_ipi_raw(APIC_DEST_ALLESELF | APIC_TRIGMOD_LEVEL | + APIC_LEVEL_DEASSERT | APIC_DESTMODE_PHY | APIC_DELMODE_INIT, 0); + + /* wait for pending status end */ + DELAY(10000); /* wait ~10mS */ + lapic_ipi_wait(-1); + + /* + * next we do a STARTUP IPI: the previous INIT IPI might still be + * latched, (P5 bug) this 1st STARTUP would then terminate + * immediately, and the previously started INIT IPI would continue. OR + * the previous INIT IPI has already run. and this STARTUP IPI will + * run. OR the previous INIT IPI was ignored. and this STARTUP IPI + * will run. + */ + + /* do a STARTUP IPI */ + lapic_ipi_raw(APIC_DEST_DESTFLD | APIC_TRIGMOD_EDGE | + APIC_LEVEL_DEASSERT | APIC_DESTMODE_PHY | APIC_DELMODE_STARTUP | + vector, apic_id); + lapic_ipi_wait(-1); + DELAY(200); /* wait ~200uS */ + + /* + * finally we do a 2nd STARTUP IPI: this 2nd STARTUP IPI should run IF + * the previous STARTUP IPI was cancelled by a latched INIT IPI. OR + * this STARTUP IPI will be ignored, as only ONE STARTUP IPI is + * recognized after hardware RESET or INIT IPI. + */ + + lapic_ipi_raw(APIC_DEST_DESTFLD | APIC_TRIGMOD_EDGE | + APIC_LEVEL_DEASSERT | APIC_DESTMODE_PHY | APIC_DELMODE_STARTUP | + vector, apic_id); + lapic_ipi_wait(-1); + DELAY(200); /* wait ~200uS */ + + /* Wait up to 5 seconds for it to start. */ + for (ms = 0; ms < 5000; ms++) { + if (*(int *)(WAKECODE_VADDR(sc) + wakeup_cpu) == 0) + return (1); /* return SUCCESS */ + DELAY(1000); + } + return (0); /* return FAILURE */ +} + +#define WARMBOOT_TARGET 0 +#define WARMBOOT_OFF (KERNBASE + 0x0467) +#define WARMBOOT_SEG (KERNBASE + 0x0469) + +#define CMOS_REG (0x70) +#define CMOS_DATA (0x71) +#define BIOS_RESET (0x0f) +#define BIOS_WARM (0x0a) + +static void +acpi_wakeup_cpus(struct acpi_softc *sc, cpumask_t wakeup_cpus) +{ + uint32_t mpbioswarmvec; + cpumask_t map; + int cpu; + u_char mpbiosreason; + + /* save the current value of the warm-start vector */ + mpbioswarmvec = *((uint32_t *)WARMBOOT_OFF); + outb(CMOS_REG, BIOS_RESET); + mpbiosreason = inb(CMOS_DATA); + + /* setup a vector to our boot code */ + *((volatile u_short *)WARMBOOT_OFF) = WARMBOOT_TARGET; + *((volatile u_short *)WARMBOOT_SEG) = WAKECODE_PADDR(sc) >> 4; + outb(CMOS_REG, BIOS_RESET); + outb(CMOS_DATA, BIOS_WARM); /* 'warm-start' */ + + /* Wake up each AP. */ + for (cpu = 1; cpu < mp_ncpus; cpu++) { + map = 1ul << cpu; + if ((wakeup_cpus & map) != map) + continue; + if (acpi_wakeup_ap(sc, cpu) == 0) { + /* restore the warmstart vector */ + *(uint32_t *)WARMBOOT_OFF = mpbioswarmvec; + panic("acpi_wakeup: failed to resume AP #%d (PHY #%d)", + cpu, cpu_apic_ids[cpu]); + } + } + + /* restore the warmstart vector */ + *(uint32_t *)WARMBOOT_OFF = mpbioswarmvec; + + outb(CMOS_REG, BIOS_RESET); + outb(CMOS_DATA, mpbiosreason); +} +#endif + +int +acpi_sleep_machdep(struct acpi_softc *sc, int state) +{ + struct savefpu *stopfpu; +#ifdef SMP + cpumask_t wakeup_cpus; +#endif + register_t cr3, rf; + ACPI_STATUS status; + int ret; + + ret = -1; + + if (sc->acpi_wakeaddr == 0ul) + return (ret); + +#ifdef SMP + wakeup_cpus = PCPU_GET(other_cpus); +#endif + + AcpiSetFirmwareWakingVector(WAKECODE_PADDR(sc)); + + rf = intr_disable(); + intr_suspend(); + + /* + * Temporarily switch to the kernel pmap because it provides + * an identity mapping (setup at boot) for the low physical + * memory region containing the wakeup code. + */ + cr3 = rcr3(); + load_cr3(KPML4phys); + + stopfpu = &stopxpcbs[0].xpcb_pcb.pcb_save; + if (acpi_savecpu(&stopxpcbs[0])) { + fpugetregs(curthread, stopfpu); + +#ifdef SMP + if (wakeup_cpus != 0 && suspend_cpus(wakeup_cpus) == 0) { + device_printf(sc->acpi_dev, + "Failed to suspend APs: CPU mask = 0x%jx\n", + (uintmax_t)(wakeup_cpus & ~stopped_cpus)); + goto out; + } +#endif + + WAKECODE_FIXUP(resume_beep, uint8_t, (acpi_resume_beep != 0)); + WAKECODE_FIXUP(reset_video, uint8_t, (acpi_reset_video != 0)); + + WAKECODE_FIXUP(wakeup_xpcb, struct xpcb *, &stopxpcbs[0]); + WAKECODE_FIXUP(wakeup_gdt, uint16_t, + stopxpcbs[0].xpcb_gdt.rd_limit); + WAKECODE_FIXUP(wakeup_gdt + 2, uint64_t, + stopxpcbs[0].xpcb_gdt.rd_base); + WAKECODE_FIXUP(wakeup_cpu, int, 0); + + /* Call ACPICA to enter the desired sleep state */ + if (state == ACPI_STATE_S4 && sc->acpi_s4bios) + status = AcpiEnterSleepStateS4bios(); + else + status = AcpiEnterSleepState(state); + + if (status != AE_OK) { + device_printf(sc->acpi_dev, + "AcpiEnterSleepState failed - %s\n", + AcpiFormatException(status)); + goto out; + } + + for (;;) + ia32_pause(); + } else { + fpusetregs(curthread, stopfpu); +#ifdef SMP + if (wakeup_cpus != 0) + acpi_wakeup_cpus(sc, wakeup_cpus); +#endif + acpi_resync_clock(sc); + ret = 0; + } + +out: +#ifdef SMP + if (wakeup_cpus != 0) + restart_cpus(wakeup_cpus); +#endif + + load_cr3(cr3); + intr_resume(); + intr_restore(rf); + + AcpiSetFirmwareWakingVector(0); + + if (ret == 0 && mem_range_softc.mr_op != NULL && + mem_range_softc.mr_op->reinit != NULL) + mem_range_softc.mr_op->reinit(&mem_range_softc); + + /* If we beeped, turn it off after a delay. */ + if (acpi_resume_beep) + timeout(acpi_stop_beep, NULL, 3 * hz); + + return (ret); +} + +static vm_offset_t acpi_wakeaddr; + +static void +acpi_alloc_wakeup_handler(void) +{ + void *wakeaddr; + + if (!cold) + return; + + /* + * Specify the region for our wakeup code. We want it in the low 1 MB + * region, excluding video memory and above (0xa0000). We ask for + * it to be page-aligned, just to be safe. + */ + wakeaddr = contigmalloc(4 * PAGE_SIZE, M_DEVBUF, M_NOWAIT, 0, 0x9ffff, + PAGE_SIZE, 0ul); + if (wakeaddr == NULL) { + printf("%s: can't alloc wake memory\n", __func__); + return; + } + stopxpcbs = malloc(mp_ncpus * sizeof(*stopxpcbs), M_DEVBUF, M_NOWAIT); + if (stopxpcbs == NULL) { + contigfree(wakeaddr, 4 * PAGE_SIZE, M_DEVBUF); + printf("%s: can't alloc CPU state memory\n", __func__); + return; + } + acpi_wakeaddr = (vm_offset_t)wakeaddr; +} + +SYSINIT(acpiwakeup, SI_SUB_KMEM, SI_ORDER_ANY, acpi_alloc_wakeup_handler, 0); + +void +acpi_install_wakeup_handler(struct acpi_softc *sc) +{ + uint64_t *pt4, *pt3, *pt2; + int i; + + if (acpi_wakeaddr == 0ul) + return; + + sc->acpi_wakeaddr = acpi_wakeaddr; + sc->acpi_wakephys = vtophys(acpi_wakeaddr); + + bcopy(wakecode, (void *)WAKECODE_VADDR(sc), sizeof(wakecode)); + + /* Patch GDT base address, ljmp targets and page table base address. */ + WAKECODE_FIXUP((bootgdtdesc + 2), uint32_t, + WAKECODE_PADDR(sc) + bootgdt); + WAKECODE_FIXUP((wakeup_sw32 + 2), uint32_t, + WAKECODE_PADDR(sc) + wakeup_32); + WAKECODE_FIXUP((wakeup_sw64 + 1), uint32_t, + WAKECODE_PADDR(sc) + wakeup_64); + WAKECODE_FIXUP(wakeup_pagetables, uint32_t, sc->acpi_wakephys); + + /* Save pointers to some global data. */ + WAKECODE_FIXUP(wakeup_retaddr, void *, acpi_restorecpu); + WAKECODE_FIXUP(wakeup_kpml4, uint64_t, KPML4phys); + WAKECODE_FIXUP(wakeup_ctx, vm_offset_t, + WAKECODE_VADDR(sc) + wakeup_ctx); + WAKECODE_FIXUP(wakeup_efer, uint64_t, rdmsr(MSR_EFER)); + WAKECODE_FIXUP(wakeup_pat, uint64_t, rdmsr(MSR_PAT)); + WAKECODE_FIXUP(wakeup_star, uint64_t, rdmsr(MSR_STAR)); + WAKECODE_FIXUP(wakeup_lstar, uint64_t, rdmsr(MSR_LSTAR)); + WAKECODE_FIXUP(wakeup_cstar, uint64_t, rdmsr(MSR_CSTAR)); + WAKECODE_FIXUP(wakeup_sfmask, uint64_t, rdmsr(MSR_SF_MASK)); + + /* Build temporary page tables below realmode code. */ + pt4 = (uint64_t *)acpi_wakeaddr; + pt3 = pt4 + (PAGE_SIZE) / sizeof(uint64_t); + pt2 = pt3 + (PAGE_SIZE) / sizeof(uint64_t); + + /* Create the initial 1GB replicated page tables */ + for (i = 0; i < 512; i++) { + /* + * Each slot of the level 4 pages points + * to the same level 3 page + */ + pt4[i] = (uint64_t)(sc->acpi_wakephys + PAGE_SIZE); + pt4[i] |= PG_V | PG_RW | PG_U; + + /* + * Each slot of the level 3 pages points + * to the same level 2 page + */ + pt3[i] = (uint64_t)(sc->acpi_wakephys + (2 * PAGE_SIZE)); + pt3[i] |= PG_V | PG_RW | PG_U; + + /* The level 2 page slots are mapped with 2MB pages for 1GB. */ + pt2[i] = i * (2 * 1024 * 1024); + pt2[i] |= PG_V | PG_RW | PG_PS | PG_U; + } + + if (bootverbose) + device_printf(sc->acpi_dev, "wakeup code va %p pa %p\n", + (void *)sc->acpi_wakeaddr, (void *)sc->acpi_wakephys); +} diff --git a/sys/amd64/acpica/genwakecode.sh b/sys/amd64/acpica/genwakecode.sh new file mode 100755 index 000000000000..c9d0077de56a --- /dev/null +++ b/sys/amd64/acpica/genwakecode.sh @@ -0,0 +1,6 @@ +#!/bin/sh +# $FreeBSD$ +# +file2c -sx 'static char wakecode[] = {' '};' <acpi_wakecode.bin + +exit 0 diff --git a/sys/amd64/acpica/genwakedata.sh b/sys/amd64/acpica/genwakedata.sh new file mode 100755 index 000000000000..6d4181ecacaf --- /dev/null +++ b/sys/amd64/acpica/genwakedata.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# $FreeBSD$ +# +nm -n --defined-only acpi_wakecode.o | while read offset dummy what +do + echo "#define ${what} 0x${offset}" +done + +exit 0 diff --git a/sys/amd64/acpica/madt.c b/sys/amd64/acpica/madt.c new file mode 100644 index 000000000000..a8df55d20190 --- /dev/null +++ b/sys/amd64/acpica/madt.c @@ -0,0 +1,786 @@ +/*- + * Copyright (c) 2003 John Baldwin <jhb@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. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * 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. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/smp.h> + +#include <vm/vm.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> + +#include <machine/apicreg.h> +#include <machine/frame.h> +#include <machine/intr_machdep.h> +#include <machine/apicvar.h> +#include <machine/md_var.h> +#include <machine/specialreg.h> + +#include <contrib/dev/acpica/acpi.h> +#include <contrib/dev/acpica/actables.h> +#include <dev/acpica/acpivar.h> +#include <dev/pci/pcivar.h> + +typedef void madt_entry_handler(ACPI_SUBTABLE_HEADER *entry, void *arg); + +/* These two arrays are indexed by APIC IDs. */ +struct ioapic_info { + void *io_apic; + UINT32 io_vector; +} ioapics[MAX_APIC_ID + 1]; + +struct lapic_info { + u_int la_enabled:1; + u_int la_acpi_id:8; +} lapics[MAX_APIC_ID + 1]; + +static int madt_found_sci_override; +static ACPI_TABLE_MADT *madt; +static vm_paddr_t madt_physaddr; +static vm_offset_t madt_length; + +MALLOC_DEFINE(M_MADT, "madt_table", "ACPI MADT Table Items"); + +static enum intr_polarity interrupt_polarity(UINT16 IntiFlags, UINT8 Source); +static enum intr_trigger interrupt_trigger(UINT16 IntiFlags, UINT8 Source); +static int madt_find_cpu(u_int acpi_id, u_int *apic_id); +static int madt_find_interrupt(int intr, void **apic, u_int *pin); +static void *madt_map(vm_paddr_t pa, int offset, vm_offset_t length); +static void *madt_map_table(vm_paddr_t pa, int offset, const char *sig); +static void madt_parse_apics(ACPI_SUBTABLE_HEADER *entry, void *arg); +static void madt_parse_interrupt_override( + ACPI_MADT_INTERRUPT_OVERRIDE *intr); +static void madt_parse_ints(ACPI_SUBTABLE_HEADER *entry, + void *arg __unused); +static void madt_parse_local_nmi(ACPI_MADT_LOCAL_APIC_NMI *nmi); +static void madt_parse_nmi(ACPI_MADT_NMI_SOURCE *nmi); +static int madt_probe(void); +static int madt_probe_cpus(void); +static void madt_probe_cpus_handler(ACPI_SUBTABLE_HEADER *entry, + void *arg __unused); +static int madt_probe_table(vm_paddr_t address); +static void madt_register(void *dummy); +static int madt_setup_local(void); +static int madt_setup_io(void); +static void madt_unmap(void *data, vm_offset_t length); +static void madt_unmap_table(void *table); +static void madt_walk_table(madt_entry_handler *handler, void *arg); + +static struct apic_enumerator madt_enumerator = { + "MADT", + madt_probe, + madt_probe_cpus, + madt_setup_local, + madt_setup_io +}; + +/* + * Code to abuse the crashdump map to map in the tables for the early + * probe. We cheat and make the following assumptions about how we + * use this KVA: pages 0 and 1 are used to map in the header of each + * table found via the RSDT or XSDT and pages 2 to n are used to map + * in the RSDT or XSDT. We have to use 2 pages for the table headers + * in case a header spans a page boundary. The offset is in pages; + * the length is in bytes. + */ +static void * +madt_map(vm_paddr_t pa, int offset, vm_offset_t length) +{ + vm_offset_t va, off; + void *data; + + off = pa & PAGE_MASK; + length = roundup(length + off, PAGE_SIZE); + pa = pa & PG_FRAME; + va = (vm_offset_t)pmap_kenter_temporary(pa, offset) + + (offset * PAGE_SIZE); + data = (void *)(va + off); + length -= PAGE_SIZE; + while (length > 0) { + va += PAGE_SIZE; + pa += PAGE_SIZE; + length -= PAGE_SIZE; + pmap_kenter(va, pa); + invlpg(va); + } + return (data); +} + +static void +madt_unmap(void *data, vm_offset_t length) +{ + vm_offset_t va, off; + + va = (vm_offset_t)data; + off = va & PAGE_MASK; + length = roundup(length + off, PAGE_SIZE); + va &= ~PAGE_MASK; + while (length > 0) { + pmap_kremove(va); + invlpg(va); + va += PAGE_SIZE; + length -= PAGE_SIZE; + } +} + +static void * +madt_map_table(vm_paddr_t pa, int offset, const char *sig) +{ + ACPI_TABLE_HEADER *header; + vm_offset_t length; + void *table; + + header = madt_map(pa, offset, sizeof(ACPI_TABLE_HEADER)); + if (strncmp(header->Signature, sig, ACPI_NAME_SIZE) != 0) { + madt_unmap(header, sizeof(ACPI_TABLE_HEADER)); + return (NULL); + } + length = header->Length; + madt_unmap(header, sizeof(ACPI_TABLE_HEADER)); + table = madt_map(pa, offset, length); + if (ACPI_FAILURE(AcpiTbChecksum(table, length))) { + if (bootverbose) + printf("MADT: Failed checksum for table %s\n", sig); + madt_unmap(table, length); + return (NULL); + } + return (table); +} + +static void +madt_unmap_table(void *table) +{ + ACPI_TABLE_HEADER *header; + + header = (ACPI_TABLE_HEADER *)table; + madt_unmap(table, header->Length); +} + +/* + * Look for an ACPI Multiple APIC Description Table ("APIC") + */ +static int +madt_probe(void) +{ + ACPI_PHYSICAL_ADDRESS rsdp_ptr; + ACPI_TABLE_RSDP *rsdp; + ACPI_TABLE_RSDT *rsdt; + ACPI_TABLE_XSDT *xsdt; + int i, count; + + if (resource_disabled("acpi", 0)) + return (ENXIO); + + /* + * Map in the RSDP. Since ACPI uses AcpiOsMapMemory() which in turn + * calls pmap_mapbios() to find the RSDP, we assume that we can use + * pmap_mapbios() to map the RSDP. + */ + if ((rsdp_ptr = AcpiOsGetRootPointer()) == 0) + return (ENXIO); + rsdp = pmap_mapbios(rsdp_ptr, sizeof(ACPI_TABLE_RSDP)); + if (rsdp == NULL) { + if (bootverbose) + printf("MADT: Failed to map RSDP\n"); + return (ENXIO); + } + + /* + * For ACPI >= 2.0, use the XSDT if it is available. + * Otherwise, use the RSDT. We map the XSDT or RSDT at page 1 + * in the crashdump area. Page 0 is used to map in the + * headers of candidate ACPI tables. + */ + if (rsdp->Revision >= 2 && rsdp->XsdtPhysicalAddress != 0) { + /* + * AcpiOsGetRootPointer only verifies the checksum for + * the version 1.0 portion of the RSDP. Version 2.0 has + * an additional checksum that we verify first. + */ + if (AcpiTbChecksum((UINT8 *)rsdp, ACPI_RSDP_XCHECKSUM_LENGTH)) { + if (bootverbose) + printf("MADT: RSDP failed extended checksum\n"); + return (ENXIO); + } + xsdt = madt_map_table(rsdp->XsdtPhysicalAddress, 2, + ACPI_SIG_XSDT); + if (xsdt == NULL) { + if (bootverbose) + printf("MADT: Failed to map XSDT\n"); + return (ENXIO); + } + count = (xsdt->Header.Length - sizeof(ACPI_TABLE_HEADER)) / + sizeof(UINT64); + for (i = 0; i < count; i++) + if (madt_probe_table(xsdt->TableOffsetEntry[i])) + break; + madt_unmap_table(xsdt); + } else { + rsdt = madt_map_table(rsdp->RsdtPhysicalAddress, 2, + ACPI_SIG_RSDT); + if (rsdt == NULL) { + if (bootverbose) + printf("MADT: Failed to map RSDT\n"); + return (ENXIO); + } + count = (rsdt->Header.Length - sizeof(ACPI_TABLE_HEADER)) / + sizeof(UINT32); + for (i = 0; i < count; i++) + if (madt_probe_table(rsdt->TableOffsetEntry[i])) + break; + madt_unmap_table(rsdt); + } + pmap_unmapbios((vm_offset_t)rsdp, sizeof(ACPI_TABLE_RSDP)); + if (madt_physaddr == 0) { + if (bootverbose) + printf("MADT: No MADT table found\n"); + return (ENXIO); + } + if (bootverbose) + printf("MADT: Found table at 0x%jx\n", + (uintmax_t)madt_physaddr); + + /* + * Verify that we can map the full table and that its checksum is + * correct, etc. + */ + madt = madt_map_table(madt_physaddr, 0, ACPI_SIG_MADT); + if (madt == NULL) + return (ENXIO); + madt_unmap_table(madt); + madt = NULL; + + return (0); +} + +/* + * See if a given ACPI table is the MADT. + */ +static int +madt_probe_table(vm_paddr_t address) +{ + ACPI_TABLE_HEADER *table; + + table = madt_map(address, 0, sizeof(ACPI_TABLE_HEADER)); + if (table == NULL) { + if (bootverbose) + printf("MADT: Failed to map table at 0x%jx\n", + (uintmax_t)address); + return (0); + } + if (bootverbose) + printf("Table '%.4s' at 0x%jx\n", table->Signature, + (uintmax_t)address); + + if (strncmp(table->Signature, ACPI_SIG_MADT, ACPI_NAME_SIZE) != 0) { + madt_unmap(table, sizeof(ACPI_TABLE_HEADER)); + return (0); + } + madt_physaddr = address; + madt_length = table->Length; + madt_unmap(table, sizeof(ACPI_TABLE_HEADER)); + return (1); +} + +/* + * Run through the MP table enumerating CPUs. + */ +static int +madt_probe_cpus(void) +{ + + madt = madt_map_table(madt_physaddr, 0, ACPI_SIG_MADT); + KASSERT(madt != NULL, ("Unable to re-map MADT")); + madt_walk_table(madt_probe_cpus_handler, NULL); + madt_unmap_table(madt); + madt = NULL; + return (0); +} + +/* + * Initialize the local APIC on the BSP. + */ +static int +madt_setup_local(void) +{ + + madt = pmap_mapbios(madt_physaddr, madt_length); + lapic_init(madt->Address); + printf("ACPI APIC Table: <%.*s %.*s>\n", + (int)sizeof(madt->Header.OemId), madt->Header.OemId, + (int)sizeof(madt->Header.OemTableId), madt->Header.OemTableId); + + /* + * We ignore 64-bit local APIC override entries. Should we + * perhaps emit a warning here if we find one? + */ + return (0); +} + +/* + * Enumerate I/O APICs and setup interrupt sources. + */ +static int +madt_setup_io(void) +{ + void *ioapic; + u_int pin; + int i; + + /* Try to initialize ACPI so that we can access the FADT. */ + i = acpi_Startup(); + if (ACPI_FAILURE(i)) { + printf("MADT: ACPI Startup failed with %s\n", + AcpiFormatException(i)); + printf("Try disabling either ACPI or apic support.\n"); + panic("Using MADT but ACPI doesn't work"); + } + + /* First, we run through adding I/O APIC's. */ + madt_walk_table(madt_parse_apics, NULL); + + /* Second, we run through the table tweaking interrupt sources. */ + madt_walk_table(madt_parse_ints, NULL); + + /* + * If there was not an explicit override entry for the SCI, + * force it to use level trigger and active-low polarity. + */ + if (!madt_found_sci_override) { + if (madt_find_interrupt(AcpiGbl_FADT.SciInterrupt, &ioapic, + &pin) != 0) + printf("MADT: Could not find APIC for SCI IRQ %u\n", + AcpiGbl_FADT.SciInterrupt); + else { + printf( + "MADT: Forcing active-low polarity and level trigger for SCI\n"); + ioapic_set_polarity(ioapic, pin, INTR_POLARITY_LOW); + ioapic_set_triggermode(ioapic, pin, INTR_TRIGGER_LEVEL); + } + } + + /* Third, we register all the I/O APIC's. */ + for (i = 0; i <= MAX_APIC_ID; i++) + if (ioapics[i].io_apic != NULL) + ioapic_register(ioapics[i].io_apic); + + /* Finally, we throw the switch to enable the I/O APIC's. */ + acpi_SetDefaultIntrModel(ACPI_INTR_APIC); + + return (0); +} + +static void +madt_register(void *dummy __unused) +{ + + apic_register_enumerator(&madt_enumerator); +} +SYSINIT(madt_register, SI_SUB_TUNABLES - 1, SI_ORDER_FIRST, + madt_register, NULL); + +/* + * Call the handler routine for each entry in the MADT table. + */ +static void +madt_walk_table(madt_entry_handler *handler, void *arg) +{ + ACPI_SUBTABLE_HEADER *entry; + u_char *p, *end; + + end = (u_char *)(madt) + madt->Header.Length; + for (p = (u_char *)(madt + 1); p < end; ) { + entry = (ACPI_SUBTABLE_HEADER *)p; + handler(entry, arg); + p += entry->Length; + } +} + +static void +madt_probe_cpus_handler(ACPI_SUBTABLE_HEADER *entry, void *arg) +{ + ACPI_MADT_LOCAL_APIC *proc; + struct lapic_info *la; + + switch (entry->Type) { + case ACPI_MADT_TYPE_LOCAL_APIC: + /* + * The MADT does not include a BSP flag, so we have to + * let the MP code figure out which CPU is the BSP on + * its own. + */ + proc = (ACPI_MADT_LOCAL_APIC *)entry; + if (bootverbose) + printf("MADT: Found CPU APIC ID %u ACPI ID %u: %s\n", + proc->Id, proc->ProcessorId, + (proc->LapicFlags & ACPI_MADT_ENABLED) ? + "enabled" : "disabled"); + if (!(proc->LapicFlags & ACPI_MADT_ENABLED)) + break; + if (proc->Id > MAX_APIC_ID) + panic("%s: CPU ID %u too high", __func__, proc->Id); + la = &lapics[proc->Id]; + KASSERT(la->la_enabled == 0, + ("Duplicate local APIC ID %u", proc->Id)); + la->la_enabled = 1; + la->la_acpi_id = proc->ProcessorId; + lapic_create(proc->Id, 0); + break; + } +} + + +/* + * Add an I/O APIC from an entry in the table. + */ +static void +madt_parse_apics(ACPI_SUBTABLE_HEADER *entry, void *arg __unused) +{ + ACPI_MADT_IO_APIC *apic; + + switch (entry->Type) { + case ACPI_MADT_TYPE_IO_APIC: + apic = (ACPI_MADT_IO_APIC *)entry; + if (bootverbose) + printf( + "MADT: Found IO APIC ID %u, Interrupt %u at %p\n", + apic->Id, apic->GlobalIrqBase, + (void *)(uintptr_t)apic->Address); + if (apic->Id > MAX_APIC_ID) + panic("%s: I/O APIC ID %u too high", __func__, + apic->Id); + if (ioapics[apic->Id].io_apic != NULL) + panic("%s: Double APIC ID %u", __func__, apic->Id); + if (apic->GlobalIrqBase >= FIRST_MSI_INT) { + printf("MADT: Ignoring bogus I/O APIC ID %u", apic->Id); + break; + } + ioapics[apic->Id].io_apic = ioapic_create(apic->Address, + apic->Id, apic->GlobalIrqBase); + ioapics[apic->Id].io_vector = apic->GlobalIrqBase; + break; + default: + break; + } +} + +/* + * Determine properties of an interrupt source. Note that for ACPI these + * functions are only used for ISA interrupts, so we assume ISA bus values + * (Active Hi, Edge Triggered) for conforming values except for the ACPI + * SCI for which we use Active Lo, Level Triggered. + */ +static enum intr_polarity +interrupt_polarity(UINT16 IntiFlags, UINT8 Source) +{ + + switch (IntiFlags & ACPI_MADT_POLARITY_MASK) { + case ACPI_MADT_POLARITY_CONFORMS: + if (Source == AcpiGbl_FADT.SciInterrupt) + return (INTR_POLARITY_LOW); + else + return (INTR_POLARITY_HIGH); + case ACPI_MADT_POLARITY_ACTIVE_HIGH: + return (INTR_POLARITY_HIGH); + case ACPI_MADT_POLARITY_ACTIVE_LOW: + return (INTR_POLARITY_LOW); + default: + panic("Bogus Interrupt Polarity"); + } +} + +static enum intr_trigger +interrupt_trigger(UINT16 IntiFlags, UINT8 Source) +{ + + switch (IntiFlags & ACPI_MADT_TRIGGER_MASK) { + case ACPI_MADT_TRIGGER_CONFORMS: + if (Source == AcpiGbl_FADT.SciInterrupt) + return (INTR_TRIGGER_LEVEL); + else + return (INTR_TRIGGER_EDGE); + case ACPI_MADT_TRIGGER_EDGE: + return (INTR_TRIGGER_EDGE); + case ACPI_MADT_TRIGGER_LEVEL: + return (INTR_TRIGGER_LEVEL); + default: + panic("Bogus Interrupt Trigger Mode"); + } +} + +/* + * Find the local APIC ID associated with a given ACPI Processor ID. + */ +static int +madt_find_cpu(u_int acpi_id, u_int *apic_id) +{ + int i; + + for (i = 0; i <= MAX_APIC_ID; i++) { + if (!lapics[i].la_enabled) + continue; + if (lapics[i].la_acpi_id != acpi_id) + continue; + *apic_id = i; + return (0); + } + return (ENOENT); +} + +/* + * Find the IO APIC and pin on that APIC associated with a given global + * interrupt. + */ +static int +madt_find_interrupt(int intr, void **apic, u_int *pin) +{ + int i, best; + + best = -1; + for (i = 0; i <= MAX_APIC_ID; i++) { + if (ioapics[i].io_apic == NULL || + ioapics[i].io_vector > intr) + continue; + if (best == -1 || + ioapics[best].io_vector < ioapics[i].io_vector) + best = i; + } + if (best == -1) + return (ENOENT); + *apic = ioapics[best].io_apic; + *pin = intr - ioapics[best].io_vector; + if (*pin > 32) + printf("WARNING: Found intpin of %u for vector %d\n", *pin, + intr); + return (0); +} + +/* + * Parse an interrupt source override for an ISA interrupt. + */ +static void +madt_parse_interrupt_override(ACPI_MADT_INTERRUPT_OVERRIDE *intr) +{ + void *new_ioapic, *old_ioapic; + u_int new_pin, old_pin; + enum intr_trigger trig; + enum intr_polarity pol; + char buf[64]; + + if (acpi_quirks & ACPI_Q_MADT_IRQ0 && intr->SourceIrq == 0 && + intr->GlobalIrq == 2) { + if (bootverbose) + printf("MADT: Skipping timer override\n"); + return; + } + if (bootverbose) + printf("MADT: Interrupt override: source %u, irq %u\n", + intr->SourceIrq, intr->GlobalIrq); + KASSERT(intr->Bus == 0, ("bus for interrupt overrides must be zero")); + if (madt_find_interrupt(intr->GlobalIrq, &new_ioapic, &new_pin) != 0) { + printf("MADT: Could not find APIC for vector %u (IRQ %u)\n", + intr->GlobalIrq, intr->SourceIrq); + return; + } + + /* + * Lookup the appropriate trigger and polarity modes for this + * entry. + */ + trig = interrupt_trigger(intr->IntiFlags, intr->SourceIrq); + pol = interrupt_polarity(intr->IntiFlags, intr->SourceIrq); + + /* + * If the SCI is identity mapped but has edge trigger and + * active-hi polarity or the force_sci_lo tunable is set, + * force it to use level/lo. + */ + if (intr->SourceIrq == AcpiGbl_FADT.SciInterrupt) { + madt_found_sci_override = 1; + if (getenv_string("hw.acpi.sci.trigger", buf, sizeof(buf))) { + if (tolower(buf[0]) == 'e') + trig = INTR_TRIGGER_EDGE; + else if (tolower(buf[0]) == 'l') + trig = INTR_TRIGGER_LEVEL; + else + panic( + "Invalid trigger %s: must be 'edge' or 'level'", + buf); + printf("MADT: Forcing SCI to %s trigger\n", + trig == INTR_TRIGGER_EDGE ? "edge" : "level"); + } + if (getenv_string("hw.acpi.sci.polarity", buf, sizeof(buf))) { + if (tolower(buf[0]) == 'h') + pol = INTR_POLARITY_HIGH; + else if (tolower(buf[0]) == 'l') + pol = INTR_POLARITY_LOW; + else + panic( + "Invalid polarity %s: must be 'high' or 'low'", + buf); + printf("MADT: Forcing SCI to active %s polarity\n", + pol == INTR_POLARITY_HIGH ? "high" : "low"); + } + } + + /* Remap the IRQ if it is mapped to a different interrupt vector. */ + if (intr->SourceIrq != intr->GlobalIrq) { + /* + * If the SCI is remapped to a non-ISA global interrupt, + * then override the vector we use to setup and allocate + * the interrupt. + */ + if (intr->GlobalIrq > 15 && + intr->SourceIrq == AcpiGbl_FADT.SciInterrupt) + acpi_OverrideInterruptLevel(intr->GlobalIrq); + else + ioapic_remap_vector(new_ioapic, new_pin, + intr->SourceIrq); + if (madt_find_interrupt(intr->SourceIrq, &old_ioapic, + &old_pin) != 0) + printf("MADT: Could not find APIC for source IRQ %u\n", + intr->SourceIrq); + else if (ioapic_get_vector(old_ioapic, old_pin) == + intr->SourceIrq) + ioapic_disable_pin(old_ioapic, old_pin); + } + + /* Program the polarity and trigger mode. */ + ioapic_set_triggermode(new_ioapic, new_pin, trig); + ioapic_set_polarity(new_ioapic, new_pin, pol); +} + +/* + * Parse an entry for an NMI routed to an IO APIC. + */ +static void +madt_parse_nmi(ACPI_MADT_NMI_SOURCE *nmi) +{ + void *ioapic; + u_int pin; + + if (madt_find_interrupt(nmi->GlobalIrq, &ioapic, &pin) != 0) { + printf("MADT: Could not find APIC for vector %u\n", + nmi->GlobalIrq); + return; + } + + ioapic_set_nmi(ioapic, pin); + if (!(nmi->IntiFlags & ACPI_MADT_TRIGGER_CONFORMS)) + ioapic_set_triggermode(ioapic, pin, + interrupt_trigger(nmi->IntiFlags, 0)); + if (!(nmi->IntiFlags & ACPI_MADT_TRIGGER_CONFORMS)) + ioapic_set_polarity(ioapic, pin, + interrupt_polarity(nmi->IntiFlags, 0)); +} + +/* + * Parse an entry for an NMI routed to a local APIC LVT pin. + */ +static void +madt_parse_local_nmi(ACPI_MADT_LOCAL_APIC_NMI *nmi) +{ + u_int apic_id, pin; + + if (nmi->ProcessorId == 0xff) + apic_id = APIC_ID_ALL; + else if (madt_find_cpu(nmi->ProcessorId, &apic_id) != 0) { + if (bootverbose) + printf("MADT: Ignoring local NMI routed to " + "ACPI CPU %u\n", nmi->ProcessorId); + return; + } + if (nmi->Lint == 0) + pin = LVT_LINT0; + else + pin = LVT_LINT1; + lapic_set_lvt_mode(apic_id, pin, APIC_LVT_DM_NMI); + if (!(nmi->IntiFlags & ACPI_MADT_TRIGGER_CONFORMS)) + lapic_set_lvt_triggermode(apic_id, pin, + interrupt_trigger(nmi->IntiFlags, 0)); + if (!(nmi->IntiFlags & ACPI_MADT_POLARITY_CONFORMS)) + lapic_set_lvt_polarity(apic_id, pin, + interrupt_polarity(nmi->IntiFlags, 0)); +} + +/* + * Parse interrupt entries. + */ +static void +madt_parse_ints(ACPI_SUBTABLE_HEADER *entry, void *arg __unused) +{ + + switch (entry->Type) { + case ACPI_MADT_TYPE_INTERRUPT_OVERRIDE: + madt_parse_interrupt_override( + (ACPI_MADT_INTERRUPT_OVERRIDE *)entry); + break; + case ACPI_MADT_TYPE_NMI_SOURCE: + madt_parse_nmi((ACPI_MADT_NMI_SOURCE *)entry); + break; + case ACPI_MADT_TYPE_LOCAL_APIC_NMI: + madt_parse_local_nmi((ACPI_MADT_LOCAL_APIC_NMI *)entry); + break; + } +} + +/* + * Setup per-CPU ACPI IDs. + */ +static void +madt_set_ids(void *dummy) +{ + struct lapic_info *la; + struct pcpu *pc; + u_int i; + + if (madt == NULL) + return; + for (i = 0; i < MAXCPU; i++) { + if (CPU_ABSENT(i)) + continue; + pc = pcpu_find(i); + KASSERT(pc != NULL, ("no pcpu data for CPU %u", i)); + la = &lapics[pc->pc_apic_id]; + if (!la->la_enabled) + panic("APIC: CPU with APIC ID %u is not enabled", + pc->pc_apic_id); + pc->pc_acpi_id = la->la_acpi_id; + if (bootverbose) + printf("APIC: CPU %u has ACPI ID %u\n", i, + la->la_acpi_id); + } +} +SYSINIT(madt_set_ids, SI_SUB_CPU, SI_ORDER_ANY, madt_set_ids, NULL); |
