Move C1E workaround into its own idle function. Previous workaround works

only during initial booting process, while there are laptops/BIOSes that
tend to act 'smarter' by force enabling C1E if the main power adapter
being pulled out, rendering previous workaround ineffective. Given the
fact that we still rely on local APIC to drive timer interrupt, this
workaround should keep all Turion (probably Phenom too) X\d+ alive whether
its on battery power or not.

URL:		http://lists.freebsd.org/pipermail/freebsd-acpi/2008-April/004858.html
    		http://lists.freebsd.org/pipermail/freebsd-acpi/2008-May/004888.html

Tested by:	Peter Jeremy <peterjeremy at optushome d com d au>
This commit is contained in:
Ariff Abdullah 2009-06-09 04:17:36 +00:00
parent e87b19e38c
commit 867cdecd40
4 changed files with 150 additions and 46 deletions

View File

@ -329,29 +329,6 @@ lapic_setup(int boot)
/* XXX: Error and thermal LVTs */
if (cpu_vendor_id == CPU_VENDOR_AMD) {
/*
* Detect the presence of C1E capability mostly on latest
* dual-cores (or future) k8 family. This feature renders
* the local APIC timer dead, so we disable it by reading
* the Interrupt Pending Message register and clearing both
* C1eOnCmpHalt (bit 28) and SmiOnCmpHalt (bit 27).
*
* Reference:
* "BIOS and Kernel Developer's Guide for AMD NPT
* Family 0Fh Processors"
* #32559 revision 3.00
*/
if ((cpu_id & 0x00000f00) == 0x00000f00 &&
(cpu_id & 0x0fff0000) >= 0x00040000) {
uint64_t msr;
msr = rdmsr(0xc0010055);
if (msr & 0x18000000)
wrmsr(0xc0010055, msr & ~0x18000000ULL);
}
}
intr_restore(eflags);
}

View File

@ -600,6 +600,69 @@ cpu_idle_acpi(int busy)
__asm __volatile("sti; hlt");
}
static int cpu_ident_amdc1e = 0;
static int
cpu_probe_amdc1e(void)
{
int i;
/*
* Forget it, if we're not using local APIC timer.
*/
if (resource_disabled("apic", 0) ||
(resource_int_value("apic", 0, "clock", &i) == 0 && i == 0))
return (0);
/*
* Detect the presence of C1E capability mostly on latest
* dual-cores (or future) k8 family.
*/
if (cpu_vendor_id == CPU_VENDOR_AMD &&
(cpu_id & 0x00000f00) == 0x00000f00 &&
(cpu_id & 0x0fff0000) >= 0x00040000) {
cpu_ident_amdc1e = 1;
return (1);
}
return (0);
}
/*
* C1E renders the local APIC timer dead, so we disable it by
* reading the Interrupt Pending Message register and clearing
* both C1eOnCmpHalt (bit 28) and SmiOnCmpHalt (bit 27).
*
* Reference:
* "BIOS and Kernel Developer's Guide for AMD NPT Family 0Fh Processors"
* #32559 revision 3.00+
*/
#define MSR_AMDK8_IPM 0xc0010055
#define AMDK8_SMIONCMPHALT (1ULL << 27)
#define AMDK8_C1EONCMPHALT (1ULL << 28)
#define AMDK8_CMPHALT (AMDK8_SMIONCMPHALT | AMDK8_C1EONCMPHALT)
static void
cpu_idle_amdc1e(int busy)
{
disable_intr();
if (sched_runnable())
enable_intr();
else {
uint64_t msr;
msr = rdmsr(MSR_AMDK8_IPM);
if (msr & AMDK8_CMPHALT)
wrmsr(MSR_AMDK8_IPM, msr & ~AMDK8_CMPHALT);
if (cpu_idle_hook)
cpu_idle_hook();
else
__asm __volatile("sti; hlt");
}
}
static void
cpu_idle_spin(int busy)
{
@ -697,6 +760,7 @@ struct {
{ cpu_idle_spin, "spin" },
{ cpu_idle_mwait, "mwait" },
{ cpu_idle_mwait_hlt, "mwait_hlt" },
{ cpu_idle_amdc1e, "amdc1e" },
{ cpu_idle_hlt, "hlt" },
{ cpu_idle_acpi, "acpi" },
{ NULL, NULL }
@ -715,6 +779,9 @@ idle_sysctl_available(SYSCTL_HANDLER_ARGS)
if (strstr(idle_tbl[i].id_name, "mwait") &&
(cpu_feature2 & CPUID2_MON) == 0)
continue;
if (strcmp(idle_tbl[i].id_name, "amdc1e") == 0 &&
cpu_ident_amdc1e == 0)
continue;
p += sprintf(p, "%s, ", idle_tbl[i].id_name);
}
error = sysctl_handle_string(oidp, avail, 0, req);
@ -745,6 +812,9 @@ idle_sysctl(SYSCTL_HANDLER_ARGS)
if (strstr(idle_tbl[i].id_name, "mwait") &&
(cpu_feature2 & CPUID2_MON) == 0)
continue;
if (strcmp(idle_tbl[i].id_name, "amdc1e") == 0 &&
cpu_ident_amdc1e == 0)
continue;
if (strcmp(idle_tbl[i].id_name, buf))
continue;
cpu_idle_fn = idle_tbl[i].id_fn;
@ -1593,6 +1663,9 @@ hammer_time(u_int64_t modulep, u_int64_t physfree)
}
#endif
if (cpu_probe_amdc1e())
cpu_idle_fn = cpu_idle_amdc1e;
/* Location of kernel stack for locore */
return ((u_int64_t)thread0.td_pcb);
}

