xref: /dragonfly/sys/dev/powermng/clockmod/clockmod.c (revision 65cc0652)
1 /*
2  * Copyright (c) 2014 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Sepherosa Ziehau <sepherosa@gmail.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  */
34 
35 #include <sys/param.h>
36 #include <sys/conf.h>
37 #include <sys/kernel.h>
38 #include <sys/bus.h>
39 #include <sys/cpu_topology.h>
40 #include <sys/cpuhelper.h>
41 #include <sys/module.h>
42 #include <sys/queue.h>
43 #include <sys/serialize.h>
44 #include <sys/sysctl.h>
45 #include <sys/systm.h>
46 
47 #include <machine/specialreg.h>
48 #include <machine/cpufunc.h>
49 #include <machine/cputypes.h>
50 #include <machine/md_var.h>
51 
52 struct clockmod_dom;
53 
54 struct clockmod_softc {
55 	TAILQ_ENTRY(clockmod_softc) sc_link;
56 	struct clockmod_dom	*sc_dom;
57 	int			sc_cpuid;
58 };
59 
60 struct clockmod_dom {
61 	TAILQ_ENTRY(clockmod_dom) dom_link;
62 	TAILQ_HEAD(, clockmod_softc) dom_list;
63 	struct sysctl_ctx_list	dom_sysctl_ctx;
64 	struct sysctl_oid	*dom_sysctl_tree;
65 	cpumask_t		dom_cpumask;
66 	char			dom_name[16];
67 	int			dom_select;
68 	uint32_t		dom_flags;
69 };
70 
71 #define CLOCKMOD_DOM_FLAG_ACTIVE	0x1
72 
73 struct clockmod_dom_ctrl {
74 	char			ctl_name[8];
75 	uint64_t		ctl_value;
76 };
77 
78 static int	clockmod_dom_attach(struct clockmod_softc *);
79 static void	clockmod_dom_detach(struct clockmod_softc *);
80 static struct clockmod_dom *clockmod_dom_find(cpumask_t);
81 static struct clockmod_dom *clockmod_dom_create(cpumask_t);
82 static void	clockmod_dom_destroy(struct clockmod_dom *);
83 
84 static int	clockmod_dom_sysctl_select(SYSCTL_HANDLER_ARGS);
85 static int	clockmod_dom_sysctl_members(SYSCTL_HANDLER_ARGS);
86 static int	clockmod_dom_sysctl_available(SYSCTL_HANDLER_ARGS);
87 
88 static void	clockmod_identify(driver_t *, device_t);
89 static int	clockmod_probe(device_t);
90 static int	clockmod_attach(device_t);
91 static int	clockmod_detach(device_t);
92 
93 static void	clockmod_select_handler(struct cpuhelper_msg *);
94 static int	clockmod_select(const struct clockmod_softc *,
95 		    const struct clockmod_dom_ctrl *);
96 
97 static boolean_t clockmod_errata_duty(int);
98 
99 static struct lwkt_serialize clockmod_dom_slize = LWKT_SERIALIZE_INITIALIZER;
100 static int	clockmod_dom_id;
101 static TAILQ_HEAD(, clockmod_dom) clockmod_dom_list =
102     TAILQ_HEAD_INITIALIZER(clockmod_dom_list);
103 static int	clockmod_dom_nctrl;
104 static struct clockmod_dom_ctrl *clockmod_dom_controls;
105 
106 static device_method_t clockmod_methods[] = {
107 	/* Device interface */
108 	DEVMETHOD(device_identify,	clockmod_identify),
109 	DEVMETHOD(device_probe,		clockmod_probe),
110 	DEVMETHOD(device_attach,	clockmod_attach),
111 	DEVMETHOD(device_detach,	clockmod_detach),
112 
113 	DEVMETHOD_END
114 };
115 
116 static driver_t clockmod_driver = {
117 	"clockmod",
118 	clockmod_methods,
119 	sizeof(struct clockmod_softc),
120 };
121 
122 static devclass_t clockmod_devclass;
123 DRIVER_MODULE(clockmod, cpu, clockmod_driver, clockmod_devclass, NULL, NULL);
124 
125 static void
126 clockmod_identify(driver_t *driver, device_t parent)
127 {
128 	device_t child;
129 
130 	if (device_find_child(parent, "clockmod", -1) != NULL)
131 		return;
132 
133 	if (cpu_vendor_id != CPU_VENDOR_INTEL)
134 		return;
135 
136 	if ((cpu_feature & (CPUID_ACPI | CPUID_TM)) != (CPUID_ACPI | CPUID_TM))
137 		return;
138 
139 	child = device_add_child(parent, "clockmod", device_get_unit(parent));
140 	if (child == NULL)
141 		device_printf(parent, "add clockmod failed\n");
142 }
143 
144 static int
145 clockmod_probe(device_t dev)
146 {
147 	device_set_desc(dev, "CPU clock modulation");
148 	return 0;
149 }
150 
151 static int
152 clockmod_attach(device_t dev)
153 {
154 	struct clockmod_softc *sc = device_get_softc(dev);
155 	int error;
156 
157 	sc->sc_cpuid = device_get_unit(dev);
158 
159 	error = clockmod_dom_attach(sc);
160 	if (error) {
161 		device_printf(dev, "domain attach failed\n");
162 		return error;
163 	}
164 
165 	return 0;
166 }
167 
168 static int
169 clockmod_detach(device_t dev)
170 {
171 	clockmod_dom_detach(device_get_softc(dev));
172 	return 0;
173 }
174 
175 static int
176 clockmod_dom_attach(struct clockmod_softc *sc)
177 {
178 	struct clockmod_softc *sc1;
179 	struct clockmod_dom *dom;
180 	cpumask_t mask, found_mask;
181 	int error = 0;
182 
183 	CPUMASK_ASSZERO(found_mask);
184 
185 	mask = get_cpumask_from_level(sc->sc_cpuid, CORE_LEVEL);
186 	if (CPUMASK_TESTZERO(mask))
187 		CPUMASK_ASSBIT(mask, sc->sc_cpuid);
188 
189 	lwkt_serialize_enter(&clockmod_dom_slize);
190 
191 	dom = clockmod_dom_find(mask);
192 	if (dom == NULL) {
193 		dom = clockmod_dom_create(mask);
194 		if (dom == NULL) {
195 			error = ENOMEM;
196 			goto back;
197 		}
198 	}
199 
200 	sc->sc_dom = dom;
201 	TAILQ_INSERT_TAIL(&dom->dom_list, sc, sc_link);
202 
203 	TAILQ_FOREACH(sc1, &dom->dom_list, sc_link)
204 		CPUMASK_ORBIT(found_mask, sc1->sc_cpuid);
205 
206 	if (CPUMASK_CMPMASKEQ(found_mask, dom->dom_cpumask)) {
207 		/* All cpus in this domain is found */
208 		dom->dom_flags |= CLOCKMOD_DOM_FLAG_ACTIVE;
209 	}
210 back:
211 	lwkt_serialize_exit(&clockmod_dom_slize);
212 	return error;
213 }
214 
215 static void
216 clockmod_dom_detach(struct clockmod_softc *sc)
217 {
218 	struct clockmod_dom *dom;
219 
220 	lwkt_serialize_enter(&clockmod_dom_slize);
221 
222 	dom = sc->sc_dom;
223 	sc->sc_dom = NULL;
224 
225 	if (dom->dom_flags & CLOCKMOD_DOM_FLAG_ACTIVE) {
226 		struct clockmod_softc *sc1;
227 
228 		/* Raise to 100% */
229 		TAILQ_FOREACH(sc1, &dom->dom_list, sc_link)
230 			clockmod_select(sc1, &clockmod_dom_controls[0]);
231 	}
232 
233 	/* One cpu is leaving; domain is no longer active */
234 	dom->dom_flags &= ~CLOCKMOD_DOM_FLAG_ACTIVE;
235 
236 	TAILQ_REMOVE(&dom->dom_list, sc, sc_link);
237 	if (TAILQ_EMPTY(&dom->dom_list))
238 		clockmod_dom_destroy(dom);
239 
240 	lwkt_serialize_exit(&clockmod_dom_slize);
241 }
242 
243 static struct clockmod_dom *
244 clockmod_dom_find(cpumask_t mask)
245 {
246 	struct clockmod_dom *dom;
247 
248 	TAILQ_FOREACH(dom, &clockmod_dom_list, dom_link) {
249 		if (CPUMASK_CMPMASKEQ(dom->dom_cpumask, mask))
250 			return dom;
251 	}
252 	return NULL;
253 }
254 
255 static struct clockmod_dom *
256 clockmod_dom_create(cpumask_t mask)
257 {
258 	struct clockmod_dom *dom;
259 	int id;
260 
261 	id = clockmod_dom_id++;
262 	dom = kmalloc(sizeof(*dom), M_DEVBUF, M_WAITOK | M_ZERO);
263 
264 	TAILQ_INIT(&dom->dom_list);
265 	dom->dom_cpumask = mask;
266 	ksnprintf(dom->dom_name, sizeof(dom->dom_name), "clockmod_dom%d", id);
267 
268 	sysctl_ctx_init(&dom->dom_sysctl_ctx);
269 	dom->dom_sysctl_tree = SYSCTL_ADD_NODE(&dom->dom_sysctl_ctx,
270 	    SYSCTL_STATIC_CHILDREN(_machdep), OID_AUTO, dom->dom_name,
271 	    CTLFLAG_RD, 0, "");
272 	if (dom->dom_sysctl_tree == NULL) {
273 		kprintf("%s: can't add sysctl node\n", dom->dom_name);
274 		kfree(dom, M_DEVBUF);
275 		return NULL;
276 	}
277 
278 	SYSCTL_ADD_PROC(&dom->dom_sysctl_ctx,
279 	    SYSCTL_CHILDREN(dom->dom_sysctl_tree),
280 	    OID_AUTO, "members", CTLTYPE_STRING | CTLFLAG_RD,
281 	    dom, 0, clockmod_dom_sysctl_members, "A", "member cpus");
282 
283 	SYSCTL_ADD_PROC(&dom->dom_sysctl_ctx,
284 	    SYSCTL_CHILDREN(dom->dom_sysctl_tree),
285 	    OID_AUTO, "available", CTLTYPE_STRING | CTLFLAG_RD,
286 	    dom, 0, clockmod_dom_sysctl_available, "A",
287 	    "available duty percent");
288 
289 	SYSCTL_ADD_PROC(&dom->dom_sysctl_ctx,
290 	    SYSCTL_CHILDREN(dom->dom_sysctl_tree),
291 	    OID_AUTO, "select", CTLTYPE_STRING | CTLFLAG_RW,
292 	    dom, 0, clockmod_dom_sysctl_select, "A", "select duty");
293 
294 	TAILQ_INSERT_TAIL(&clockmod_dom_list, dom, dom_link);
295 
296 	if (clockmod_dom_controls == NULL) {
297 		int nctrl, step, i, shift, cnt;
298 
299 #ifdef __x86_64__
300 		if (cpu_thermal_feature & CPUID_THERMAL_ECMD)
301 			shift = 0;
302 		else
303 #endif
304 			shift = 1;
305 
306 		nctrl = 8 << (1 - shift);
307 		step = 10000 / nctrl;
308 
309 		clockmod_dom_controls =
310 		    kmalloc(sizeof(struct clockmod_dom_ctrl) * nctrl, M_DEVBUF,
311 		    M_WAITOK | M_ZERO);
312 
313 		if (bootverbose)
314 			kprintf("clock modulation:\n");
315 
316 		cnt = 0;
317 		for (i = 0; i < nctrl; ++i) {
318 			struct clockmod_dom_ctrl *ctrl =
319 			    &clockmod_dom_controls[cnt];
320 			int duty;
321 
322 			duty = 10000 - (i * step);
323 			if (clockmod_errata_duty(duty))
324 				continue;
325 			++cnt;
326 
327 			ksnprintf(ctrl->ctl_name, sizeof(ctrl->ctl_name),
328 			    "%d.%02d%%", duty / 100, duty % 100);
329 			ctrl->ctl_value = (((nctrl - i) << shift) & 0xf);
330 			if (i != 0)
331 				ctrl->ctl_value |= 1 << 4;
332 
333 			if (bootverbose) {
334 				kprintf("  0x%04jx %s\n",
335 				    (uintmax_t)ctrl->ctl_value,
336 				    ctrl->ctl_name);
337 			}
338 		}
339 		clockmod_dom_nctrl = cnt;
340 	}
341 	return dom;
342 }
343 
344 static void
345 clockmod_dom_destroy(struct clockmod_dom *dom)
346 {
347 	KASSERT(TAILQ_EMPTY(&dom->dom_list),
348 	    ("%s: still has member cpus", dom->dom_name));
349 	TAILQ_REMOVE(&clockmod_dom_list, dom, dom_link);
350 
351 	sysctl_ctx_free(&dom->dom_sysctl_ctx);
352 	kfree(dom, M_DEVBUF);
353 
354 	if (TAILQ_EMPTY(&clockmod_dom_list)) {
355 		clockmod_dom_nctrl = 0;
356 		kfree(clockmod_dom_controls, M_DEVBUF);
357 		clockmod_dom_controls = NULL;
358 	}
359 }
360 
361 static int
362 clockmod_dom_sysctl_members(SYSCTL_HANDLER_ARGS)
363 {
364 	struct clockmod_dom *dom = arg1;
365 	struct clockmod_softc *sc;
366 	int loop, error;
367 
368 	lwkt_serialize_enter(&clockmod_dom_slize);
369 
370 	loop = error = 0;
371 	TAILQ_FOREACH(sc, &dom->dom_list, sc_link) {
372 		char buf[16];
373 
374 		if (error == 0 && loop)
375 			error = SYSCTL_OUT(req, " ", 1);
376 		if (error == 0) {
377 			ksnprintf(buf, sizeof(buf), "cpu%d", sc->sc_cpuid);
378 			error = SYSCTL_OUT(req, buf, strlen(buf));
379 		}
380 		++loop;
381 	}
382 
383 	lwkt_serialize_exit(&clockmod_dom_slize);
384 	return error;
385 }
386 
387 static int
388 clockmod_dom_sysctl_available(SYSCTL_HANDLER_ARGS)
389 {
390 	struct clockmod_dom *dom = arg1;
391 	int loop, error, i;
392 
393 	lwkt_serialize_enter(&clockmod_dom_slize);
394 
395 	if ((dom->dom_flags & CLOCKMOD_DOM_FLAG_ACTIVE) == 0) {
396 		error = SYSCTL_OUT(req, " ", 1);
397 		goto done;
398 	}
399 
400 	loop = error = 0;
401 	for (i = 0; i < clockmod_dom_nctrl; ++i) {
402 		if (error == 0 && loop)
403 			error = SYSCTL_OUT(req, " ", 1);
404 		if (error == 0) {
405 			error = SYSCTL_OUT(req,
406 			    clockmod_dom_controls[i].ctl_name,
407 			    strlen(clockmod_dom_controls[i].ctl_name));
408 		}
409 		++loop;
410 	}
411 done:
412 	lwkt_serialize_exit(&clockmod_dom_slize);
413 	return error;
414 }
415 
416 static int
417 clockmod_dom_sysctl_select(SYSCTL_HANDLER_ARGS)
418 {
419 	struct clockmod_dom *dom = arg1;
420 	struct clockmod_softc *sc;
421 	const struct clockmod_dom_ctrl *ctrl = NULL;
422 	char duty[16];
423 	int error, i;
424 
425 	lwkt_serialize_enter(&clockmod_dom_slize);
426 	KKASSERT(dom->dom_select >= 0 && dom->dom_select < clockmod_dom_nctrl);
427 	ksnprintf(duty, sizeof(duty), "%s",
428 	    clockmod_dom_controls[dom->dom_select].ctl_name);
429 	lwkt_serialize_exit(&clockmod_dom_slize);
430 
431 	error = sysctl_handle_string(oidp, duty, sizeof(duty), req);
432 	if (error != 0 || req->newptr == NULL)
433 		return error;
434 
435 	lwkt_serialize_enter(&clockmod_dom_slize);
436 
437 	if ((dom->dom_flags & CLOCKMOD_DOM_FLAG_ACTIVE) == 0) {
438 		error = EOPNOTSUPP;
439 		goto back;
440 	}
441 
442 	for (i = 0; i < clockmod_dom_nctrl; ++i) {
443 		ctrl = &clockmod_dom_controls[i];
444 		if (strcmp(duty, ctrl->ctl_name) == 0)
445 			break;
446 	}
447 	if (i == clockmod_dom_nctrl) {
448 		error = EINVAL;
449 		goto back;
450 	}
451 	dom->dom_select = i;
452 
453 	TAILQ_FOREACH(sc, &dom->dom_list, sc_link)
454 		clockmod_select(sc, ctrl);
455 back:
456 	lwkt_serialize_exit(&clockmod_dom_slize);
457 	return error;
458 }
459 
460 static void
461 clockmod_select_handler(struct cpuhelper_msg *msg)
462 {
463 	uint64_t ctl_value = *((const uint64_t *)msg->ch_cbarg);
464 
465 #if 0
466 	if (bootverbose) {
467 		kprintf("cpu%d: clockmod 0x%04jx\n", mycpuid,
468 		    (uintmax_t)ctl_value);
469 	}
470 #endif
471 	wrmsr(MSR_THERM_CONTROL, ctl_value);
472 	cpuhelper_replymsg(msg, 0);
473 }
474 
475 static int
476 clockmod_select(const struct clockmod_softc *sc,
477     const struct clockmod_dom_ctrl *ctrl)
478 {
479 	struct cpuhelper_msg msg;
480 
481 	cpuhelper_initmsg(&msg, &curthread->td_msgport,
482 	    clockmod_select_handler, __DECONST(void *, &ctrl->ctl_value),
483 	    MSGF_PRIORITY);
484 	return (cpuhelper_domsg(&msg, sc->sc_cpuid));
485 }
486 
487 static boolean_t
488 clockmod_errata_duty(int duty)
489 {
490 	uint32_t model, stepping;
491 
492 	/*
493 	 * This is obtained from the original p4tcc code.
494 	 *
495 	 * The original errata checking code in p4tcc is obviously wrong.
496 	 * However, I am no longer being able to find the errata mentioned
497 	 * in the code.  The guess is that the errata only affects family
498 	 * 0x0f CPUs, since:
499 	 * - The errata applies to only to model 0x00, 0x01 and 0x02 in
500 	 *   the original p4tcc code.
501 	 * - Software controlled clock modulation has been supported since
502 	 *   0f_00 and the model of the oldest family 0x06 CPUs supporting
503 	 *   this feature is 0x09.
504 	 */
505 	if (CPUID_TO_FAMILY(cpu_id) != 0xf)
506 		return FALSE;
507 
508 	model = CPUID_TO_MODEL(cpu_id);
509 	stepping = cpu_id & 0xf;
510 
511 	if (model == 0x6) {
512 		switch (stepping) {
513 		case 0x2:
514 		case 0x4:
515 		case 0x5:
516 			/* Hang w/ 12.50% and 25.00% */
517 			if (duty == 1250 || duty == 2500)
518 				return TRUE;
519 			break;
520 		}
521 	} else if (model == 0x2) {
522 		switch (stepping) {
523 		case 0x2:
524 		case 0x4:
525 		case 0x5:
526 		case 0x7:
527 		case 0x9:
528 			/* Hang w/ 12.50% */
529 			if (duty == 1250)
530 				return TRUE;
531 			break;
532 		}
533 	} else if (model == 0x1) {
534 		switch (stepping) {
535 		case 0x2:
536 		case 0x3:
537 			/* Hang w/ 12.50% and 25.00% */
538 			if (duty == 1250 || duty == 2500)
539 				return TRUE;
540 			break;
541 		}
542 	} else if (model == 0x0) {
543 		switch (stepping) {
544 		case 0x7:
545 		case 0xa:
546 			/* Hang w/ 12.50% and 25.00% */
547 			if (duty == 1250 || duty == 2500)
548 				return TRUE;
549 			break;
550 		}
551 	}
552 	return FALSE;
553 }
554