NetBSD-5.0.2/sys/arch/x86/acpi/acpi_wakeup.c

Compare this file to the similar file:
Show the results in this format:

/*	$NetBSD: acpi_wakeup.c,v 1.10.4.2 2009/03/24 20:20:57 snj Exp $	*/

/*-
 * Copyright (c) 2002 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Takuya SHIOZAKI.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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>
__KERNEL_RCSID(0, "$NetBSD: acpi_wakeup.c,v 1.10.4.2 2009/03/24 20:20:57 snj Exp $");

/*-
 * Copyright (c) 2001 Takanori Watanabe <takawata@jp.freebsd.org>
 * Copyright (c) 2001 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: src/sys/i386/acpica/acpi_wakeup.c,v 1.9 2002/01/10 03:26:46 wes Exp
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <machine/bus.h>
#include <sys/proc.h>
#include <sys/sysctl.h>

#include <uvm/uvm_extern.h>
#include <uvm/uvm_page.h>

#ifdef __i386__
#include "opt_mtrr.h"
#endif
#include "ioapic.h"
#include "lapic.h"

#if NLAPIC > 0
#include <machine/i82489var.h>
#endif
#if NIOAPIC > 0
#include <machine/i82093var.h>
#endif
#include <machine/i8259.h>

#include "acpi.h"

#include <dev/ic/i8253reg.h>
#include <dev/acpi/acpica.h>
#include <dev/acpi/acpivar.h>
#define ACPI_MACHDEP_PRIVATE
#include <machine/acpi_machdep.h>
#include <machine/cpu.h>
#ifdef __i386__
#  include <machine/npx.h>
#else
#  include <machine/fpu.h>
#endif
#include <machine/mtrr.h>

#include <x86/cpuvar.h>
#include <x86/x86/tsc.h>

#include "acpi_wakecode.h"

/* Address is also hard-coded in acpi_wakecode.S */
static paddr_t acpi_wakeup_paddr = 3 * PAGE_SIZE;
static vaddr_t acpi_wakeup_vaddr;

static int acpi_md_node = CTL_EOL;
int acpi_md_vbios_reset = 1; /* Referenced by dev/pci/vga_pci.c */
static int acpi_md_beep_on_reset = 0;

static int	sysctl_md_acpi_vbios_reset(SYSCTLFN_ARGS);
static int	sysctl_md_acpi_beep_on_reset(SYSCTLFN_ARGS);

/* Implemented in acpi_wakeup_low.S. */
int	acpi_md_sleep_prepare(int);
int	acpi_md_sleep_exit(int);	
/* Referenced by acpi_wakeup_low.S. */
void	acpi_md_sleep_enter(int);

#ifdef MULTIPROCESSOR
/* Referenced in ipifuncs.c. */
void	acpi_cpu_sleep(struct cpu_info *);
#endif

static void
acpi_md_sleep_patch(struct cpu_info *ci)
{
#define WAKECODE_FIXUP(offset, type, val) do	{		\
	type	*addr;						\
	addr = (type *)(acpi_wakeup_vaddr + offset);		\
	*addr = val;						\
} while (0)

#define WAKECODE_BCOPY(offset, type, val) do	{		\
	void	**addr;						\
	addr = (void **)(acpi_wakeup_vaddr + offset);		\
	bcopy(&(val), addr, sizeof(type));			\
} while (0)

	paddr_t				tmp_pdir;

	tmp_pdir = pmap_init_tmp_pgtbl(acpi_wakeup_paddr);

	/* Execute Sleep */
	bcopy(wakecode, (void *)acpi_wakeup_vaddr, sizeof(wakecode));

	if (CPU_IS_PRIMARY(ci)) {
		WAKECODE_FIXUP(WAKEUP_vbios_reset, uint8_t, acpi_md_vbios_reset);
		WAKECODE_FIXUP(WAKEUP_beep_on_reset, uint8_t, acpi_md_beep_on_reset);
	} else {
		WAKECODE_FIXUP(WAKEUP_vbios_reset, uint8_t, 0);
		WAKECODE_FIXUP(WAKEUP_beep_on_reset, uint8_t, 0);
	}

#ifdef __i386__
	WAKECODE_FIXUP(WAKEUP_r_cr4, uint32_t, ci->ci_suspend_cr4);
#else
	WAKECODE_FIXUP(WAKEUP_efer, uint32_t, ci->ci_suspend_efer);
#endif

	WAKECODE_FIXUP(WAKEUP_curcpu, void *, ci);
