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