Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 17 Mar 2009 00:48:12 +0000 (UTC)
From:      Jung-uk Kim <jkim@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r189903 - in head/sys: amd64/acpica amd64/amd64 amd64/include conf dev/acpica i386/i386 kern sys
Message-ID:  <200903170048.n2H0mCw4076588@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: jkim
Date: Tue Mar 17 00:48:11 2009
New Revision: 189903
URL: http://svn.freebsd.org/changeset/base/189903

Log:
  Initial suspend/resume support for amd64.
  
  This code is heavily inspired by Takanori Watanabe's experimental SMP patch
  for i386 and large portion was shamelessly cut and pasted from Peter Wemm's
  AP boot code.

Added:
  head/sys/amd64/acpica/Makefile   (contents, props changed)
  head/sys/amd64/acpica/acpi_switch.S   (contents, props changed)
  head/sys/amd64/acpica/acpi_wakecode.S   (contents, props changed)
  head/sys/amd64/acpica/genwakecode.sh   (contents, props changed)
  head/sys/amd64/acpica/genwakedata.sh   (contents, props changed)
Modified:
  head/sys/amd64/acpica/acpi_machdep.c
  head/sys/amd64/acpica/acpi_wakeup.c
  head/sys/amd64/amd64/amd64_mem.c
  head/sys/amd64/amd64/apic_vector.S
  head/sys/amd64/amd64/cpu_switch.S
  head/sys/amd64/amd64/db_trace.c
  head/sys/amd64/amd64/genassym.c
  head/sys/amd64/amd64/mp_machdep.c
  head/sys/amd64/include/apicvar.h
  head/sys/amd64/include/pcb.h
  head/sys/amd64/include/smp.h
  head/sys/conf/files.amd64
  head/sys/dev/acpica/acpi.c
  head/sys/dev/acpica/acpi_ec.c
  head/sys/i386/i386/i686_mem.c
  head/sys/i386/i386/k6_mem.c
  head/sys/kern/subr_smp.c
  head/sys/sys/memrange.h
  head/sys/sys/smp.h

Added: head/sys/amd64/acpica/Makefile
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/amd64/acpica/Makefile	Tue Mar 17 00:48:11 2009	(r189903)
@@ -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>

Modified: head/sys/amd64/acpica/acpi_machdep.c
==============================================================================
--- head/sys/amd64/acpica/acpi_machdep.c	Mon Mar 16 23:56:28 2009	(r189902)
+++ head/sys/amd64/acpica/acpi_machdep.c	Tue Mar 17 00:48:11 2009	(r189903)
@@ -31,25 +31,50 @@ __FBSDID("$FreeBSD$");
 #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);
+
+uint32_t acpi_resume_beep;
+TUNABLE_INT("debug.acpi.resume_beep", &acpi_resume_beep);
+SYSCTL_UINT(_debug_acpi, OID_AUTO, resume_beep, CTLFLAG_RW, &acpi_resume_beep,
+    0, "Beep the PC speaker when resuming");
+uint32_t 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;
+	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);
 }
 

