smist.c revision 145287
1/*-
2 * Copyright (c) 2005 Bruno Ducrot
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25/*
26 * This driver is based upon information found by examining speedstep-0.5
27 * from Marc Lehman, which includes all the reverse engineering effort of
28 * Malik Martin (function 1 and 2 of the GSI).
29 *
30 * The correct way for the OS to take ownership from the BIOS was found by
31 * Hiroshi Miura (function 0 of the GSI).
32 *
33 * Finally, the int 15h call interface was (partially) documented by Intel.
34 *
35 * Many thanks to Jon Noack for testing and debugging this driver.
36 */
37
38#include <sys/cdefs.h>
39__FBSDID("$FreeBSD: head/sys/i386/cpufreq/smist.c 145287 2005-04-19 16:38:24Z njl $");
40
41#include <sys/param.h>
42#include <sys/bus.h>
43#include <sys/cpu.h>
44#include <sys/kernel.h>
45#include <sys/module.h>
46#include <sys/systm.h>
47
48#include <machine/md_var.h>
49#include <machine/vm86.h>
50
51#include <dev/pci/pcivar.h>
52#include <dev/pci/pcireg.h>
53
54#include <vm/vm.h>
55#include <vm/pmap.h>
56
57#include "cpufreq_if.h"
58
59#if 0
60#define DPRINT(dev, x...)	device_printf(dev, x)
61#else
62#define DPRINT(dev, x...)
63#endif
64
65struct smist_softc {
66	device_t		 dev;
67	int			 smi_cmd;
68	int			 smi_data;
69	int			 command;
70	int			 flags;
71	struct cf_setting	 sets[2];	/* Only two settings. */
72};
73
74static void	smist_identify(driver_t *driver, device_t parent);
75static int	smist_probe(device_t dev);
76static int	smist_attach(device_t dev);
77static int	smist_detach(device_t dev);
78static int	smist_settings(device_t dev, struct cf_setting *sets,
79		    int *count);
80static int	smist_set(device_t dev, const struct cf_setting *set);
81static int	smist_get(device_t dev, struct cf_setting *set);
82static int	smist_type(device_t dev, int *type);
83
84static device_method_t smist_methods[] = {
85	/* Device interface */
86	DEVMETHOD(device_identify,	smist_identify),
87	DEVMETHOD(device_probe,		smist_probe),
88	DEVMETHOD(device_attach,	smist_attach),
89	DEVMETHOD(device_detach,	smist_detach),
90
91	/* cpufreq interface */
92	DEVMETHOD(cpufreq_drv_set,	smist_set),
93	DEVMETHOD(cpufreq_drv_get,	smist_get),
94	DEVMETHOD(cpufreq_drv_type,	smist_type),
95	DEVMETHOD(cpufreq_drv_settings,	smist_settings),
96
97	{0, 0}
98};
99
100static driver_t smist_driver = {
101	"smist", smist_methods, sizeof(struct smist_softc)
102};
103static devclass_t smist_devclass;
104DRIVER_MODULE(smist, cpu, smist_driver, smist_devclass, 0, 0);
105
106struct piix4_pci_device {
107	uint16_t		 vendor;
108	uint16_t		 device;
109	char			*desc;
110};
111
112static struct piix4_pci_device piix4_pci_devices[] = {
113	{0x8086, 0x7113, "Intel PIIX4 ISA bridge"},
114	{0x8086, 0x719b, "Intel PIIX4 ISA bridge (embedded in MX440 chipset)"},
115
116	{0, 0, NULL},
117};
118
119#define SET_OWNERSHIP		0
120#define GET_STATE		1
121#define SET_STATE		2
122
123static int
124int15_gsic_call(int *sig, int *smi_cmd, int *command, int *smi_data, int *flags)
125{
126	struct vm86frame vmf;
127
128	bzero(&vmf, sizeof(vmf));
129	vmf.vmf_eax = 0x0000E980;	/* IST support */
130	vmf.vmf_edx = 0x47534943;	/* 'GSIC' in ASCII */
131	vm86_intcall(0x15, &vmf);
132
133	if (vmf.vmf_eax == 0x47534943) {
134		*sig = vmf.vmf_eax;
135		*smi_cmd = vmf.vmf_ebx & 0xff;
136		*command = (vmf.vmf_ebx >> 16) & 0xff;
137		*smi_data = vmf.vmf_ecx;
138		*flags = vmf.vmf_edx;
139	} else {
140		*sig = -1;
141		*smi_cmd = -1;
142		*command = -1;
143		*smi_data = -1;
144		*flags = -1;
145	}
146
147	return (0);
148}
149
150static int
151set_ownership(device_t dev)
152{
153	int result;
154	struct smist_softc *sc;
155	vm_paddr_t pmagic;
156	static char magic[] = "Copyright (c) 1999 Intel Corporation";
157
158	sc = device_get_softc(dev);
159	if (!sc)
160		return (ENXIO);
161
162	pmagic = vtophys(magic);
163
164	__asm __volatile(
165	    "movl $-1, %%edi\n\t"
166	    "out %%al, (%%dx)\n"
167	    : "=D" (result)
168	    : "a" (sc->command),
169	      "b" (0),
170	      "c" (0),
171	      "d" (sc->smi_cmd),
172	      "S" (pmagic)
173	);
174
175	DPRINT(dev, "taking ownership over BIOS return %d\n", result);
176
177	return (result ? ENXIO : 0);
178}
179
180static int
181getset_state(struct smist_softc *sc, int *state, int function)
182{
183	int new_state;
184	int result;
185	int eax;
186
187	if (!sc)
188		return (ENXIO);
189
190	if (function != GET_STATE && function != SET_STATE)
191		return (EINVAL);
192
193	DPRINT(sc->dev, "calling GSI\n");
194
195	__asm __volatile(
196	     "movl $-1, %%edi\n\t"
197	     "out %%al, (%%dx)\n"
198	   : "=a" (eax),
199	     "=b" (new_state),
200	     "=D" (result)
201	   : "a" (sc->command),
202	     "b" (function),
203	     "c" (*state),
204	     "d" (sc->smi_cmd)
205	);
206
207	DPRINT(sc->dev, "GSI returned: eax %.8x ebx %.8x edi %.8x\n",
208	    eax, new_state, result);
209
210	*state = new_state & 1;
211
212	switch (function) {
213	case GET_STATE:
214		if (eax)
215			return (ENXIO);
216		break;
217	case SET_STATE:
218		if (result)
219			return (ENXIO);
220		break;
221	}
222	return (0);
223}
224
225static void
226smist_identify(driver_t *driver, device_t parent)
227{
228	struct piix4_pci_device *id;
229	device_t piix4 = NULL;
230
231	if (resource_disabled("ichst", 0))
232		return;
233
234	/* Check for a supported processor */
235	if (strcmp(cpu_vendor, "GenuineIntel") != 0)
236		return;
237	switch (cpu_id & 0xff0) {
238	case 0x680:	/* Pentium III [coppermine] */
239	case 0x6a0:	/* Pentium III [Tualatin] */
240		break;
241	default:
242		return;
243	}
244
245	/* Check for a supported PCI-ISA bridge */
246	for (id = piix4_pci_devices; id->desc != NULL; ++id) {
247		if ((piix4 = pci_find_device(id->vendor, id->device)) != NULL)
248			break;
249	}
250	if (!piix4)
251		return;
252
253	if (bootverbose)
254		printf("smist: found supported isa bridge %s\n", id->desc);
255
256	if (device_find_child(parent, "smist", -1) != NULL)
257		return;
258	if (BUS_ADD_CHILD(parent, 0, "smist", -1) == NULL)
259		device_printf(parent, "smist: add child failed\n");
260}
261
262static int
263smist_probe(device_t dev)
264{
265	struct smist_softc *sc;
266	device_t ichss_dev, perf_dev;
267	int sig, smi_cmd, command, smi_data, flags;
268	int type;
269	int rv;
270
271	if (resource_disabled("smist", 0))
272		return (ENXIO);
273
274	sc = device_get_softc(dev);
275
276	/*
277	 * If the ACPI perf or ICH SpeedStep drivers have attached and not
278	 * just offering info, let them manage things.
279	 */
280	perf_dev = device_find_child(device_get_parent(dev), "acpi_perf", -1);
281	if (perf_dev && device_is_attached(perf_dev)) {
282		rv = CPUFREQ_DRV_TYPE(perf_dev, &type);
283		if (rv == 0 && (type & CPUFREQ_FLAG_INFO_ONLY) == 0)
284			return (ENXIO);
285	}
286	ichss_dev = device_find_child(device_get_parent(dev), "ichss", -1);
287	if (ichss_dev && device_is_attached(ichss_dev))
288		return (ENXIO);
289
290	int15_gsic_call(&sig, &smi_cmd, &command, &smi_data, &flags);
291	if (bootverbose)
292		device_printf(dev, "sig %.8x smi_cmd %.4x command %.2x "
293		    "smi_data %.4x flags %.8x\n",
294		    sig, smi_cmd, command, smi_data, flags);
295
296	if (sig != -1) {
297		sc->smi_cmd = smi_cmd;
298		sc->smi_data = smi_data;
299
300		/*
301		 * Sometimes int 15h 'GSIC' returns 0x80 for command, when
302		 * it is actually 0x82.  The Windows driver will overwrite
303		 * this value given by the registry.
304		 */
305		if (command == 0x80) {
306			device_printf(dev,
307			    "GSIC returned cmd 0x80, should be 0x82\n");
308			command = 0x82;
309		}
310		sc->command = (sig & 0xffffff00) | (command & 0xff);
311		sc->flags = flags;
312	} else {
313		/* Give some default values */
314		sc->smi_cmd = 0xb2;
315		sc->smi_data = 0xb3;
316		sc->command = 0x47534982;
317		sc->flags = 0;
318	}
319
320	device_set_desc(dev, "SpeedStep SMI");
321
322	return (-1500);
323}
324
325static int
326smist_attach(device_t dev)
327{
328	struct smist_softc *sc;
329
330	sc = device_get_softc(dev);
331	sc->dev = dev;
332
333	/* If we can't take ownership over BIOS, then bail out */
334	if (set_ownership(dev) != 0)
335		return (ENXIO);
336
337	/* Setup some defaults for our exported settings. */
338	sc->sets[0].freq = CPUFREQ_VAL_UNKNOWN;
339	sc->sets[0].volts = CPUFREQ_VAL_UNKNOWN;
340	sc->sets[0].power = CPUFREQ_VAL_UNKNOWN;
341	sc->sets[0].lat = 1000;
342	sc->sets[0].dev = dev;
343	sc->sets[1] = sc->sets[0];
344
345	cpufreq_register(dev);
346
347	return (0);
348}
349
350static int
351smist_detach(device_t dev)
352{
353	cpufreq_unregister(dev);
354	return (0);
355}
356
357static int
358smist_settings(device_t dev, struct cf_setting *sets, int *count)
359{
360	struct smist_softc *sc;
361	struct cf_setting set;
362	int first, i;
363
364	if (sets == NULL || count == NULL)
365		return (EINVAL);
366	if (*count < 2) {
367		*count = 2;
368		return (E2BIG);
369	}
370	sc = device_get_softc(dev);
371
372	/*
373	 * Estimate frequencies for both levels, temporarily switching to
374	 * the other one if we haven't calibrated it yet.
375	 */
376	for (i = 0; i < 2; i++) {
377		if (sc->sets[i].freq == CPUFREQ_VAL_UNKNOWN) {
378			first = (i == 0) ? 1 : 0;
379			smist_set(dev, &sc->sets[i]);
380			smist_get(dev, &set);
381			smist_set(dev, &sc->sets[first]);
382		}
383	}
384
385	bcopy(sc->sets, sets, sizeof(sc->sets));
386	*count = 2;
387
388	return (0);
389}
390
391static int
392smist_set(device_t dev, const struct cf_setting *set)
393{
394	struct smist_softc *sc;
395	int rv, state, req_state, try;
396
397	/* Look up appropriate bit value based on frequency. */
398	sc = device_get_softc(dev);
399	if (CPUFREQ_CMP(set->freq, sc->sets[0].freq))
400		req_state = 0;
401	else if (CPUFREQ_CMP(set->freq, sc->sets[1].freq))
402		req_state = 1;
403	else
404		return (EINVAL);
405
406	DPRINT(dev, "requested setting %d\n", req_state);
407
408	rv = getset_state(sc, &state, GET_STATE);
409	if (state == req_state)
410		return (0);
411
412	try = 3;
413	do {
414		rv = getset_state(sc, &req_state, SET_STATE);
415
416		/* Sleep for 200 microseconds.  This value is just a guess. */
417		if (rv)
418			DELAY(200);
419	} while (rv && --try);
420	DPRINT(dev, "set_state return %d, tried %d times\n",
421	    rv, 4 - try);
422
423	return (rv);
424}
425
426static int
427smist_get(device_t dev, struct cf_setting *set)
428{
429	struct smist_softc *sc;
430	uint64_t rate;
431	int state;
432	int rv;
433
434	sc = device_get_softc(dev);
435	rv = getset_state(sc, &state, GET_STATE);
436	if (rv != 0)
437		return (rv);
438
439	/* If we haven't changed settings yet, estimate the current value. */
440	if (sc->sets[state].freq == CPUFREQ_VAL_UNKNOWN) {
441		cpu_est_clockrate(0, &rate);
442		sc->sets[state].freq = rate / 1000000;
443		DPRINT(dev, "get calibrated new rate of %d\n",
444		    sc->sets[state].freq);
445	}
446	*set = sc->sets[state];
447
448	return (0);
449}
450
451static int
452smist_type(device_t dev, int *type)
453{
454
455	if (type == NULL)
456		return (EINVAL);
457
458	*type = CPUFREQ_TYPE_ABSOLUTE;
459	return (0);
460}
461