diff options
author | njl <njl@FreeBSD.org> | 2007-03-26 18:03:29 +0000 |
---|---|---|
committer | njl <njl@FreeBSD.org> | 2007-03-26 18:03:29 +0000 |
commit | 4933ca0aa02fff68e3e30186b454ed25d7d926fb (patch) | |
tree | 38a5baa5f3b7261150b7178ed80307e7f2bd65c9 /sys/kern/kern_cpu.c | |
parent | 3cb53690e0c2c81ed3b336133bf003c7b3173abf (diff) | |
download | FreeBSD-src-4933ca0aa02fff68e3e30186b454ed25d7d926fb.zip FreeBSD-src-4933ca0aa02fff68e3e30186b454ed25d7d926fb.tar.gz |
Add an interface for drivers to be notified of changes to CPU frequency.
cpufreq_pre_change is called before the change, giving each driver a chance
to revoke the change. cpufreq_post_change provides the results of the
change (success or failure). cpufreq_levels_changed gives the unit number
of the cpufreq device whose number of available levels has changed. Hook
in all the drivers I could find that needed it.
* TSC: update TSC frequency value. When the available levels change, take the
highest possible level and notify the timecounter set_cputicker() of that
freq. This gets rid of the "calcru: runtime went backwards" messages.
* identcpu: updates the sysctl hw.clockrate value
* Profiling: if profiling is active when the clock changes, let the user
know the results may be inaccurate.
Reviewed by: bde, phk
MFC after: 1 month
Diffstat (limited to 'sys/kern/kern_cpu.c')
-rw-r--r-- | sys/kern/kern_cpu.c | 66 |
1 files changed, 40 insertions, 26 deletions
diff --git a/sys/kern/kern_cpu.c b/sys/kern/kern_cpu.c index eb5bbb8..3d2adfd 100644 --- a/sys/kern/kern_cpu.c +++ b/sys/kern/kern_cpu.c @@ -1,5 +1,5 @@ /*- - * Copyright (c) 2004-2005 Nate Lawson (SDG) + * Copyright (c) 2004-2007 Nate Lawson (SDG) * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,6 +43,7 @@ __FBSDID("$FreeBSD$"); #include <sys/sbuf.h> #include <sys/sx.h> #include <sys/timetc.h> +#include <sys/taskqueue.h> #include "cpufreq_if.h" @@ -73,6 +74,7 @@ struct cpufreq_softc { int max_mhz; device_t dev; struct sysctl_ctx_list sysctl_ctx; + struct task startup_task; }; struct cf_setting_array { @@ -94,8 +96,8 @@ TAILQ_HEAD(cf_setting_lst, cf_setting_array); } while (0) static int cpufreq_attach(device_t dev); +static void cpufreq_startup_task(void *ctx, int pending); static int cpufreq_detach(device_t dev); -static void cpufreq_evaluate(void *arg); static int cf_set_method(device_t dev, const struct cf_level *level, int priority); static int cf_get_method(device_t dev, struct cf_level *level); @@ -127,8 +129,6 @@ static driver_t cpufreq_driver = { static devclass_t cpufreq_dc; DRIVER_MODULE(cpufreq, cpu, cpufreq_driver, cpufreq_dc, 0, 0); -static eventhandler_tag cf_ev_tag; - static int cf_lowest_freq; static int cf_verbose; TUNABLE_INT("debug.cpufreq.lowest", &cf_lowest_freq); @@ -176,12 +176,25 @@ cpufreq_attach(device_t dev) SYSCTL_CHILDREN(device_get_sysctl_tree(parent)), OID_AUTO, "freq_levels", CTLTYPE_STRING | CTLFLAG_RD, sc, 0, cpufreq_levels_sysctl, "A", "CPU frequency levels"); - cf_ev_tag = EVENTHANDLER_REGISTER(cpufreq_changed, cpufreq_evaluate, - NULL, EVENTHANDLER_PRI_ANY); + + /* + * Queue a one-shot broadcast that levels have changed. + * It will run once the system has completed booting. + */ + TASK_INIT(&sc->startup_task, 0, cpufreq_startup_task, dev); + taskqueue_enqueue(taskqueue_thread, &sc->startup_task); return (0); } +/* Handle any work to be done for all drivers that attached during boot. */ +static void +cpufreq_startup_task(void *ctx, int pending) +{ + + cpufreq_settings_changed((device_t)ctx); +} + static int cpufreq_detach(device_t dev) { @@ -202,18 +215,11 @@ cpufreq_detach(device_t dev) numdevs = devclass_get_count(cpufreq_dc); if (numdevs == 1) { CF_DEBUG("final shutdown for %s\n", device_get_nameunit(dev)); - EVENTHANDLER_DEREGISTER(cpufreq_changed, cf_ev_tag); } return (0); } -static void -cpufreq_evaluate(void *arg) -{ - /* TODO: Re-evaluate when notified of changes to drivers. */ -} - static int cf_set_method(device_t dev, const struct cf_level *level, int priority) { @@ -222,25 +228,17 @@ cf_set_method(device_t dev, const struct cf_level *level, int priority) struct cf_saved_freq *saved_freq, *curr_freq; struct pcpu *pc; int cpu_id, error, i; - static int once; sc = device_get_softc(dev); error = 0; set = NULL; saved_freq = NULL; - /* - * Check that the TSC isn't being used as a timecounter. - * If it is, then return EBUSY and refuse to change the - * clock speed. - */ - if (strcmp(timecounter->tc_name, "TSC") == 0) { - if (!once) { - printf("cpufreq: frequency change with timecounter" - " TSC not allowed, see cpufreq(4)\n"); - once = 1; - } - return (EBUSY); + /* We are going to change levels so notify the pre-change handler. */ + EVENTHANDLER_INVOKE(cpufreq_pre_change, level, &error); + if (error != 0) { + EVENTHANDLER_INVOKE(cpufreq_post_change, level, error); + return (error); } CF_MTX_LOCK(&sc->lock); @@ -378,8 +376,15 @@ skip: out: CF_MTX_UNLOCK(&sc->lock); + + /* + * We changed levels (or attempted to) so notify the post-change + * handler of new frequency or error. + */ + EVENTHANDLER_INVOKE(cpufreq_post_change, level, error); if (error && set) device_printf(set->dev, "set freq failed, err %d\n", error); + return (error); } @@ -1021,3 +1026,12 @@ cpufreq_unregister(device_t dev) return (0); } + +int +cpufreq_settings_changed(device_t dev) +{ + + EVENTHANDLER_INVOKE(cpufreq_levels_changed, + device_get_unit(device_get_parent(dev))); + return (0); +} |