aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@linux.intel.com>2010-12-03 10:30:34 -0800
committerH. Peter Anvin <hpa@linux.intel.com>2010-12-14 10:48:40 -0800
commit8062e70d346f6be5aae1d0064890a934bd4e9209 (patch)
tree3a2b1132d370e11e8d15f4361498a39759fface8
parent58d95233ad8aa2fde72f8b9ebbf8ebbd91635358 (diff)
downloadmrst-s0i3-test-8062e70d346f6be5aae1d0064890a934bd4e9209.tar.gz
mrst-s0i3-test-8062e70d346f6be5aae1d0064890a934bd4e9209.tar.xz
mrst-s0i3-test-8062e70d346f6be5aae1d0064890a934bd4e9209.zip
Initial S0i3 code test
-rw-r--r--drivers/idle/Kconfig6
-rw-r--r--drivers/idle/Makefile3
-rw-r--r--drivers/idle/intel_idle.c81
-rw-r--r--drivers/idle/mrst_s0i3.c289
-rw-r--r--drivers/idle/mrst_s0i3.h61
-rw-r--r--drivers/idle/mrst_s0i3_asm.S162
6 files changed, 591 insertions, 11 deletions
diff --git a/drivers/idle/Kconfig b/drivers/idle/Kconfig
index 8489eb58a52..a428cc73089 100644
--- a/drivers/idle/Kconfig
+++ b/drivers/idle/Kconfig
@@ -9,6 +9,12 @@ config INTEL_IDLE
can be configured at the same time, in order to handle
processors intel_idle does not support.
+config MRST_S0I3
+ bool "S0i3 Power State for Intel Moorestown MID"
+ depends on INTEL_IDLE
+ depends on X86_MRST
+ default y
+
menu "Memory power savings"
depends on X86_64
diff --git a/drivers/idle/Makefile b/drivers/idle/Makefile
index 23d295cf10f..40f85482192 100644
--- a/drivers/idle/Makefile
+++ b/drivers/idle/Makefile
@@ -1,3 +1,4 @@
obj-$(CONFIG_I7300_IDLE) += i7300_idle.o
obj-$(CONFIG_INTEL_IDLE) += intel_idle.o
-
+obj-$(CONFIG_MRST_S0I3) += mrst_s0i3.o
+obj-$(CONFIG_MRST_S0I3) += mrst_s0i3_asm.o
diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c
index 41665d2f9f9..df7ebfc982f 100644
--- a/drivers/idle/intel_idle.c
+++ b/drivers/idle/intel_idle.c
@@ -60,6 +60,7 @@
#include <trace/events/power.h>
#include <linux/sched.h>
#include <asm/mwait.h>
+#include "mrst_s0i3.h"
#define INTEL_IDLE_VERSION "0.4"
#define PREFIX "intel_idle: "
@@ -77,11 +78,14 @@ static unsigned int mwait_substates;
static unsigned int lapic_timer_reliable_states = (1 << 1); /* Default to only C1 */
static struct cpuidle_device __percpu *intel_idle_cpuidle_devices;
-static int intel_idle(struct cpuidle_device *dev, struct cpuidle_state *state);
-
static struct cpuidle_state *cpuidle_state_table;
/*
+ * Indicates that this is not a proper MWAIT state
+ */
+#define CPUIDLE_FLAG_INTEL_FAKE 0x10000
+
+/*
* States are indexed by the cstate number,
* which is also the index into the MWAIT hint array.
* Thus C0 is a dummy.
@@ -188,13 +192,62 @@ static struct cpuidle_state atom_cstates[MWAIT_MAX_NUM_CSTATES] = {
.enter = &intel_idle },
};
+static struct cpuidle_state mrst_cstates[MWAIT_MAX_NUM_CSTATES] = {
+ { /* MWAIT C0 */ },
+ { /* MWAIT C1 */
+ .name = "ATM-C1",
+ .desc = "MWAIT 0x00",
+ .driver_data = (void *) 0x00,
+ .flags = CPUIDLE_FLAG_TIME_VALID,
+ .exit_latency = 1,
+ .target_residency = 4,
+ .enter = &intel_idle },
+ { /* MWAIT C2 */
+ .name = "ATM-C2",
+ .desc = "MWAIT 0x10",
+ .driver_data = (void *) 0x10,
+ .flags = CPUIDLE_FLAG_TIME_VALID,
+ .exit_latency = 20,
+ .target_residency = 80,
+ .enter = &intel_idle },
+ { /* MWAIT C3 */ },
+ { /* MWAIT C4 */
+ .name = "ATM-C4",
+ .desc = "MWAIT 0x30",
+ .driver_data = (void *) 0x30,
+ .flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED,
+ .exit_latency = 100,
+ .target_residency = 400,
+ .enter = &intel_idle },
+ { /* MWAIT C5 */ },
+ { /* MWAIT C6 */
+ .name = "ATM-C6",
+ .desc = "MWAIT 0x52",
+ .driver_data = (void *) 0x52,
+ .flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED,
+ .exit_latency = 140,
+ .target_residency = 560,
+ .enter = &intel_idle },
+#ifdef CONFIG_MRST_S0I3
+ { /* MRST S0i3 */
+ .name = "MRST-S0i3",
+ .desc = "MWAIT 0x60",
+ .driver_data = (void *) 0x60,
+ .flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED |
+ CPUIDLE_FLAG_INTEL_FAKE,
+ .exit_latency = 140, /* XXX */
+ .target_residency = 560, /* XXX */
+ .enter = &mrst_s0i3_idle },
+#endif
+};
+
/**
* intel_idle
* @dev: cpuidle_device
* @state: cpuidle state
*
*/
-static int intel_idle(struct cpuidle_device *dev, struct cpuidle_state *state)
+int intel_idle(struct cpuidle_device *dev, struct cpuidle_state *state)
{
unsigned long ecx = 1; /* break on interrupt flag */
unsigned long eax = (unsigned long)cpuidle_get_statedata(state);
@@ -294,11 +347,15 @@ static int intel_idle_probe(void)
break;
case 0x1C: /* 28 - Atom Processor */
- case 0x26: /* 38 - Lincroft Atom Processor */
lapic_timer_reliable_states = (1 << 1); /* C1 */
cpuidle_state_table = atom_cstates;
break;
+ case 0x26: /* 38 - Lincroft Atom Processor */
+ lapic_timer_reliable_states = (1 << 1); /* C1 */
+ cpuidle_state_table = mrst_cstates;
+ break;
+
case 0x2A: /* SNB */
case 0x2D: /* SNB Xeon */
cpuidle_state_table = snb_cstates;
@@ -358,8 +415,6 @@ static int intel_idle_cpuidle_devices_init(void)
dev->state_count = 1;
for (cstate = 1; cstate < MWAIT_MAX_NUM_CSTATES; ++cstate) {
- int num_substates;
-
if (cstate > max_cstate) {
printk(PREFIX "max_cstate %d reached\n",
max_cstate);
@@ -367,10 +422,16 @@ static int intel_idle_cpuidle_devices_init(void)
}
/* does the state exist in CPUID.MWAIT? */
- num_substates = (mwait_substates >> ((cstate) * 4))
- & MWAIT_SUBSTATE_MASK;
- if (num_substates == 0)
- continue;
+ if (!(cpuidle_state_table[cstate].flags &
+ CPUIDLE_FLAG_INTEL_FAKE)) {
+ int num_substates;
+
+ num_substates = (mwait_substates >> (cstate*4))
+ & MWAIT_SUBSTATE_MASK;
+ if (num_substates == 0)
+ continue;
+ }
+
/* is the state not enabled? */
if (cpuidle_state_table[cstate].enter == NULL) {
/* does the driver not know about the state? */
diff --git a/drivers/idle/mrst_s0i3.c b/drivers/idle/mrst_s0i3.c
new file mode 100644
index 00000000000..6a851b7b54f
--- /dev/null
+++ b/drivers/idle/mrst_s0i3.c
@@ -0,0 +1,289 @@
+/*
+ * mrst_s0i3.c - super-deep sleep state for the Moorestown MID platform
+ *
+ * Copyright (c) 2010, Intel Corporation.
+ * H. Peter Anvin <hpa@linux.intel.com>
+ * Len Brown <len.brown@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/cpuidle.h>
+#include <linux/clockchips.h>
+#include <linux/hrtimer.h> /* ktime_get_real() */
+#include <linux/pci.h>
+#include <linux/cpu.h>
+#include <trace/events/power.h>
+#include <linux/sched.h>
+#include <linux/suspend.h>
+#include <asm/msr.h>
+#include <asm/mtrr.h>
+#include <asm/mwait.h>
+#include "mrst_s0i3.h"
+
+static void do_s0i3(void);
+volatile u32 *pmu_reg;
+struct pci_dev *pmu_dev; /* South Complex PMU unit */
+
+/**
+ * mrst_s0i3_idle
+ * @dev: cpuidle_device
+ * @state: cpuidle state
+ *
+ */
+int mrst_s0i3_idle(struct cpuidle_device *dev, struct cpuidle_state *state)
+{
+ ktime_t kt_before, kt_after;
+ s64 usec_delta;
+ int cpu = smp_processor_id();
+
+ local_irq_disable();
+
+ /* We must be the only executing CPU thread for this to be legal */
+ if (!pmu_reg || !cpumask_equal(cpu_online_mask, cpumask_of(cpu))) {
+ local_irq_enable();
+ return intel_idle(dev, state-1); /* Revert to C6 */
+ }
+
+ /*
+ * leave_mm() to avoid costly and often unnecessary wakeups
+ * for flushing the user TLB's associated with the active mm.
+ */
+ if (state->flags & CPUIDLE_FLAG_TLB_FLUSHED)
+ leave_mm(cpu);
+
+ clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &cpu);
+
+ kt_before = ktime_get_real();
+
+ stop_critical_timings();
+
+ if (!need_resched())
+ do_s0i3();
+
+ start_critical_timings();
+
+ kt_after = ktime_get_real();
+ usec_delta = ktime_to_us(ktime_sub(kt_after, kt_before));
+
+ local_irq_enable();
+
+ clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &cpu);
+
+ return usec_delta;
+}
+
+/*
+ * List of MSRs to be saved/restored, *other* than what is handled by
+ * * save_processor_state/restore_processor_state. * This is
+ * specific to Langwell, and any future processors would need a new
+ * list.
+ *
+ * XXX: check how much on this list is actually necessary.
+ */
+static const u32 s0i3_msr_list[] =
+{
+ MSR_IA32_EBL_CR_POWERON,
+ MSR_IA32_FEATURE_CONTROL,
+ MSR_IA32_PERFCTR0,
+ MSR_IA32_PERFCTR1,
+ MSR_IA32_MPERF,
+ MSR_IA32_THERM_INTERRUPT,
+ MSR_CORE_PERF_FIXED_CTR0,
+ MSR_CORE_PERF_FIXED_CTR1,
+ MSR_CORE_PERF_FIXED_CTR2,
+ MSR_IA32_DS_AREA,
+ MSR_IA32_CR_PAT,
+};
+
+static struct msr s0i3_msr_data[ARRAY_SIZE(s0i3_msr_list)];
+
+static void s0i3_save_msrs(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(s0i3_msr_list); i++)
+ s0i3_msr_data[i].q = native_read_msr(s0i3_msr_list[i]);
+}
+
+static void s0i3_restore_msrs(void)
+{
+ int i;
+
+ for (i = ARRAY_SIZE(s0i3_msr_list) - 1; i >= 0; i--)
+ native_write_msr(s0i3_msr_list[i],
+ s0i3_msr_data[i].l, s0i3_msr_data[i].h);
+}
+
+/*
+ * List of APIC registers to be saved/restored.
+ * XXX: Verify that this list is actually complete.
+ * XXX: Try to figure out a better way to do this using kernel facilities.
+ *
+ * Note: these are open-coded to minimize delay and therefore improve the
+ * power consumption.
+ */
+static const u32 s0i3_lapic_list[] =
+{
+ APIC_LDR,
+ APIC_ICR,
+ APIC_ICR2,
+ APIC_LVTT,
+ APIC_LVTTHMR,
+ APIC_LVTPC,
+ APIC_LVT0,
+ APIC_LVT1,
+ APIC_LVTERR,
+ APIC_TMCCT,
+ APIC_TDCR
+};
+
+static u32 s0i3_lapic_data[ARRAY_SIZE(s0i3_msr_list)];
+
+static void s0i3_save_lapic(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(s0i3_lapic_list); i++) {
+ volatile u32 *addr = (volatile u32 *)
+ (APIC_BASE + s0i3_lapic_list[i]);
+
+ s0i3_lapic_data[i] = readl(addr);
+ }
+}
+
+static void s0i3_restore_lapic(void)
+{
+ int i;
+
+ for (i = ARRAY_SIZE(s0i3_msr_list) - 1; i >= 0; i--) {
+ volatile u32 *addr = (volatile u32 *)
+ (APIC_BASE + s0i3_lapic_list[i]);
+
+ writel(s0i3_lapic_data[i], addr);
+ }
+}
+
+/*
+ * Leaving S0i3 will have put the other CPU thread into wait for SIPI;
+ * we need to put it back into C6 in order to be able to use S0i3
+ * again.
+ */
+static void s0i3_poke_other_cpu(void)
+{
+ cpu_up(1);
+ cpu_down(1);
+}
+
+/*
+ * Send a command to the PMU to shut down the south complex
+ */
+enum pmu_regs {
+ PM_STS = 0,
+ PM_CMD = 1,
+ PM_ICS = 2,
+ PM_WKC0 = 4,
+ PM_WKS0 = 6,
+ PM_SSC0 = 8,
+ PM_SSS0 = 12,
+ PM_WSSC0 = 16,
+ PM_MSIC = 22
+};
+
+#define WAKE_CAPABLE 0x80000000
+#define AUTO_CLK_GATE_VALUE 0x555551
+#define SUB_SYS_D0I2_VALUE 0xaaaaaa
+#define WAKE_ENABLE_VALUE 0x4786
+#define SUSPEND_GFX 0xc
+
+static void s0i3_prep_pmu(void)
+{
+ /* Program the wakeup */
+ writel(WAKE_ENABLE_VALUE, &pmu_reg[PM_WKC0]);
+ writel(AUTO_CLK_GATE_VALUE, &pmu_reg[PM_WSSC0]);
+
+ /* Clock gate Langwell */
+ writel(SUB_SYS_D0I2_VALUE, &pmu_reg[PM_SSC0]);
+
+ /* Do this when S0i3 is entered */
+ writel((0 << 31) | /* Reserved */
+ (0 << 30) | /* Core must be idle */
+ (0xc2 << 22) | /* ACK C6 trigger */
+ (3 << 19) | /* Trigger on DMI message */
+ (3 << 16) | /* Enter S0i3 */
+ (3 << 13) | /* Numeric mode ID (sw) */
+ (3 << 9) | /* Trigger mode */
+ (0 << 8) | /* Do not interrupt */
+ (1 << 0), /* Set configuration */
+ &pmu_reg[PM_CMD]);
+}
+
+static void s0i3_wait_for_pmu(void)
+{
+ while (readl(&pmu_reg[PM_STS]) & (1 << 8))
+ cpu_relax();
+}
+
+static noinline void do_s0i3(void)
+{
+ s0i3_save_lapic();
+ s0i3_save_msrs();
+ save_processor_state();
+ s0i3_wait_for_pmu();
+ s0i3_prep_pmu();
+ if (mrst_s0i3_entry()) {
+ s0i3_restore_msrs();
+ restore_processor_state();
+ s0i3_restore_lapic();
+
+ /* HACK HACK HACK Enable MSI interrupts again */
+ writel(0x0, &pmu_dev[PM_MSIC]);
+
+ s0i3_poke_other_cpu();
+ }
+}
+
+/* Hacky! */
+static int s0i3_prepare(void)
+{
+ int err;
+
+ /* Map the PMU unit */
+ pmu_dev = pci_get_device(0x8086, 0x0810, NULL);
+ if (!pmu_dev)
+ return -ENODEV;
+
+ err = pci_enable_device(pmu_dev);
+ if (err)
+ return err;
+
+ err = pci_request_regions(pmu_dev, "mrst_s0i3");
+ if (err)
+ goto err_disable_pdev;
+
+ pmu_reg = pci_iomap(pmu_dev, 0, 0);
+ if (!pmu_reg) {
+ err = -ENOMEM;
+ goto err_disable_pdev;
+ }
+
+ return 0;
+
+err_disable_pdev:
+ pci_disable_device(pmu_dev);
+ return err;
+}
+
+device_initcall(s0i3_prepare);
diff --git a/drivers/idle/mrst_s0i3.h b/drivers/idle/mrst_s0i3.h
new file mode 100644
index 00000000000..38d3de55a13
--- /dev/null
+++ b/drivers/idle/mrst_s0i3.h
@@ -0,0 +1,61 @@
+/* -----------------------------------------------------------------------
+ *
+ * Copyright 2010 Intel Corporation; author: H. Peter Anvin
+ *
+ * This file is part of the Linux kernel, and is made available under
+ * the terms of the GNU General Public License version 2 or (at your
+ * option) any later version; incorporated herein by reference.
+ *
+ * ----------------------------------------------------------------------- */
+
+#ifndef MRST_S0I3_H
+#define MRST_S0I3_H
+
+#define MRST_C6_HINTS_EAX 0x52
+#define MRST_C6_HINTS_ECX 1
+
+#ifndef __ASSEMBLY__
+
+#include <linux/types.h>
+
+struct mrst_s0i3_resume_stack {
+ u32 eflags; /* 0 - eflags */
+
+ u16 pgdt_pad; /* 4 - pad */
+ u16 pgdt_len; /* 6 - GDT pointer (length) */
+ u32 pgdt_ptr; /* 8 - GDT pointer (base - physical) */
+
+ u64 misc_enable; /* 12 - MISC_ENABLE MSR */
+
+ u64 efer; /* 12 - EFER MSR */
+ u32 cr4; /* 20 - %cr4 */
+ u32 cr0; /* 24 - %cr0 */
+
+ u16 gdt_pad; /* 28 - pad */
+ u16 gdt_len; /* 30 - GDT pointer (length) */
+ u32 gdt_ptr; /* 32 - GDT pointer (base - linear) */
+
+ u32 cr3; /* 36 - %cr3 */
+ u32 fs; /* 40 - %fs */
+ u32 gs; /* 44 - %gs */
+
+ u16 idt_pad; /* 48 - pad */
+ u16 idt_len; /* 50 - IDT pointer (length) */
+ u32 idt_ptr; /* 52 - IDT pointer (base - linear) */
+
+ u32 ldtr; /* 56 - %ldtr */
+
+ u32 ebx; /* 60 - %ebx */
+ u32 ebp; /* 64 - %ebp */
+ u32 esi; /* 68 - %esi */
+ u32 edi; /* 72 - %edi */
+
+ u32 ret; /* 76 - return address */
+} __attribute__((packed));
+
+int intel_idle(struct cpuidle_device *dev, struct cpuidle_state *state);
+int mrst_s0i3_idle(struct cpuidle_device *dev, struct cpuidle_state *state);
+int mrst_s0i3_entry(void);
+
+#endif /* __ASSEMBLY__ */
+#endif /* MRST_S0I3_H */
diff --git a/drivers/idle/mrst_s0i3_asm.S b/drivers/idle/mrst_s0i3_asm.S
new file mode 100644
index 00000000000..6ad08b4fac2
--- /dev/null
+++ b/drivers/idle/mrst_s0i3_asm.S
@@ -0,0 +1,162 @@
+/* -----------------------------------------------------------------------
+ *
+ * Copyright 2010 Intel Corporation; author: H. Peter Anvin
+ *
+ * This file is part of the Linux kernel, and is made available under
+ * the terms of the GNU General Public License version 2 or (at your
+ * option) any later version; incorporated herein by reference.
+ *
+ * ----------------------------------------------------------------------- */
+
+#include <linux/linkage.h>
+#include <asm/asm.h>
+#include <asm/msr-index.h>
+#include <asm/page.h>
+#include <asm/segment.h>
+
+#include "mrst_s0i3.h"
+
+/* Physical address */
+#define pa(X) ((X) - __PAGE_OFFSET)
+
+ .code32 /* MRST boots up in flat 32-bit mode */
+ .text
+ENTRY(mrst_s0i3_entry)
+ pushl %edi
+ pushl %esi
+ pushl %ebp
+ pushl %ebx
+
+ movl %esp, %esi
+
+ subl $8, %esp
+ sidtl 2(%esp)
+
+ pushl %gs
+ pushl %fs
+
+ movl %cr3, %eax
+ pushl %eax
+
+ subl $8, %esp
+ sgdtl 2(%esp)
+
+ movl %cr0, %eax
+ pushl %eax
+
+ movl %cr4, %eax
+ pushl %eax
+
+ movl $(MSR_EFER), %ecx
+ rdmsr
+ pushl %edx
+ pushl %eax
+
+ movl (32-12)(%esp), %eax /* GDT base */
+ subl $(__PAGE_OFFSET), %eax /* Convert to a physical address */
+ pushl %eax
+ pushl (28-8)(%esp) /* GDT length, pad */
+
+ pushfl
+
+ leal (-__PAGE_OFFSET)(%esp), %eax
+ movl %eax, mrst_s0i3_resume_stack
+
+ wbinvd
+
+ movl %esp, %eax /* As good as anything... */
+ xorl %edx, %edx
+ xorl %ecx, %ecx
+ monitor
+
+ movl $MRST_C6_HINTS_EAX, %eax
+ movl $MRST_C6_HINTS_ECX, %ecx
+ sti
+ mwait
+
+ /* If MWAIT wakes us up, assume something happened... */
+ cli
+ movl %esi, %esp
+
+ xorl %eax, %eax /* Not really S0i3 */
+ popl %ebx
+ popl %ebp
+ popl %esi
+ popl %edi
+ ret
+END(mrst_s0i3_entry)
+
+/*
+ * After S0i3 the MRST firmare will put us back in 32-bit flat mode
+ */
+ENTRY(mrst_s0i3_resume)
+ cli
+
+ movl pa(mrst_s0i3_resume_stack), %esp
+ popfl /* 0 - %eflags */
+ lgdtl 2(%esp) /* 4 - physical GDT pointer */
+
+ addl $8, %esp
+ movl $(__KERNEL_DS), %eax
+ movl %eax, %ss
+ movl %eax, %ds
+ movl %eax, %es
+
+ popl %eax
+ popl %edx
+ movl $(MSR_IA32_MISC_ENABLE), %ecx /* 12 - MISC_ENABLE MSR */
+ wrmsr
+
+ popl %eax /* 12 - EFER */
+ popl %edx
+ movl $(MSR_EFER), %ecx
+ wrmsr
+
+ popl %eax /* 20 - %cr4 */
+ movl %eax, %cr4
+
+ movl $pa(initial_page_table), %eax
+ movl %eax, %cr3
+
+ popl %eax /* 24 - %cr0 */
+ movl %eax, %cr0 /* Enables paging! */
+ ljmpl $(__KERNEL_CS), $1f
+1:
+
+ addl $__PAGE_OFFSET, %esp
+ lgdtl 2(%esp) /* 32 - linear GDT pointer */
+ popl %ebx /* 32 - GDT length + junk */
+ popl %ebx /* 32 - linear address of GDT */
+
+ popl %eax /* 36 - %cr3 */
+ movl %eax, %cr3
+
+ movl $(GDT_ENTRY_TSS*8), %eax
+ andb $~0x02, (GDT_ENTRY_TSS*8+5) /* Clear the TSS busy bit */
+ ltr %ax /* Set the TSS */
+
+ popl %fs /* 40 - %fs */
+ popl %gs /* 44 - %gs */
+
+ /* x86-64 would need to restore MSR_GS_BASE too */
+
+ lidtl 2(%esp) /* 48 - IDT pointer */
+ addl $8, %esp
+
+ popl %eax /* 56 - %ldtr */
+ lldt %ax
+
+ movl $1, %eax /* Resume from actual S0i3 */
+ popl %ebx
+ popl %ebp
+ popl %esi
+ popl %edi
+
+ ret
+END(mrst_s0i3_resume)
+
+ .bss
+ .balign 4
+ENTRY(mrst_s0i3_resume_stack)
+ .space 4
+END(mrst_s0i3_resume_stack)