#ifdef __i386__
	WAKECODE_FIXUP(WAKEUP_r_cr3, uint32_t, tmp_pdir);
#else
	WAKECODE_FIXUP(WAKEUP_r_cr3, uint64_t, tmp_pdir);
#endif
	WAKECODE_FIXUP(WAKEUP_restorecpu, void *, acpi_md_sleep_exit);
#undef WAKECODE_FIXUP
#undef WAKECODE_BCOPY
}

/*
 * S4 sleep using S4BIOS support, from FreeBSD.
 *
 * FreeBSD: src/sys/dev/acpica/acpica_support.c,v 1.4 2002/03/12 09:45:17 dfr Exp
 */

static ACPI_STATUS
enter_s4_with_bios(void)
{
	ACPI_OBJECT_LIST	ArgList;
	ACPI_OBJECT		Arg;
	UINT32			ret;
	ACPI_STATUS		status;

	/* 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);

	AcpiHwDisableAllGpes();
	AcpiHwEnableAllWakeupGpes();

	/* 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.SmiCommand,
				AcpiGbl_FADT.S4BiosRequest, 8);
		status = AcpiGetRegister(ACPI_BITREG_WAKE_STATUS, &ret);
		if (ACPI_FAILURE(status))
			break;
	} while (!ret);

	AcpiHwDisableAllGpes();
	AcpiHwEnableAllRuntimeGpes();

	return (AE_OK);
}

void
acpi_md_sleep_enter(int state)
{
	ACPI_STATUS			status;
	struct cpu_info			*ci;

	ci = curcpu();

#ifdef MULTIPROCESSOR
	if (!CPU_IS_PRIMARY(ci)) {
		atomic_and_32(&ci->ci_flags, ~CPUF_RUNNING);
		atomic_and_32(&cpus_running, ~ci->ci_cpumask);

		ACPI_FLUSH_CPU_CACHE();

		for (;;)
			x86_hlt();
	}
#endif

	acpi_md_sleep_patch(ci);

	ACPI_FLUSH_CPU_CACHE();

	if (state == ACPI_STATE_S4) {
		ACPI_TABLE_FACS *facs;
		status = AcpiGetTable(ACPI_SIG_FACS, 0, (ACPI_TABLE_HEADER **)&facs);
		if (ACPI_FAILURE(status)) {
			printf("acpi: S4BIOS not supported: cannot load FACS\n");
			return;
		}
		if (facs == NULL ||
		    (facs->Flags & ACPI_FACS_S4_BIOS_PRESENT) == 0) {
			printf("acpi: S4BIOS not supported: not present");
			return;
		}
		status = enter_s4_with_bios();
	} else {
		status = AcpiEnterSleepState(state);
	}

	if (ACPI_FAILURE(status)) {
		printf("acpi: AcpiEnterSleepState failed: %s\n",
		       AcpiFormatException(status));
		return;
	}

	for (;;)
		x86_hlt();
}

#ifdef MULTIPROCESSOR
void
acpi_cpu_sleep(struct cpu_info *ci)
{
	KASSERT(!CPU_IS_PRIMARY(ci));
	KASSERT(ci == curcpu());

	x86_disable_intr();

	if (acpi_md_sleep_prepare(-1))
		return;

	/* Execute Wakeup */
#ifdef __i386__
	npxinit(ci);
#else
	cpu_init_msrs(ci, false);
	fpuinit(ci);
#endif
#if NLAPIC > 0
	lapic_enable();
	lapic_set_lvt();
	lapic_initclocks();
#endif

	atomic_or_32(&ci->ci_flags, CPUF_RUNNING);
	atomic_or_32(&cpus_running, ci->ci_cpumask);
	tsc_sync_ap(ci);
	tsc_sync_ap(ci);

	x86_enable_intr();
}
#endif

