Subject: Re: Driver for AMD K7 POWERNOW
To: None <port-i386@netbsd.org>
From: Sergio Jimenez <tripledes@eslack.org>
List: port-i386
Date: 07/16/2004 20:33:16
--C7zPtVaVf+AK4Oqc
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
On Mon, Jul 12, 2004 at 08:14:42PM +0100, Sergio Jimenez wrote:
> >
> >Hi,
> >
> >I have written a driver for AMD's POWERNOW function on K7 CPUs. It
> >enables CPU frequency scaling which allows laptops with AMD K7 CPUs to
> >have longer battery life and stay cool.
> >
> >http://www.amd.com/us-en/Processors/ProductInformation/0,,30_118_10220_10221%5E964,00.html
> >
> >I have made it compatible to i386/est.c (Intel SpeedStep) sysctl's
> >interface.
> >
>
> After a little work to apply the patch successfully, it seems to be
> working. I've compiled a kernel using the max and min frequencies
> allowed by my laptop and the times are quite differents:
>
> - 500MHz
>
> # time ./build.sh kernel=GENERIC
> ...
> 1760.88s real 1594.39s user 175.95s system
>
> - 1400MHz
>
> # time ./build.sh kernel=GENERIC
> ...
> 777.60s real 654.42s user 125.30s system
>
> * After the first built I rebooted the box.
>
> - Machine:
>
> Acer Aspire 1300
> mobile AMD Athlon XP 1600+
> machdep.powernow.frequency.available = 500 600 700 900 1000 1100 1200
> 1400
>
> The lastest bios revision (3A71) for this serie from Acer has a bogus pst table
> and less frequencies are allowed.
>
> Thanks for the patch :)
Here I attached a working patch with a bug fixed.
Also I'd like to explain how to get this working with a bogus bios,
because some bioses return 0x780 as a cpuid and this driver needs 0x680,
so if this is your case add this to pnow_k7.c:
--- pnow_k7.c.orig Fri Jul 16 20:29:50 2004
+++ pnow_k7.c Fri Jul 16 20:30:41 2004
@@ -40,6 +40,7 @@
#include <machine/cpu.h>
#include <machine/isa_machdep.h>
+#define BROKEN 0x780
#define BIOS_START 0xe0000
#define BIOS_END 0xFFFFF
#define BIOS_LEN BIOS_END - BIOS_START
@@ -170,7 +171,7 @@
ptr += sizeof(struct pst_s);
/* Use the first PST with matching CPUID
* */
- if (cpuid == pst->cpuid)
+ if (cpuid == pst->cpuid || BROKEN ==
pst->cpuid)
{
/*
* XXX I need more info on this.
This was recommended by the patch author :)
--C7zPtVaVf+AK4Oqc
Content-Type: text/plain; charset=iso-8859-1
Content-Disposition: attachment; filename="pnowk7.patch"
Content-Transfer-Encoding: 8bit
Index: conf/files.i386
===================================================================
RCS file: /cvsroot/src/sys/arch/i386/conf/files.i386,v
retrieving revision 1.259
diff -u -r1.259 files.i386
--- conf/files.i386 10 Jul 2004 18:51:01 -0000 1.259
+++ conf/files.i386 16 Jul 2004 18:54:04 -0000
@@ -64,6 +64,9 @@
# Enhanced SpeedStep
defflag ENHANCED_SPEEDSTEP
+# PowerNOW K7
+#defflag POWERNOW_K7
+
file arch/i386/i386/autoconf.c
file arch/i386/i386/db_dbgreg.S ddb | kstack_check_dr0
file arch/i386/i386/db_disasm.c ddb
@@ -474,4 +477,7 @@
file arch/i386/i386/est.c enhanced_speedstep
defflag opt_est.h EST_FREQ_USERWRITE
+# PowerNOW K7
+file arch/i386/i386/pnow_k7.c
+
include "arch/i386/conf/majors.i386"
Index: i386/identcpu.c
===================================================================
RCS file: /cvsroot/src/sys/arch/i386/i386/identcpu.c,v
retrieving revision 1.15
diff -u -r1.15 identcpu.c
--- i386/identcpu.c 11 Jul 2004 15:22:05 -0000 1.15
+++ i386/identcpu.c 16 Jul 2004 18:54:06 -0000
@@ -1375,4 +1375,16 @@
}
#endif /* ENHANCED_SPEEDSTEP */
+#ifdef POWERNOW_K7
+ /* XXX verify me */
+
+ /* Let's make sure it's a Mobile AMD K7 cpu */
+ if (ci->ci_cpu_class == CPUCLASS_686 && *cpu_brand_string != '\0') {
+ if (!memcmp("obile AMD Athlon(tm)", cpu_brand_string+1, 20)) {
+ pnowk7_init(ci);
+ }
+ }
+
+#endif /* POWERNOW_K7 */
+
}
Index: include/cpu.h
===================================================================
RCS file: /cvsroot/src/sys/arch/i386/include/cpu.h,v
retrieving revision 1.115
diff -u -r1.115 cpu.h
--- include/cpu.h 16 May 2004 12:32:53 -0000 1.115
+++ include/cpu.h 16 Jul 2004 18:54:06 -0000
@@ -436,6 +436,9 @@
/* est.c */
void est_init(struct cpu_info *);
+/* pnow_k7.c */
+void pnowk7_init(struct cpu_info *);
+
#endif /* _KERNEL */
/*
--- /dev/null 2004-07-10 19:21:49.000000000 -0400
+++ ./i386/pnow_k7.c 2004-07-10 19:40:56.000000000 -0400
@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) 2004 Martin Végiard.
+ * 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.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * 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.
+ */
+
+/* AMD POWERNOW K7 driver */
+
+/* Sysctl related code was adapted from NetBSD's i386/est.c for compatibility */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/malloc.h>
+#include <sys/sysctl.h>
+
+#include <dev/isa/isareg.h>
+
+#include <machine/cpu.h>
+#include <machine/isa_machdep.h>
+
+#define BIOS_START 0xe0000
+#define BIOS_END 0x20000
+#define BIOS_LEN BIOS_END - BIOS_START
+
+#define MSR_K7_CTL 0xC0010041
+#define CTL_SET_FID 0x0000000000010000ULL
+#define CTL_SET_VID 0x0000000000020000ULL
+
+#define cpufreq(x) fsb * fid_codes[x] / 10
+
+struct psb_s {
+ char signature[10]; /* AMDK7PNOW! */
+ uint8_t version;
+ uint8_t flags;
+ uint16_t ttime; /* Min Settling time */
+ uint8_t reserved;
+ uint8_t n_pst;
+};
+
+struct pst_s {
+ cpuid_t cpuid;
+ uint8_t fsb; /* Front Side Bus frequency (Mhz) */
+ uint8_t fid; /* Max Frequency code */
+ uint8_t vid; /* Max Voltage code */
+ uint8_t n_states; /* Number of states */
+};
+
+struct state_s {
+ uint8_t fid; /* Frequency code */
+ uint8_t vid; /* Voltage code */
+};
+
+struct freq_table_s {
+ unsigned int frequency;
+ struct state_s *state;
+};
+
+/* Taken from powernow-k7.c/Linux by Dave Jones */
+static int fid_codes[32] = {
+ 110, 115, 120, 125, 50, 55, 60, 65,
+ 70, 75, 80, 85, 90, 95, 100, 105,
+ 30, 190, 40, 200, 130, 135, 140, 210,
+ 150, 225, 160, 165, 170, 180, -1, -1
+};
+
+/* Static variables */
+static unsigned int fsb;
+static unsigned int cur_freq;
+static unsigned int ttime;
+static unsigned int n_states;
+static struct freq_table_s *freq_table;
+static int powernow_node_target, powernow_node_current;
+
+
+/* Prototypes */
+struct state_s *pnowk7_getstates(cpuid_t cpuid);
+int pnowk7_setstate(unsigned int freq);
+static int powernow_sysctl_helper(SYSCTLFN_ARGS);
+
+static int
+powernow_sysctl_helper(SYSCTLFN_ARGS)
+{
+ struct sysctlnode node;
+ int fq, oldfq, error;
+
+ if (freq_table == NULL)
+ return (EOPNOTSUPP);
+
+ node = *rnode;
+ node.sysctl_data = &fq;
+
+ oldfq = 0;
+ if (rnode->sysctl_num == powernow_node_target)
+ fq = oldfq = cur_freq;
+ else if (rnode->sysctl_num == powernow_node_current)
+ fq = cur_freq;
+ else
+ return (EOPNOTSUPP);
+
+ error = sysctl_lookup(SYSCTLFN_CALL(&node));
+ if (error || newp == NULL)
+ return (error);
+
+ /* support writing to ...frequency.target */
+ if (rnode->sysctl_num == powernow_node_target && fq != oldfq) {
+
+ if (pnowk7_setstate(fq) == 0)
+ {
+ cur_freq = fq;
+ } else
+ aprint_normal("\nInvalid frequency\n");
+ }
+
+ return (0);
+}
+
+struct state_s *
+pnowk7_getstates(cpuid_t cpuid)
+{
+ unsigned int i, j;
+ char *ptr;
+
+ struct psb_s *psb;
+ struct pst_s *pst;
+
+ /*
+ * Look in the 0xe0000 - 0x20000 physical address
+ * range for the pst tables; 16 byte blocks
+ */
+
+ ptr = (char *)ISA_HOLE_VADDR(BIOS_START);
+
+ for (i = 0; i < BIOS_LEN; i += 16, ptr += 16) {
+ if (memcmp(ptr, "AMDK7PNOW!", 10) == 0)
+ {
+ psb = (struct psb_s *) ptr;
+ ptr += sizeof(struct psb_s);
+
+ ttime = psb->ttime;
+
+ /* Only this version is supported */
+ if (psb->version != 0x12)
+ return 0;
+
+ /* Find the right PST */
+ for (j = 0; j < psb->n_pst; j++) {
+ pst = (struct pst_s *) ptr;
+ ptr += sizeof(struct pst_s);
+
+ /* Use the first PST with matching CPUID */
+ if (cpuid == pst->cpuid)
+ {
+ /*
+ * XXX I need more info on this.
+ * For now, let's just ignore it
+ */
+ if ((cpuid & 0xFF) == 0x60)
+ return 0;
+
+ fsb = pst->fsb;
+ n_states = pst->n_states;
+ return (struct state_s *) ptr;
+ } else
+ ptr += sizeof(struct state_s) \
+ * pst->n_states;
+ }
+
+ aprint_normal("No match was found for your CPUID\n");
+ return 0;
+ }
+ }
+
+ aprint_normal("Power state table not found\n");
+ return 0;
+}
+
+int
+pnowk7_setstate(unsigned int freq)
+{
+ unsigned int i;
+ u_int32_t sgtc, vid, fid;
+ u_int64_t ctl;
+
+ vid = fid = 0;
+
+ for (i = 0; i < n_states; i++) {
+ /* Do we know how to set that frequency? */
+ if (freq_table[i].frequency == freq)
+ {
+ fid = freq_table[i].state->fid;
+ vid = freq_table[i].state->vid;
+ }
+ }
+
+ if (fid == 0 || vid == 0)
+ return -1;
+
+ /* Get CTL and only modify fid/vid/sgtc */
+ ctl = rdmsr(MSR_K7_CTL);
+
+ /* FID */
+ ctl &= 0xFFFFFFFFFFFFFF00ULL;
+ ctl |= fid;
+
+ /* VID */
+ ctl &= 0xFFFFFFFFFFFF00FFULL;
+ ctl |= vid << 8;
+
+ /* SGTC */
+ if ((sgtc = ttime * 100) < 10000) sgtc = 10000;
+ ctl &= 0xFFF00000FFFFFFFFULL;
+ ctl |= (u_int64_t)sgtc << 32;
+
+ if (cur_freq > freq) {
+ wrmsr(MSR_K7_CTL, ctl | CTL_SET_FID);
+ wrmsr(MSR_K7_CTL, ctl | CTL_SET_VID);
+ } else {
+ wrmsr(MSR_K7_CTL, ctl | CTL_SET_VID);
+ wrmsr(MSR_K7_CTL, ctl | CTL_SET_FID);
+ }
+
+ ctl = rdmsr(MSR_K7_CTL);
+
+ return 0;
+}
+
+void
+pnowk7_init(struct cpu_info *ci)
+{
+ int rc;
+ unsigned int i, freq_names_len, len = 0;
+ char *freq_names;
+ struct sysctlnode *node, *pnownode, *freqnode;
+ struct state_s *s = pnowk7_getstates(ci->ci_signature);
+
+ if (s == 0) {
+ aprint_normal("AMD POWERNOW not supported\n");
+ return;
+ }
+
+ freq_names_len = n_states * (sizeof("9999 ")-1) + 1;
+ freq_names = malloc(freq_names_len, M_SYSCTLDATA, M_WAITOK);
+
+ freq_table = malloc(sizeof(struct freq_table_s) * n_states, \
+ M_TEMP, M_WAITOK);
+
+
+ for (i = 0; i < n_states; i++, s++) {
+ freq_table[i].frequency = cpufreq(s->fid);
+ freq_table[i].state = s;
+
+ len += snprintf(freq_names + len, freq_names_len - len, "%d%s",
+ freq_table[i].frequency, i < n_states - 1 ? " " : "");
+ }
+
+ /* On bootup the frequency should be at it's max */
+ cur_freq = freq_table[i-1].frequency;
+
+ /* Create sysctl machdep.powernow.frequency. */
+ if ((rc = sysctl_createv(NULL, 0, NULL, &node,
+ CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL,
+ NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL)) != 0)
+ goto err;
+
+ if ((rc = sysctl_createv(NULL, 0, &node, &pnownode,
+ 0, CTLTYPE_NODE, "powernow", NULL,
+ NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL)) != 0)
+ goto err;
+
+ if ((rc = sysctl_createv(NULL, 0, &pnownode, &freqnode,
+ 0, CTLTYPE_NODE, "frequency", NULL,
+ NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL)) != 0)
+ goto err;
+
+ if ((rc = sysctl_createv(NULL, 0, &freqnode, &node,
+ CTLFLAG_READWRITE, CTLTYPE_INT, "target", NULL,
+ powernow_sysctl_helper, 0, NULL, 0, CTL_CREATE, CTL_EOL)) != 0)
+ goto err;
+ powernow_node_target = node->sysctl_num;
+
+ if ((rc = sysctl_createv(NULL, 0, &freqnode, &node,
+ 0, CTLTYPE_INT, "current", NULL,
+ powernow_sysctl_helper, 0, NULL, 0, CTL_CREATE, CTL_EOL)) != 0)
+ goto err;
+ powernow_node_current = node->sysctl_num;
+
+ if ((rc = sysctl_createv(NULL, 0, &freqnode, &node,
+ 0, CTLTYPE_STRING, "available", NULL,
+ NULL, 0, freq_names, freq_names_len, CTL_CREATE, CTL_EOL)) != 0)
+ goto err;
+
+ aprint_normal("AMD POWERNOW supported current frequency : %d Mhz\n", \
+ cur_freq);
+ aprint_normal("Available frequencies (Mhz) : %s\n", freq_names);
+ return;
+ err:
+ aprint_normal("%s: sysctl_createv failed (rc = %d)\n", __func__, rc);
+}
+
--C7zPtVaVf+AK4Oqc--