The Design and Implementation of the FreeBSD Operating System, Second Edition
Now available: The Design and Implementation of the FreeBSD Operating System, Second Edition

[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]

FreeBSD/Linux Kernel Cross Reference
sys/kern/kern_cpu.c

Version: -  FREEBSD  -  FREEBSD10  -  FREEBSD9  -  FREEBSD92  -  FREEBSD91  -  FREEBSD90  -  FREEBSD8  -  FREEBSD82  -  FREEBSD81  -  FREEBSD80  -  FREEBSD7  -  FREEBSD74  -  FREEBSD73  -  FREEBSD72  -  FREEBSD71  -  FREEBSD70  -  FREEBSD6  -  FREEBSD64  -  FREEBSD63  -  FREEBSD62  -  FREEBSD61  -  FREEBSD60  -  FREEBSD5  -  FREEBSD55  -  FREEBSD54  -  FREEBSD53  -  FREEBSD52  -  FREEBSD51  -  FREEBSD50  -  FREEBSD4  -  FREEBSD3  -  FREEBSD22  -  cheribsd  -  linux-2.6  -  linux-2.4.22  -  MK83  -  MK84  -  PLAN9  -  DFBSD  -  NETBSD  -  NETBSD5  -  NETBSD4  -  NETBSD3  -  NETBSD20  -  OPENBSD  -  xnu-517  -  xnu-792  -  xnu-792.6.70  -  xnu-1228  -  xnu-1456.1.26  -  xnu-1699.24.8  -  xnu-2050.18.24  -  OPENSOLARIS  -  minix-3-1-1  -  FREEBSD-LIBC  -  FREEBSD8-LIBC  -  FREEBSD7-LIBC  -  FREEBSD6-LIBC  -  GLIBC27 
SearchContext: -  none  -  3  -  10 

    1 /*-
    2  * Copyright (c) 2004-2005 Nate Lawson (SDG)
    3  * All rights reserved.
    4  *
    5  * Redistribution and use in source and binary forms, with or without
    6  * modification, are permitted provided that the following conditions
    7  * are met:
    8  * 1. Redistributions of source code must retain the above copyright
    9  *    notice, this list of conditions and the following disclaimer.
   10  * 2. Redistributions in binary form must reproduce the above copyright
   11  *    notice, this list of conditions and the following disclaimer in the
   12  *    documentation and/or other materials provided with the distribution.
   13  *
   14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
   15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
   18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   24  * SUCH DAMAGE.
   25  */
   26 
   27 #include <sys/cdefs.h>
   28 __FBSDID("$FreeBSD: src/sys/kern/kern_cpu.c,v 1.10.2.1 2005/02/25 21:45:14 njl Exp $");
   29 
   30 #include <sys/param.h>
   31 #include <sys/bus.h>
   32 #include <sys/cpu.h>
   33 #include <sys/eventhandler.h>
   34 #include <sys/kernel.h>
   35 #include <sys/malloc.h>
   36 #include <sys/module.h>
   37 #include <sys/proc.h>
   38 #include <sys/queue.h>
   39 #include <sys/sched.h>
   40 #include <sys/sysctl.h>
   41 #include <sys/systm.h>
   42 #include <sys/sbuf.h>
   43 #include <sys/timetc.h>
   44 
   45 #include "cpufreq_if.h"
   46 
   47 /*
   48  * Common CPU frequency glue code.  Drivers for specific hardware can
   49  * attach this interface to allow users to get/set the CPU frequency.
   50  */
   51 
   52 /*
   53  * Number of levels we can handle.  Levels are synthesized from settings
   54  * so for M settings and N drivers, there may be M*N levels.
   55  */
   56 #define CF_MAX_LEVELS   64
   57 
   58 struct cpufreq_softc {
   59         struct cf_level                 curr_level;
   60         int                             curr_priority;
   61         struct cf_level                 saved_level;
   62         int                             saved_priority;
   63         struct cf_level_lst             all_levels;
   64         int                             all_count;
   65         int                             max_mhz;
   66         device_t                        dev;
   67         struct sysctl_ctx_list          sysctl_ctx;
   68 };
   69 
   70 struct cf_setting_array {
   71         struct cf_setting               sets[MAX_SETTINGS];
   72         int                             count;
   73         TAILQ_ENTRY(cf_setting_array)   link;
   74 };
   75 
   76 TAILQ_HEAD(cf_setting_lst, cf_setting_array);
   77 
   78 static int      cpufreq_attach(device_t dev);
   79 static int      cpufreq_detach(device_t dev);
   80 static void     cpufreq_evaluate(void *arg);
   81 static int      cf_set_method(device_t dev, const struct cf_level *level,
   82                     int priority);
   83 static int      cf_get_method(device_t dev, struct cf_level *level);
   84 static int      cf_levels_method(device_t dev, struct cf_level *levels,
   85                     int *count);
   86 static int      cpufreq_insert_abs(struct cpufreq_softc *sc,
   87                     struct cf_setting *sets, int count);
   88 static int      cpufreq_expand_set(struct cpufreq_softc *sc,
   89                     struct cf_setting_array *set_arr);
   90 static struct cf_level *cpufreq_dup_set(struct cpufreq_softc *sc,
   91                     struct cf_level *dup, struct cf_setting *set);
   92 static int      cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS);
   93 static int      cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS);
   94 static int      cpufreq_settings_sysctl(SYSCTL_HANDLER_ARGS);
   95 
   96 static device_method_t cpufreq_methods[] = {
   97         DEVMETHOD(device_probe,         bus_generic_probe),
   98         DEVMETHOD(device_attach,        cpufreq_attach),
   99         DEVMETHOD(device_detach,        cpufreq_detach),
  100 
  101         DEVMETHOD(cpufreq_set,          cf_set_method),
  102         DEVMETHOD(cpufreq_get,          cf_get_method),
  103         DEVMETHOD(cpufreq_levels,       cf_levels_method),
  104         {0, 0}
  105 };
  106 static driver_t cpufreq_driver = {
  107         "cpufreq", cpufreq_methods, sizeof(struct cpufreq_softc)
  108 };
  109 static devclass_t cpufreq_dc;
  110 DRIVER_MODULE(cpufreq, cpu, cpufreq_driver, cpufreq_dc, 0, 0);
  111 
  112 static eventhandler_tag cf_ev_tag;
  113 
  114 static int
  115 cpufreq_attach(device_t dev)
  116 {
  117         struct cpufreq_softc *sc;
  118         device_t parent;
  119         int numdevs;
  120 
  121         sc = device_get_softc(dev);
  122         parent = device_get_parent(dev);
  123         sc->dev = dev;
  124         sysctl_ctx_init(&sc->sysctl_ctx);
  125         TAILQ_INIT(&sc->all_levels);
  126         sc->curr_level.total_set.freq = CPUFREQ_VAL_UNKNOWN;
  127         sc->saved_level.total_set.freq = CPUFREQ_VAL_UNKNOWN;
  128         sc->max_mhz = CPUFREQ_VAL_UNKNOWN;
  129 
  130         /*
  131          * Only initialize one set of sysctls for all CPUs.  In the future,
  132          * if multiple CPUs can have different settings, we can move these
  133          * sysctls to be under every CPU instead of just the first one.
  134          */
  135         numdevs = devclass_get_count(cpufreq_dc);
  136         if (numdevs > 1)
  137                 return (0);
  138 
  139         SYSCTL_ADD_PROC(&sc->sysctl_ctx,
  140             SYSCTL_CHILDREN(device_get_sysctl_tree(parent)),
  141             OID_AUTO, "freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
  142             cpufreq_curr_sysctl, "I", "Current CPU frequency");
  143         SYSCTL_ADD_PROC(&sc->sysctl_ctx,
  144             SYSCTL_CHILDREN(device_get_sysctl_tree(parent)),
  145             OID_AUTO, "freq_levels", CTLTYPE_STRING | CTLFLAG_RD, sc, 0,
  146             cpufreq_levels_sysctl, "A", "CPU frequency levels");
  147         cf_ev_tag = EVENTHANDLER_REGISTER(cpufreq_changed, cpufreq_evaluate,
  148             NULL, EVENTHANDLER_PRI_ANY);
  149 
  150         return (0);
  151 }
  152 
  153 static int
  154 cpufreq_detach(device_t dev)
  155 {
  156         struct cpufreq_softc *sc;
  157         int numdevs;
  158 
  159         sc = device_get_softc(dev);
  160         sysctl_ctx_free(&sc->sysctl_ctx);
  161 
  162         /* Only clean up these resources when the last device is detaching. */
  163         numdevs = devclass_get_count(cpufreq_dc);
  164         if (numdevs == 1)
  165                 EVENTHANDLER_DEREGISTER(cpufreq_changed, cf_ev_tag);
  166 
  167         return (0);
  168 }
  169 
  170 static void
  171 cpufreq_evaluate(void *arg)
  172 {
  173         /* TODO: Re-evaluate when notified of changes to drivers. */
  174 }
  175 
  176 static int
  177 cf_set_method(device_t dev, const struct cf_level *level, int priority)
  178 {
  179         struct cpufreq_softc *sc;
  180         const struct cf_setting *set;
  181         struct pcpu *pc;
  182         int cpu_id, error, i;
  183 
  184         sc = device_get_softc(dev);
  185 
  186         /*
  187          * Check that the TSC isn't being used as a timecounter.
  188          * If it is, then return EBUSY and refuse to change the
  189          * clock speed.
  190          */
  191         if (strcmp(timecounter->tc_name, "TSC") == 0)
  192                 return (EBUSY);
  193 
  194         /*
  195          * If the caller didn't specify a level and one is saved, prepare to
  196          * restore the saved level.  If none has been saved, return an error.
  197          * If they did specify one, but the requested level has a lower
  198          * priority, don't allow the new level right now.
  199          */
  200         if (level == NULL) {
  201                 if (sc->saved_level.total_set.freq != CPUFREQ_VAL_UNKNOWN) {
  202                         level = &sc->saved_level;
  203                         priority = sc->saved_priority;
  204                 } else
  205                         return (ENXIO);
  206         } else if (priority < sc->curr_priority)
  207                 return (EPERM);
  208 
  209         /* If already at this level, just return. */
  210         if (CPUFREQ_CMP(sc->curr_level.total_set.freq, level->total_set.freq))
  211                 return (0);
  212 
  213         /* First, set the absolute frequency via its driver. */
  214         set = &level->abs_set;
  215         if (set->dev) {
  216                 if (!device_is_attached(set->dev)) {
  217                         error = ENXIO;
  218                         goto out;
  219                 }
  220 
  221                 /* Bind to the target CPU before switching, if necessary. */
  222                 cpu_id = PCPU_GET(cpuid);
  223                 pc = cpu_get_pcpu(set->dev);
  224                 if (cpu_id != pc->pc_cpuid) {
  225                         mtx_lock_spin(&sched_lock);
  226                         sched_bind(curthread, pc->pc_cpuid);
  227                         mtx_unlock_spin(&sched_lock);
  228                 }
  229                 error = CPUFREQ_DRV_SET(set->dev, set);
  230                 if (cpu_id != pc->pc_cpuid) {
  231                         mtx_lock_spin(&sched_lock);
  232                         sched_unbind(curthread);
  233                         mtx_unlock_spin(&sched_lock);
  234                 }
  235                 if (error) {
  236                         goto out;
  237                 }
  238         }
  239 
  240         /* Next, set any/all relative frequencies via their drivers. */
  241         for (i = 0; i < level->rel_count; i++) {
  242                 set = &level->rel_set[i];
  243                 if (!device_is_attached(set->dev)) {
  244                         error = ENXIO;
  245                         goto out;
  246                 }
  247 
  248                 /* Bind to the target CPU before switching, if necessary. */
  249                 cpu_id = PCPU_GET(cpuid);
  250                 pc = cpu_get_pcpu(set->dev);
  251                 if (cpu_id != pc->pc_cpuid) {
  252                         mtx_lock_spin(&sched_lock);
  253                         sched_bind(curthread, pc->pc_cpuid);
  254                         mtx_unlock_spin(&sched_lock);
  255                 }
  256                 error = CPUFREQ_DRV_SET(set->dev, set);
  257                 if (cpu_id != pc->pc_cpuid) {
  258                         mtx_lock_spin(&sched_lock);
  259                         sched_unbind(curthread);
  260                         mtx_unlock_spin(&sched_lock);
  261                 }
  262                 if (error) {
  263                         /* XXX Back out any successful setting? */
  264                         goto out;
  265                 }
  266         }
  267 
  268         /* If we were restoring a saved state, reset it to "unused". */
  269         if (level == &sc->saved_level) {
  270                 sc->saved_level.total_set.freq = CPUFREQ_VAL_UNKNOWN;
  271                 sc->saved_priority = 0;
  272         }
  273 
  274         /*
  275          * Before recording the current level, check if we're going to a
  276          * higher priority and have not saved a level yet.  If so, save the
  277          * previous level and priority.
  278          */
  279         if (sc->curr_level.total_set.freq != CPUFREQ_VAL_UNKNOWN &&
  280             sc->saved_level.total_set.freq == CPUFREQ_VAL_UNKNOWN &&
  281             priority > sc->curr_priority) {
  282                 sc->saved_level = sc->curr_level;
  283                 sc->saved_priority = sc->curr_priority;
  284         }
  285         sc->curr_level = *level;
  286         sc->curr_priority = priority;
  287         error = 0;
  288 
  289 out:
  290         if (error)
  291                 device_printf(set->dev, "set freq failed, err %d\n", error);
  292         return (error);
  293 }
  294 
  295 static int
  296 cf_get_method(device_t dev, struct cf_level *level)
  297 {
  298         struct cpufreq_softc *sc;
  299         struct cf_level *levels;
  300         struct cf_setting *curr_set, set;
  301         struct pcpu *pc;
  302         device_t *devs;
  303         int count, error, i, numdevs;
  304         uint64_t rate;
  305 
  306         sc = device_get_softc(dev);
  307         curr_set = &sc->curr_level.total_set;
  308         levels = NULL;
  309 
  310         /* If we already know the current frequency, we're done. */
  311         if (curr_set->freq != CPUFREQ_VAL_UNKNOWN)
  312                 goto out;
  313 
  314         /*
  315          * We need to figure out the current level.  Loop through every
  316          * driver, getting the current setting.  Then, attempt to get a best
  317          * match of settings against each level.
  318          */
  319         count = CF_MAX_LEVELS;
  320         levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT);
  321         if (levels == NULL)
  322                 return (ENOMEM);
  323         error = CPUFREQ_LEVELS(sc->dev, levels, &count);
  324         if (error) {
  325                 if (error == E2BIG)
  326                         printf("cpufreq: need to increase CF_MAX_LEVELS\n");
  327                 goto out;
  328         }
  329         error = device_get_children(device_get_parent(dev), &devs, &numdevs);
  330         if (error)
  331                 goto out;
  332         for (i = 0; i < numdevs && curr_set->freq == CPUFREQ_VAL_UNKNOWN; i++) {
  333                 if (!device_is_attached(devs[i]))
  334                         continue;
  335                 error = CPUFREQ_DRV_GET(devs[i], &set);
  336                 if (error)
  337                         continue;
  338                 for (i = 0; i < count; i++) {
  339                         if (CPUFREQ_CMP(set.freq, levels[i].total_set.freq)) {
  340                                 sc->curr_level = levels[i];
  341                                 break;
  342                         }
  343                 }
  344         }
  345         free(devs, M_TEMP);
  346         if (curr_set->freq != CPUFREQ_VAL_UNKNOWN)
  347                 goto out;
  348 
  349         /*
  350          * We couldn't find an exact match, so attempt to estimate and then
  351          * match against a level.
  352          */
  353         pc = cpu_get_pcpu(dev);
  354         if (pc == NULL) {
  355                 error = ENXIO;
  356                 goto out;
  357         }
  358         cpu_est_clockrate(pc->pc_cpuid, &rate);
  359         rate /= 1000000;
  360         for (i = 0; i < count; i++) {
  361                 if (CPUFREQ_CMP(rate, levels[i].total_set.freq)) {
  362                         sc->curr_level = levels[i];
  363                         break;
  364                 }
  365         }
  366 
  367 out:
  368         if (levels)
  369                 free(levels, M_TEMP);
  370         *level = sc->curr_level;
  371         return (0);
  372 }
  373 
  374 static int
  375 cf_levels_method(device_t dev, struct cf_level *levels, int *count)
  376 {
  377         struct cf_setting_array *set_arr;
  378         struct cf_setting_lst rel_sets;
  379         struct cpufreq_softc *sc;
  380         struct cf_level *lev;
  381         struct cf_setting *sets;
  382         struct pcpu *pc;
  383         device_t *devs;
  384         int error, i, numdevs, set_count, type;
  385         uint64_t rate;
  386 
  387         if (levels == NULL || count == NULL)
  388                 return (EINVAL);
  389 
  390         TAILQ_INIT(&rel_sets);
  391         sc = device_get_softc(dev);
  392         error = device_get_children(device_get_parent(dev), &devs, &numdevs);
  393         if (error)
  394                 return (error);
  395         sets = malloc(MAX_SETTINGS * sizeof(*sets), M_TEMP, M_NOWAIT);
  396         if (sets == NULL) {
  397                 free(devs, M_TEMP);
  398                 return (ENOMEM);
  399         }
  400 
  401         /* Get settings from all cpufreq drivers. */
  402         for (i = 0; i < numdevs; i++) {
  403                 /* Skip devices that aren't ready. */
  404                 if (!device_is_attached(devs[i]))
  405                         continue;
  406 
  407                 /*
  408                  * Get settings, skipping drivers that offer no settings or
  409                  * provide settings for informational purposes only.
  410                  */
  411                 error = CPUFREQ_DRV_TYPE(devs[i], &type);
  412                 if (error || (type & CPUFREQ_FLAG_INFO_ONLY))
  413                         continue;
  414                 set_count = MAX_SETTINGS;
  415                 error = CPUFREQ_DRV_SETTINGS(devs[i], sets, &set_count);
  416                 if (error || set_count == 0)
  417                         continue;
  418 
  419                 /* Add the settings to our absolute/relative lists. */
  420                 switch (type & CPUFREQ_TYPE_MASK) {
  421                 case CPUFREQ_TYPE_ABSOLUTE:
  422                         error = cpufreq_insert_abs(sc, sets, set_count);
  423                         break;
  424                 case CPUFREQ_TYPE_RELATIVE:
  425                         set_arr = malloc(sizeof(*set_arr), M_TEMP, M_NOWAIT);
  426                         if (set_arr == NULL) {
  427                                 error = ENOMEM;
  428                                 goto out;
  429                         }
  430                         bcopy(sets, set_arr->sets, set_count * sizeof(*sets));
  431                         set_arr->count = set_count;
  432                         TAILQ_INSERT_TAIL(&rel_sets, set_arr, link);
  433                         break;
  434                 default:
  435                         error = EINVAL;
  436                         break;
  437                 }
  438                 if (error)
  439                         goto out;
  440         }
  441 
  442         /*
  443          * If there are no absolute levels, create a fake one at 100%.  We
  444          * then cache the clockrate for later use as our base frequency.
  445          *
  446          * XXX This assumes that the first time through, if we only have
  447          * relative drivers, the CPU is currently running at 100%.
  448          */
  449         if (TAILQ_EMPTY(&sc->all_levels)) {
  450                 if (sc->max_mhz == CPUFREQ_VAL_UNKNOWN) {
  451                         pc = cpu_get_pcpu(dev);
  452                         cpu_est_clockrate(pc->pc_cpuid, &rate);
  453                         sc->max_mhz = rate / 1000000;
  454                 }
  455                 memset(&sets[0], CPUFREQ_VAL_UNKNOWN, sizeof(*sets));
  456                 sets[0].freq = sc->max_mhz;
  457                 sets[0].dev = NULL;
  458                 error = cpufreq_insert_abs(sc, sets, 1);
  459                 if (error)
  460                         goto out;
  461         }
  462 
  463         /* Create a combined list of absolute + relative levels. */
  464         TAILQ_FOREACH(set_arr, &rel_sets, link)
  465                 cpufreq_expand_set(sc, set_arr);
  466 
  467         /* If the caller doesn't have enough space, return the actual count. */
  468         if (sc->all_count > *count) {
  469                 *count = sc->all_count;
  470                 error = E2BIG;
  471                 goto out;
  472         }
  473 
  474         /* Finally, output the list of levels. */
  475         i = 0;
  476         TAILQ_FOREACH(lev, &sc->all_levels, link) {
  477                 levels[i] = *lev;
  478                 i++;
  479         }
  480         *count = sc->all_count;
  481         error = 0;
  482 
  483 out:
  484         /* Clear all levels since we regenerate them each time. */
  485         while ((lev = TAILQ_FIRST(&sc->all_levels)) != NULL) {
  486                 TAILQ_REMOVE(&sc->all_levels, lev, link);
  487                 free(lev, M_TEMP);
  488         }
  489         while ((set_arr = TAILQ_FIRST(&rel_sets)) != NULL) {
  490                 TAILQ_REMOVE(&rel_sets, set_arr, link);
  491                 free(set_arr, M_TEMP);
  492         }
  493         sc->all_count = 0;
  494         free(devs, M_TEMP);
  495         free(sets, M_TEMP);
  496         return (error);
  497 }
  498 
  499 /*
  500  * Create levels for an array of absolute settings and insert them in
  501  * sorted order in the specified list.
  502  */
  503 static int
  504 cpufreq_insert_abs(struct cpufreq_softc *sc, struct cf_setting *sets,
  505     int count)
  506 {
  507         struct cf_level_lst *list;
  508         struct cf_level *level, *search;
  509         int i;
  510 
  511         list = &sc->all_levels;
  512         for (i = 0; i < count; i++) {
  513                 level = malloc(sizeof(*level), M_TEMP, M_NOWAIT | M_ZERO);
  514                 if (level == NULL)
  515                         return (ENOMEM);
  516                 level->abs_set = sets[i];
  517                 level->total_set = sets[i];
  518                 level->total_set.dev = NULL;
  519                 sc->all_count++;
  520 
  521                 if (TAILQ_EMPTY(list)) {
  522                         TAILQ_INSERT_HEAD(list, level, link);
  523                         continue;
  524                 }
  525 
  526                 TAILQ_FOREACH_REVERSE(search, list, cf_level_lst, link) {
  527                         if (sets[i].freq <= search->total_set.freq) {
  528                                 TAILQ_INSERT_AFTER(list, search, level, link);
  529                                 break;
  530                         }
  531                 }
  532         }
  533         return (0);
  534 }
  535 
  536 /*
  537  * Expand a group of relative settings, creating derived levels from them.
  538  */
  539 static int
  540 cpufreq_expand_set(struct cpufreq_softc *sc, struct cf_setting_array *set_arr)
  541 {
  542         struct cf_level *fill, *search;
  543         struct cf_setting *set;
  544         int i;
  545 
  546         TAILQ_FOREACH(search, &sc->all_levels, link) {
  547                 /* Skip this level if we've already modified it. */
  548                 for (i = 0; i < search->rel_count; i++) {
  549                         if (search->rel_set[i].dev == set_arr->sets[0].dev)
  550                                 break;
  551                 }
  552                 if (i != search->rel_count)
  553                         continue;
  554 
  555                 /* Add each setting to the level, duplicating if necessary. */
  556                 for (i = 0; i < set_arr->count; i++) {
  557                         set = &set_arr->sets[i];
  558 
  559                         /*
  560                          * If this setting is less than 100%, split the level
  561                          * into two and add this setting to the new level.
  562                          */
  563                         fill = search;
  564                         if (set->freq < 10000)
  565                                 fill = cpufreq_dup_set(sc, search, set);
  566 
  567                         /*
  568                          * The new level was a duplicate of an existing level
  569                          * so we freed it.  Go to the next setting.
  570                          */
  571                         if (fill == NULL)
  572                                 continue;
  573 
  574                         /* Add this setting to the existing or new level. */
  575                         KASSERT(fill->rel_count < MAX_SETTINGS,
  576                             ("cpufreq: too many relative drivers (%d)",
  577                             MAX_SETTINGS));
  578                         fill->rel_set[fill->rel_count] = *set;
  579                         fill->rel_count++;
  580                 }
  581         }
  582 
  583         return (0);
  584 }
  585 
  586 static struct cf_level *
  587 cpufreq_dup_set(struct cpufreq_softc *sc, struct cf_level *dup,
  588     struct cf_setting *set)
  589 {
  590         struct cf_level_lst *list;
  591         struct cf_level *fill, *itr;
  592         struct cf_setting *fill_set, *itr_set;
  593         int i;
  594 
  595         /*
  596          * Create a new level, copy it from the old one, and update the
  597          * total frequency and power by the percentage specified in the
  598          * relative setting.
  599          */
  600         fill = malloc(sizeof(*fill), M_TEMP, M_NOWAIT);
  601         if (fill == NULL)
  602                 return (NULL);
  603         *fill = *dup;
  604         fill_set = &fill->total_set;
  605         fill_set->freq =
  606             ((uint64_t)fill_set->freq * set->freq) / 10000;
  607         if (fill_set->power != CPUFREQ_VAL_UNKNOWN) {
  608                 fill_set->power = ((uint64_t)fill_set->power * set->freq)
  609                     / 10000;
  610         }
  611         if (set->lat != CPUFREQ_VAL_UNKNOWN) {
  612                 if (fill_set->lat != CPUFREQ_VAL_UNKNOWN)
  613                         fill_set->lat += set->lat;
  614                 else
  615                         fill_set->lat = set->lat;
  616         }
  617 
  618         /*
  619          * If we copied an old level that we already modified (say, at 100%),
  620          * we need to remove that setting before adding this one.  Since we
  621          * process each setting array in order, we know any settings for this
  622          * driver will be found at the end.
  623          */
  624         for (i = fill->rel_count; i != 0; i--) {
  625                 if (fill->rel_set[i - 1].dev != set->dev)
  626                         break;
  627                 fill->rel_count--;
  628         }
  629 
  630         /*
  631          * Insert the new level in sorted order.  If we find a duplicate,
  632          * free the new level.  We can do this since any existing level will
  633          * be guaranteed to have the same or less settings and thus consume
  634          * less power.  For example, a level with one absolute setting of
  635          * 800 Mhz uses less power than one composed of an absolute setting
  636          * of 1600 Mhz and a relative setting at 50%.
  637          */
  638         list = &sc->all_levels;
  639         if (TAILQ_EMPTY(list)) {
  640                 TAILQ_INSERT_HEAD(list, fill, link);
  641         } else {
  642                 TAILQ_FOREACH_REVERSE(itr, list, cf_level_lst, link) {
  643                         itr_set = &itr->total_set;
  644                         if (CPUFREQ_CMP(fill_set->freq, itr_set->freq)) {
  645                                 free(fill, M_TEMP);
  646                                 fill = NULL;
  647                                 break;
  648                         } else if (fill_set->freq < itr_set->freq) {
  649                                 TAILQ_INSERT_AFTER(list, itr, fill, link);
  650                                 sc->all_count++;
  651                                 break;
  652                         }
  653                 }
  654         }
  655 
  656         return (fill);
  657 }
  658 
  659 static int
  660 cpufreq_curr_sysctl(SYSCTL_HANDLER_ARGS)
  661 {
  662         struct cpufreq_softc *sc;
  663         struct cf_level *levels;
  664         int count, devcount, error, freq, i, n;
  665         device_t *devs;
  666 
  667         devs = NULL;
  668         sc = oidp->oid_arg1;
  669         levels = malloc(CF_MAX_LEVELS * sizeof(*levels), M_TEMP, M_NOWAIT);
  670         if (levels == NULL)
  671                 return (ENOMEM);
  672 
  673         error = CPUFREQ_GET(sc->dev, &levels[0]);
  674         if (error)
  675                 goto out;
  676         freq = levels[0].total_set.freq;
  677         error = sysctl_handle_int(oidp, &freq, 0, req);
  678         if (error != 0 || req->newptr == NULL)
  679                 goto out;
  680 
  681         /*
  682          * While we only call cpufreq_get() on one device (assuming all
  683          * CPUs have equal levels), we call cpufreq_set() on all CPUs.
  684          * This is needed for some MP systems.
  685          */
  686         error = devclass_get_devices(cpufreq_dc, &devs, &devcount);
  687         if (error)
  688                 goto out;
  689         for (n = 0; n < devcount; n++) {
  690                 count = CF_MAX_LEVELS;
  691                 error = CPUFREQ_LEVELS(devs[n], levels, &count);
  692                 if (error) {
  693                         if (error == E2BIG)
  694                                 printf(
  695                         "cpufreq: need to increase CF_MAX_LEVELS\n");
  696                         break;
  697                 }
  698                 for (i = 0; i < count; i++) {
  699                         if (CPUFREQ_CMP(levels[i].total_set.freq, freq)) {
  700                                 error = CPUFREQ_SET(devs[n], &levels[i],
  701                                     CPUFREQ_PRIO_USER);
  702                                 break;
  703                         }
  704                 }
  705                 if (i == count) {
  706                         error = EINVAL;
  707                         break;
  708                 }
  709         }
  710 
  711 out:
  712         if (devs)
  713                 free(devs, M_TEMP);
  714         if (levels)
  715                 free(levels, M_TEMP);
  716         return (error);
  717 }
  718 
  719 static int
  720 cpufreq_levels_sysctl(SYSCTL_HANDLER_ARGS)
  721 {
  722         struct cpufreq_softc *sc;
  723         struct cf_level *levels;
  724         struct cf_setting *set;
  725         struct sbuf sb;
  726         int count, error, i;
  727 
  728         sc = oidp->oid_arg1;
  729         sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND);
  730 
  731         /* Get settings from the device and generate the output string. */
  732         count = CF_MAX_LEVELS;
  733         levels = malloc(count * sizeof(*levels), M_TEMP, M_NOWAIT);
  734         if (levels == NULL)
  735                 return (ENOMEM);
  736         error = CPUFREQ_LEVELS(sc->dev, levels, &count);
  737         if (error) {
  738                 if (error == E2BIG)
  739                         printf("cpufreq: need to increase CF_MAX_LEVELS\n");
  740                 goto out;
  741         }
  742         if (count) {
  743                 for (i = 0; i < count; i++) {
  744                         set = &levels[i].total_set;
  745                         sbuf_printf(&sb, "%d/%d ", set->freq, set->power);
  746                 }
  747         } else
  748                 sbuf_cpy(&sb, "");
  749         sbuf_trim(&sb);
  750         sbuf_finish(&sb);
  751         error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req);
  752 
  753 out:
  754         free(levels, M_TEMP);
  755         sbuf_delete(&sb);
  756         return (error);
  757 }
  758 
  759 static int
  760 cpufreq_settings_sysctl(SYSCTL_HANDLER_ARGS)
  761 {
  762         device_t dev;
  763         struct cf_setting *sets;
  764         struct sbuf sb;
  765         int error, i, set_count;
  766 
  767         dev = oidp->oid_arg1;
  768         sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND);
  769 
  770         /* Get settings from the device and generate the output string. */
  771         set_count = MAX_SETTINGS;
  772         sets = malloc(set_count * sizeof(*sets), M_TEMP, M_NOWAIT);
  773         if (sets == NULL)
  774                 return (ENOMEM);
  775         error = CPUFREQ_DRV_SETTINGS(dev, sets, &set_count);
  776         if (error)
  777                 goto out;
  778         if (set_count) {
  779                 for (i = 0; i < set_count; i++)
  780                         sbuf_printf(&sb, "%d/%d ", sets[i].freq, sets[i].power);
  781         } else
  782                 sbuf_cpy(&sb, "");
  783         sbuf_trim(&sb);
  784         sbuf_finish(&sb);
  785         error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req);
  786 
  787 out:
  788         free(sets, M_TEMP);
  789         sbuf_delete(&sb);
  790         return (error);
  791 }
  792 
  793 int
  794 cpufreq_register(device_t dev)
  795 {
  796         struct cpufreq_softc *sc;
  797         device_t cf_dev, cpu_dev;
  798 
  799         /* Add a sysctl to get each driver's settings separately. */
  800         SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
  801             SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
  802             OID_AUTO, "freq_settings", CTLTYPE_STRING | CTLFLAG_RD, dev, 0,
  803             cpufreq_settings_sysctl, "A", "CPU frequency driver settings");
  804 
  805         /*
  806          * Add only one cpufreq device to each CPU.  Currently, all CPUs
  807          * must offer the same levels and be switched at the same time.
  808          */
  809         cpu_dev = device_get_parent(dev);
  810         if ((cf_dev = device_find_child(cpu_dev, "cpufreq", -1))) {
  811                 sc = device_get_softc(cf_dev);
  812                 sc->max_mhz = CPUFREQ_VAL_UNKNOWN;
  813                 return (0);
  814         }
  815 
  816         /* Add the child device and possibly sysctls. */
  817         cf_dev = BUS_ADD_CHILD(cpu_dev, 0, "cpufreq", -1);
  818         if (cf_dev == NULL)
  819                 return (ENOMEM);
  820         device_quiet(cf_dev);
  821 
  822         return (device_probe_and_attach(cf_dev));
  823 }
  824 
  825 int
  826 cpufreq_unregister(device_t dev)
  827 {
  828         device_t cf_dev, *devs;
  829         int cfcount, devcount, error, i, type;
  830 
  831         /*
  832          * If this is the last cpufreq child device, remove the control
  833          * device as well.  We identify cpufreq children by calling a method
  834          * they support.
  835          */
  836         error = device_get_children(device_get_parent(dev), &devs, &devcount);
  837         if (error)
  838                 return (error);
  839         cf_dev = device_find_child(device_get_parent(dev), "cpufreq", -1);
  840         cfcount = 0;
  841         for (i = 0; i < devcount; i++) {
  842                 if (!device_is_attached(devs[i]))
  843                         continue;
  844                 if (CPUFREQ_DRV_TYPE(devs[i], &type) == 0)
  845                         cfcount++;
  846         }
  847         if (cfcount <= 1)
  848                 device_delete_child(device_get_parent(cf_dev), cf_dev);
  849         free(devs, M_TEMP);
  850 
  851         return (0);
  852 }

Cache object: a22c40d6b5c2139b78c7a21da85cb212


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]


This page is part of the FreeBSD/Linux Linux Kernel Cross-Reference, and was automatically generated using a modified version of the LXR engine.