kern_cpu.c revision 141240
1141240Snjl/*-
2141240Snjl * Copyright (c) 2004-2005 Nate Lawson (SDG)
3141240Snjl * All rights reserved.
4141240Snjl *
5141240Snjl * Redistribution and use in source and binary forms, with or without
6141240Snjl * modification, are permitted provided that the following conditions
7141240Snjl * are met:
8141240Snjl * 1. Redistributions of source code must retain the above copyright
9141240Snjl *    notice, this list of conditions and the following disclaimer.
10141240Snjl * 2. Redistributions in binary form must reproduce the above copyright
11141240Snjl *    notice, this list of conditions and the following disclaimer in the
12141240Snjl *    documentation and/or other materials provided with the distribution.
13141240Snjl *
14141240Snjl * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15141240Snjl * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16141240Snjl * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17141240Snjl * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18141240Snjl * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19141240Snjl * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20141240Snjl * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21141240Snjl * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22141240Snjl * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23141240Snjl * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24141240Snjl * SUCH DAMAGE.
25141240Snjl */
26141240Snjl
27141240Snjl#include <sys/cdefs.h>
28141240Snjl__FBSDID("$FreeBSD: head/sys/kern/kern_cpu.c 141240 2005-02-04 05:39:19Z njl $");
29141240Snjl
30141240Snjl#include <sys/param.h>
31141240Snjl#include <sys/bus.h>
32141240Snjl#include <sys/cpu.h>
33141240Snjl#include <sys/eventhandler.h>
34141240Snjl#include <sys/kernel.h>
35141240Snjl#include <sys/malloc.h>
36141240Snjl#include <sys/module.h>
37141240Snjl#include <sys/proc.h>
38141240Snjl#include <sys/queue.h>
39141240Snjl#include <sys/sched.h>
40141240Snjl#include <sys/sysctl.h>
41141240Snjl#include <sys/systm.h>
42141240Snjl#include <sys/sbuf.h>
43141240Snjl
44141240Snjl#include "cpufreq_if.h"
45141240Snjl
46141240Snjl/*
47141240Snjl * Common CPU frequency glue code.  Drivers for specific hardware can
48141240Snjl * attach this interface to allow users to get/set the CPU frequency.
49141240Snjl */
50141240Snjl
51141240Snjl/*
52141240Snjl * Number of levels we can handle.  Levels are synthesized from settings
53141240Snjl * so for N settings there may be N^2 levels.
54141240Snjl */
55141240Snjl#define CF_MAX_LEVELS	32
56141240Snjl
57141240Snjlstruct cpufreq_softc {
58141240Snjl	struct cf_level			curr_level;
59141240Snjl	int				priority;
60141240Snjl	struct cf_level_lst		all_levels;
61141240Snjl	device_t			dev;
62141240Snjl	struct sysctl_ctx_list		sysctl_ctx;
63141240Snjl};
64141240Snjl
65141240Snjlstruct cf_setting_array {
66141240Snjl	struct cf_setting		sets[MAX_SETTINGS];
67141240Snjl	int				count;
68141240Snjl	TAILQ_ENTRY(cf_setting_array)	link;
69141240Snjl};
70141240Snjl
71141240SnjlTAILQ_HEAD(cf_setting_lst, cf_setting_array);
72141240Snjl
73141240Snjlstatic int	cpufreq_attach(device_t dev);
74141240Snjlstatic int	cpufreq_detach(device_t dev);
75141240Snjlstatic void	cpufreq_evaluate(void *arg);
76141240Snjlstatic int	cf_set_method(device_t dev, const struct cf_level *level,
77141240Snjl		    int priority);
78141240Snjlstatic int	cf_get_method(device_t dev, struct cf_level *level);
79141240Snjlstatic int	cf_levels_method(device_t dev, struct cf_level *levels,
80141240Snjl		    int *count);
81141240Snjlstatic int	cpufreq_insert_abs(struct cf_level_lst *list,
82141240Snjl		    struct cf_setting *sets, int count);
83141240Snjlstatic int	cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS);
84141240Snjlstatic int	cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS);
85141240Snjl
86141240Snjlstatic device_method_t cpufreq_methods[] = {
87141240Snjl	DEVMETHOD(device_probe,		bus_generic_probe),
88141240Snjl	DEVMETHOD(device_attach,	cpufreq_attach),
89141240Snjl	DEVMETHOD(device_detach,	cpufreq_detach),
90141240Snjl
91141240Snjl        DEVMETHOD(cpufreq_set,		cf_set_method),
92141240Snjl        DEVMETHOD(cpufreq_get,		cf_get_method),
93141240Snjl        DEVMETHOD(cpufreq_levels,	cf_levels_method),
94141240Snjl	{0, 0}
95141240Snjl};
96141240Snjlstatic driver_t cpufreq_driver = {
97141240Snjl	"cpufreq", cpufreq_methods, sizeof(struct cpufreq_softc)
98141240Snjl};
99141240Snjlstatic devclass_t cpufreq_dc;
100141240SnjlDRIVER_MODULE(cpufreq, cpu, cpufreq_driver, cpufreq_dc, 0, 0);
101141240Snjl
102141240Snjlstatic eventhandler_tag cf_ev_tag;
103141240Snjl
104141240Snjlstatic int
105141240Snjlcpufreq_attach(device_t dev)
106141240Snjl{
107141240Snjl	struct cpufreq_softc *sc;
108141240Snjl	device_t parent;
109141240Snjl	int numdevs;
110141240Snjl
111141240Snjl	sc = device_get_softc(dev);
112141240Snjl	parent = device_get_parent(dev);
113141240Snjl	sc->dev = dev;
114141240Snjl	sysctl_ctx_init(&sc->sysctl_ctx);
115141240Snjl	TAILQ_INIT(&sc->all_levels);
116141240Snjl	sc->curr_level.total_set.freq = CPUFREQ_VAL_UNKNOWN;
117141240Snjl
118141240Snjl	/*
119141240Snjl	 * Only initialize one set of sysctls for all CPUs.  In the future,
120141240Snjl	 * if multiple CPUs can have different settings, we can move these
121141240Snjl	 * sysctls to be under every CPU instead of just the first one.
122141240Snjl	 */
123141240Snjl	numdevs = devclass_get_count(cpufreq_dc);
124141240Snjl	if (numdevs > 1)
125141240Snjl		return (0);
126141240Snjl
127141240Snjl	SYSCTL_ADD_PROC(&sc->sysctl_ctx,
128141240Snjl	    SYSCTL_CHILDREN(device_get_sysctl_tree(parent)),
129141240Snjl	    OID_AUTO, "freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
130141240Snjl	    cpufreq_curr_sysctl, "I", "Current CPU frequency");
131141240Snjl	SYSCTL_ADD_PROC(&sc->sysctl_ctx,
132141240Snjl	    SYSCTL_CHILDREN(device_get_sysctl_tree(parent)),
133141240Snjl	    OID_AUTO, "freq_levels", CTLTYPE_STRING | CTLFLAG_RD, sc, 0,
134141240Snjl	    cpufreq_levels_sysctl, "A", "CPU frequency levels");
135141240Snjl	cf_ev_tag = EVENTHANDLER_REGISTER(cpufreq_changed, cpufreq_evaluate,
136141240Snjl	    NULL, EVENTHANDLER_PRI_ANY);
137141240Snjl
138141240Snjl	return (0);
139141240Snjl}
140141240Snjl
141141240Snjlstatic int
142141240Snjlcpufreq_detach(device_t dev)
143141240Snjl{
144141240Snjl	struct cpufreq_softc *sc;
145141240Snjl	int numdevs;
146141240Snjl
147141240Snjl	sc = device_get_softc(dev);
148141240Snjl	sysctl_ctx_free(&sc->sysctl_ctx);
149141240Snjl
150141240Snjl	/* Only clean up these resources when the last device is detaching. */
151141240Snjl	numdevs = devclass_get_count(cpufreq_dc);
152141240Snjl	if (numdevs == 1)
153141240Snjl		EVENTHANDLER_DEREGISTER(cpufreq_changed, cf_ev_tag);
154141240Snjl
155141240Snjl	return (0);
156141240Snjl}
157141240Snjl
158141240Snjlstatic void
159141240Snjlcpufreq_evaluate(void *arg)
160141240Snjl{
161141240Snjl	/* TODO: Re-evaluate when notified of changes to drivers. */
162141240Snjl}
163141240Snjl
164141240Snjlstatic int
165141240Snjlcf_set_method(device_t dev, const struct cf_level *level, int priority)
166141240Snjl{
167141240Snjl	struct cpufreq_softc *sc;
168141240Snjl	const struct cf_setting *set;
169141240Snjl	int error;
170141240Snjl
171141240Snjl	sc = device_get_softc(dev);
172141240Snjl
173141240Snjl	/* If already at this level, just return. */
174141240Snjl	if (CPUFREQ_CMP(sc->curr_level.total_set.freq, level->total_set.freq))
175141240Snjl		return (0);
176141240Snjl
177141240Snjl	/* First, set the absolute frequency via its driver. */
178141240Snjl	set = &level->abs_set;
179141240Snjl	if (set->dev) {
180141240Snjl		if (!device_is_attached(set->dev)) {
181141240Snjl			error = ENXIO;
182141240Snjl			goto out;
183141240Snjl		}
184141240Snjl		error = CPUFREQ_DRV_SET(set->dev, set);
185141240Snjl		if (error) {
186141240Snjl			goto out;
187141240Snjl		}
188141240Snjl	}
189141240Snjl
190141240Snjl	/* TODO: Next, set any/all relative frequencies via their drivers. */
191141240Snjl
192141240Snjl	/* Record the current level. */
193141240Snjl	sc->curr_level = *level;
194141240Snjl	sc->priority = priority;
195141240Snjl	error = 0;
196141240Snjl
197141240Snjlout:
198141240Snjl	if (error)
199141240Snjl		device_printf(set->dev, "set freq failed, err %d\n", error);
200141240Snjl	return (error);
201141240Snjl}
202141240Snjl
203141240Snjlstatic int
204141240Snjlcf_get_method(device_t dev, struct cf_level *level)
205141240Snjl{
206141240Snjl	struct cpufreq_softc *sc;
207141240Snjl	struct cf_level *levels;
208141240Snjl	struct cf_setting *curr_set, set;
209141240Snjl	struct pcpu *pc;
210141240Snjl	device_t *devs;
211141240Snjl	int count, error, i, numdevs;
212141240Snjl	uint64_t rate;
213141240Snjl
214141240Snjl	sc = device_get_softc(dev);
215141240Snjl	curr_set = &sc->curr_level.total_set;
216141240Snjl	levels = NULL;
217141240Snjl
218141240Snjl	/* If we already know the current frequency, we're done. */
219141240Snjl	if (curr_set->freq != CPUFREQ_VAL_UNKNOWN)
220141240Snjl		goto out;
221141240Snjl
222141240Snjl	/*
223141240Snjl	 * We need to figure out the current level.  Loop through every
224141240Snjl	 * driver, getting the current setting.  Then, attempt to get a best
225141240Snjl	 * match of settings against each level.
226141240Snjl	 */
227141240Snjl	count = CF_MAX_LEVELS;
228141240Snjl	levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT);
229141240Snjl	if (levels == NULL)
230141240Snjl		return (ENOMEM);
231141240Snjl	error = CPUFREQ_LEVELS(sc->dev, levels, &count);
232141240Snjl	if (error)
233141240Snjl		goto out;
234141240Snjl	error = device_get_children(device_get_parent(dev), &devs, &numdevs);
235141240Snjl	if (error)
236141240Snjl		goto out;
237141240Snjl	for (i = 0; i < numdevs && curr_set->freq == CPUFREQ_VAL_UNKNOWN; i++) {
238141240Snjl		if (!device_is_attached(devs[i]))
239141240Snjl			continue;
240141240Snjl		error = CPUFREQ_DRV_GET(devs[i], &set);
241141240Snjl		if (error)
242141240Snjl			continue;
243141240Snjl		for (i = 0; i < count; i++) {
244141240Snjl			if (CPUFREQ_CMP(set.freq, levels[i].abs_set.freq)) {
245141240Snjl				sc->curr_level = levels[i];
246141240Snjl				break;
247141240Snjl			}
248141240Snjl		}
249141240Snjl	}
250141240Snjl	free(devs, M_TEMP);
251141240Snjl	if (curr_set->freq != CPUFREQ_VAL_UNKNOWN)
252141240Snjl		goto out;
253141240Snjl
254141240Snjl	/*
255141240Snjl	 * We couldn't find an exact match, so attempt to estimate and then
256141240Snjl	 * match against a level.
257141240Snjl	 */
258141240Snjl	pc = cpu_get_pcpu(dev);
259141240Snjl	if (pc == NULL) {
260141240Snjl		error = ENXIO;
261141240Snjl		goto out;
262141240Snjl	}
263141240Snjl	cpu_est_clockrate(pc->pc_cpuid, &rate);
264141240Snjl	rate /= 1000000;
265141240Snjl	for (i = 0; i < count; i++) {
266141240Snjl		if (CPUFREQ_CMP(rate, levels[i].total_set.freq)) {
267141240Snjl			sc->curr_level = levels[i];
268141240Snjl			break;
269141240Snjl		}
270141240Snjl	}
271141240Snjl
272141240Snjlout:
273141240Snjl	if (levels)
274141240Snjl		free(levels, M_TEMP);
275141240Snjl	*level = sc->curr_level;
276141240Snjl	return (0);
277141240Snjl}
278141240Snjl
279141240Snjlstatic int
280141240Snjlcf_levels_method(device_t dev, struct cf_level *levels, int *count)
281141240Snjl{
282141240Snjl	struct cf_setting_lst rel_sets;
283141240Snjl	struct cpufreq_softc *sc;
284141240Snjl	struct cf_level *lev;
285141240Snjl	struct cf_setting *sets;
286141240Snjl	struct pcpu *pc;
287141240Snjl	device_t *devs;
288141240Snjl	int error, i, numdevs, numlevels, set_count, type;
289141240Snjl	uint64_t rate;
290141240Snjl
291141240Snjl	if (levels == NULL || count == NULL)
292141240Snjl		return (EINVAL);
293141240Snjl
294141240Snjl	TAILQ_INIT(&rel_sets);
295141240Snjl	sc = device_get_softc(dev);
296141240Snjl	error = device_get_children(device_get_parent(dev), &devs, &numdevs);
297141240Snjl	if (error)
298141240Snjl		return (error);
299141240Snjl	sets = malloc(MAX_SETTINGS * sizeof(*sets), M_TEMP, M_NOWAIT);
300141240Snjl	if (sets == NULL) {
301141240Snjl		free(devs, M_TEMP);
302141240Snjl		return (ENOMEM);
303141240Snjl	}
304141240Snjl
305141240Snjl	/* Get settings from all cpufreq drivers. */
306141240Snjl	numlevels = 0;
307141240Snjl	for (i = 0; i < numdevs; i++) {
308141240Snjl		if (!device_is_attached(devs[i]))
309141240Snjl			continue;
310141240Snjl		set_count = MAX_SETTINGS;
311141240Snjl		error = CPUFREQ_DRV_SETTINGS(devs[i], sets, &set_count, &type);
312141240Snjl		if (error || set_count == 0)
313141240Snjl			continue;
314141240Snjl		error = cpufreq_insert_abs(&sc->all_levels, sets, set_count);
315141240Snjl		if (error)
316141240Snjl			goto out;
317141240Snjl		numlevels += set_count;
318141240Snjl	}
319141240Snjl
320141240Snjl	/* If the caller doesn't have enough space, return the actual count. */
321141240Snjl	if (numlevels > *count) {
322141240Snjl		*count = numlevels;
323141240Snjl		error = E2BIG;
324141240Snjl		goto out;
325141240Snjl	}
326141240Snjl
327141240Snjl	/* If there are no absolute levels, create a fake one at 100%. */
328141240Snjl	if (TAILQ_EMPTY(&sc->all_levels)) {
329141240Snjl		bzero(&sets[0], sizeof(*sets));
330141240Snjl		pc = cpu_get_pcpu(dev);
331141240Snjl		if (pc == NULL) {
332141240Snjl			error = ENXIO;
333141240Snjl			goto out;
334141240Snjl		}
335141240Snjl		cpu_est_clockrate(pc->pc_cpuid, &rate);
336141240Snjl		sets[0].freq = rate / 1000000;
337141240Snjl		error = cpufreq_insert_abs(&sc->all_levels, sets, 1);
338141240Snjl		if (error)
339141240Snjl			goto out;
340141240Snjl	}
341141240Snjl
342141240Snjl	/* TODO: Create a combined list of absolute + relative levels. */
343141240Snjl	i = 0;
344141240Snjl	TAILQ_FOREACH(lev, &sc->all_levels, link) {
345141240Snjl		/* For now, just assume total freq equals absolute freq. */
346141240Snjl		lev->total_set = lev->abs_set;
347141240Snjl		lev->total_set.dev = NULL;
348141240Snjl		levels[i] = *lev;
349141240Snjl		i++;
350141240Snjl	}
351141240Snjl	*count = i;
352141240Snjl	error = 0;
353141240Snjl
354141240Snjlout:
355141240Snjl	/* Clear all levels since we regenerate them each time. */
356141240Snjl	while ((lev = TAILQ_FIRST(&sc->all_levels)) != NULL) {
357141240Snjl		TAILQ_REMOVE(&sc->all_levels, lev, link);
358141240Snjl		free(lev, M_TEMP);
359141240Snjl	}
360141240Snjl	free(devs, M_TEMP);
361141240Snjl	free(sets, M_TEMP);
362141240Snjl	return (error);
363141240Snjl}
364141240Snjl
365141240Snjl/*
366141240Snjl * Create levels for an array of absolute settings and insert them in
367141240Snjl * sorted order in the specified list.
368141240Snjl */
369141240Snjlstatic int
370141240Snjlcpufreq_insert_abs(struct cf_level_lst *list, struct cf_setting *sets,
371141240Snjl    int count)
372141240Snjl{
373141240Snjl	struct cf_level *level, *search;
374141240Snjl	int i;
375141240Snjl
376141240Snjl	for (i = 0; i < count; i++) {
377141240Snjl		level = malloc(sizeof(*level), M_TEMP, M_NOWAIT | M_ZERO);
378141240Snjl		if (level == NULL)
379141240Snjl			return (ENOMEM);
380141240Snjl		level->abs_set = sets[i];
381141240Snjl
382141240Snjl		if (TAILQ_EMPTY(list)) {
383141240Snjl			TAILQ_INSERT_HEAD(list, level, link);
384141240Snjl			continue;
385141240Snjl		}
386141240Snjl
387141240Snjl		TAILQ_FOREACH_REVERSE(search, list, cf_level_lst, link) {
388141240Snjl			if (sets[i].freq <= search->abs_set.freq) {
389141240Snjl				TAILQ_INSERT_AFTER(list, search, level, link);
390141240Snjl				break;
391141240Snjl			}
392141240Snjl		}
393141240Snjl	}
394141240Snjl	return (0);
395141240Snjl}
396141240Snjl
397141240Snjlstatic int
398141240Snjlcpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS)
399141240Snjl{
400141240Snjl	struct cpufreq_softc *sc;
401141240Snjl	struct cf_level *levels;
402141240Snjl	int count, error, freq, i;
403141240Snjl
404141240Snjl	sc = oidp->oid_arg1;
405141240Snjl	count = CF_MAX_LEVELS;
406141240Snjl	levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT);
407141240Snjl	if (levels == NULL)
408141240Snjl		return (ENOMEM);
409141240Snjl
410141240Snjl	error = CPUFREQ_GET(sc->dev, &levels[0]);
411141240Snjl	if (error)
412141240Snjl		goto out;
413141240Snjl	freq = levels[0].total_set.freq;
414141240Snjl	error = sysctl_handle_int(oidp, &freq, 0, req);
415141240Snjl	if (error != 0 || req->newptr == NULL)
416141240Snjl		goto out;
417141240Snjl
418141240Snjl	error = CPUFREQ_LEVELS(sc->dev, levels, &count);
419141240Snjl	if (error)
420141240Snjl		goto out;
421141240Snjl	for (i = 0; i < count; i++) {
422141240Snjl		if (CPUFREQ_CMP(levels[i].total_set.freq, freq)) {
423141240Snjl			error = CPUFREQ_SET(sc->dev, &levels[i],
424141240Snjl			    CPUFREQ_PRIO_USER);
425141240Snjl			break;
426141240Snjl		}
427141240Snjl	}
428141240Snjl	if (i == count)
429141240Snjl		error = EINVAL;
430141240Snjl
431141240Snjlout:
432141240Snjl	if (levels)
433141240Snjl		free(levels, M_TEMP);
434141240Snjl	return (error);
435141240Snjl}
436141240Snjl
437141240Snjlstatic int
438141240Snjlcpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS)
439141240Snjl{
440141240Snjl	struct cpufreq_softc *sc;
441141240Snjl	struct cf_level *levels;
442141240Snjl	struct cf_setting *set;
443141240Snjl	struct sbuf sb;
444141240Snjl	int count, error, i;
445141240Snjl
446141240Snjl	sc = oidp->oid_arg1;
447141240Snjl	sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND);
448141240Snjl
449141240Snjl	/* Get settings from the device and generate the output string. */
450141240Snjl	count = CF_MAX_LEVELS;
451141240Snjl	levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT);
452141240Snjl	if (levels == NULL)
453141240Snjl		return (ENOMEM);
454141240Snjl	error = CPUFREQ_LEVELS(sc->dev, levels, &count);
455141240Snjl	if (error)
456141240Snjl		goto out;
457141240Snjl	if (count) {
458141240Snjl		for (i = 0; i < count; i++) {
459141240Snjl			set = &levels[i].total_set;
460141240Snjl			sbuf_printf(&sb, "%d/%d ", set->freq, set->power);
461141240Snjl		}
462141240Snjl	} else
463141240Snjl		sbuf_cpy(&sb, "0");
464141240Snjl	sbuf_trim(&sb);
465141240Snjl	sbuf_finish(&sb);
466141240Snjl	error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req);
467141240Snjl
468141240Snjlout:
469141240Snjl	free(levels, M_TEMP);
470141240Snjl	sbuf_delete(&sb);
471141240Snjl	return (error);
472141240Snjl}
473141240Snjl
474141240Snjlint
475141240Snjlcpufreq_register(device_t dev)
476141240Snjl{
477141240Snjl	device_t cf_dev, cpu_dev;
478141240Snjl
479141240Snjl	/*
480141240Snjl	 * Only add one cpufreq device (on cpu0) for all control.  Once
481141240Snjl	 * independent multi-cpu control appears, we can assign one cpufreq
482141240Snjl	 * device per cpu.
483141240Snjl	 */
484141240Snjl	cf_dev = devclass_get_device(cpufreq_dc, 0);
485141240Snjl	if (cf_dev) {
486141240Snjl		device_printf(dev,
487141240Snjl		    "warning: only one cpufreq device at a time supported\n");
488141240Snjl		return (0);
489141240Snjl	}
490141240Snjl
491141240Snjl	/* Add the child device and sysctls. */
492141240Snjl	cpu_dev = devclass_get_device(devclass_find("cpu"), 0);
493141240Snjl	cf_dev = BUS_ADD_CHILD(cpu_dev, 0, "cpufreq", 0);
494141240Snjl	if (cf_dev == NULL)
495141240Snjl		return (ENOMEM);
496141240Snjl	device_quiet(cf_dev);
497141240Snjl
498141240Snjl	return (device_probe_and_attach(cf_dev));
499141240Snjl}
500141240Snjl
501141240Snjlint
502141240Snjlcpufreq_unregister(device_t dev)
503141240Snjl{
504141240Snjl	device_t cf_dev, *devs;
505141240Snjl	int cfcount, count, devcount, error, i, type;
506141240Snjl	struct cf_setting set;
507141240Snjl
508141240Snjl	/*
509141240Snjl	 * If this is the last cpufreq child device, remove the control
510141240Snjl	 * device as well.  We identify cpufreq children by calling a method
511141240Snjl	 * they support.
512141240Snjl	 */
513141240Snjl	error = device_get_children(device_get_parent(dev), &devs, &devcount);
514141240Snjl	if (error)
515141240Snjl		return (error);
516141240Snjl	cf_dev = devclass_get_device(cpufreq_dc, 0);
517141240Snjl	KASSERT(cf_dev != NULL, ("unregister with no cpufreq dev"));
518141240Snjl	cfcount = 0;
519141240Snjl	for (i = 0; i < devcount; i++) {
520141240Snjl		if (!device_is_attached(devs[i]))
521141240Snjl			continue;
522141240Snjl		count = 1;
523141240Snjl		if (CPUFREQ_DRV_SETTINGS(devs[i], &set, &count, &type) == 0)
524141240Snjl			cfcount++;
525141240Snjl	}
526141240Snjl	if (cfcount <= 1) {
527141240Snjl		device_delete_child(device_get_parent(cf_dev), cf_dev);
528141240Snjl	}
529141240Snjl	free(devs, M_TEMP);
530141240Snjl
531141240Snjl	return (0);
532141240Snjl}
533