xref: /freebsd/sys/kern/kern_cpu.c (revision 1196826a)
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>
430325089dSNate Lawson #include <sys/timetc.h>
4473347b07SNate Lawson 
4573347b07SNate Lawson #include "cpufreq_if.h"
4673347b07SNate Lawson 
4773347b07SNate Lawson /*
4873347b07SNate Lawson  * Common CPU frequency glue code.  Drivers for specific hardware can
4973347b07SNate Lawson  * attach this interface to allow users to get/set the CPU frequency.
5073347b07SNate Lawson  */
5173347b07SNate Lawson 
5273347b07SNate Lawson /*
5373347b07SNate Lawson  * Number of levels we can handle.  Levels are synthesized from settings
5473347b07SNate Lawson  * so for N settings there may be N^2 levels.
5573347b07SNate Lawson  */
5673347b07SNate Lawson #define CF_MAX_LEVELS	32
5773347b07SNate Lawson 
5873347b07SNate Lawson struct cpufreq_softc {
5973347b07SNate Lawson 	struct cf_level			curr_level;
605f0afa04SNate Lawson 	int				curr_priority;
615f0afa04SNate Lawson 	struct cf_level			saved_level;
625f0afa04SNate Lawson 	int				saved_priority;
6373347b07SNate Lawson 	struct cf_level_lst		all_levels;
645f0afa04SNate Lawson 	int				all_count;
6573347b07SNate Lawson 	device_t			dev;
6673347b07SNate Lawson 	struct sysctl_ctx_list		sysctl_ctx;
6773347b07SNate Lawson };
6873347b07SNate Lawson 
6973347b07SNate Lawson struct cf_setting_array {
7073347b07SNate Lawson 	struct cf_setting		sets[MAX_SETTINGS];
7173347b07SNate Lawson 	int				count;
7273347b07SNate Lawson 	TAILQ_ENTRY(cf_setting_array)	link;
7373347b07SNate Lawson };
7473347b07SNate Lawson 
7573347b07SNate Lawson TAILQ_HEAD(cf_setting_lst, cf_setting_array);
7673347b07SNate Lawson 
7773347b07SNate Lawson static int	cpufreq_attach(device_t dev);
7873347b07SNate Lawson static int	cpufreq_detach(device_t dev);
7973347b07SNate Lawson static void	cpufreq_evaluate(void *arg);
8073347b07SNate Lawson static int	cf_set_method(device_t dev, const struct cf_level *level,
8173347b07SNate Lawson 		    int priority);
8273347b07SNate Lawson static int	cf_get_method(device_t dev, struct cf_level *level);
8373347b07SNate Lawson static int	cf_levels_method(device_t dev, struct cf_level *levels,
8473347b07SNate Lawson 		    int *count);
8588c9b54cSNate Lawson static int	cpufreq_insert_abs(struct cpufreq_softc *sc,
8673347b07SNate Lawson 		    struct cf_setting *sets, int count);
8788c9b54cSNate Lawson static int	cpufreq_expand_set(struct cpufreq_softc *sc,
8888c9b54cSNate Lawson 		    struct cf_setting_array *set_arr);
8988c9b54cSNate Lawson static struct cf_level *cpufreq_dup_set(struct cpufreq_softc *sc,
9088c9b54cSNate Lawson 		    struct cf_level *dup, struct cf_setting *set);
9173347b07SNate Lawson static int	cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS);
9273347b07SNate Lawson static int	cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS);
9373347b07SNate Lawson 
9473347b07SNate Lawson static device_method_t cpufreq_methods[] = {
9573347b07SNate Lawson 	DEVMETHOD(device_probe,		bus_generic_probe),
9673347b07SNate Lawson 	DEVMETHOD(device_attach,	cpufreq_attach),
9773347b07SNate Lawson 	DEVMETHOD(device_detach,	cpufreq_detach),
9873347b07SNate Lawson 
9973347b07SNate Lawson         DEVMETHOD(cpufreq_set,		cf_set_method),
10073347b07SNate Lawson         DEVMETHOD(cpufreq_get,		cf_get_method),
10173347b07SNate Lawson         DEVMETHOD(cpufreq_levels,	cf_levels_method),
10273347b07SNate Lawson 	{0, 0}
10373347b07SNate Lawson };
10473347b07SNate Lawson static driver_t cpufreq_driver = {
10573347b07SNate Lawson 	"cpufreq", cpufreq_methods, sizeof(struct cpufreq_softc)
10673347b07SNate Lawson };
10773347b07SNate Lawson static devclass_t cpufreq_dc;
10873347b07SNate Lawson DRIVER_MODULE(cpufreq, cpu, cpufreq_driver, cpufreq_dc, 0, 0);
10973347b07SNate Lawson 
11073347b07SNate Lawson static eventhandler_tag cf_ev_tag;
11173347b07SNate Lawson 
11273347b07SNate Lawson static int
11373347b07SNate Lawson cpufreq_attach(device_t dev)
11473347b07SNate Lawson {
11573347b07SNate Lawson 	struct cpufreq_softc *sc;
11673347b07SNate Lawson 	device_t parent;
11773347b07SNate Lawson 	int numdevs;
11873347b07SNate Lawson 
11973347b07SNate Lawson 	sc = device_get_softc(dev);
12073347b07SNate Lawson 	parent = device_get_parent(dev);
12173347b07SNate Lawson 	sc->dev = dev;
12273347b07SNate Lawson 	sysctl_ctx_init(&sc->sysctl_ctx);
12373347b07SNate Lawson 	TAILQ_INIT(&sc->all_levels);
12473347b07SNate Lawson 	sc->curr_level.total_set.freq = CPUFREQ_VAL_UNKNOWN;
1255f0afa04SNate Lawson 	sc->saved_level.total_set.freq = CPUFREQ_VAL_UNKNOWN;
12673347b07SNate Lawson 
12773347b07SNate Lawson 	/*
12873347b07SNate Lawson 	 * Only initialize one set of sysctls for all CPUs.  In the future,
12973347b07SNate Lawson 	 * if multiple CPUs can have different settings, we can move these
13073347b07SNate Lawson 	 * sysctls to be under every CPU instead of just the first one.
13173347b07SNate Lawson 	 */
13273347b07SNate Lawson 	numdevs = devclass_get_count(cpufreq_dc);
13373347b07SNate Lawson 	if (numdevs > 1)
13473347b07SNate Lawson 		return (0);
13573347b07SNate Lawson 
13673347b07SNate Lawson 	SYSCTL_ADD_PROC(&sc->sysctl_ctx,
13773347b07SNate Lawson 	    SYSCTL_CHILDREN(device_get_sysctl_tree(parent)),
13873347b07SNate Lawson 	    OID_AUTO, "freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
13973347b07SNate Lawson 	    cpufreq_curr_sysctl, "I", "Current CPU frequency");
14073347b07SNate Lawson 	SYSCTL_ADD_PROC(&sc->sysctl_ctx,
14173347b07SNate Lawson 	    SYSCTL_CHILDREN(device_get_sysctl_tree(parent)),
14273347b07SNate Lawson 	    OID_AUTO, "freq_levels", CTLTYPE_STRING | CTLFLAG_RD, sc, 0,
14373347b07SNate Lawson 	    cpufreq_levels_sysctl, "A", "CPU frequency levels");
14473347b07SNate Lawson 	cf_ev_tag = EVENTHANDLER_REGISTER(cpufreq_changed, cpufreq_evaluate,
14573347b07SNate Lawson 	    NULL, EVENTHANDLER_PRI_ANY);
14673347b07SNate Lawson 
14773347b07SNate Lawson 	return (0);
14873347b07SNate Lawson }
14973347b07SNate Lawson 
15073347b07SNate Lawson static int
15173347b07SNate Lawson cpufreq_detach(device_t dev)
15273347b07SNate Lawson {
15373347b07SNate Lawson 	struct cpufreq_softc *sc;
15473347b07SNate Lawson 	int numdevs;
15573347b07SNate Lawson 
15673347b07SNate Lawson 	sc = device_get_softc(dev);
15773347b07SNate Lawson 	sysctl_ctx_free(&sc->sysctl_ctx);
15873347b07SNate Lawson 
15973347b07SNate Lawson 	/* Only clean up these resources when the last device is detaching. */
16073347b07SNate Lawson 	numdevs = devclass_get_count(cpufreq_dc);
16173347b07SNate Lawson 	if (numdevs == 1)
16273347b07SNate Lawson 		EVENTHANDLER_DEREGISTER(cpufreq_changed, cf_ev_tag);
16373347b07SNate Lawson 
16473347b07SNate Lawson 	return (0);
16573347b07SNate Lawson }
16673347b07SNate Lawson 
16773347b07SNate Lawson static void
16873347b07SNate Lawson cpufreq_evaluate(void *arg)
16973347b07SNate Lawson {
17073347b07SNate Lawson 	/* TODO: Re-evaluate when notified of changes to drivers. */
17173347b07SNate Lawson }
17273347b07SNate Lawson 
17373347b07SNate Lawson static int
17473347b07SNate Lawson cf_set_method(device_t dev, const struct cf_level *level, int priority)
17573347b07SNate Lawson {
17673347b07SNate Lawson 	struct cpufreq_softc *sc;
17773347b07SNate Lawson 	const struct cf_setting *set;
1780325089dSNate Lawson 	struct pcpu *pc;
1790325089dSNate Lawson 	int cpu_id, error, i;
18073347b07SNate Lawson 
18173347b07SNate Lawson 	sc = device_get_softc(dev);
18273347b07SNate Lawson 
1830325089dSNate Lawson 	/*
1840325089dSNate Lawson 	 * Check that the TSC isn't being used as a timecounter.
1850325089dSNate Lawson 	 * If it is, then return EBUSY and refuse to change the
1860325089dSNate Lawson 	 * clock speed.
1870325089dSNate Lawson 	 */
1880325089dSNate Lawson 	if (strcmp(timecounter->tc_name, "TSC") == 0)
1890325089dSNate Lawson 		return (EBUSY);
1900325089dSNate Lawson 
1915f0afa04SNate Lawson 	/*
1925f0afa04SNate Lawson 	 * If the caller didn't specify a level and one is saved, prepare to
1935f0afa04SNate Lawson 	 * restore the saved level.  If none has been saved, return an error.
1945f0afa04SNate Lawson 	 * If they did specify one, but the requested level has a lower
1955f0afa04SNate Lawson 	 * priority, don't allow the new level right now.
1965f0afa04SNate Lawson 	 */
1975f0afa04SNate Lawson 	if (level == NULL) {
1985f0afa04SNate Lawson 		if (sc->saved_level.total_set.freq != CPUFREQ_VAL_UNKNOWN) {
1995f0afa04SNate Lawson 			level = &sc->saved_level;
2005f0afa04SNate Lawson 			priority = sc->saved_priority;
2015f0afa04SNate Lawson 		} else
2025f0afa04SNate Lawson 			return (ENXIO);
2035f0afa04SNate Lawson 	} else if (priority < sc->curr_priority)
2045f0afa04SNate Lawson 		return (EPERM);
2055f0afa04SNate Lawson 
20673347b07SNate Lawson 	/* If already at this level, just return. */
20773347b07SNate Lawson 	if (CPUFREQ_CMP(sc->curr_level.total_set.freq, level->total_set.freq))
20873347b07SNate Lawson 		return (0);
20973347b07SNate Lawson 
21073347b07SNate Lawson 	/* First, set the absolute frequency via its driver. */
21173347b07SNate Lawson 	set = &level->abs_set;
21273347b07SNate Lawson 	if (set->dev) {
21373347b07SNate Lawson 		if (!device_is_attached(set->dev)) {
21473347b07SNate Lawson 			error = ENXIO;
21573347b07SNate Lawson 			goto out;
21673347b07SNate Lawson 		}
2171196826aSNate Lawson 
2181196826aSNate Lawson 		/* Bind to the target CPU before switching, if necessary. */
2191196826aSNate Lawson 		cpu_id = PCPU_GET(cpuid);
2201196826aSNate Lawson 		pc = cpu_get_pcpu(set->dev);
2211196826aSNate Lawson 		if (cpu_id != pc->pc_cpuid) {
2221196826aSNate Lawson 			mtx_lock_spin(&sched_lock);
2231196826aSNate Lawson 			sched_bind(curthread, pc->pc_cpuid);
2241196826aSNate Lawson 			mtx_unlock_spin(&sched_lock);
2251196826aSNate Lawson 		}
22673347b07SNate Lawson 		error = CPUFREQ_DRV_SET(set->dev, set);
2271196826aSNate Lawson 		if (cpu_id != pc->pc_cpuid) {
2281196826aSNate Lawson 			mtx_lock_spin(&sched_lock);
2291196826aSNate Lawson 			sched_unbind(curthread);
2301196826aSNate Lawson 			mtx_unlock_spin(&sched_lock);
2311196826aSNate Lawson 		}
23273347b07SNate Lawson 		if (error) {
23373347b07SNate Lawson 			goto out;
23473347b07SNate Lawson 		}
23573347b07SNate Lawson 	}
23673347b07SNate Lawson 
23788c9b54cSNate Lawson 	/* Next, set any/all relative frequencies via their drivers. */
23888c9b54cSNate Lawson 	for (i = 0; i < level->rel_count; i++) {
23988c9b54cSNate Lawson 		set = &level->rel_set[i];
24088c9b54cSNate Lawson 		if (!device_is_attached(set->dev)) {
24188c9b54cSNate Lawson 			error = ENXIO;
24288c9b54cSNate Lawson 			goto out;
24388c9b54cSNate Lawson 		}
2441196826aSNate Lawson 
2451196826aSNate Lawson 		/* Bind to the target CPU before switching, if necessary. */
2461196826aSNate Lawson 		cpu_id = PCPU_GET(cpuid);
2471196826aSNate Lawson 		pc = cpu_get_pcpu(set->dev);
2481196826aSNate Lawson 		if (cpu_id != pc->pc_cpuid) {
2491196826aSNate Lawson 			mtx_lock_spin(&sched_lock);
2501196826aSNate Lawson 			sched_bind(curthread, pc->pc_cpuid);
2511196826aSNate Lawson 			mtx_unlock_spin(&sched_lock);
2521196826aSNate Lawson 		}
25388c9b54cSNate Lawson 		error = CPUFREQ_DRV_SET(set->dev, set);
2541196826aSNate Lawson 		if (cpu_id != pc->pc_cpuid) {
2551196826aSNate Lawson 			mtx_lock_spin(&sched_lock);
2561196826aSNate Lawson 			sched_unbind(curthread);
2571196826aSNate Lawson 			mtx_unlock_spin(&sched_lock);
2581196826aSNate Lawson 		}
25988c9b54cSNate Lawson 		if (error) {
26088c9b54cSNate Lawson 			/* XXX Back out any successful setting? */
26188c9b54cSNate Lawson 			goto out;
26288c9b54cSNate Lawson 		}
26388c9b54cSNate Lawson 	}
26473347b07SNate Lawson 
2655f0afa04SNate Lawson 	/* If we were restoring a saved state, reset it to "unused". */
2665f0afa04SNate Lawson 	if (level == &sc->saved_level) {
2675f0afa04SNate Lawson 		sc->saved_level.total_set.freq = CPUFREQ_VAL_UNKNOWN;
2685f0afa04SNate Lawson 		sc->saved_priority = 0;
2695f0afa04SNate Lawson 	}
2705f0afa04SNate Lawson 
2715f0afa04SNate Lawson 	/*
2725f0afa04SNate Lawson 	 * Before recording the current level, check if we're going to a
2735f0afa04SNate Lawson 	 * higher priority and have not saved a level yet.  If so, save the
2745f0afa04SNate Lawson 	 * previous level and priority.
2755f0afa04SNate Lawson 	 */
2765f0afa04SNate Lawson 	if (sc->curr_level.total_set.freq != CPUFREQ_VAL_UNKNOWN &&
2775f0afa04SNate Lawson 	    sc->saved_level.total_set.freq == CPUFREQ_VAL_UNKNOWN &&
2785f0afa04SNate Lawson 	    priority > sc->curr_priority) {
2795f0afa04SNate Lawson 		sc->saved_level = sc->curr_level;
2805f0afa04SNate Lawson 		sc->saved_priority = sc->curr_priority;
2815f0afa04SNate Lawson 	}
28273347b07SNate Lawson 	sc->curr_level = *level;
2835f0afa04SNate Lawson 	sc->curr_priority = priority;
28473347b07SNate Lawson 	error = 0;
28573347b07SNate Lawson 
28673347b07SNate Lawson out:
28773347b07SNate Lawson 	if (error)
28873347b07SNate Lawson 		device_printf(set->dev, "set freq failed, err %d\n", error);
28973347b07SNate Lawson 	return (error);
29073347b07SNate Lawson }
29173347b07SNate Lawson 
29273347b07SNate Lawson static int
29373347b07SNate Lawson cf_get_method(device_t dev, struct cf_level *level)
29473347b07SNate Lawson {
29573347b07SNate Lawson 	struct cpufreq_softc *sc;
29673347b07SNate Lawson 	struct cf_level *levels;
29773347b07SNate Lawson 	struct cf_setting *curr_set, set;
29873347b07SNate Lawson 	struct pcpu *pc;
29973347b07SNate Lawson 	device_t *devs;
30073347b07SNate Lawson 	int count, error, i, numdevs;
30173347b07SNate Lawson 	uint64_t rate;
30273347b07SNate Lawson 
30373347b07SNate Lawson 	sc = device_get_softc(dev);
30473347b07SNate Lawson 	curr_set = &sc->curr_level.total_set;
30573347b07SNate Lawson 	levels = NULL;
30673347b07SNate Lawson 
30773347b07SNate Lawson 	/* If we already know the current frequency, we're done. */
30873347b07SNate Lawson 	if (curr_set->freq != CPUFREQ_VAL_UNKNOWN)
30973347b07SNate Lawson 		goto out;
31073347b07SNate Lawson 
31173347b07SNate Lawson 	/*
31273347b07SNate Lawson 	 * We need to figure out the current level.  Loop through every
31373347b07SNate Lawson 	 * driver, getting the current setting.  Then, attempt to get a best
31473347b07SNate Lawson 	 * match of settings against each level.
31573347b07SNate Lawson 	 */
31673347b07SNate Lawson 	count = CF_MAX_LEVELS;
31773347b07SNate Lawson 	levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT);
31873347b07SNate Lawson 	if (levels == NULL)
31973347b07SNate Lawson 		return (ENOMEM);
32073347b07SNate Lawson 	error = CPUFREQ_LEVELS(sc->dev, levels, &count);
32173347b07SNate Lawson 	if (error)
32273347b07SNate Lawson 		goto out;
32373347b07SNate Lawson 	error = device_get_children(device_get_parent(dev), &devs, &numdevs);
32473347b07SNate Lawson 	if (error)
32573347b07SNate Lawson 		goto out;
32673347b07SNate Lawson 	for (i = 0; i < numdevs && curr_set->freq == CPUFREQ_VAL_UNKNOWN; i++) {
32773347b07SNate Lawson 		if (!device_is_attached(devs[i]))
32873347b07SNate Lawson 			continue;
32973347b07SNate Lawson 		error = CPUFREQ_DRV_GET(devs[i], &set);
33073347b07SNate Lawson 		if (error)
33173347b07SNate Lawson 			continue;
33273347b07SNate Lawson 		for (i = 0; i < count; i++) {
33388c9b54cSNate Lawson 			if (CPUFREQ_CMP(set.freq, levels[i].total_set.freq)) {
33473347b07SNate Lawson 				sc->curr_level = levels[i];
33573347b07SNate Lawson 				break;
33673347b07SNate Lawson 			}
33773347b07SNate Lawson 		}
33873347b07SNate Lawson 	}
33973347b07SNate Lawson 	free(devs, M_TEMP);
34073347b07SNate Lawson 	if (curr_set->freq != CPUFREQ_VAL_UNKNOWN)
34173347b07SNate Lawson 		goto out;
34273347b07SNate Lawson 
34373347b07SNate Lawson 	/*
34473347b07SNate Lawson 	 * We couldn't find an exact match, so attempt to estimate and then
34573347b07SNate Lawson 	 * match against a level.
34673347b07SNate Lawson 	 */
34773347b07SNate Lawson 	pc = cpu_get_pcpu(dev);
34873347b07SNate Lawson 	if (pc == NULL) {
34973347b07SNate Lawson 		error = ENXIO;
35073347b07SNate Lawson 		goto out;
35173347b07SNate Lawson 	}
35273347b07SNate Lawson 	cpu_est_clockrate(pc->pc_cpuid, &rate);
35373347b07SNate Lawson 	rate /= 1000000;
35473347b07SNate Lawson 	for (i = 0; i < count; i++) {
35573347b07SNate Lawson 		if (CPUFREQ_CMP(rate, levels[i].total_set.freq)) {
35673347b07SNate Lawson 			sc->curr_level = levels[i];
35773347b07SNate Lawson 			break;
35873347b07SNate Lawson 		}
35973347b07SNate Lawson 	}
36073347b07SNate Lawson 
36173347b07SNate Lawson out:
36273347b07SNate Lawson 	if (levels)
36373347b07SNate Lawson 		free(levels, M_TEMP);
36473347b07SNate Lawson 	*level = sc->curr_level;
36573347b07SNate Lawson 	return (0);
36673347b07SNate Lawson }
36773347b07SNate Lawson 
36873347b07SNate Lawson static int
36973347b07SNate Lawson cf_levels_method(device_t dev, struct cf_level *levels, int *count)
37073347b07SNate Lawson {
37188c9b54cSNate Lawson 	struct cf_setting_array *set_arr;
37273347b07SNate Lawson 	struct cf_setting_lst rel_sets;
37373347b07SNate Lawson 	struct cpufreq_softc *sc;
37473347b07SNate Lawson 	struct cf_level *lev;
37573347b07SNate Lawson 	struct cf_setting *sets;
37673347b07SNate Lawson 	struct pcpu *pc;
37773347b07SNate Lawson 	device_t *devs;
37888c9b54cSNate Lawson 	int error, i, numdevs, set_count, type;
37973347b07SNate Lawson 	uint64_t rate;
38073347b07SNate Lawson 
38173347b07SNate Lawson 	if (levels == NULL || count == NULL)
38273347b07SNate Lawson 		return (EINVAL);
38373347b07SNate Lawson 
38473347b07SNate Lawson 	TAILQ_INIT(&rel_sets);
38573347b07SNate Lawson 	sc = device_get_softc(dev);
38673347b07SNate Lawson 	error = device_get_children(device_get_parent(dev), &devs, &numdevs);
38773347b07SNate Lawson 	if (error)
38873347b07SNate Lawson 		return (error);
38973347b07SNate Lawson 	sets = malloc(MAX_SETTINGS * sizeof(*sets), M_TEMP, M_NOWAIT);
39073347b07SNate Lawson 	if (sets == NULL) {
39173347b07SNate Lawson 		free(devs, M_TEMP);
39273347b07SNate Lawson 		return (ENOMEM);
39373347b07SNate Lawson 	}
39473347b07SNate Lawson 
39573347b07SNate Lawson 	/* Get settings from all cpufreq drivers. */
39673347b07SNate Lawson 	for (i = 0; i < numdevs; i++) {
397e22cd41cSNate Lawson 		/* Skip devices that aren't ready. */
39873347b07SNate Lawson 		if (!device_is_attached(devs[i]))
39973347b07SNate Lawson 			continue;
400e22cd41cSNate Lawson 
401e22cd41cSNate Lawson 		/*
402e22cd41cSNate Lawson 		 * Get settings, skipping drivers that offer no settings or
403e22cd41cSNate Lawson 		 * provide settings for informational purposes only.
404e22cd41cSNate Lawson 		 */
40573347b07SNate Lawson 		set_count = MAX_SETTINGS;
40673347b07SNate Lawson 		error = CPUFREQ_DRV_SETTINGS(devs[i], sets, &set_count, &type);
407e22cd41cSNate Lawson 		if (error || set_count == 0 || (type & CPUFREQ_FLAG_INFO_ONLY))
40873347b07SNate Lawson 			continue;
40973347b07SNate Lawson 
410e22cd41cSNate Lawson 		/* Add the settings to our absolute/relative lists. */
4110325089dSNate Lawson 		switch (type & CPUFREQ_TYPE_MASK) {
41288c9b54cSNate Lawson 		case CPUFREQ_TYPE_ABSOLUTE:
41388c9b54cSNate Lawson 			error = cpufreq_insert_abs(sc, sets, set_count);
41488c9b54cSNate Lawson 			break;
41588c9b54cSNate Lawson 		case CPUFREQ_TYPE_RELATIVE:
41688c9b54cSNate Lawson 			set_arr = malloc(sizeof(*set_arr), M_TEMP, M_NOWAIT);
41788c9b54cSNate Lawson 			if (set_arr == NULL) {
41888c9b54cSNate Lawson 				error = ENOMEM;
41988c9b54cSNate Lawson 				goto out;
42088c9b54cSNate Lawson 			}
42188c9b54cSNate Lawson 			bcopy(sets, set_arr->sets, set_count * sizeof(*sets));
42288c9b54cSNate Lawson 			set_arr->count = set_count;
42388c9b54cSNate Lawson 			TAILQ_INSERT_TAIL(&rel_sets, set_arr, link);
42488c9b54cSNate Lawson 			break;
42588c9b54cSNate Lawson 		default:
42688c9b54cSNate Lawson 			error = EINVAL;
42788c9b54cSNate Lawson 			break;
42888c9b54cSNate Lawson 		}
42988c9b54cSNate Lawson 		if (error)
43073347b07SNate Lawson 			goto out;
43173347b07SNate Lawson 	}
43273347b07SNate Lawson 
43373347b07SNate Lawson 	/* If there are no absolute levels, create a fake one at 100%. */
43473347b07SNate Lawson 	if (TAILQ_EMPTY(&sc->all_levels)) {
43573347b07SNate Lawson 		bzero(&sets[0], sizeof(*sets));
43673347b07SNate Lawson 		pc = cpu_get_pcpu(dev);
43773347b07SNate Lawson 		if (pc == NULL) {
43873347b07SNate Lawson 			error = ENXIO;
43973347b07SNate Lawson 			goto out;
44073347b07SNate Lawson 		}
44173347b07SNate Lawson 		cpu_est_clockrate(pc->pc_cpuid, &rate);
44273347b07SNate Lawson 		sets[0].freq = rate / 1000000;
44388c9b54cSNate Lawson 		error = cpufreq_insert_abs(sc, sets, 1);
44473347b07SNate Lawson 		if (error)
44573347b07SNate Lawson 			goto out;
44673347b07SNate Lawson 	}
44773347b07SNate Lawson 
44888c9b54cSNate Lawson 	/* Create a combined list of absolute + relative levels. */
44988c9b54cSNate Lawson 	TAILQ_FOREACH(set_arr, &rel_sets, link)
45088c9b54cSNate Lawson 		cpufreq_expand_set(sc, set_arr);
45188c9b54cSNate Lawson 
45288c9b54cSNate Lawson 	/* If the caller doesn't have enough space, return the actual count. */
45388c9b54cSNate Lawson 	if (sc->all_count > *count) {
45488c9b54cSNate Lawson 		*count = sc->all_count;
45588c9b54cSNate Lawson 		error = E2BIG;
45688c9b54cSNate Lawson 		goto out;
45788c9b54cSNate Lawson 	}
45888c9b54cSNate Lawson 
45988c9b54cSNate Lawson 	/* Finally, output the list of levels. */
46073347b07SNate Lawson 	i = 0;
46173347b07SNate Lawson 	TAILQ_FOREACH(lev, &sc->all_levels, link) {
46273347b07SNate Lawson 		levels[i] = *lev;
46373347b07SNate Lawson 		i++;
46473347b07SNate Lawson 	}
46588c9b54cSNate Lawson 	*count = sc->all_count;
46673347b07SNate Lawson 	error = 0;
46773347b07SNate Lawson 
46873347b07SNate Lawson out:
46973347b07SNate Lawson 	/* Clear all levels since we regenerate them each time. */
47073347b07SNate Lawson 	while ((lev = TAILQ_FIRST(&sc->all_levels)) != NULL) {
47173347b07SNate Lawson 		TAILQ_REMOVE(&sc->all_levels, lev, link);
47273347b07SNate Lawson 		free(lev, M_TEMP);
47373347b07SNate Lawson 	}
47488c9b54cSNate Lawson 	while ((set_arr = TAILQ_FIRST(&rel_sets)) != NULL) {
47588c9b54cSNate Lawson 		TAILQ_REMOVE(&rel_sets, set_arr, link);
47688c9b54cSNate Lawson 		free(set_arr, M_TEMP);
47788c9b54cSNate Lawson 	}
47888c9b54cSNate Lawson 	sc->all_count = 0;
47973347b07SNate Lawson 	free(devs, M_TEMP);
48073347b07SNate Lawson 	free(sets, M_TEMP);
48173347b07SNate Lawson 	return (error);
48273347b07SNate Lawson }
48373347b07SNate Lawson 
48473347b07SNate Lawson /*
48573347b07SNate Lawson  * Create levels for an array of absolute settings and insert them in
48673347b07SNate Lawson  * sorted order in the specified list.
48773347b07SNate Lawson  */
48873347b07SNate Lawson static int
48988c9b54cSNate Lawson cpufreq_insert_abs(struct cpufreq_softc *sc, struct cf_setting *sets,
49073347b07SNate Lawson     int count)
49173347b07SNate Lawson {
49288c9b54cSNate Lawson 	struct cf_level_lst *list;
49373347b07SNate Lawson 	struct cf_level *level, *search;
49473347b07SNate Lawson 	int i;
49573347b07SNate Lawson 
49688c9b54cSNate Lawson 	list = &sc->all_levels;
49773347b07SNate Lawson 	for (i = 0; i < count; i++) {
49873347b07SNate Lawson 		level = malloc(sizeof(*level), M_TEMP, M_NOWAIT | M_ZERO);
49973347b07SNate Lawson 		if (level == NULL)
50073347b07SNate Lawson 			return (ENOMEM);
50173347b07SNate Lawson 		level->abs_set = sets[i];
50288c9b54cSNate Lawson 		level->total_set = sets[i];
50388c9b54cSNate Lawson 		level->total_set.dev = NULL;
50488c9b54cSNate Lawson 		sc->all_count++;
50573347b07SNate Lawson 
50673347b07SNate Lawson 		if (TAILQ_EMPTY(list)) {
50773347b07SNate Lawson 			TAILQ_INSERT_HEAD(list, level, link);
50873347b07SNate Lawson 			continue;
50973347b07SNate Lawson 		}
51073347b07SNate Lawson 
51173347b07SNate Lawson 		TAILQ_FOREACH_REVERSE(search, list, cf_level_lst, link) {
51288c9b54cSNate Lawson 			if (sets[i].freq <= search->total_set.freq) {
51373347b07SNate Lawson 				TAILQ_INSERT_AFTER(list, search, level, link);
51473347b07SNate Lawson 				break;
51573347b07SNate Lawson 			}
51673347b07SNate Lawson 		}
51773347b07SNate Lawson 	}
51873347b07SNate Lawson 	return (0);
51973347b07SNate Lawson }
52073347b07SNate Lawson 
52188c9b54cSNate Lawson /*
52288c9b54cSNate Lawson  * Expand a group of relative settings, creating derived levels from them.
52388c9b54cSNate Lawson  */
52488c9b54cSNate Lawson static int
52588c9b54cSNate Lawson cpufreq_expand_set(struct cpufreq_softc *sc, struct cf_setting_array *set_arr)
52688c9b54cSNate Lawson {
52788c9b54cSNate Lawson 	struct cf_level *fill, *search;
52888c9b54cSNate Lawson 	struct cf_setting *set;
52988c9b54cSNate Lawson 	int i;
53088c9b54cSNate Lawson 
53188c9b54cSNate Lawson 	TAILQ_FOREACH(search, &sc->all_levels, link) {
53288c9b54cSNate Lawson 		/* Skip this level if we've already modified it. */
53388c9b54cSNate Lawson 		for (i = 0; i < search->rel_count; i++) {
53488c9b54cSNate Lawson 			if (search->rel_set[i].dev == set_arr->sets[0].dev)
53588c9b54cSNate Lawson 				break;
53688c9b54cSNate Lawson 		}
53788c9b54cSNate Lawson 		if (i != search->rel_count)
53888c9b54cSNate Lawson 			continue;
53988c9b54cSNate Lawson 
54088c9b54cSNate Lawson 		/* Add each setting to the level, duplicating if necessary. */
54188c9b54cSNate Lawson 		for (i = 0; i < set_arr->count; i++) {
54288c9b54cSNate Lawson 			set = &set_arr->sets[i];
54388c9b54cSNate Lawson 
54488c9b54cSNate Lawson 			/*
54588c9b54cSNate Lawson 			 * If this setting is less than 100%, split the level
54688c9b54cSNate Lawson 			 * into two and add this setting to the new level.
54788c9b54cSNate Lawson 			 */
54888c9b54cSNate Lawson 			fill = search;
54988c9b54cSNate Lawson 			if (set->freq < 10000)
55088c9b54cSNate Lawson 				fill = cpufreq_dup_set(sc, search, set);
55188c9b54cSNate Lawson 
55288c9b54cSNate Lawson 			/*
55388c9b54cSNate Lawson 			 * The new level was a duplicate of an existing level
55488c9b54cSNate Lawson 			 * so we freed it.  Go to the next setting.
55588c9b54cSNate Lawson 			 */
55688c9b54cSNate Lawson 			if (fill == NULL)
55788c9b54cSNate Lawson 				continue;
55888c9b54cSNate Lawson 
55988c9b54cSNate Lawson 			/* Add this setting to the existing or new level. */
56088c9b54cSNate Lawson 			KASSERT(fill->rel_count < MAX_SETTINGS,
56188c9b54cSNate Lawson 			    ("cpufreq: too many relative drivers (%d)",
56288c9b54cSNate Lawson 			    MAX_SETTINGS));
56388c9b54cSNate Lawson 			fill->rel_set[fill->rel_count] = *set;
56488c9b54cSNate Lawson 			fill->rel_count++;
56588c9b54cSNate Lawson 		}
56688c9b54cSNate Lawson 	}
56788c9b54cSNate Lawson 
56888c9b54cSNate Lawson 	return (0);
56988c9b54cSNate Lawson }
57088c9b54cSNate Lawson 
57188c9b54cSNate Lawson static struct cf_level *
57288c9b54cSNate Lawson cpufreq_dup_set(struct cpufreq_softc *sc, struct cf_level *dup,
57388c9b54cSNate Lawson     struct cf_setting *set)
57488c9b54cSNate Lawson {
57588c9b54cSNate Lawson 	struct cf_level_lst *list;
57688c9b54cSNate Lawson 	struct cf_level *fill, *itr;
57788c9b54cSNate Lawson 	struct cf_setting *fill_set, *itr_set;
57888c9b54cSNate Lawson 	int i;
57988c9b54cSNate Lawson 
58088c9b54cSNate Lawson 	/*
58188c9b54cSNate Lawson 	 * Create a new level, copy it from the old one, and update the
58288c9b54cSNate Lawson 	 * total frequency and power by the percentage specified in the
58388c9b54cSNate Lawson 	 * relative setting.
58488c9b54cSNate Lawson 	 */
58588c9b54cSNate Lawson 	fill = malloc(sizeof(*fill), M_TEMP, M_NOWAIT);
58688c9b54cSNate Lawson 	if (fill == NULL)
58788c9b54cSNate Lawson 		return (NULL);
58888c9b54cSNate Lawson 	*fill = *dup;
58988c9b54cSNate Lawson 	fill_set = &fill->total_set;
59088c9b54cSNate Lawson 	fill_set->freq =
59188c9b54cSNate Lawson 	    ((uint64_t)fill_set->freq * set->freq) / 10000;
59288c9b54cSNate Lawson 	if (fill_set->power != CPUFREQ_VAL_UNKNOWN) {
59388c9b54cSNate Lawson 		fill_set->power = ((uint64_t)fill_set->power * set->freq)
59488c9b54cSNate Lawson 		    / 10000;
59588c9b54cSNate Lawson 	}
59688c9b54cSNate Lawson 	if (set->lat != CPUFREQ_VAL_UNKNOWN) {
59788c9b54cSNate Lawson 		if (fill_set->lat != CPUFREQ_VAL_UNKNOWN)
59888c9b54cSNate Lawson 			fill_set->lat += set->lat;
59988c9b54cSNate Lawson 		else
60088c9b54cSNate Lawson 			fill_set->lat = set->lat;
60188c9b54cSNate Lawson 	}
60288c9b54cSNate Lawson 
60388c9b54cSNate Lawson 	/*
60488c9b54cSNate Lawson 	 * If we copied an old level that we already modified (say, at 100%),
60588c9b54cSNate Lawson 	 * we need to remove that setting before adding this one.  Since we
60688c9b54cSNate Lawson 	 * process each setting array in order, we know any settings for this
60788c9b54cSNate Lawson 	 * driver will be found at the end.
60888c9b54cSNate Lawson 	 */
60988c9b54cSNate Lawson 	for (i = fill->rel_count; i != 0; i--) {
61088c9b54cSNate Lawson 		if (fill->rel_set[i - 1].dev != set->dev)
61188c9b54cSNate Lawson 			break;
61288c9b54cSNate Lawson 		fill->rel_count--;
61388c9b54cSNate Lawson 	}
61488c9b54cSNate Lawson 
61588c9b54cSNate Lawson 	/*
61688c9b54cSNate Lawson 	 * Insert the new level in sorted order.  If we find a duplicate,
61788c9b54cSNate Lawson 	 * free the new level.  We can do this since any existing level will
61888c9b54cSNate Lawson 	 * be guaranteed to have the same or less settings and thus consume
61988c9b54cSNate Lawson 	 * less power.  For example, a level with one absolute setting of
62088c9b54cSNate Lawson 	 * 800 Mhz uses less power than one composed of an absolute setting
62188c9b54cSNate Lawson 	 * of 1600 Mhz and a relative setting at 50%.
62288c9b54cSNate Lawson 	 */
62388c9b54cSNate Lawson 	list = &sc->all_levels;
62488c9b54cSNate Lawson 	if (TAILQ_EMPTY(list)) {
62588c9b54cSNate Lawson 		TAILQ_INSERT_HEAD(list, fill, link);
62688c9b54cSNate Lawson 	} else {
62788c9b54cSNate Lawson 		TAILQ_FOREACH_REVERSE(itr, list, cf_level_lst, link) {
62888c9b54cSNate Lawson 			itr_set = &itr->total_set;
62988c9b54cSNate Lawson 			if (CPUFREQ_CMP(fill_set->freq, itr_set->freq)) {
63088c9b54cSNate Lawson 				free(fill, M_TEMP);
63188c9b54cSNate Lawson 				fill = NULL;
63288c9b54cSNate Lawson 				break;
63388c9b54cSNate Lawson 			} else if (fill_set->freq < itr_set->freq) {
63488c9b54cSNate Lawson 				TAILQ_INSERT_AFTER(list, itr, fill, link);
63588c9b54cSNate Lawson 				sc->all_count++;
63688c9b54cSNate Lawson 				break;
63788c9b54cSNate Lawson 			}
63888c9b54cSNate Lawson 		}
63988c9b54cSNate Lawson 	}
64088c9b54cSNate Lawson 
64188c9b54cSNate Lawson 	return (fill);
64288c9b54cSNate Lawson }
64388c9b54cSNate Lawson 
64473347b07SNate Lawson static int
64573347b07SNate Lawson cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS)
64673347b07SNate Lawson {
64773347b07SNate Lawson 	struct cpufreq_softc *sc;
64873347b07SNate Lawson 	struct cf_level *levels;
6490325089dSNate Lawson 	int count, devcount, error, freq, i, n;
6500325089dSNate Lawson 	device_t *devs;
65173347b07SNate Lawson 
6520325089dSNate Lawson 	devs = NULL;
65373347b07SNate Lawson 	sc = oidp->oid_arg1;
6540325089dSNate Lawson 	levels = malloc(CF_MAX_LEVELS * sizeof(*levels), M_TEMP, M_NOWAIT);
65573347b07SNate Lawson 	if (levels == NULL)
65673347b07SNate Lawson 		return (ENOMEM);
65773347b07SNate Lawson 
65873347b07SNate Lawson 	error = CPUFREQ_GET(sc->dev, &levels[0]);
65973347b07SNate Lawson 	if (error)
66073347b07SNate Lawson 		goto out;
66173347b07SNate Lawson 	freq = levels[0].total_set.freq;
66273347b07SNate Lawson 	error = sysctl_handle_int(oidp, &freq, 0, req);
66373347b07SNate Lawson 	if (error != 0 || req->newptr == NULL)
66473347b07SNate Lawson 		goto out;
66573347b07SNate Lawson 
6660325089dSNate Lawson 	/*
6670325089dSNate Lawson 	 * While we only call cpufreq_get() on one device (assuming all
6680325089dSNate Lawson 	 * CPUs have equal levels), we call cpufreq_set() on all CPUs.
6690325089dSNate Lawson 	 * This is needed for some MP systems.
6700325089dSNate Lawson 	 */
6710325089dSNate Lawson 	error = devclass_get_devices(cpufreq_dc, &devs, &devcount);
67273347b07SNate Lawson 	if (error)
67373347b07SNate Lawson 		goto out;
6740325089dSNate Lawson 	for (n = 0; n < devcount; n++) {
6750325089dSNate Lawson 		count = CF_MAX_LEVELS;
6760325089dSNate Lawson 		error = CPUFREQ_LEVELS(devs[n], levels, &count);
6770325089dSNate Lawson 		if (error)
6780325089dSNate Lawson 			break;
67973347b07SNate Lawson 		for (i = 0; i < count; i++) {
68073347b07SNate Lawson 			if (CPUFREQ_CMP(levels[i].total_set.freq, freq)) {
6810325089dSNate Lawson 				error = CPUFREQ_SET(devs[n], &levels[i],
68273347b07SNate Lawson 				    CPUFREQ_PRIO_USER);
68373347b07SNate Lawson 				break;
68473347b07SNate Lawson 			}
68573347b07SNate Lawson 		}
6860325089dSNate Lawson 		if (i == count) {
68773347b07SNate Lawson 			error = EINVAL;
6880325089dSNate Lawson 			break;
6890325089dSNate Lawson 		}
6900325089dSNate Lawson 	}
69173347b07SNate Lawson 
69273347b07SNate Lawson out:
6930325089dSNate Lawson 	if (devs)
6940325089dSNate Lawson 		free(devs, M_TEMP);
69573347b07SNate Lawson 	if (levels)
69673347b07SNate Lawson 		free(levels, M_TEMP);
69773347b07SNate Lawson 	return (error);
69873347b07SNate Lawson }
69973347b07SNate Lawson 
70073347b07SNate Lawson static int
70173347b07SNate Lawson cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS)
70273347b07SNate Lawson {
70373347b07SNate Lawson 	struct cpufreq_softc *sc;
70473347b07SNate Lawson 	struct cf_level *levels;
70573347b07SNate Lawson 	struct cf_setting *set;
70673347b07SNate Lawson 	struct sbuf sb;
70773347b07SNate Lawson 	int count, error, i;
70873347b07SNate Lawson 
70973347b07SNate Lawson 	sc = oidp->oid_arg1;
71073347b07SNate Lawson 	sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND);
71173347b07SNate Lawson 
71273347b07SNate Lawson 	/* Get settings from the device and generate the output string. */
71373347b07SNate Lawson 	count = CF_MAX_LEVELS;
71473347b07SNate Lawson 	levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT);
71573347b07SNate Lawson 	if (levels == NULL)
71673347b07SNate Lawson 		return (ENOMEM);
71773347b07SNate Lawson 	error = CPUFREQ_LEVELS(sc->dev, levels, &count);
71873347b07SNate Lawson 	if (error)
71973347b07SNate Lawson 		goto out;
72073347b07SNate Lawson 	if (count) {
72173347b07SNate Lawson 		for (i = 0; i < count; i++) {
72273347b07SNate Lawson 			set = &levels[i].total_set;
72373347b07SNate Lawson 			sbuf_printf(&sb, "%d/%d ", set->freq, set->power);
72473347b07SNate Lawson 		}
72573347b07SNate Lawson 	} else
72673347b07SNate Lawson 		sbuf_cpy(&sb, "0");
72773347b07SNate Lawson 	sbuf_trim(&sb);
72873347b07SNate Lawson 	sbuf_finish(&sb);
72973347b07SNate Lawson 	error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req);
73073347b07SNate Lawson 
73173347b07SNate Lawson out:
73273347b07SNate Lawson 	free(levels, M_TEMP);
73373347b07SNate Lawson 	sbuf_delete(&sb);
73473347b07SNate Lawson 	return (error);
73573347b07SNate Lawson }
73673347b07SNate Lawson 
73773347b07SNate Lawson int
73873347b07SNate Lawson cpufreq_register(device_t dev)
73973347b07SNate Lawson {
74073347b07SNate Lawson 	device_t cf_dev, cpu_dev;
74173347b07SNate Lawson 
74273347b07SNate Lawson 	/*
7430325089dSNate Lawson 	 * Add only one cpufreq device to each CPU.  Currently, all CPUs
7440325089dSNate Lawson 	 * must offer the same levels and be switched at the same time.
74573347b07SNate Lawson 	 */
7460325089dSNate Lawson 	cpu_dev = device_get_parent(dev);
7470325089dSNate Lawson 	if (device_find_child(cpu_dev, "cpufreq", -1))
74873347b07SNate Lawson 		return (0);
74973347b07SNate Lawson 
7500325089dSNate Lawson 	/* Add the child device and possibly sysctls. */
7510325089dSNate Lawson 	cf_dev = BUS_ADD_CHILD(cpu_dev, 0, "cpufreq", -1);
75273347b07SNate Lawson 	if (cf_dev == NULL)
75373347b07SNate Lawson 		return (ENOMEM);
75473347b07SNate Lawson 	device_quiet(cf_dev);
75573347b07SNate Lawson 
75673347b07SNate Lawson 	return (device_probe_and_attach(cf_dev));
75773347b07SNate Lawson }
75873347b07SNate Lawson 
75973347b07SNate Lawson int
76073347b07SNate Lawson cpufreq_unregister(device_t dev)
76173347b07SNate Lawson {
76273347b07SNate Lawson 	device_t cf_dev, *devs;
76373347b07SNate Lawson 	int cfcount, count, devcount, error, i, type;
76473347b07SNate Lawson 	struct cf_setting set;
76573347b07SNate Lawson 
76673347b07SNate Lawson 	/*
76773347b07SNate Lawson 	 * If this is the last cpufreq child device, remove the control
76873347b07SNate Lawson 	 * device as well.  We identify cpufreq children by calling a method
76973347b07SNate Lawson 	 * they support.
77073347b07SNate Lawson 	 */
77173347b07SNate Lawson 	error = device_get_children(device_get_parent(dev), &devs, &devcount);
77273347b07SNate Lawson 	if (error)
77373347b07SNate Lawson 		return (error);
77473347b07SNate Lawson 	cf_dev = devclass_get_device(cpufreq_dc, 0);
77573347b07SNate Lawson 	cfcount = 0;
77673347b07SNate Lawson 	for (i = 0; i < devcount; i++) {
77773347b07SNate Lawson 		if (!device_is_attached(devs[i]))
77873347b07SNate Lawson 			continue;
77973347b07SNate Lawson 		count = 1;
78073347b07SNate Lawson 		if (CPUFREQ_DRV_SETTINGS(devs[i], &set, &count, &type) == 0)
78173347b07SNate Lawson 			cfcount++;
78273347b07SNate Lawson 	}
7830325089dSNate Lawson 	if (cfcount <= 1)
78473347b07SNate Lawson 		device_delete_child(device_get_parent(cf_dev), cf_dev);
78573347b07SNate Lawson 	free(devs, M_TEMP);
78673347b07SNate Lawson 
78773347b07SNate Lawson 	return (0);
78873347b07SNate Lawson }
789