int
acpi_md_sleep(int state)
{
	int s, ret = 0;
#ifdef MULTIPROCESSOR
	struct cpu_info *ci;
	CPU_INFO_ITERATOR cii;
#endif

	KASSERT(acpi_wakeup_paddr != 0);
	KASSERT(sizeof(wakecode) <= PAGE_SIZE);

	if (!CPU_IS_PRIMARY(curcpu())) {
		printf("acpi0: WARNING: ignoring sleep from secondary CPU\n");
		return -1;
	}

	AcpiSetFirmwareWakingVector(acpi_wakeup_paddr);

	s = splhigh();
#ifdef __i386__
	npxsave_cpu(true);
#else
	fpusave_cpu(true);
#endif
	x86_disable_intr();

#ifdef MULTIPROCESSOR
	/* Save and suspend Application Processors */
	x86_broadcast_ipi(X86_IPI_ACPI_CPU_SLEEP);
	while (cpus_running != curcpu()->ci_cpumask)
		delay(1); 
#endif

	if (acpi_md_sleep_prepare(state))
		goto out;

	/* Execute Wakeup */
#ifdef __i386__
	npxinit(&cpu_info_primary);
#else
	cpu_init_msrs(&cpu_info_primary, false);
	fpuinit(&cpu_info_primary);
#endif
	i8259_reinit();
#if NLAPIC > 0
	lapic_enable();
	lapic_set_lvt();
	lapic_initclocks();
#endif
#if NIOAPIC > 0
	ioapic_reenable();
#endif

	initrtclock(TIMER_FREQ);
	inittodr(time_second);

	AcpiClearEvent(ACPI_EVENT_PMTIMER);
	AcpiClearEvent(ACPI_EVENT_GLOBAL);
	AcpiClearEvent(ACPI_EVENT_POWER_BUTTON);
	AcpiClearEvent(ACPI_EVENT_SLEEP_BUTTON);
	AcpiClearEvent(ACPI_EVENT_RTC);
	AcpiHwDisableAllGpes ();

	acpi_pci_link_resume();

out:

#ifdef MULTIPROCESSOR
	for (CPU_INFO_FOREACH(cii, ci)) {
		if (CPU_IS_PRIMARY(ci))
			continue;
		acpi_md_sleep_patch(ci);

		CPU_STARTUP(ci, acpi_wakeup_paddr);
		CPU_START_CLEANUP(ci);

		while ((ci->ci_flags & CPUF_RUNNING) == 0)
			x86_pause();

		tsc_sync_bp(ci);
		tsc_sync_bp(ci);
	}
#endif

	x86_enable_intr();
	splx(s);

#ifdef MTRR
	if (mtrr_funcs != NULL)
		mtrr_commit();
#endif

	return (ret);
}

void
acpi_md_sleep_init(void)
{
	/* Map ACPI wakecode */
	acpi_wakeup_vaddr = uvm_km_alloc(kernel_map, PAGE_SIZE, 0,
	    UVM_KMF_VAONLY);
	if (acpi_wakeup_vaddr == 0)
		panic("acpi: can't allocate address for wakecode.\n");

	pmap_kenter_pa(acpi_wakeup_vaddr, acpi_wakeup_paddr,
	    VM_PROT_READ | VM_PROT_WRITE);
	pmap_update(pmap_kernel());
}

SYSCTL_SETUP(sysctl_md_acpi_setup, "acpi x86 sysctl setup")
{
	const struct sysctlnode *node;
	const struct sysctlnode *ssnode;

	if (sysctl_createv(NULL, 0, NULL, &node, CTLFLAG_PERMANENT,
	    CTLTYPE_NODE, "machdep", NULL, NULL, 0, NULL, 0, CTL_MACHDEP,
	    CTL_EOL) != 0)
		return;
	if (sysctl_createv(NULL, 0, &node, &ssnode, CTLFLAG_READWRITE,
	    CTLTYPE_INT, "acpi_vbios_reset", NULL, sysctl_md_acpi_vbios_reset,
	    0, NULL, 0, CTL_CREATE, CTL_EOL) != 0)
		return;
	if (sysctl_createv(NULL, 0, &node, &ssnode, CTLFLAG_READWRITE,
	    CTLTYPE_INT, "acpi_beep_on_reset", NULL, sysctl_md_acpi_beep_on_reset,
	    0, NULL, 0, CTL_CREATE, CTL_EOL) != 0)
		return;

	acpi_md_node = node->sysctl_num;
}

static int
sysctl_md_acpi_vbios_reset(SYSCTLFN_ARGS)
{
	int error, t;
	struct sysctlnode node;

	node = *rnode;
	t = acpi_md_vbios_reset;
	node.sysctl_data = &t;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (error || newp == NULL)
		return error;

	if (t < 0 || t > 2)
		return EINVAL;

	acpi_md_vbios_reset = t;

	return 0;
}

static int
sysctl_md_acpi_beep_on_reset(SYSCTLFN_ARGS)
{
	int error, t;
	struct sysctlnode node;

	node = *rnode;
	t = acpi_md_beep_on_reset;
	node.sysctl_data = &t;
	error = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (error || newp == NULL)
		return error;

	if (t < 0 || t > 1)
		return EINVAL;

	acpi_md_beep_on_reset = t;

	return 0;
}