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:38:05
--k+w/mQv8wyuph6w0
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

On Fri, Jul 16, 2004 at 08:33:16PM +0100, Sergio Jimenez wrote:
> 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 :)

Sorry wrong patch. :)

Cheers,
Sergio

--k+w/mQv8wyuph6w0
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		0xFFFFF
+#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 - 0xffffff 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);
+}
+

--k+w/mQv8wyuph6w0--