/*- * Copyright (c) 2005 Nate Lawson * Copyright (c) 2004 Colin Percival * Copyright (c) 2004-2005 Bruno Durcot * Copyright (c) 2004 FUKUDA Nobuhiko * Copyright (c) 2009 Michael Reifenberger * Copyright (c) 2009 Norikatsu Shigemura * Copyright (c) 2008-2009 Gen Otsuji * Copyright (c) 2012 Jung-uk Kim * * This code is depending on kern_cpu.c, est.c, powernow.c, p4tcc.c, smist.c * in various parts. The authors of these files are Nate Lawson, * Colin Percival, Bruno Durcot, and FUKUDA Nobuhiko. * This code contains patches by Michael Reifenberger and Norikatsu Shigemura. * Thank you. * * Redistribution and use in source and binary forms, with or without * modification, are permitted providing 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``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 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. */ /* * BKDG for AMD Family 10h Processors * 31116 Rev 3.48 - April 22, 2010 * BKDG for AMD Family 11h Processors * 41256 Rev 3.00 - July 07, 2008 * BKDG for AMD Family 12h Processors * 41131 Rev 3.02 - October 06, 2011 * BKDG for AMD Family 14h Models 00h-0Fh Processors * 43170 Rev 3.13 - February 17, 2012 * BKDG for AMD Family 15h Models 00h-0Fh Processors * 42301 Rev 3.08 - March 12, 2012 */ #include __FBSDID("$FreeBSD: head/sys/x86/cpufreq/hwpstate.c 215398 2010-11-16 12:43:45Z avg $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "acpi_if.h" #include "cpufreq_if.h" #define MSR_AMD_PSTATE_LIMIT 0xc0010061 #define MSR_AMD_PSTATE_CONTROL 0xc0010062 #define MSR_AMD_PSTATE_STATUS 0xc0010063 #define MSR_AMD_PSTATE_CONFIG 0xc0010064 #define HWPSTATE_MAX_PSTATES 16 /* MSR_AMD_PSTATE_LIMIT */ #define HWPSTATE_MAX_VAL(msr) (((msr) >> 4) & 0x7) /* MSR_AMD_PSTATE_CONFIG */ #define HWPSTATE_10H_CUR_DID(msr) (((msr) >> 6) & 0x07) #define HWPSTATE_10H_CUR_FID(msr) ((msr) & 0x3f) #define HWPSTATE_12H_CUR_DID(msr) ((msr) & 0x0f) #define HWPSTATE_12H_CUR_FID(msr) (((msr) >> 4) & 0x1f) #define HWPSTATE_15H_CUR_DID(msr) (((msr) >> 6) & 0x07) #define HWPSTATE_15H_CUR_FID(msr) ((msr) & 0x1f) #define HWPSTATE_ENABLED(msr) (((msr) >> 63) & 0x01) #define HWPSTATE_DEBUG(dev, msg...) \ do { \ if (hwpstate_verbose) \ device_printf(dev, msg); \ } while(0) static struct cf_setting hwpstate_sets[HWPSTATE_MAX_PSTATES]; static int hwpstate_cfnum; static void hwpstate_identify(driver_t *, device_t); static int hwpstate_probe(device_t); static int hwpstate_attach(device_t); static int hwpstate_detach(device_t); static int hwpstate_set(device_t, const struct cf_setting *); static int hwpstate_get(device_t, struct cf_setting *); static int hwpstate_settings(device_t, struct cf_setting *, int *); static int hwpstate_type(device_t, int *); static int hwpstate_features(driver_t *, u_int *); static int hwpstate_get_info_from_acpi_perf(device_t, device_t); static int hwpstate_get_info_from_msr(device_t); static int hwpstate_verbose; SYSCTL_INT(_debug, OID_AUTO, hwpstate_verbose, CTLFLAG_RW | CTLFLAG_TUN, &hwpstate_verbose, 0, "Debug hwpstate"); TUNABLE_INT("debug.hwpstate_verbose", &hwpstate_verbose); static device_method_t hwpstate_methods[] = { /* Device interface */ DEVMETHOD(device_identify, hwpstate_identify), DEVMETHOD(device_probe, hwpstate_probe), DEVMETHOD(device_attach, hwpstate_attach), DEVMETHOD(device_detach, hwpstate_detach), /* cpufreq interface */ DEVMETHOD(cpufreq_drv_set, hwpstate_set), DEVMETHOD(cpufreq_drv_get, hwpstate_get), DEVMETHOD(cpufreq_drv_settings, hwpstate_settings), DEVMETHOD(cpufreq_drv_type, hwpstate_type), /* ACPI interface */ DEVMETHOD(acpi_get_features, hwpstate_features), DEVMETHOD_END }; static devclass_t hwpstate_devclass; static driver_t hwpstate_driver = { "hwpstate", hwpstate_methods, }; DRIVER_MODULE(hwpstate, cpu, hwpstate_driver, hwpstate_devclass, 0, 0); static int hwpstate_set(device_t dev, const struct cf_setting *cf) { int i, j; if (cf == NULL) return (EINVAL); for (i = 0; i < hwpstate_cfnum; i++) if (CPUFREQ_CMP(cf->freq, hwpstate_sets[i].freq)) break; if (i == hwpstate_cfnum) return (EINVAL); wrmsr(MSR_AMD_PSTATE_CONTROL, i); for (j = 0; j < 1000; j++) { DELAY(100); if (rdmsr(MSR_AMD_PSTATE_STATUS) == i) break; } if (j == 1000) { HWPSTATE_DEBUG(dev, "timed out\n"); return (ENXIO); } return (0); } static int hwpstate_get(device_t dev, struct cf_setting *cf) { int i; if (cf == NULL) return (EINVAL); i = rdmsr(MSR_AMD_PSTATE_STATUS); if (i >= hwpstate_cfnum) return (EINVAL); cf->freq = hwpstate_sets[i].freq; cf->volts = hwpstate_sets[i].volts; cf->power = hwpstate_sets[i].power; cf->lat = hwpstate_sets[i].lat; cf->dev = dev; return (0); } static int hwpstate_settings(device_t dev, struct cf_setting *sets, int *count) { int i; if (sets == NULL || count == NULL) return (EINVAL); if (*count < hwpstate_cfnum) return (E2BIG); for (i = 0; i < hwpstate_cfnum; i++, sets++) { sets->freq = hwpstate_sets[i].freq; sets->volts = hwpstate_sets[i].volts; sets->power = hwpstate_sets[i].power; sets->lat = hwpstate_sets[i].lat; sets->dev = dev; } *count = hwpstate_cfnum; return (0); } static int hwpstate_type(device_t dev, int *type) { if (type == NULL) return (EINVAL); *type = CPUFREQ_TYPE_ABSOLUTE; return (0); } static void hwpstate_identify(driver_t *driver, device_t parent) { if (device_find_child(parent, "hwpstate", -1) != NULL) return; if (cpu_vendor_id != CPU_VENDOR_AMD || CPUID_TO_FAMILY(cpu_id) < 0x10) return; if ((amd_pminfo & AMDPM_HW_PSTATE) == 0) { HWPSTATE_DEBUG(parent, "enable bit is not set\n"); return; } if (BUS_ADD_CHILD(parent, 10, "hwpstate", -1) == NULL) device_printf(parent, "failed to add child\n"); } static int hwpstate_probe(device_t dev) { if (rdmsr(MSR_AMD_PSTATE_LIMIT) == 0) return (ENXIO); device_set_desc(dev, "Cool`n'Quiet 2.0/3.0"); return (0); } static int hwpstate_attach(device_t dev) { device_t perf_dev; int error; perf_dev = device_find_child(device_get_parent(dev), "acpi_perf", -1); error = hwpstate_get_info_from_acpi_perf(dev, perf_dev); if (error) error = hwpstate_get_info_from_msr(dev); if (error) return (error); return (cpufreq_register(dev)); } static int hwpstate_get_info_from_msr(device_t dev) { uint64_t msr; int did, fid; int family, i; family = CPUID_TO_FAMILY(cpu_id); i = HWPSTATE_MAX_VAL(rdmsr(MSR_AMD_PSTATE_LIMIT)); hwpstate_cfnum = i + 1; for (i = 0; i < hwpstate_cfnum; i++) { msr = rdmsr(MSR_AMD_PSTATE_CONFIG + i); if (!HWPSTATE_ENABLED(msr)) { HWPSTATE_DEBUG(dev, "invalid MSR\n"); return (ENXIO); } switch(family) { case 0x10: case 0x11: did = HWPSTATE_10H_CUR_DID(msr); fid = HWPSTATE_10H_CUR_FID(msr); break; case 0x12: did = HWPSTATE_12H_CUR_DID(msr); fid = HWPSTATE_12H_CUR_FID(msr); break; case 0x15: did = HWPSTATE_15H_CUR_DID(msr); fid = HWPSTATE_15H_CUR_FID(msr); break; default: HWPSTATE_DEBUG(dev, "Family %xh processors not supported\n", family); return (ENXIO); } hwpstate_sets[i].freq = 100 * (fid + (family == 0x11 ? 0x08 : 0x10)) / (1 << did); hwpstate_sets[i].volts = CPUFREQ_VAL_UNKNOWN; hwpstate_sets[i].power = CPUFREQ_VAL_UNKNOWN; hwpstate_sets[i].lat = CPUFREQ_VAL_UNKNOWN; } return (0); } static int hwpstate_get_info_from_acpi_perf(device_t dev, device_t perf_dev) { struct cf_setting *perf_sets; int error, i, type; if (perf_dev == NULL || CPUFREQ_DRV_TYPE(perf_dev, &type) != 0 || (type & CPUFREQ_FLAG_INFO_ONLY) == 0) return (ENXIO); perf_sets = malloc(MAX_SETTINGS * sizeof(*perf_sets), M_TEMP, M_NOWAIT); if (perf_sets == NULL) { HWPSTATE_DEBUG(dev, "cannot allocate memory\n"); return (ENOMEM); } /* * Fetch settings from acpi_perf. * Now it is attached, and has info only flag. */ hwpstate_cfnum = MAX_SETTINGS; error = CPUFREQ_DRV_SETTINGS(perf_dev, perf_sets, &hwpstate_cfnum); if (error) goto out; i = HWPSTATE_MAX_VAL(rdmsr(MSR_AMD_PSTATE_LIMIT)); if (hwpstate_cfnum != i + 1) { HWPSTATE_DEBUG(dev, "MSR and ACPI _PSS count mismatch\n"); error = ENXIO; goto out; } for (i = 0; i < hwpstate_cfnum; i++) { if (i == perf_sets[i].spec[0]) { hwpstate_sets[i].freq = perf_sets[i].freq; hwpstate_sets[i].volts = perf_sets[i].volts; hwpstate_sets[i].power = perf_sets[i].power; hwpstate_sets[i].lat = perf_sets[i].lat; } else { HWPSTATE_DEBUG(dev, "ACPI _PSS object mismatch.\n"); error = ENXIO; goto out; } } out: if (perf_sets != NULL) free(perf_sets, M_TEMP); return (error); } static int hwpstate_detach(device_t dev) { thread_lock(curthread); sched_bind(curthread, device_get_unit(dev)); thread_unlock(curthread); wrmsr(MSR_AMD_PSTATE_CONTROL, 0); thread_lock(curthread); sched_unbind(curthread); thread_unlock(curthread); return (cpufreq_unregister(dev)); } static int hwpstate_features(driver_t *driver, u_int *features) { /* Notify the ACPI CPU that we support direct access to MSRs */ *features = ACPI_CAP_PERF_MSRS; return (0); }