Added: head/sys/amd64/acpica/acpi_switch.S
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/amd64/acpica/acpi_switch.S	Tue Mar 17 00:48:11 2009	(r189903)
@@ -0,0 +1,177 @@
+/*-
+ * 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
+
+	/* Restore segment registers. */
+	mov	WAKEUP_PCB(DS), %ds
+	mov	WAKEUP_PCB(ES), %es
+	mov	WAKEUP_XPCB(SS), %ss
+	mov	WAKEUP_PCB(FS), %fs
+	mov	WAKEUP_PCB(GS), %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)
+	movw	WAKEUP_XPCB(TR), %ax
+	ltr	%ax
+
+	/* 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)

Added: head/sys/amd64/acpica/acpi_wakecode.S
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/amd64/acpica/acpi_wakecode.S	Tue Mar 17 00:48:11 2009	(r189903)
@@ -0,0 +1,278 @@
+/*-
+ * 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 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
+	pushw	$0
+	popfw
+
+	/* To debug resume hangs, beep the speaker if the user requested. */
+	cmpw	$0, resume_beep - wakeup_start
+	je	1f
+	movb	$0xc0, %al
+	outb	%al, $0x42
+	movb	$0x04, %al
+	outb	%al, $0x42
+	inb	$0x61, %al
+	orb	$0x3, %al
+	outb	%al, $0x61
+	movw	$0, resume_beep - wakeup_start
+1:
+
+	/* Re-initialize video BIOS if the reset_video tunable is set. */
+	cmpw	$0, reset_video - wakeup_start
+	je	1f
+	lcall	$0xc000, $3
+	movw	$0, reset_video - wakeup_start
+
+	/*
+	 * Set up segment registers for real mode again in case the
+	 * previous BIOS call clobbers them.
+	 */
+	mov	%cs, %ax
+	mov	%ax, %ds
+	mov	%ax, %ss
+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.
+	 */
+	.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
+
+	ALIGN_DATA
+bootgdt:
+	.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
+resume_beep:
+	.long	0
+reset_video:
+	.long	0
+wakeup_retaddr:
+	.quad	0
+wakeup_kpml4:
+	.quad	0
+
+wakeup_ctx:
+	.quad	0
+wakeup_xpcb:
+	.quad	0
+wakeup_gdt:
+	.word	0
+	.quad	0
+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:

Modified: head/sys/amd64/acpica/acpi_wakeup.c
==============================================================================
--- head/sys/amd64/acpica/acpi_wakeup.c	Mon Mar 16 23:56:28 2009	(r189902)
+++ head/sys/amd64/acpica/acpi_wakeup.c	Tue Mar 17 00:48:11 2009	(r189903)
@@ -1,6 +1,8 @@
 /*-
  * 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
@@ -29,18 +31,411 @@
 __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/sysctl.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 uint32_t		acpi_resume_beep;
+extern uint32_t		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_reset_tss(int cpu);
+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);
+
+	acpi_reset_tss(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
+
+static void
+acpi_reset_tss(int cpu)
+{
+	uint32_t	*tss;
+
+	/*
+	 * We have to clear "task busy" bit in TSS to restore
+	 * task register later.  Otherwise, ltr causes GPF.
+	 */
+	tss = (uint32_t *)&gdt[NGDT * cpu + GPROC0_SEL] + 1;
+	*tss &= ~((SDT_SYSBSY ^ SDT_SYSTSS) << 8);
+}
+
 int
 acpi_sleep_machdep(struct acpi_softc *sc, int state)
 {
-	return (0);
+	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, uint32_t, acpi_resume_beep);
+		WAKECODE_FIXUP(reset_video, uint32_t, acpi_reset_video);
+
+		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);
+
+		acpi_reset_tss(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);
+
+		WAKECODE_FIXUP(resume_beep, uint32_t, 0);
+		WAKECODE_FIXUP(reset_video, uint32_t, 0);
+#ifdef SMP
+		if (wakeup_cpus != 0)
+			acpi_wakeup_cpus(sc, wakeup_cpus);
+#endif
+		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);
 }

Added: head/sys/amd64/acpica/genwakecode.sh
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/amd64/acpica/genwakecode.sh	Tue Mar 17 00:48:11 2009	(r189903)
@@ -0,0 +1,6 @@
+#!/bin/sh
+# $FreeBSD$
+#
+file2c 'static char wakecode[] = {' '};' <acpi_wakecode.bin
+
+exit 0

Added: head/sys/amd64/acpica/genwakedata.sh
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/amd64/acpica/genwakedata.sh	Tue Mar 17 00:48:11 2009	(r189903)
@@ -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

Modified: head/sys/amd64/amd64/amd64_mem.c
==============================================================================
--- head/sys/amd64/amd64/amd64_mem.c	Mon Mar 16 23:56:28 2009	(r189902)
+++ head/sys/amd64/amd64/amd64_mem.c	Tue Mar 17 00:48:11 2009	(r189903)
@@ -73,11 +73,13 @@ static void	amd64_mrinit(struct mem_rang
 static int	amd64_mrset(struct mem_range_softc *sc,

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200903170048.n2H0mCw4076588>