View File

@ -331,29 +331,6 @@ lapic_setup(int boot)
/* XXX: Error and thermal LVTs */
if (cpu_vendor_id == CPU_VENDOR_AMD) {
/*
* Detect the presence of C1E capability mostly on latest
* dual-cores (or future) k8 family. This feature renders
* the local APIC timer dead, so we disable it by reading
* the Interrupt Pending Message register and clearing both
* C1eOnCmpHalt (bit 28) and SmiOnCmpHalt (bit 27).
*
* Reference:
* "BIOS and Kernel Developer's Guide for AMD NPT
* Family 0Fh Processors"
* #32559 revision 3.00
*/
if ((cpu_id & 0x00000f00) == 0x00000f00 &&
(cpu_id & 0x0fff0000) >= 0x00040000) {
uint64_t msr;
msr = rdmsr(0xc0010055);
if (msr & 0x18000000)
wrmsr(0xc0010055, msr & ~0x18000000ULL);
}
}
intr_restore(eflags);
}

View File

@ -1231,6 +1231,70 @@ cpu_idle_acpi(int busy)
__asm __volatile("sti; hlt");
}
static int cpu_ident_amdc1e = 0;
static int
cpu_probe_amdc1e(void)
{
#ifdef DEV_APIC
int i;
/*
* Forget it, if we're not using local APIC timer.
*/
if (resource_disabled("apic", 0) ||
(resource_int_value("apic", 0, "clock", &i) == 0 && i == 0))
return (0);
/*
* Detect the presence of C1E capability mostly on latest
* dual-cores (or future) k8 family.
*/
if (cpu_vendor_id == CPU_VENDOR_AMD &&
(cpu_id & 0x00000f00) == 0x00000f00 &&
(cpu_id & 0x0fff0000) >= 0x00040000) {
cpu_ident_amdc1e = 1;
return (1);
}
#endif
return (0);
}
/*
* C1E renders the local APIC timer dead, so we disable it by
* reading the Interrupt Pending Message register and clearing
* both C1eOnCmpHalt (bit 28) and SmiOnCmpHalt (bit 27).
*
* Reference:
* "BIOS and Kernel Developer's Guide for AMD NPT Family 0Fh Processors"
* #32559 revision 3.00+
*/
#define MSR_AMDK8_IPM 0xc0010055
#define AMDK8_SMIONCMPHALT (1ULL << 27)
#define AMDK8_C1EONCMPHALT (1ULL << 28)
#define AMDK8_CMPHALT (AMDK8_SMIONCMPHALT | AMDK8_C1EONCMPHALT)
static void
cpu_idle_amdc1e(int busy)
{
disable_intr();
if (sched_runnable())
enable_intr();
else {
uint64_t msr;
msr = rdmsr(MSR_AMDK8_IPM);
if (msr & AMDK8_CMPHALT)
wrmsr(MSR_AMDK8_IPM, msr & ~AMDK8_CMPHALT);
if (cpu_idle_hook)
cpu_idle_hook();
else
__asm __volatile("sti; hlt");
}
}
static void
cpu_idle_spin(int busy)
{
@ -1332,6 +1396,7 @@ struct {
{ cpu_idle_spin, "spin" },
{ cpu_idle_mwait, "mwait" },
{ cpu_idle_mwait_hlt, "mwait_hlt" },
{ cpu_idle_amdc1e, "amdc1e" },
{ cpu_idle_hlt, "hlt" },
{ cpu_idle_acpi, "acpi" },
{ NULL, NULL }
@ -1350,6 +1415,9 @@ idle_sysctl_available(SYSCTL_HANDLER_ARGS)
if (strstr(idle_tbl[i].id_name, "mwait") &&
(cpu_feature2 & CPUID2_MON) == 0)
continue;
if (strcmp(idle_tbl[i].id_name, "amdc1e") == 0 &&
cpu_ident_amdc1e == 0)
continue;
p += sprintf(p, "%s, ", idle_tbl[i].id_name);
}
error = sysctl_handle_string(oidp, avail, 0, req);
@ -1380,6 +1448,9 @@ idle_sysctl(SYSCTL_HANDLER_ARGS)
if (strstr(idle_tbl[i].id_name, "mwait") &&
(cpu_feature2 & CPUID2_MON) == 0)
continue;
if (strcmp(idle_tbl[i].id_name, "amdc1e") == 0 &&
cpu_ident_amdc1e == 0)
continue;
if (strcmp(idle_tbl[i].id_name, buf))
continue;
cpu_idle_fn = idle_tbl[i].id_fn;
@ -2583,6 +2654,9 @@ init386(first)
thread0.td_frame = &proc0_tf;
thread0.td_pcb->pcb_fsd = PCPU_GET(fsgs_gdt)[0];
thread0.td_pcb->pcb_gsd = PCPU_GET(fsgs_gdt)[1];
if (cpu_probe_amdc1e())
cpu_idle_fn = cpu_idle_amdc1e;
}
#else
@ -2847,6 +2921,9 @@ init386(first)
#endif
thread0.td_pcb->pcb_ext = 0;
thread0.td_frame = &proc0_tf;
if (cpu_probe_amdc1e())
cpu_idle_fn = cpu_idle_amdc1e;
}
#endif