xref: /freebsd/sys/kern/kern_cpu.c (revision 73347b071dc793c0d88a5205897726b727b565e5)
173347b07SNate Lawson /*-
273347b07SNate Lawson  * Copyright (c) 2004-2005 Nate Lawson (SDG)
373347b07SNate Lawson  * All rights reserved.
473347b07SNate Lawson  *
573347b07SNate Lawson  * Redistribution and use in source and binary forms, with or without
673347b07SNate Lawson  * modification, are permitted provided that the following conditions
773347b07SNate Lawson  * are met:
873347b07SNate Lawson  * 1. Redistributions of source code must retain the above copyright
973347b07SNate Lawson  *    notice, this list of conditions and the following disclaimer.
1073347b07SNate Lawson  * 2. Redistributions in binary form must reproduce the above copyright
1173347b07SNate Lawson  *    notice, this list of conditions and the following disclaimer in the
1273347b07SNate Lawson  *    documentation and/or other materials provided with the distribution.
1373347b07SNate Lawson  *
1473347b07SNate Lawson  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1573347b07SNate Lawson  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1673347b07SNate Lawson  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1773347b07SNate Lawson  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1873347b07SNate Lawson  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
1973347b07SNate Lawson  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2073347b07SNate Lawson  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2173347b07SNate Lawson  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2273347b07SNate Lawson  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2373347b07SNate Lawson  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2473347b07SNate Lawson  * SUCH DAMAGE.
2573347b07SNate Lawson  */
2673347b07SNate Lawson 
2773347b07SNate Lawson #include <sys/cdefs.h>
2873347b07SNate Lawson __FBSDID("$FreeBSD$");
2973347b07SNate Lawson 
3073347b07SNate Lawson #include <sys/param.h>
3173347b07SNate Lawson #include <sys/bus.h>
3273347b07SNate Lawson #include <sys/cpu.h>
3373347b07SNate Lawson #include <sys/eventhandler.h>
3473347b07SNate Lawson #include <sys/kernel.h>
3573347b07SNate Lawson #include <sys/malloc.h>
3673347b07SNate Lawson #include <sys/module.h>
3773347b07SNate Lawson #include <sys/proc.h>
3873347b07SNate Lawson #include <sys/queue.h>
3973347b07SNate Lawson #include <sys/sched.h>
4073347b07SNate Lawson #include <sys/sysctl.h>
4173347b07SNate Lawson #include <sys/systm.h>
4273347b07SNate Lawson #include <sys/sbuf.h>
4373347b07SNate Lawson 
4473347b07SNate Lawson #include "cpufreq_if.h"
4573347b07SNate Lawson 
4673347b07SNate Lawson /*
4773347b07SNate Lawson  * Common CPU frequency glue code.  Drivers for specific hardware can
4873347b07SNate Lawson  * attach this interface to allow users to get/set the CPU frequency.
4973347b07SNate Lawson  */
5073347b07SNate Lawson 
5173347b07SNate Lawson /*
5273347b07SNate Lawson  * Number of levels we can handle.  Levels are synthesized from settings
5373347b07SNate Lawson  * so for N settings there may be N^2 levels.
5473347b07SNate Lawson  */
5573347b07SNate Lawson #define CF_MAX_LEVELS	32
5673347b07SNate Lawson 
5773347b07SNate Lawson struct cpufreq_softc {
5873347b07SNate Lawson 	struct cf_level			curr_level;
5973347b07SNate Lawson 	int				priority;
6073347b07SNate Lawson 	struct cf_level_lst		all_levels;
6173347b07SNate Lawson 	device_t			dev;
6273347b07SNate Lawson 	struct sysctl_ctx_list		sysctl_ctx;
6373347b07SNate Lawson };
6473347b07SNate Lawson 
6573347b07SNate Lawson struct cf_setting_array {
6673347b07SNate Lawson 	struct cf_setting		sets[MAX_SETTINGS];
6773347b07SNate Lawson 	int				count;
6873347b07SNate Lawson 	TAILQ_ENTRY(cf_setting_array)	link;
6973347b07SNate Lawson };
7073347b07SNate Lawson 
7173347b07SNate Lawson TAILQ_HEAD(cf_setting_lst, cf_setting_array);
7273347b07SNate Lawson 
7373347b07SNate Lawson static int	cpufreq_attach(device_t dev);
7473347b07SNate Lawson static int	cpufreq_detach(device_t dev);
7573347b07SNate Lawson static void	cpufreq_evaluate(void *arg);
7673347b07SNate Lawson static int	cf_set_method(device_t dev, const struct cf_level *level,
7773347b07SNate Lawson 		    int priority);
7873347b07SNate Lawson static int	cf_get_method(device_t dev, struct cf_level *level);
7973347b07SNate Lawson static int	cf_levels_method(device_t dev, struct cf_level *levels,
8073347b07SNate Lawson 		    int *count);
8173347b07SNate Lawson static int	cpufreq_insert_abs(struct cf_level_lst *list,
8273347b07SNate Lawson 		    struct cf_setting *sets, int count);
8373347b07SNate Lawson static int	cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS);
8473347b07SNate Lawson static int	cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS);
8573347b07SNate Lawson 
8673347b07SNate Lawson static device_method_t cpufreq_methods[] = {
8773347b07SNate Lawson 	DEVMETHOD(device_probe,		bus_generic_probe),
8873347b07SNate Lawson 	DEVMETHOD(device_attach,	cpufreq_attach),
8973347b07SNate Lawson 	DEVMETHOD(device_detach,	cpufreq_detach),
9073347b07SNate Lawson 
9173347b07SNate Lawson         DEVMETHOD(cpufreq_set,		cf_set_method),
9273347b07SNate Lawson         DEVMETHOD(cpufreq_get,		cf_get_method),
9373347b07SNate Lawson         DEVMETHOD(cpufreq_levels,	cf_levels_method),
9473347b07SNate Lawson 	{0, 0}
9573347b07SNate Lawson };
9673347b07SNate Lawson static driver_t cpufreq_driver = {
9773347b07SNate Lawson 	"cpufreq", cpufreq_methods, sizeof(struct cpufreq_softc)
9873347b07SNate Lawson };
9973347b07SNate Lawson static devclass_t cpufreq_dc;
10073347b07SNate Lawson DRIVER_MODULE(cpufreq, cpu, cpufreq_driver, cpufreq_dc, 0, 0);
10173347b07SNate Lawson 
10273347b07SNate Lawson static eventhandler_tag cf_ev_tag;
10373347b07SNate Lawson 
10473347b07SNate Lawson static int
10573347b07SNate Lawson cpufreq_attach(device_t dev)
10673347b07SNate Lawson {
10773347b07SNate Lawson 	struct cpufreq_softc *sc;
10873347b07SNate Lawson 	device_t parent;
10973347b07SNate Lawson 	int numdevs;
11073347b07SNate Lawson 
11173347b07SNate Lawson 	sc = device_get_softc(dev);
11273347b07SNate Lawson 	parent = device_get_parent(dev);
11373347b07SNate Lawson 	sc->dev = dev;
11473347b07SNate Lawson 	sysctl_ctx_init(&sc->sysctl_ctx);
11573347b07SNate Lawson 	TAILQ_INIT(&sc->all_levels);
11673347b07SNate Lawson 	sc->curr_level.total_set.freq = CPUFREQ_VAL_UNKNOWN;
11773347b07SNate Lawson 
11873347b07SNate Lawson 	/*
11973347b07SNate Lawson 	 * Only initialize one set of sysctls for all CPUs.  In the future,
12073347b07SNate Lawson 	 * if multiple CPUs can have different settings, we can move these
12173347b07SNate Lawson 	 * sysctls to be under every CPU instead of just the first one.
12273347b07SNate Lawson 	 */
12373347b07SNate Lawson 	numdevs = devclass_get_count(cpufreq_dc);
12473347b07SNate Lawson 	if (numdevs > 1)
12573347b07SNate Lawson 		return (0);
12673347b07SNate Lawson 
12773347b07SNate Lawson 	SYSCTL_ADD_PROC(&sc->sysctl_ctx,
12873347b07SNate Lawson 	    SYSCTL_CHILDREN(device_get_sysctl_tree(parent)),
12973347b07SNate Lawson 	    OID_AUTO, "freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
13073347b07SNate Lawson 	    cpufreq_curr_sysctl, "I", "Current CPU frequency");
13173347b07SNate Lawson 	SYSCTL_ADD_PROC(&sc->sysctl_ctx,
13273347b07SNate Lawson 	    SYSCTL_CHILDREN(device_get_sysctl_tree(parent)),
13373347b07SNate Lawson 	    OID_AUTO, "freq_levels", CTLTYPE_STRING | CTLFLAG_RD, sc, 0,
13473347b07SNate Lawson 	    cpufreq_levels_sysctl, "A", "CPU frequency levels");
13573347b07SNate Lawson 	cf_ev_tag = EVENTHANDLER_REGISTER(cpufreq_changed, cpufreq_evaluate,
13673347b07SNate Lawson 	    NULL, EVENTHANDLER_PRI_ANY);
13773347b07SNate Lawson 
13873347b07SNate Lawson 	return (0);
13973347b07SNate Lawson }
14073347b07SNate Lawson 
14173347b07SNate Lawson static int
14273347b07SNate Lawson cpufreq_detach(device_t dev)
14373347b07SNate Lawson {
14473347b07SNate Lawson 	struct cpufreq_softc *sc;
14573347b07SNate Lawson 	int numdevs;
14673347b07SNate Lawson 
14773347b07SNate Lawson 	sc = device_get_softc(dev);
14873347b07SNate Lawson 	sysctl_ctx_free(&sc->sysctl_ctx);
14973347b07SNate Lawson 
15073347b07SNate Lawson 	/* Only clean up these resources when the last device is detaching. */
15173347b07SNate Lawson 	numdevs = devclass_get_count(cpufreq_dc);
15273347b07SNate Lawson 	if (numdevs == 1)
15373347b07SNate Lawson 		EVENTHANDLER_DEREGISTER(cpufreq_changed, cf_ev_tag);
15473347b07SNate Lawson 
15573347b07SNate Lawson 	return (0);
15673347b07SNate Lawson }
15773347b07SNate Lawson 
15873347b07SNate Lawson static void
15973347b07SNate Lawson cpufreq_evaluate(void *arg)
16073347b07SNate Lawson {
16173347b07SNate Lawson 	/* TODO: Re-evaluate when notified of changes to drivers. */
16273347b07SNate Lawson }
16373347b07SNate Lawson 
16473347b07SNate Lawson static int
16573347b07SNate Lawson cf_set_method(device_t dev, const struct cf_level *level, int priority)
16673347b07SNate Lawson {
16773347b07SNate Lawson 	struct cpufreq_softc *sc;
16873347b07SNate Lawson 	const struct cf_setting *set;
16973347b07SNate Lawson 	int error;
17073347b07SNate Lawson 
17173347b07SNate Lawson 	sc = device_get_softc(dev);
17273347b07SNate Lawson 
17373347b07SNate Lawson 	/* If already at this level, just return. */
17473347b07SNate Lawson 	if (CPUFREQ_CMP(sc->curr_level.total_set.freq, level->total_set.freq))
17573347b07SNate Lawson 		return (0);
17673347b07SNate Lawson 
17773347b07SNate Lawson 	/* First, set the absolute frequency via its driver. */
17873347b07SNate Lawson 	set = &level->abs_set;
17973347b07SNate Lawson 	if (set->dev) {
18073347b07SNate Lawson 		if (!device_is_attached(set->dev)) {
18173347b07SNate Lawson 			error = ENXIO;
18273347b07SNate Lawson 			goto out;
18373347b07SNate Lawson 		}
18473347b07SNate Lawson 		error = CPUFREQ_DRV_SET(set->dev, set);
18573347b07SNate Lawson 		if (error) {
18673347b07SNate Lawson 			goto out;
18773347b07SNate Lawson 		}
18873347b07SNate Lawson 	}
18973347b07SNate Lawson 
19073347b07SNate Lawson 	/* TODO: Next, set any/all relative frequencies via their drivers. */
19173347b07SNate Lawson 
19273347b07SNate Lawson 	/* Record the current level. */
19373347b07SNate Lawson 	sc->curr_level = *level;
19473347b07SNate Lawson 	sc->priority = priority;
19573347b07SNate Lawson 	error = 0;
19673347b07SNate Lawson 
19773347b07SNate Lawson out:
19873347b07SNate Lawson 	if (error)
19973347b07SNate Lawson 		device_printf(set->dev, "set freq failed, err %d\n", error);
20073347b07SNate Lawson 	return (error);
20173347b07SNate Lawson }
20273347b07SNate Lawson 
20373347b07SNate Lawson static int
20473347b07SNate Lawson cf_get_method(device_t dev, struct cf_level *level)
20573347b07SNate Lawson {
20673347b07SNate Lawson 	struct cpufreq_softc *sc;
20773347b07SNate Lawson 	struct cf_level *levels;
20873347b07SNate Lawson 	struct cf_setting *curr_set, set;
20973347b07SNate Lawson 	struct pcpu *pc;
21073347b07SNate Lawson 	device_t *devs;
21173347b07SNate Lawson 	int count, error, i, numdevs;
21273347b07SNate Lawson 	uint64_t rate;
21373347b07SNate Lawson 
21473347b07SNate Lawson 	sc = device_get_softc(dev);
21573347b07SNate Lawson 	curr_set = &sc->curr_level.total_set;
21673347b07SNate Lawson 	levels = NULL;
21773347b07SNate Lawson 
21873347b07SNate Lawson 	/* If we already know the current frequency, we're done. */
21973347b07SNate Lawson 	if (curr_set->freq != CPUFREQ_VAL_UNKNOWN)
22073347b07SNate Lawson 		goto out;
22173347b07SNate Lawson 
22273347b07SNate Lawson 	/*
22373347b07SNate Lawson 	 * We need to figure out the current level.  Loop through every
22473347b07SNate Lawson 	 * driver, getting the current setting.  Then, attempt to get a best
22573347b07SNate Lawson 	 * match of settings against each level.
22673347b07SNate Lawson 	 */
22773347b07SNate Lawson 	count = CF_MAX_LEVELS;
22873347b07SNate Lawson 	levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT);
22973347b07SNate Lawson 	if (levels == NULL)
23073347b07SNate Lawson 		return (ENOMEM);
23173347b07SNate Lawson 	error = CPUFREQ_LEVELS(sc->dev, levels, &count);
23273347b07SNate Lawson 	if (error)
23373347b07SNate Lawson 		goto out;
23473347b07SNate Lawson 	error = device_get_children(device_get_parent(dev), &devs, &numdevs);
23573347b07SNate Lawson 	if (error)
23673347b07SNate Lawson 		goto out;
23773347b07SNate Lawson 	for (i = 0; i < numdevs && curr_set->freq == CPUFREQ_VAL_UNKNOWN; i++) {
23873347b07SNate Lawson 		if (!device_is_attached(devs[i]))
23973347b07SNate Lawson 			continue;
24073347b07SNate Lawson 		error = CPUFREQ_DRV_GET(devs[i], &set);
24173347b07SNate Lawson 		if (error)
24273347b07SNate Lawson 			continue;
24373347b07SNate Lawson 		for (i = 0; i < count; i++) {
24473347b07SNate Lawson 			if (CPUFREQ_CMP(set.freq, levels[i].abs_set.freq)) {
24573347b07SNate Lawson 				sc->curr_level = levels[i];
24673347b07SNate Lawson 				break;
24773347b07SNate Lawson 			}
24873347b07SNate Lawson 		}
24973347b07SNate Lawson 	}
25073347b07SNate Lawson 	free(devs, M_TEMP);
25173347b07SNate Lawson 	if (curr_set->freq != CPUFREQ_VAL_UNKNOWN)
25273347b07SNate Lawson 		goto out;
25373347b07SNate Lawson 
25473347b07SNate Lawson 	/*
25573347b07SNate Lawson 	 * We couldn't find an exact match, so attempt to estimate and then
25673347b07SNate Lawson 	 * match against a level.
25773347b07SNate Lawson 	 */
25873347b07SNate Lawson 	pc = cpu_get_pcpu(dev);
25973347b07SNate Lawson 	if (pc == NULL) {
26073347b07SNate Lawson 		error = ENXIO;
26173347b07SNate Lawson 		goto out;
26273347b07SNate Lawson 	}
26373347b07SNate Lawson 	cpu_est_clockrate(pc->pc_cpuid, &rate);
26473347b07SNate Lawson 	rate /= 1000000;
26573347b07SNate Lawson 	for (i = 0; i < count; i++) {
26673347b07SNate Lawson 		if (CPUFREQ_CMP(rate, levels[i].total_set.freq)) {
26773347b07SNate Lawson 			sc->curr_level = levels[i];
26873347b07SNate Lawson 			break;
26973347b07SNate Lawson 		}
27073347b07SNate Lawson 	}
27173347b07SNate Lawson 
27273347b07SNate Lawson out:
27373347b07SNate Lawson 	if (levels)
27473347b07SNate Lawson 		free(levels, M_TEMP);
27573347b07SNate Lawson 	*level = sc->curr_level;
27673347b07SNate Lawson 	return (0);
27773347b07SNate Lawson }
27873347b07SNate Lawson 
27973347b07SNate Lawson static int
28073347b07SNate Lawson cf_levels_method(device_t dev, struct cf_level *levels, int *count)
28173347b07SNate Lawson {
28273347b07SNate Lawson 	struct cf_setting_lst rel_sets;
28373347b07SNate Lawson 	struct cpufreq_softc *sc;
28473347b07SNate Lawson 	struct cf_level *lev;
28573347b07SNate Lawson 	struct cf_setting *sets;
28673347b07SNate Lawson 	struct pcpu *pc;
28773347b07SNate Lawson 	device_t *devs;
28873347b07SNate Lawson 	int error, i, numdevs, numlevels, set_count, type;
28973347b07SNate Lawson 	uint64_t rate;
29073347b07SNate Lawson 
29173347b07SNate Lawson 	if (levels == NULL || count == NULL)
29273347b07SNate Lawson 		return (EINVAL);
29373347b07SNate Lawson 
29473347b07SNate Lawson 	TAILQ_INIT(&rel_sets);
29573347b07SNate Lawson 	sc = device_get_softc(dev);
29673347b07SNate Lawson 	error = device_get_children(device_get_parent(dev), &devs, &numdevs);
29773347b07SNate Lawson 	if (error)
29873347b07SNate Lawson 		return (error);
29973347b07SNate Lawson 	sets = malloc(MAX_SETTINGS * sizeof(*sets), M_TEMP, M_NOWAIT);
30073347b07SNate Lawson 	if (sets == NULL) {
30173347b07SNate Lawson 		free(devs, M_TEMP);
30273347b07SNate Lawson 		return (ENOMEM);
30373347b07SNate Lawson 	}
30473347b07SNate Lawson 
30573347b07SNate Lawson 	/* Get settings from all cpufreq drivers. */
30673347b07SNate Lawson 	numlevels = 0;
30773347b07SNate Lawson 	for (i = 0; i < numdevs; i++) {
30873347b07SNate Lawson 		if (!device_is_attached(devs[i]))
30973347b07SNate Lawson 			continue;
31073347b07SNate Lawson 		set_count = MAX_SETTINGS;
31173347b07SNate Lawson 		error = CPUFREQ_DRV_SETTINGS(devs[i], sets, &set_count, &type);
31273347b07SNate Lawson 		if (error || set_count == 0)
31373347b07SNate Lawson 			continue;
31473347b07SNate Lawson 		error = cpufreq_insert_abs(&sc->all_levels, sets, set_count);
31573347b07SNate Lawson 		if (error)
31673347b07SNate Lawson 			goto out;
31773347b07SNate Lawson 		numlevels += set_count;
31873347b07SNate Lawson 	}
31973347b07SNate Lawson 
32073347b07SNate Lawson 	/* If the caller doesn't have enough space, return the actual count. */
32173347b07SNate Lawson 	if (numlevels > *count) {
32273347b07SNate Lawson 		*count = numlevels;
32373347b07SNate Lawson 		error = E2BIG;
32473347b07SNate Lawson 		goto out;
32573347b07SNate Lawson 	}
32673347b07SNate Lawson 
32773347b07SNate Lawson 	/* If there are no absolute levels, create a fake one at 100%. */
32873347b07SNate Lawson 	if (TAILQ_EMPTY(&sc->all_levels)) {
32973347b07SNate Lawson 		bzero(&sets[0], sizeof(*sets));
33073347b07SNate Lawson 		pc = cpu_get_pcpu(dev);
33173347b07SNate Lawson 		if (pc == NULL) {
33273347b07SNate Lawson 			error = ENXIO;
33373347b07SNate Lawson 			goto out;
33473347b07SNate Lawson 		}
33573347b07SNate Lawson 		cpu_est_clockrate(pc->pc_cpuid, &rate);
33673347b07SNate Lawson 		sets[0].freq = rate / 1000000;
33773347b07SNate Lawson 		error = cpufreq_insert_abs(&sc->all_levels, sets, 1);
33873347b07SNate Lawson 		if (error)
33973347b07SNate Lawson 			goto out;
34073347b07SNate Lawson 	}
34173347b07SNate Lawson 
34273347b07SNate Lawson 	/* TODO: Create a combined list of absolute + relative levels. */
34373347b07SNate Lawson 	i = 0;
34473347b07SNate Lawson 	TAILQ_FOREACH(lev, &sc->all_levels, link) {
34573347b07SNate Lawson 		/* For now, just assume total freq equals absolute freq. */
34673347b07SNate Lawson 		lev->total_set = lev->abs_set;
34773347b07SNate Lawson 		lev->total_set.dev = NULL;
34873347b07SNate Lawson 		levels[i] = *lev;
34973347b07SNate Lawson 		i++;
35073347b07SNate Lawson 	}
35173347b07SNate Lawson 	*count = i;
35273347b07SNate Lawson 	error = 0;
35373347b07SNate Lawson 
35473347b07SNate Lawson out:
35573347b07SNate Lawson 	/* Clear all levels since we regenerate them each time. */
35673347b07SNate Lawson 	while ((lev = TAILQ_FIRST(&sc->all_levels)) != NULL) {
35773347b07SNate Lawson 		TAILQ_REMOVE(&sc->all_levels, lev, link);
35873347b07SNate Lawson 		free(lev, M_TEMP);
35973347b07SNate Lawson 	}
36073347b07SNate Lawson 	free(devs, M_TEMP);
36173347b07SNate Lawson 	free(sets, M_TEMP);
36273347b07SNate Lawson 	return (error);
36373347b07SNate Lawson }
36473347b07SNate Lawson 
36573347b07SNate Lawson /*
36673347b07SNate Lawson  * Create levels for an array of absolute settings and insert them in
36773347b07SNate Lawson  * sorted order in the specified list.
36873347b07SNate Lawson  */
36973347b07SNate Lawson static int
37073347b07SNate Lawson cpufreq_insert_abs(struct cf_level_lst *list, struct cf_setting *sets,
37173347b07SNate Lawson     int count)
37273347b07SNate Lawson {
37373347b07SNate Lawson 	struct cf_level *level, *search;
37473347b07SNate Lawson 	int i;
37573347b07SNate Lawson 
37673347b07SNate Lawson 	for (i = 0; i < count; i++) {
37773347b07SNate Lawson 		level = malloc(sizeof(*level), M_TEMP, M_NOWAIT | M_ZERO);
37873347b07SNate Lawson 		if (level == NULL)
37973347b07SNate Lawson 			return (ENOMEM);
38073347b07SNate Lawson 		level->abs_set = sets[i];
38173347b07SNate Lawson 
38273347b07SNate Lawson 		if (TAILQ_EMPTY(list)) {
38373347b07SNate Lawson 			TAILQ_INSERT_HEAD(list, level, link);
38473347b07SNate Lawson 			continue;
38573347b07SNate Lawson 		}
38673347b07SNate Lawson 
38773347b07SNate Lawson 		TAILQ_FOREACH_REVERSE(search, list, cf_level_lst, link) {
38873347b07SNate Lawson 			if (sets[i].freq <= search->abs_set.freq) {
38973347b07SNate Lawson 				TAILQ_INSERT_AFTER(list, search, level, link);
39073347b07SNate Lawson 				break;
39173347b07SNate Lawson 			}
39273347b07SNate Lawson 		}
39373347b07SNate Lawson 	}
39473347b07SNate Lawson 	return (0);
39573347b07SNate Lawson }
39673347b07SNate Lawson 
39773347b07SNate Lawson static int
39873347b07SNate Lawson cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS)
39973347b07SNate Lawson {
40073347b07SNate Lawson 	struct cpufreq_softc *sc;
40173347b07SNate Lawson 	struct cf_level *levels;
40273347b07SNate Lawson 	int count, error, freq, i;
40373347b07SNate Lawson 
40473347b07SNate Lawson 	sc = oidp->oid_arg1;
40573347b07SNate Lawson 	count = CF_MAX_LEVELS;
40673347b07SNate Lawson 	levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT);
40773347b07SNate Lawson 	if (levels == NULL)
40873347b07SNate Lawson 		return (ENOMEM);
40973347b07SNate Lawson 
41073347b07SNate Lawson 	error = CPUFREQ_GET(sc->dev, &levels[0]);
41173347b07SNate Lawson 	if (error)
41273347b07SNate Lawson 		goto out;
41373347b07SNate Lawson 	freq = levels[0].total_set.freq;
41473347b07SNate Lawson 	error = sysctl_handle_int(oidp, &freq, 0, req);
41573347b07SNate Lawson 	if (error != 0 || req->newptr == NULL)
41673347b07SNate Lawson 		goto out;
41773347b07SNate Lawson 
41873347b07SNate Lawson 	error = CPUFREQ_LEVELS(sc->dev, levels, &count);
41973347b07SNate Lawson 	if (error)
42073347b07SNate Lawson 		goto out;
42173347b07SNate Lawson 	for (i = 0; i < count; i++) {
42273347b07SNate Lawson 		if (CPUFREQ_CMP(levels[i].total_set.freq, freq)) {
42373347b07SNate Lawson 			error = CPUFREQ_SET(sc->dev, &levels[i],
42473347b07SNate Lawson 			    CPUFREQ_PRIO_USER);
42573347b07SNate Lawson 			break;
42673347b07SNate Lawson 		}
42773347b07SNate Lawson 	}
42873347b07SNate Lawson 	if (i == count)
42973347b07SNate Lawson 		error = EINVAL;
43073347b07SNate Lawson 
43173347b07SNate Lawson out:
43273347b07SNate Lawson 	if (levels)
43373347b07SNate Lawson 		free(levels, M_TEMP);
43473347b07SNate Lawson 	return (error);
43573347b07SNate Lawson }
43673347b07SNate Lawson 
43773347b07SNate Lawson static int
43873347b07SNate Lawson cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS)
43973347b07SNate Lawson {
44073347b07SNate Lawson 	struct cpufreq_softc *sc;
44173347b07SNate Lawson 	struct cf_level *levels;
44273347b07SNate Lawson 	struct cf_setting *set;
44373347b07SNate Lawson 	struct sbuf sb;
44473347b07SNate Lawson 	int count, error, i;
44573347b07SNate Lawson 
44673347b07SNate Lawson 	sc = oidp->oid_arg1;
44773347b07SNate Lawson 	sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND);
44873347b07SNate Lawson 
44973347b07SNate Lawson 	/* Get settings from the device and generate the output string. */
45073347b07SNate Lawson 	count = CF_MAX_LEVELS;
45173347b07SNate Lawson 	levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT);
45273347b07SNate Lawson 	if (levels == NULL)
45373347b07SNate Lawson 		return (ENOMEM);
45473347b07SNate Lawson 	error = CPUFREQ_LEVELS(sc->dev, levels, &count);
45573347b07SNate Lawson 	if (error)
45673347b07SNate Lawson 		goto out;
45773347b07SNate Lawson 	if (count) {
45873347b07SNate Lawson 		for (i = 0; i < count; i++) {
45973347b07SNate Lawson 			set = &levels[i].total_set;
46073347b07SNate Lawson 			sbuf_printf(&sb, "%d/%d ", set->freq, set->power);
46173347b07SNate Lawson 		}
46273347b07SNate Lawson 	} else
46373347b07SNate Lawson 		sbuf_cpy(&sb, "0");
46473347b07SNate Lawson 	sbuf_trim(&sb);
46573347b07SNate Lawson 	sbuf_finish(&sb);
46673347b07SNate Lawson 	error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req);
46773347b07SNate Lawson 
46873347b07SNate Lawson out:
46973347b07SNate Lawson 	free(levels, M_TEMP);
47073347b07SNate Lawson 	sbuf_delete(&sb);
47173347b07SNate Lawson 	return (error);
47273347b07SNate Lawson }
47373347b07SNate Lawson 
47473347b07SNate Lawson int
47573347b07SNate Lawson cpufreq_register(device_t dev)
47673347b07SNate Lawson {
47773347b07SNate Lawson 	device_t cf_dev, cpu_dev;
47873347b07SNate Lawson 
47973347b07SNate Lawson 	/*
48073347b07SNate Lawson 	 * Only add one cpufreq device (on cpu0) for all control.  Once
48173347b07SNate Lawson 	 * independent multi-cpu control appears, we can assign one cpufreq
48273347b07SNate Lawson 	 * device per cpu.
48373347b07SNate Lawson 	 */
48473347b07SNate Lawson 	cf_dev = devclass_get_device(cpufreq_dc, 0);
48573347b07SNate Lawson 	if (cf_dev) {
48673347b07SNate Lawson 		device_printf(dev,
48773347b07SNate Lawson 		    "warning: only one cpufreq device at a time supported\n");
48873347b07SNate Lawson 		return (0);
48973347b07SNate Lawson 	}
49073347b07SNate Lawson 
49173347b07SNate Lawson 	/* Add the child device and sysctls. */
49273347b07SNate Lawson 	cpu_dev = devclass_get_device(devclass_find("cpu"), 0);
49373347b07SNate Lawson 	cf_dev = BUS_ADD_CHILD(cpu_dev, 0, "cpufreq", 0);
49473347b07SNate Lawson 	if (cf_dev == NULL)
49573347b07SNate Lawson 		return (ENOMEM);
49673347b07SNate Lawson 	device_quiet(cf_dev);
49773347b07SNate Lawson 
49873347b07SNate Lawson 	return (device_probe_and_attach(cf_dev));
49973347b07SNate Lawson }
50073347b07SNate Lawson 
50173347b07SNate Lawson int
50273347b07SNate Lawson cpufreq_unregister(device_t dev)
50373347b07SNate Lawson {
50473347b07SNate Lawson 	device_t cf_dev, *devs;
50573347b07SNate Lawson 	int cfcount, count, devcount, error, i, type;
50673347b07SNate Lawson 	struct cf_setting set;
50773347b07SNate Lawson 
50873347b07SNate Lawson 	/*
50973347b07SNate Lawson 	 * If this is the last cpufreq child device, remove the control
51073347b07SNate Lawson 	 * device as well.  We identify cpufreq children by calling a method
51173347b07SNate Lawson 	 * they support.
51273347b07SNate Lawson 	 */
51373347b07SNate Lawson 	error = device_get_children(device_get_parent(dev), &devs, &devcount);
51473347b07SNate Lawson 	if (error)
51573347b07SNate Lawson 		return (error);
51673347b07SNate Lawson 	cf_dev = devclass_get_device(cpufreq_dc, 0);
51773347b07SNate Lawson 	KASSERT(cf_dev != NULL, ("unregister with no cpufreq dev"));
51873347b07SNate Lawson 	cfcount = 0;
51973347b07SNate Lawson 	for (i = 0; i < devcount; i++) {
52073347b07SNate Lawson 		if (!device_is_attached(devs[i]))
52173347b07SNate Lawson 			continue;
52273347b07SNate Lawson 		count = 1;
52373347b07SNate Lawson 		if (CPUFREQ_DRV_SETTINGS(devs[i], &set, &count, &type) == 0)
52473347b07SNate Lawson 			cfcount++;
52573347b07SNate Lawson 	}
52673347b07SNate Lawson 	if (cfcount <= 1) {
52773347b07SNate Lawson 		device_delete_child(device_get_parent(cf_dev), cf_dev);
52873347b07SNate Lawson 	}
52973347b07SNate Lawson 	free(devs, M_TEMP);
53073347b07SNate Lawson 
53173347b07SNate Lawson 	return (0);
53273347b07SNate Lawson }
533