1141240Snjl/*- 2167905Snjl * Copyright (c) 2004-2007 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$"); 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> 35142603Snjl#include <sys/lock.h> 36141240Snjl#include <sys/malloc.h> 37141240Snjl#include <sys/module.h> 38141240Snjl#include <sys/proc.h> 39141240Snjl#include <sys/queue.h> 40173204Snjl#include <sys/sbuf.h> 41141240Snjl#include <sys/sched.h> 42173204Snjl#include <sys/smp.h> 43141240Snjl#include <sys/sysctl.h> 44141240Snjl#include <sys/systm.h> 45142603Snjl#include <sys/sx.h> 46141814Snjl#include <sys/timetc.h> 47167905Snjl#include <sys/taskqueue.h> 48141240Snjl 49141240Snjl#include "cpufreq_if.h" 50141240Snjl 51141240Snjl/* 52141240Snjl * Common CPU frequency glue code. Drivers for specific hardware can 53141240Snjl * attach this interface to allow users to get/set the CPU frequency. 54141240Snjl */ 55141240Snjl 56141240Snjl/* 57141240Snjl * Number of levels we can handle. Levels are synthesized from settings 58142395Snjl * so for M settings and N drivers, there may be M*N levels. 59141240Snjl */ 60142395Snjl#define CF_MAX_LEVELS 64 61141240Snjl 62150847Sumestruct cf_saved_freq { 63150847Sume struct cf_level level; 64150847Sume int priority; 65150847Sume SLIST_ENTRY(cf_saved_freq) link; 66150847Sume}; 67150847Sume 68141240Snjlstruct cpufreq_softc { 69142603Snjl struct sx lock; 70141240Snjl struct cf_level curr_level; 71141923Snjl int curr_priority; 72150847Sume SLIST_HEAD(, cf_saved_freq) saved_freq; 73141923Snjl struct cf_level_lst all_levels; 74141413Snjl int all_count; 75141945Snjl int max_mhz; 76141240Snjl device_t dev; 77141240Snjl struct sysctl_ctx_list sysctl_ctx; 78167905Snjl struct task startup_task; 79210422Savg struct cf_level *levels_buf; 80141240Snjl}; 81141240Snjl 82141240Snjlstruct cf_setting_array { 83141240Snjl struct cf_setting sets[MAX_SETTINGS]; 84141240Snjl int count; 85141240Snjl TAILQ_ENTRY(cf_setting_array) link; 86141240Snjl}; 87141240Snjl 88141240SnjlTAILQ_HEAD(cf_setting_lst, cf_setting_array); 89141240Snjl 90142603Snjl#define CF_MTX_INIT(x) sx_init((x), "cpufreq lock") 91142603Snjl#define CF_MTX_LOCK(x) sx_xlock((x)) 92142603Snjl#define CF_MTX_UNLOCK(x) sx_xunlock((x)) 93142603Snjl#define CF_MTX_ASSERT(x) sx_assert((x), SX_XLOCKED) 94142603Snjl 95144876Snjl#define CF_DEBUG(msg...) do { \ 96144876Snjl if (cf_verbose) \ 97144876Snjl printf("cpufreq: " msg); \ 98144876Snjl } while (0) 99144876Snjl 100141240Snjlstatic int cpufreq_attach(device_t dev); 101167905Snjlstatic void cpufreq_startup_task(void *ctx, int pending); 102141240Snjlstatic int cpufreq_detach(device_t dev); 103141240Snjlstatic int cf_set_method(device_t dev, const struct cf_level *level, 104141240Snjl int priority); 105141240Snjlstatic int cf_get_method(device_t dev, struct cf_level *level); 106141240Snjlstatic int cf_levels_method(device_t dev, struct cf_level *levels, 107141240Snjl int *count); 108141413Snjlstatic int cpufreq_insert_abs(struct cpufreq_softc *sc, 109141240Snjl struct cf_setting *sets, int count); 110141413Snjlstatic int cpufreq_expand_set(struct cpufreq_softc *sc, 111141413Snjl struct cf_setting_array *set_arr); 112141413Snjlstatic struct cf_level *cpufreq_dup_set(struct cpufreq_softc *sc, 113141413Snjl struct cf_level *dup, struct cf_setting *set); 114141240Snjlstatic int cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS); 115141240Snjlstatic int cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS); 116142114Snjlstatic int cpufreq_settings_sysctl(SYSCTL_HANDLER_ARGS); 117141240Snjl 118141240Snjlstatic device_method_t cpufreq_methods[] = { 119141240Snjl DEVMETHOD(device_probe, bus_generic_probe), 120141240Snjl DEVMETHOD(device_attach, cpufreq_attach), 121141240Snjl DEVMETHOD(device_detach, cpufreq_detach), 122141240Snjl 123141240Snjl DEVMETHOD(cpufreq_set, cf_set_method), 124141240Snjl DEVMETHOD(cpufreq_get, cf_get_method), 125141240Snjl DEVMETHOD(cpufreq_levels, cf_levels_method), 126141240Snjl {0, 0} 127141240Snjl}; 128141240Snjlstatic driver_t cpufreq_driver = { 129141240Snjl "cpufreq", cpufreq_methods, sizeof(struct cpufreq_softc) 130141240Snjl}; 131141240Snjlstatic devclass_t cpufreq_dc; 132141240SnjlDRIVER_MODULE(cpufreq, cpu, cpufreq_driver, cpufreq_dc, 0, 0); 133141240Snjl 134142590Snjlstatic int cf_lowest_freq; 135144876Snjlstatic int cf_verbose; 136227309Sedstatic SYSCTL_NODE(_debug, OID_AUTO, cpufreq, CTLFLAG_RD, NULL, 137227309Sed "cpufreq debugging"); 138267992ShselaskySYSCTL_INT(_debug_cpufreq, OID_AUTO, lowest, CTLFLAG_RWTUN, &cf_lowest_freq, 1, 139142590Snjl "Don't provide levels below this frequency."); 140267992ShselaskySYSCTL_INT(_debug_cpufreq, OID_AUTO, verbose, CTLFLAG_RWTUN, &cf_verbose, 1, 141144876Snjl "Print verbose debugging messages"); 142142590Snjl 143141240Snjlstatic int 144141240Snjlcpufreq_attach(device_t dev) 145141240Snjl{ 146141240Snjl struct cpufreq_softc *sc; 147186154Smav struct pcpu *pc; 148141240Snjl device_t parent; 149186154Smav uint64_t rate; 150141240Snjl int numdevs; 151141240Snjl 152144876Snjl CF_DEBUG("initializing %s\n", device_get_nameunit(dev)); 153141240Snjl sc = device_get_softc(dev); 154141240Snjl parent = device_get_parent(dev); 155141240Snjl sc->dev = dev; 156141240Snjl sysctl_ctx_init(&sc->sysctl_ctx); 157141240Snjl TAILQ_INIT(&sc->all_levels); 158142603Snjl CF_MTX_INIT(&sc->lock); 159141240Snjl sc->curr_level.total_set.freq = CPUFREQ_VAL_UNKNOWN; 160150847Sume SLIST_INIT(&sc->saved_freq); 161193155Snwhitehorn /* Try to get nominal CPU freq to use it as maximum later if needed */ 162193155Snwhitehorn sc->max_mhz = cpu_get_nominal_mhz(dev); 163193155Snwhitehorn /* If that fails, try to measure the current rate */ 164193155Snwhitehorn if (sc->max_mhz <= 0) { 165193155Snwhitehorn pc = cpu_get_pcpu(dev); 166193155Snwhitehorn if (cpu_est_clockrate(pc->pc_cpuid, &rate) == 0) 167193155Snwhitehorn sc->max_mhz = rate / 1000000; 168193155Snwhitehorn else 169193155Snwhitehorn sc->max_mhz = CPUFREQ_VAL_UNKNOWN; 170193155Snwhitehorn } 171141240Snjl 172141240Snjl /* 173141240Snjl * Only initialize one set of sysctls for all CPUs. In the future, 174141240Snjl * if multiple CPUs can have different settings, we can move these 175141240Snjl * sysctls to be under every CPU instead of just the first one. 176141240Snjl */ 177141240Snjl numdevs = devclass_get_count(cpufreq_dc); 178141240Snjl if (numdevs > 1) 179141240Snjl return (0); 180141240Snjl 181144876Snjl CF_DEBUG("initializing one-time data for %s\n", 182144876Snjl device_get_nameunit(dev)); 183210422Savg sc->levels_buf = malloc(CF_MAX_LEVELS * sizeof(*sc->levels_buf), 184210422Savg M_DEVBUF, M_WAITOK); 185141240Snjl SYSCTL_ADD_PROC(&sc->sysctl_ctx, 186141240Snjl SYSCTL_CHILDREN(device_get_sysctl_tree(parent)), 187141240Snjl OID_AUTO, "freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0, 188141240Snjl cpufreq_curr_sysctl, "I", "Current CPU frequency"); 189141240Snjl SYSCTL_ADD_PROC(&sc->sysctl_ctx, 190141240Snjl SYSCTL_CHILDREN(device_get_sysctl_tree(parent)), 191141240Snjl OID_AUTO, "freq_levels", CTLTYPE_STRING | CTLFLAG_RD, sc, 0, 192141240Snjl cpufreq_levels_sysctl, "A", "CPU frequency levels"); 193141240Snjl 194167905Snjl /* 195167905Snjl * Queue a one-shot broadcast that levels have changed. 196167905Snjl * It will run once the system has completed booting. 197167905Snjl */ 198167905Snjl TASK_INIT(&sc->startup_task, 0, cpufreq_startup_task, dev); 199167905Snjl taskqueue_enqueue(taskqueue_thread, &sc->startup_task); 200167905Snjl 201141240Snjl return (0); 202141240Snjl} 203141240Snjl 204167905Snjl/* Handle any work to be done for all drivers that attached during boot. */ 205167905Snjlstatic void 206167905Snjlcpufreq_startup_task(void *ctx, int pending) 207167905Snjl{ 208167905Snjl 209167905Snjl cpufreq_settings_changed((device_t)ctx); 210167905Snjl} 211167905Snjl 212141240Snjlstatic int 213141240Snjlcpufreq_detach(device_t dev) 214141240Snjl{ 215141240Snjl struct cpufreq_softc *sc; 216150847Sume struct cf_saved_freq *saved_freq; 217141240Snjl int numdevs; 218141240Snjl 219144876Snjl CF_DEBUG("shutdown %s\n", device_get_nameunit(dev)); 220141240Snjl sc = device_get_softc(dev); 221141240Snjl sysctl_ctx_free(&sc->sysctl_ctx); 222141240Snjl 223150847Sume while ((saved_freq = SLIST_FIRST(&sc->saved_freq)) != NULL) { 224150847Sume SLIST_REMOVE_HEAD(&sc->saved_freq, link); 225150847Sume free(saved_freq, M_TEMP); 226150847Sume } 227150847Sume 228141240Snjl /* Only clean up these resources when the last device is detaching. */ 229141240Snjl numdevs = devclass_get_count(cpufreq_dc); 230144876Snjl if (numdevs == 1) { 231144876Snjl CF_DEBUG("final shutdown for %s\n", device_get_nameunit(dev)); 232210422Savg free(sc->levels_buf, M_DEVBUF); 233144876Snjl } 234141240Snjl 235141240Snjl return (0); 236141240Snjl} 237141240Snjl 238141240Snjlstatic int 239141240Snjlcf_set_method(device_t dev, const struct cf_level *level, int priority) 240141240Snjl{ 241141240Snjl struct cpufreq_softc *sc; 242141240Snjl const struct cf_setting *set; 243150847Sume struct cf_saved_freq *saved_freq, *curr_freq; 244141814Snjl struct pcpu *pc; 245171898Snjl int error, i; 246141240Snjl 247141240Snjl sc = device_get_softc(dev); 248142603Snjl error = 0; 249142603Snjl set = NULL; 250150847Sume saved_freq = NULL; 251141240Snjl 252167905Snjl /* We are going to change levels so notify the pre-change handler. */ 253167905Snjl EVENTHANDLER_INVOKE(cpufreq_pre_change, level, &error); 254167905Snjl if (error != 0) { 255167905Snjl EVENTHANDLER_INVOKE(cpufreq_post_change, level, error); 256167905Snjl return (error); 257156228Smnag } 258141814Snjl 259150847Sume CF_MTX_LOCK(&sc->lock); 260150847Sume 261173204Snjl#ifdef SMP 262299746Sjhb#ifdef EARLY_AP_STARTUP 263299746Sjhb MPASS(mp_ncpus == 1 || smp_started); 264299746Sjhb#else 265141923Snjl /* 266173204Snjl * If still booting and secondary CPUs not started yet, don't allow 267173204Snjl * changing the frequency until they're online. This is because we 268173204Snjl * can't switch to them using sched_bind() and thus we'd only be 269173204Snjl * switching the main CPU. XXXTODO: Need to think more about how to 270173204Snjl * handle having different CPUs at different frequencies. 271173204Snjl */ 272264984Sscottl if (mp_ncpus > 1 && !smp_started) { 273173204Snjl device_printf(dev, "rejecting change, SMP not started yet\n"); 274173204Snjl error = ENXIO; 275173204Snjl goto out; 276173204Snjl } 277299746Sjhb#endif 278173204Snjl#endif /* SMP */ 279173204Snjl 280173204Snjl /* 281150847Sume * If the requested level has a lower priority, don't allow 282150847Sume * the new level right now. 283150847Sume */ 284150847Sume if (priority < sc->curr_priority) { 285150847Sume CF_DEBUG("ignoring, curr prio %d less than %d\n", priority, 286150847Sume sc->curr_priority); 287150847Sume error = EPERM; 288150847Sume goto out; 289150847Sume } 290150847Sume 291150847Sume /* 292141923Snjl * If the caller didn't specify a level and one is saved, prepare to 293141923Snjl * restore the saved level. If none has been saved, return an error. 294141923Snjl */ 295141923Snjl if (level == NULL) { 296150847Sume saved_freq = SLIST_FIRST(&sc->saved_freq); 297150847Sume if (saved_freq == NULL) { 298144876Snjl CF_DEBUG("NULL level, no saved level\n"); 299142603Snjl error = ENXIO; 300142603Snjl goto out; 301142603Snjl } 302150847Sume level = &saved_freq->level; 303150847Sume priority = saved_freq->priority; 304150847Sume CF_DEBUG("restoring saved level, freq %d prio %d\n", 305150847Sume level->total_set.freq, priority); 306142603Snjl } 307141923Snjl 308142590Snjl /* Reject levels that are below our specified threshold. */ 309148972Snjl if (level->total_set.freq < cf_lowest_freq) { 310144876Snjl CF_DEBUG("rejecting freq %d, less than %d limit\n", 311144876Snjl level->total_set.freq, cf_lowest_freq); 312142603Snjl error = EINVAL; 313142603Snjl goto out; 314142603Snjl } 315142590Snjl 316141240Snjl /* If already at this level, just return. */ 317232793Smav if (sc->curr_level.total_set.freq == level->total_set.freq) { 318144876Snjl CF_DEBUG("skipping freq %d, same as current level %d\n", 319144876Snjl level->total_set.freq, sc->curr_level.total_set.freq); 320149239Sume goto skip; 321144876Snjl } 322141240Snjl 323141240Snjl /* First, set the absolute frequency via its driver. */ 324141240Snjl set = &level->abs_set; 325141240Snjl if (set->dev) { 326141240Snjl if (!device_is_attached(set->dev)) { 327141240Snjl error = ENXIO; 328141240Snjl goto out; 329141240Snjl } 330141943Snjl 331171898Snjl /* Bind to the target CPU before switching. */ 332141943Snjl pc = cpu_get_pcpu(set->dev); 333171898Snjl thread_lock(curthread); 334171898Snjl sched_bind(curthread, pc->pc_cpuid); 335171898Snjl thread_unlock(curthread); 336144876Snjl CF_DEBUG("setting abs freq %d on %s (cpu %d)\n", set->freq, 337144876Snjl device_get_nameunit(set->dev), PCPU_GET(cpuid)); 338141240Snjl error = CPUFREQ_DRV_SET(set->dev, set); 339171898Snjl thread_lock(curthread); 340171898Snjl sched_unbind(curthread); 341171898Snjl thread_unlock(curthread); 342141240Snjl if (error) { 343141240Snjl goto out; 344141240Snjl } 345141240Snjl } 346141240Snjl 347141413Snjl /* Next, set any/all relative frequencies via their drivers. */ 348141413Snjl for (i = 0; i < level->rel_count; i++) { 349141413Snjl set = &level->rel_set[i]; 350141413Snjl if (!device_is_attached(set->dev)) { 351141413Snjl error = ENXIO; 352141413Snjl goto out; 353141413Snjl } 354141943Snjl 355171898Snjl /* Bind to the target CPU before switching. */ 356141943Snjl pc = cpu_get_pcpu(set->dev); 357171898Snjl thread_lock(curthread); 358171898Snjl sched_bind(curthread, pc->pc_cpuid); 359171898Snjl thread_unlock(curthread); 360144876Snjl CF_DEBUG("setting rel freq %d on %s (cpu %d)\n", set->freq, 361144876Snjl device_get_nameunit(set->dev), PCPU_GET(cpuid)); 362141413Snjl error = CPUFREQ_DRV_SET(set->dev, set); 363171898Snjl thread_lock(curthread); 364171898Snjl sched_unbind(curthread); 365171898Snjl thread_unlock(curthread); 366141413Snjl if (error) { 367141413Snjl /* XXX Back out any successful setting? */ 368141413Snjl goto out; 369141413Snjl } 370141413Snjl } 371141240Snjl 372149239Sumeskip: 373141923Snjl /* 374141923Snjl * Before recording the current level, check if we're going to a 375150847Sume * higher priority. If so, save the previous level and priority. 376141923Snjl */ 377141923Snjl if (sc->curr_level.total_set.freq != CPUFREQ_VAL_UNKNOWN && 378150847Sume priority > sc->curr_priority) { 379144876Snjl CF_DEBUG("saving level, freq %d prio %d\n", 380144876Snjl sc->curr_level.total_set.freq, sc->curr_priority); 381150847Sume curr_freq = malloc(sizeof(*curr_freq), M_TEMP, M_NOWAIT); 382150847Sume if (curr_freq == NULL) { 383150847Sume error = ENOMEM; 384150847Sume goto out; 385150847Sume } 386150847Sume curr_freq->level = sc->curr_level; 387150847Sume curr_freq->priority = sc->curr_priority; 388150847Sume SLIST_INSERT_HEAD(&sc->saved_freq, curr_freq, link); 389141923Snjl } 390141240Snjl sc->curr_level = *level; 391141923Snjl sc->curr_priority = priority; 392141240Snjl 393150847Sume /* If we were restoring a saved state, reset it to "unused". */ 394150847Sume if (saved_freq != NULL) { 395150847Sume CF_DEBUG("resetting saved level\n"); 396150847Sume sc->curr_level.total_set.freq = CPUFREQ_VAL_UNKNOWN; 397150847Sume SLIST_REMOVE_HEAD(&sc->saved_freq, link); 398150847Sume free(saved_freq, M_TEMP); 399150847Sume } 400150847Sume 401141240Snjlout: 402142603Snjl CF_MTX_UNLOCK(&sc->lock); 403167905Snjl 404167905Snjl /* 405167905Snjl * We changed levels (or attempted to) so notify the post-change 406167905Snjl * handler of new frequency or error. 407167905Snjl */ 408167905Snjl EVENTHANDLER_INVOKE(cpufreq_post_change, level, error); 409142603Snjl if (error && set) 410141240Snjl device_printf(set->dev, "set freq failed, err %d\n", error); 411167905Snjl 412141240Snjl return (error); 413141240Snjl} 414141240Snjl 415141240Snjlstatic int 416141240Snjlcf_get_method(device_t dev, struct cf_level *level) 417141240Snjl{ 418141240Snjl struct cpufreq_softc *sc; 419141240Snjl struct cf_level *levels; 420141240Snjl struct cf_setting *curr_set, set; 421141240Snjl struct pcpu *pc; 422141240Snjl device_t *devs; 423265876Scperciva int bdiff, count, diff, error, i, n, numdevs; 424141240Snjl uint64_t rate; 425141240Snjl 426141240Snjl sc = device_get_softc(dev); 427142603Snjl error = 0; 428141240Snjl levels = NULL; 429141240Snjl 430141240Snjl /* If we already know the current frequency, we're done. */ 431142603Snjl CF_MTX_LOCK(&sc->lock); 432142603Snjl curr_set = &sc->curr_level.total_set; 433144876Snjl if (curr_set->freq != CPUFREQ_VAL_UNKNOWN) { 434144876Snjl CF_DEBUG("get returning known freq %d\n", curr_set->freq); 435141240Snjl goto out; 436144876Snjl } 437142603Snjl CF_MTX_UNLOCK(&sc->lock); 438141240Snjl 439141240Snjl /* 440141240Snjl * We need to figure out the current level. Loop through every 441141240Snjl * driver, getting the current setting. Then, attempt to get a best 442141240Snjl * match of settings against each level. 443141240Snjl */ 444141240Snjl count = CF_MAX_LEVELS; 445141240Snjl levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT); 446141240Snjl if (levels == NULL) 447141240Snjl return (ENOMEM); 448141240Snjl error = CPUFREQ_LEVELS(sc->dev, levels, &count); 449142395Snjl if (error) { 450142395Snjl if (error == E2BIG) 451142395Snjl printf("cpufreq: need to increase CF_MAX_LEVELS\n"); 452142603Snjl free(levels, M_TEMP); 453142603Snjl return (error); 454142395Snjl } 455141240Snjl error = device_get_children(device_get_parent(dev), &devs, &numdevs); 456142603Snjl if (error) { 457142603Snjl free(levels, M_TEMP); 458142603Snjl return (error); 459142603Snjl } 460142603Snjl 461142603Snjl /* 462142603Snjl * Reacquire the lock and search for the given level. 463142603Snjl * 464142603Snjl * XXX Note: this is not quite right since we really need to go 465142603Snjl * through each level and compare both absolute and relative 466142603Snjl * settings for each driver in the system before making a match. 467142603Snjl * The estimation code below catches this case though. 468142603Snjl */ 469142603Snjl CF_MTX_LOCK(&sc->lock); 470171896Snjl for (n = 0; n < numdevs && curr_set->freq == CPUFREQ_VAL_UNKNOWN; n++) { 471171896Snjl if (!device_is_attached(devs[n])) 472141240Snjl continue; 473178787Sjhb if (CPUFREQ_DRV_GET(devs[n], &set) != 0) 474141240Snjl continue; 475141240Snjl for (i = 0; i < count; i++) { 476232793Smav if (set.freq == levels[i].total_set.freq) { 477141240Snjl sc->curr_level = levels[i]; 478141240Snjl break; 479141240Snjl } 480141240Snjl } 481141240Snjl } 482141240Snjl free(devs, M_TEMP); 483144876Snjl if (curr_set->freq != CPUFREQ_VAL_UNKNOWN) { 484144876Snjl CF_DEBUG("get matched freq %d from drivers\n", curr_set->freq); 485141240Snjl goto out; 486144876Snjl } 487141240Snjl 488141240Snjl /* 489141240Snjl * We couldn't find an exact match, so attempt to estimate and then 490141240Snjl * match against a level. 491141240Snjl */ 492141240Snjl pc = cpu_get_pcpu(dev); 493141240Snjl if (pc == NULL) { 494141240Snjl error = ENXIO; 495141240Snjl goto out; 496141240Snjl } 497141240Snjl cpu_est_clockrate(pc->pc_cpuid, &rate); 498141240Snjl rate /= 1000000; 499265876Scperciva bdiff = 1 << 30; 500141240Snjl for (i = 0; i < count; i++) { 501265876Scperciva diff = abs(levels[i].total_set.freq - rate); 502265876Scperciva if (diff < bdiff) { 503265876Scperciva bdiff = diff; 504141240Snjl sc->curr_level = levels[i]; 505141240Snjl } 506141240Snjl } 507265876Scperciva CF_DEBUG("get estimated freq %d\n", curr_set->freq); 508141240Snjl 509141240Snjlout: 510142603Snjl if (error == 0) 511142603Snjl *level = sc->curr_level; 512142603Snjl 513142603Snjl CF_MTX_UNLOCK(&sc->lock); 514141240Snjl if (levels) 515141240Snjl free(levels, M_TEMP); 516142603Snjl return (error); 517141240Snjl} 518141240Snjl 519141240Snjlstatic int 520141240Snjlcf_levels_method(device_t dev, struct cf_level *levels, int *count) 521141240Snjl{ 522141413Snjl struct cf_setting_array *set_arr; 523141240Snjl struct cf_setting_lst rel_sets; 524141240Snjl struct cpufreq_softc *sc; 525141240Snjl struct cf_level *lev; 526141240Snjl struct cf_setting *sets; 527141240Snjl struct pcpu *pc; 528141240Snjl device_t *devs; 529141413Snjl int error, i, numdevs, set_count, type; 530141240Snjl uint64_t rate; 531141240Snjl 532141240Snjl if (levels == NULL || count == NULL) 533141240Snjl return (EINVAL); 534141240Snjl 535141240Snjl TAILQ_INIT(&rel_sets); 536141240Snjl sc = device_get_softc(dev); 537141240Snjl error = device_get_children(device_get_parent(dev), &devs, &numdevs); 538141240Snjl if (error) 539141240Snjl return (error); 540141240Snjl sets = malloc(MAX_SETTINGS * sizeof(*sets), M_TEMP, M_NOWAIT); 541141240Snjl if (sets == NULL) { 542141240Snjl free(devs, M_TEMP); 543141240Snjl return (ENOMEM); 544141240Snjl } 545141240Snjl 546141240Snjl /* Get settings from all cpufreq drivers. */ 547142603Snjl CF_MTX_LOCK(&sc->lock); 548141240Snjl for (i = 0; i < numdevs; i++) { 549141824Snjl /* Skip devices that aren't ready. */ 550141240Snjl if (!device_is_attached(devs[i])) 551141240Snjl continue; 552141824Snjl 553141824Snjl /* 554141824Snjl * Get settings, skipping drivers that offer no settings or 555141824Snjl * provide settings for informational purposes only. 556141824Snjl */ 557142032Snjl error = CPUFREQ_DRV_TYPE(devs[i], &type); 558144876Snjl if (error || (type & CPUFREQ_FLAG_INFO_ONLY)) { 559144876Snjl if (error == 0) { 560144876Snjl CF_DEBUG("skipping info-only driver %s\n", 561144876Snjl device_get_nameunit(devs[i])); 562144876Snjl } 563142032Snjl continue; 564144876Snjl } 565141240Snjl set_count = MAX_SETTINGS; 566142032Snjl error = CPUFREQ_DRV_SETTINGS(devs[i], sets, &set_count); 567142032Snjl if (error || set_count == 0) 568141240Snjl continue; 569141413Snjl 570141824Snjl /* Add the settings to our absolute/relative lists. */ 571141814Snjl switch (type & CPUFREQ_TYPE_MASK) { 572141413Snjl case CPUFREQ_TYPE_ABSOLUTE: 573141413Snjl error = cpufreq_insert_abs(sc, sets, set_count); 574141413Snjl break; 575141413Snjl case CPUFREQ_TYPE_RELATIVE: 576144876Snjl CF_DEBUG("adding %d relative settings\n", set_count); 577141413Snjl set_arr = malloc(sizeof(*set_arr), M_TEMP, M_NOWAIT); 578141413Snjl if (set_arr == NULL) { 579141413Snjl error = ENOMEM; 580141413Snjl goto out; 581141413Snjl } 582141413Snjl bcopy(sets, set_arr->sets, set_count * sizeof(*sets)); 583141413Snjl set_arr->count = set_count; 584141413Snjl TAILQ_INSERT_TAIL(&rel_sets, set_arr, link); 585141413Snjl break; 586141413Snjl default: 587141413Snjl error = EINVAL; 588141413Snjl } 589141240Snjl if (error) 590141240Snjl goto out; 591141240Snjl } 592141240Snjl 593141945Snjl /* 594141945Snjl * If there are no absolute levels, create a fake one at 100%. We 595141945Snjl * then cache the clockrate for later use as our base frequency. 596141945Snjl */ 597141240Snjl if (TAILQ_EMPTY(&sc->all_levels)) { 598141945Snjl if (sc->max_mhz == CPUFREQ_VAL_UNKNOWN) { 599193155Snwhitehorn sc->max_mhz = cpu_get_nominal_mhz(dev); 600193155Snwhitehorn /* 601193155Snwhitehorn * If the CPU can't report a rate for 100%, hope 602193155Snwhitehorn * the CPU is running at its nominal rate right now, 603193155Snwhitehorn * and use that instead. 604193155Snwhitehorn */ 605193155Snwhitehorn if (sc->max_mhz <= 0) { 606193155Snwhitehorn pc = cpu_get_pcpu(dev); 607193155Snwhitehorn cpu_est_clockrate(pc->pc_cpuid, &rate); 608193155Snwhitehorn sc->max_mhz = rate / 1000000; 609193155Snwhitehorn } 610141240Snjl } 611141945Snjl memset(&sets[0], CPUFREQ_VAL_UNKNOWN, sizeof(*sets)); 612141945Snjl sets[0].freq = sc->max_mhz; 613141945Snjl sets[0].dev = NULL; 614141413Snjl error = cpufreq_insert_abs(sc, sets, 1); 615141240Snjl if (error) 616141240Snjl goto out; 617141240Snjl } 618141240Snjl 619141413Snjl /* Create a combined list of absolute + relative levels. */ 620141413Snjl TAILQ_FOREACH(set_arr, &rel_sets, link) 621141413Snjl cpufreq_expand_set(sc, set_arr); 622141413Snjl 623141413Snjl /* If the caller doesn't have enough space, return the actual count. */ 624141413Snjl if (sc->all_count > *count) { 625141413Snjl *count = sc->all_count; 626141413Snjl error = E2BIG; 627141413Snjl goto out; 628141413Snjl } 629141413Snjl 630141413Snjl /* Finally, output the list of levels. */ 631141240Snjl i = 0; 632141240Snjl TAILQ_FOREACH(lev, &sc->all_levels, link) { 633175376Snjl 634142590Snjl /* Skip levels that have a frequency that is too low. */ 635148972Snjl if (lev->total_set.freq < cf_lowest_freq) { 636142590Snjl sc->all_count--; 637142590Snjl continue; 638142590Snjl } 639142590Snjl 640141240Snjl levels[i] = *lev; 641141240Snjl i++; 642141240Snjl } 643141413Snjl *count = sc->all_count; 644141240Snjl error = 0; 645141240Snjl 646141240Snjlout: 647141240Snjl /* Clear all levels since we regenerate them each time. */ 648141240Snjl while ((lev = TAILQ_FIRST(&sc->all_levels)) != NULL) { 649141240Snjl TAILQ_REMOVE(&sc->all_levels, lev, link); 650141240Snjl free(lev, M_TEMP); 651141240Snjl } 652142603Snjl sc->all_count = 0; 653142603Snjl 654142603Snjl CF_MTX_UNLOCK(&sc->lock); 655141413Snjl while ((set_arr = TAILQ_FIRST(&rel_sets)) != NULL) { 656141413Snjl TAILQ_REMOVE(&rel_sets, set_arr, link); 657141413Snjl free(set_arr, M_TEMP); 658141413Snjl } 659141240Snjl free(devs, M_TEMP); 660141240Snjl free(sets, M_TEMP); 661141240Snjl return (error); 662141240Snjl} 663141240Snjl 664141240Snjl/* 665141240Snjl * Create levels for an array of absolute settings and insert them in 666141240Snjl * sorted order in the specified list. 667141240Snjl */ 668141240Snjlstatic int 669141413Snjlcpufreq_insert_abs(struct cpufreq_softc *sc, struct cf_setting *sets, 670141240Snjl int count) 671141240Snjl{ 672141413Snjl struct cf_level_lst *list; 673141240Snjl struct cf_level *level, *search; 674141240Snjl int i; 675141240Snjl 676142603Snjl CF_MTX_ASSERT(&sc->lock); 677142603Snjl 678141413Snjl list = &sc->all_levels; 679141240Snjl for (i = 0; i < count; i++) { 680141240Snjl level = malloc(sizeof(*level), M_TEMP, M_NOWAIT | M_ZERO); 681141240Snjl if (level == NULL) 682141240Snjl return (ENOMEM); 683141240Snjl level->abs_set = sets[i]; 684141413Snjl level->total_set = sets[i]; 685141413Snjl level->total_set.dev = NULL; 686141413Snjl sc->all_count++; 687141240Snjl 688141240Snjl if (TAILQ_EMPTY(list)) { 689144876Snjl CF_DEBUG("adding abs setting %d at head\n", 690144876Snjl sets[i].freq); 691141240Snjl TAILQ_INSERT_HEAD(list, level, link); 692141240Snjl continue; 693141240Snjl } 694141240Snjl 695141240Snjl TAILQ_FOREACH_REVERSE(search, list, cf_level_lst, link) { 696141413Snjl if (sets[i].freq <= search->total_set.freq) { 697144876Snjl CF_DEBUG("adding abs setting %d after %d\n", 698144876Snjl sets[i].freq, search->total_set.freq); 699141240Snjl TAILQ_INSERT_AFTER(list, search, level, link); 700141240Snjl break; 701141240Snjl } 702141240Snjl } 703141240Snjl } 704141240Snjl return (0); 705141240Snjl} 706141240Snjl 707141413Snjl/* 708141413Snjl * Expand a group of relative settings, creating derived levels from them. 709141413Snjl */ 710141240Snjlstatic int 711141413Snjlcpufreq_expand_set(struct cpufreq_softc *sc, struct cf_setting_array *set_arr) 712141413Snjl{ 713141413Snjl struct cf_level *fill, *search; 714141413Snjl struct cf_setting *set; 715141413Snjl int i; 716141413Snjl 717142603Snjl CF_MTX_ASSERT(&sc->lock); 718142603Snjl 719149607Snjl /* 720149607Snjl * Walk the set of all existing levels in reverse. This is so we 721149607Snjl * create derived states from the lowest absolute settings first 722149607Snjl * and discard duplicates created from higher absolute settings. 723149607Snjl * For instance, a level of 50 Mhz derived from 100 Mhz + 50% is 724149607Snjl * preferable to 200 Mhz + 25% because absolute settings are more 725149607Snjl * efficient since they often change the voltage as well. 726149607Snjl */ 727149607Snjl TAILQ_FOREACH_REVERSE(search, &sc->all_levels, cf_level_lst, link) { 728141413Snjl /* Add each setting to the level, duplicating if necessary. */ 729141413Snjl for (i = 0; i < set_arr->count; i++) { 730141413Snjl set = &set_arr->sets[i]; 731141413Snjl 732141413Snjl /* 733141413Snjl * If this setting is less than 100%, split the level 734141413Snjl * into two and add this setting to the new level. 735141413Snjl */ 736141413Snjl fill = search; 737149607Snjl if (set->freq < 10000) { 738141413Snjl fill = cpufreq_dup_set(sc, search, set); 739141413Snjl 740149607Snjl /* 741149607Snjl * The new level was a duplicate of an existing 742149607Snjl * level or its absolute setting is too high 743149607Snjl * so we freed it. For example, we discard a 744149607Snjl * derived level of 1000 MHz/25% if a level 745149607Snjl * of 500 MHz/100% already exists. 746149607Snjl */ 747149607Snjl if (fill == NULL) 748149607Snjl break; 749149607Snjl } 750141413Snjl 751141413Snjl /* Add this setting to the existing or new level. */ 752141413Snjl KASSERT(fill->rel_count < MAX_SETTINGS, 753141413Snjl ("cpufreq: too many relative drivers (%d)", 754141413Snjl MAX_SETTINGS)); 755141413Snjl fill->rel_set[fill->rel_count] = *set; 756141413Snjl fill->rel_count++; 757144876Snjl CF_DEBUG( 758144876Snjl "expand set added rel setting %d%% to %d level\n", 759144876Snjl set->freq / 100, fill->total_set.freq); 760141413Snjl } 761141413Snjl } 762141413Snjl 763141413Snjl return (0); 764141413Snjl} 765141413Snjl 766141413Snjlstatic struct cf_level * 767141413Snjlcpufreq_dup_set(struct cpufreq_softc *sc, struct cf_level *dup, 768141413Snjl struct cf_setting *set) 769141413Snjl{ 770141413Snjl struct cf_level_lst *list; 771141413Snjl struct cf_level *fill, *itr; 772141413Snjl struct cf_setting *fill_set, *itr_set; 773141413Snjl int i; 774141413Snjl 775142603Snjl CF_MTX_ASSERT(&sc->lock); 776142603Snjl 777141413Snjl /* 778141413Snjl * Create a new level, copy it from the old one, and update the 779141413Snjl * total frequency and power by the percentage specified in the 780141413Snjl * relative setting. 781141413Snjl */ 782141413Snjl fill = malloc(sizeof(*fill), M_TEMP, M_NOWAIT); 783141413Snjl if (fill == NULL) 784141413Snjl return (NULL); 785141413Snjl *fill = *dup; 786141413Snjl fill_set = &fill->total_set; 787141413Snjl fill_set->freq = 788141413Snjl ((uint64_t)fill_set->freq * set->freq) / 10000; 789141413Snjl if (fill_set->power != CPUFREQ_VAL_UNKNOWN) { 790141413Snjl fill_set->power = ((uint64_t)fill_set->power * set->freq) 791141413Snjl / 10000; 792141413Snjl } 793141413Snjl if (set->lat != CPUFREQ_VAL_UNKNOWN) { 794141413Snjl if (fill_set->lat != CPUFREQ_VAL_UNKNOWN) 795141413Snjl fill_set->lat += set->lat; 796141413Snjl else 797141413Snjl fill_set->lat = set->lat; 798141413Snjl } 799144876Snjl CF_DEBUG("dup set considering derived setting %d\n", fill_set->freq); 800141413Snjl 801141413Snjl /* 802141413Snjl * If we copied an old level that we already modified (say, at 100%), 803141413Snjl * we need to remove that setting before adding this one. Since we 804141413Snjl * process each setting array in order, we know any settings for this 805141413Snjl * driver will be found at the end. 806141413Snjl */ 807141413Snjl for (i = fill->rel_count; i != 0; i--) { 808141413Snjl if (fill->rel_set[i - 1].dev != set->dev) 809141413Snjl break; 810144876Snjl CF_DEBUG("removed last relative driver: %s\n", 811144876Snjl device_get_nameunit(set->dev)); 812141413Snjl fill->rel_count--; 813141413Snjl } 814141413Snjl 815141413Snjl /* 816149607Snjl * Insert the new level in sorted order. If it is a duplicate of an 817149607Snjl * existing level (1) or has an absolute setting higher than the 818149607Snjl * existing level (2), do not add it. We can do this since any such 819149607Snjl * level is guaranteed use less power. For example (1), a level with 820149607Snjl * one absolute setting of 800 Mhz uses less power than one composed 821149607Snjl * of an absolute setting of 1600 Mhz and a relative setting at 50%. 822149607Snjl * Also for example (2), a level of 800 Mhz/75% is preferable to 823149607Snjl * 1600 Mhz/25% even though the latter has a lower total frequency. 824141413Snjl */ 825141413Snjl list = &sc->all_levels; 826149607Snjl KASSERT(!TAILQ_EMPTY(list), ("all levels list empty in dup set")); 827149607Snjl TAILQ_FOREACH_REVERSE(itr, list, cf_level_lst, link) { 828149607Snjl itr_set = &itr->total_set; 829149724Snjl if (CPUFREQ_CMP(fill_set->freq, itr_set->freq)) { 830149724Snjl CF_DEBUG("dup set rejecting %d (dupe)\n", 831149724Snjl fill_set->freq); 832149724Snjl itr = NULL; 833149607Snjl break; 834149724Snjl } else if (fill_set->freq < itr_set->freq) { 835149724Snjl if (fill->abs_set.freq <= itr->abs_set.freq) { 836149724Snjl CF_DEBUG( 837149724Snjl "dup done, inserting new level %d after %d\n", 838149724Snjl fill_set->freq, itr_set->freq); 839149724Snjl TAILQ_INSERT_AFTER(list, itr, fill, link); 840149724Snjl sc->all_count++; 841149724Snjl } else { 842149724Snjl CF_DEBUG("dup set rejecting %d (abs too big)\n", 843149724Snjl fill_set->freq); 844149724Snjl itr = NULL; 845149724Snjl } 846149724Snjl break; 847141413Snjl } 848141413Snjl } 849141413Snjl 850149607Snjl /* We didn't find a good place for this new level so free it. */ 851149607Snjl if (itr == NULL) { 852149607Snjl CF_DEBUG("dup set freeing new level %d (not optimal)\n", 853149607Snjl fill_set->freq); 854149607Snjl free(fill, M_TEMP); 855149607Snjl fill = NULL; 856149607Snjl } 857149607Snjl 858141413Snjl return (fill); 859141413Snjl} 860141413Snjl 861141413Snjlstatic int 862141240Snjlcpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS) 863141240Snjl{ 864141240Snjl struct cpufreq_softc *sc; 865141240Snjl struct cf_level *levels; 866232793Smav int best, count, diff, bdiff, devcount, error, freq, i, n; 867141814Snjl device_t *devs; 868141240Snjl 869141814Snjl devs = NULL; 870141240Snjl sc = oidp->oid_arg1; 871210422Savg levels = sc->levels_buf; 872141240Snjl 873141240Snjl error = CPUFREQ_GET(sc->dev, &levels[0]); 874141240Snjl if (error) 875141240Snjl goto out; 876141240Snjl freq = levels[0].total_set.freq; 877141240Snjl error = sysctl_handle_int(oidp, &freq, 0, req); 878141240Snjl if (error != 0 || req->newptr == NULL) 879141240Snjl goto out; 880141240Snjl 881141814Snjl /* 882141814Snjl * While we only call cpufreq_get() on one device (assuming all 883141814Snjl * CPUs have equal levels), we call cpufreq_set() on all CPUs. 884141814Snjl * This is needed for some MP systems. 885141814Snjl */ 886141814Snjl error = devclass_get_devices(cpufreq_dc, &devs, &devcount); 887141240Snjl if (error) 888141240Snjl goto out; 889141814Snjl for (n = 0; n < devcount; n++) { 890141814Snjl count = CF_MAX_LEVELS; 891141814Snjl error = CPUFREQ_LEVELS(devs[n], levels, &count); 892142395Snjl if (error) { 893142395Snjl if (error == E2BIG) 894142395Snjl printf( 895142395Snjl "cpufreq: need to increase CF_MAX_LEVELS\n"); 896141240Snjl break; 897142395Snjl } 898232793Smav best = 0; 899232793Smav bdiff = 1 << 30; 900141814Snjl for (i = 0; i < count; i++) { 901232793Smav diff = abs(levels[i].total_set.freq - freq); 902232793Smav if (diff < bdiff) { 903232793Smav bdiff = diff; 904232793Smav best = i; 905141814Snjl } 906141240Snjl } 907232793Smav error = CPUFREQ_SET(devs[n], &levels[best], CPUFREQ_PRIO_USER); 908141240Snjl } 909141240Snjl 910141240Snjlout: 911141814Snjl if (devs) 912141814Snjl free(devs, M_TEMP); 913141240Snjl return (error); 914141240Snjl} 915141240Snjl 916141240Snjlstatic int 917141240Snjlcpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS) 918141240Snjl{ 919141240Snjl struct cpufreq_softc *sc; 920141240Snjl struct cf_level *levels; 921141240Snjl struct cf_setting *set; 922141240Snjl struct sbuf sb; 923141240Snjl int count, error, i; 924141240Snjl 925141240Snjl sc = oidp->oid_arg1; 926141240Snjl sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND); 927141240Snjl 928141240Snjl /* Get settings from the device and generate the output string. */ 929141240Snjl count = CF_MAX_LEVELS; 930210422Savg levels = sc->levels_buf; 931201848Sbrueffer if (levels == NULL) { 932201848Sbrueffer sbuf_delete(&sb); 933141240Snjl return (ENOMEM); 934201848Sbrueffer } 935141240Snjl error = CPUFREQ_LEVELS(sc->dev, levels, &count); 936142395Snjl if (error) { 937142395Snjl if (error == E2BIG) 938142395Snjl printf("cpufreq: need to increase CF_MAX_LEVELS\n"); 939141240Snjl goto out; 940142395Snjl } 941141240Snjl if (count) { 942141240Snjl for (i = 0; i < count; i++) { 943141240Snjl set = &levels[i].total_set; 944141240Snjl sbuf_printf(&sb, "%d/%d ", set->freq, set->power); 945141240Snjl } 946141240Snjl } else 947141240Snjl sbuf_cpy(&sb, "0"); 948141240Snjl sbuf_trim(&sb); 949141240Snjl sbuf_finish(&sb); 950141240Snjl error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); 951141240Snjl 952141240Snjlout: 953141240Snjl sbuf_delete(&sb); 954141240Snjl return (error); 955141240Snjl} 956141240Snjl 957142114Snjlstatic int 958142114Snjlcpufreq_settings_sysctl(SYSCTL_HANDLER_ARGS) 959142114Snjl{ 960142114Snjl device_t dev; 961142114Snjl struct cf_setting *sets; 962142114Snjl struct sbuf sb; 963142114Snjl int error, i, set_count; 964142114Snjl 965142114Snjl dev = oidp->oid_arg1; 966142114Snjl sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND); 967142114Snjl 968142114Snjl /* Get settings from the device and generate the output string. */ 969142114Snjl set_count = MAX_SETTINGS; 970142114Snjl sets = malloc(set_count * sizeof(*sets), M_TEMP, M_NOWAIT); 971201848Sbrueffer if (sets == NULL) { 972201848Sbrueffer sbuf_delete(&sb); 973142114Snjl return (ENOMEM); 974201848Sbrueffer } 975142114Snjl error = CPUFREQ_DRV_SETTINGS(dev, sets, &set_count); 976142114Snjl if (error) 977142114Snjl goto out; 978142114Snjl if (set_count) { 979142114Snjl for (i = 0; i < set_count; i++) 980142114Snjl sbuf_printf(&sb, "%d/%d ", sets[i].freq, sets[i].power); 981142114Snjl } else 982142114Snjl sbuf_cpy(&sb, "0"); 983142114Snjl sbuf_trim(&sb); 984142114Snjl sbuf_finish(&sb); 985142114Snjl error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); 986142114Snjl 987142114Snjlout: 988142114Snjl free(sets, M_TEMP); 989142114Snjl sbuf_delete(&sb); 990142114Snjl return (error); 991142114Snjl} 992142114Snjl 993141240Snjlint 994141240Snjlcpufreq_register(device_t dev) 995141240Snjl{ 996141945Snjl struct cpufreq_softc *sc; 997141240Snjl device_t cf_dev, cpu_dev; 998141240Snjl 999142114Snjl /* Add a sysctl to get each driver's settings separately. */ 1000142114Snjl SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), 1001142114Snjl SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), 1002142114Snjl OID_AUTO, "freq_settings", CTLTYPE_STRING | CTLFLAG_RD, dev, 0, 1003142114Snjl cpufreq_settings_sysctl, "A", "CPU frequency driver settings"); 1004142114Snjl 1005141240Snjl /* 1006141814Snjl * Add only one cpufreq device to each CPU. Currently, all CPUs 1007141814Snjl * must offer the same levels and be switched at the same time. 1008141240Snjl */ 1009141814Snjl cpu_dev = device_get_parent(dev); 1010141945Snjl if ((cf_dev = device_find_child(cpu_dev, "cpufreq", -1))) { 1011141945Snjl sc = device_get_softc(cf_dev); 1012141945Snjl sc->max_mhz = CPUFREQ_VAL_UNKNOWN; 1013141240Snjl return (0); 1014141945Snjl } 1015141240Snjl 1016141814Snjl /* Add the child device and possibly sysctls. */ 1017141814Snjl cf_dev = BUS_ADD_CHILD(cpu_dev, 0, "cpufreq", -1); 1018141240Snjl if (cf_dev == NULL) 1019141240Snjl return (ENOMEM); 1020141240Snjl device_quiet(cf_dev); 1021141240Snjl 1022141240Snjl return (device_probe_and_attach(cf_dev)); 1023141240Snjl} 1024141240Snjl 1025141240Snjlint 1026141240Snjlcpufreq_unregister(device_t dev) 1027141240Snjl{ 1028141240Snjl device_t cf_dev, *devs; 1029142032Snjl int cfcount, devcount, error, i, type; 1030141240Snjl 1031141240Snjl /* 1032141240Snjl * If this is the last cpufreq child device, remove the control 1033141240Snjl * device as well. We identify cpufreq children by calling a method 1034141240Snjl * they support. 1035141240Snjl */ 1036141240Snjl error = device_get_children(device_get_parent(dev), &devs, &devcount); 1037141240Snjl if (error) 1038141240Snjl return (error); 1039141945Snjl cf_dev = device_find_child(device_get_parent(dev), "cpufreq", -1); 1040144413Snjl if (cf_dev == NULL) { 1041144413Snjl device_printf(dev, 1042144413Snjl "warning: cpufreq_unregister called with no cpufreq device active\n"); 1043265244Sbrueffer free(devs, M_TEMP); 1044144413Snjl return (0); 1045144413Snjl } 1046141240Snjl cfcount = 0; 1047141240Snjl for (i = 0; i < devcount; i++) { 1048141240Snjl if (!device_is_attached(devs[i])) 1049141240Snjl continue; 1050142032Snjl if (CPUFREQ_DRV_TYPE(devs[i], &type) == 0) 1051141240Snjl cfcount++; 1052141240Snjl } 1053141814Snjl if (cfcount <= 1) 1054141240Snjl device_delete_child(device_get_parent(cf_dev), cf_dev); 1055141240Snjl free(devs, M_TEMP); 1056141240Snjl 1057141240Snjl return (0); 1058141240Snjl} 1059167905Snjl 1060167905Snjlint 1061167905Snjlcpufreq_settings_changed(device_t dev) 1062167905Snjl{ 1063167905Snjl 1064167905Snjl EVENTHANDLER_INVOKE(cpufreq_levels_changed, 1065167905Snjl device_get_unit(device_get_parent(dev))); 1066167905Snjl return (0); 1067167905Snjl} 1068