From 51636352b6292085217039c4feaf710064a53add Mon Sep 17 00:00:00 2001 From: Alexander Motin Date: Tue, 20 Jul 2010 10:58:56 +0000 Subject: [PATCH] Extend timer driver API to report also minimal and maximal supported period lengths. Make MI wrapper code to validate periods in request. Make kernel clock management code to honor these hardware limitations while choosing hz, stathz and profhz values. --- sys/dev/acpica/acpi_hpet.c | 16 +++++++++---- sys/kern/kern_clocksource.c | 46 ++++++++++++++++++++++++++++--------- sys/kern/kern_et.c | 20 ++++++++++++++++ sys/sys/timeet.h | 3 +++ sys/x86/isa/atrtc.c | 6 ++++- sys/x86/isa/clock.c | 5 ++++ sys/x86/x86/local_apic.c | 11 +++++++++ 7 files changed, 90 insertions(+), 17 deletions(-) diff --git a/sys/dev/acpica/acpi_hpet.c b/sys/dev/acpica/acpi_hpet.c index 95f2b32d9d76..a789418f2710 100644 --- a/sys/dev/acpica/acpi_hpet.c +++ b/sys/dev/acpica/acpi_hpet.c @@ -154,15 +154,16 @@ hpet_start(struct eventtimer *et, t->div = (sc->freq * (period->frac >> 32)) >> 32; if (period->sec != 0) t->div += sc->freq * period->sec; - if (first == NULL) - first = period; } else { t->mode = 2; t->div = 0; } - fdiv = (sc->freq * (first->frac >> 32)) >> 32; - if (first->sec != 0) - fdiv += sc->freq * first->sec; + if (first != NULL) { + fdiv = (sc->freq * (first->frac >> 32)) >> 32; + if (first->sec != 0) + fdiv += sc->freq * first->sec; + } else + fdiv = t->div; t->last = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); if (t->mode == 1 && (t->caps & HPET_TCAP_PER_INT)) { t->caps |= HPET_TCNF_TYPE; @@ -583,6 +584,11 @@ hpet_attach(device_t dev) if ((t->caps & HPET_TCAP_PER_INT) == 0) t->et.et_quality -= 10; t->et.et_frequency = sc->freq; + t->et.et_min_period.sec = 0; + t->et.et_min_period.frac = 0x00004000LL << 32; + t->et.et_max_period.sec = 0xffffffff / sc->freq; + t->et.et_max_period.frac = + ((0xffffffffLL << 32) / sc->freq) << 32; t->et.et_start = hpet_start; t->et.et_stop = hpet_stop; t->et.et_priv = &sc->t[i]; diff --git a/sys/kern/kern_clocksource.c b/sys/kern/kern_clocksource.c index 697275a83555..9656db67973c 100644 --- a/sys/kern/kern_clocksource.c +++ b/sys/kern/kern_clocksource.c @@ -87,11 +87,9 @@ static DPCPU_DEFINE(tc, configtimer); (bt)->sec = 0; \ (bt)->frac = ((uint64_t)0x8000000000000000 / (freq)) << 1; \ } -#define BT2FREQ(bt, freq) \ -{ \ - *(freq) = ((uint64_t)0x8000000000000000 + ((bt)->frac >> 2)) / \ - ((bt)->frac >> 1); \ -} +#define BT2FREQ(bt) \ + (((uint64_t)0x8000000000000000 + ((bt)->frac >> 2)) / \ + ((bt)->frac >> 1)) /* Per-CPU timer1 handler. */ static int @@ -295,6 +293,26 @@ configtimer(int i) #endif } +static int +round_freq(struct eventtimer *et, int freq) +{ + uint64_t div; + + if (et->et_frequency != 0) { + div = (et->et_frequency + freq / 2) / freq; + if (et->et_flags & ET_FLAGS_POW2DIV) + div = 1 << (flsl(div + div / 2) - 1); + freq = (et->et_frequency + div / 2) / div; + } + if (et->et_min_period.sec > 0) + freq = 0; + else if (et->et_max_period.frac != 0) + freq = min(freq, BT2FREQ(&et->et_min_period)); + if (et->et_max_period.sec == 0 && et->et_max_period.frac != 0) + freq = max(freq, BT2FREQ(&et->et_max_period)); + return (freq); +} + /* * Configure and start event timers. */ @@ -327,21 +345,25 @@ cpu_initclocks_bsp(void) singlemul = 4; } if (timer[1] == NULL) { - base = hz * singlemul; - if (base < 128) + base = round_freq(timer[0], hz * singlemul); + singlemul = max((base + hz / 2) / hz, 1); + hz = (base + singlemul / 2) / singlemul; + if (base <= 128) stathz = base; else { div = base / 128; - if (div % 2 == 0) + if (div >= singlemul && (div % singlemul) == 0) div++; stathz = base / div; } profhz = stathz; - while ((profhz + stathz) <= 8192) + while ((profhz + stathz) <= 128 * 64) profhz += stathz; + profhz = round_freq(timer[0], profhz); } else { - stathz = 128; - profhz = stathz * 64; + hz = round_freq(timer[0], hz); + stathz = round_freq(timer[1], 127); + profhz = round_freq(timer[1], stathz * 64); } ET_LOCK(); cpu_restartclocks(); @@ -385,7 +407,9 @@ cpu_restartclocks(void) } else { timer1hz = hz; timer2hz = profiling_on ? profhz : stathz; + timer2hz = round_freq(timer[1], timer2hz); } + timer1hz = round_freq(timer[0], timer1hz); printf("Starting kernel event timers: %s @ %dHz, %s @ %dHz\n", timer[0]->et_name, timer1hz, timer[1] ? timer[1]->et_name : "NONE", timer2hz); diff --git a/sys/kern/kern_et.c b/sys/kern/kern_et.c index 52fb70585474..61a250086191 100644 --- a/sys/kern/kern_et.c +++ b/sys/kern/kern_et.c @@ -167,6 +167,26 @@ et_start(struct eventtimer *et, if ((et->et_flags & ET_FLAGS_ONESHOT) == 0 && period == NULL) return (ENODEV); + if (first != NULL) { + if (first->sec < et->et_min_period.sec || + (first->sec == et->et_min_period.sec && + first->frac < et->et_min_period.frac)) + first = &et->et_min_period; + if (first->sec > et->et_max_period.sec || + (first->sec == et->et_max_period.sec && + first->frac > et->et_max_period.frac)) + first = &et->et_max_period; + } + if (period != NULL) { + if (period->sec < et->et_min_period.sec || + (period->sec == et->et_min_period.sec && + period->frac < et->et_min_period.frac)) + period = &et->et_min_period; + if (period->sec > et->et_max_period.sec || + (period->sec == et->et_max_period.sec && + period->frac > et->et_max_period.frac)) + period = &et->et_max_period; + } if (et->et_start) return (et->et_start(et, first, period)); return (0); diff --git a/sys/sys/timeet.h b/sys/sys/timeet.h index bafa55a52e1b..bc713d68ed33 100644 --- a/sys/sys/timeet.h +++ b/sys/sys/timeet.h @@ -61,6 +61,7 @@ struct eventtimer { #define ET_FLAGS_ONESHOT 2 #define ET_FLAGS_PERCPU 4 #define ET_FLAGS_C3STOP 8 +#define ET_FLAGS_POW2DIV 16 int et_quality; /* * Used to determine if this timecounter is better than @@ -69,6 +70,8 @@ struct eventtimer { int et_active; u_int64_t et_frequency; /* Base frequency in Hz. */ + struct bintime et_min_period; + struct bintime et_max_period; et_start_t *et_start; et_stop_t *et_stop; et_event_cb_t *et_event_cb; diff --git a/sys/x86/isa/atrtc.c b/sys/x86/isa/atrtc.c index 5a5ba63c1a63..b01e933ebd90 100644 --- a/sys/x86/isa/atrtc.c +++ b/sys/x86/isa/atrtc.c @@ -276,9 +276,13 @@ atrtc_attach(device_t dev) bus_bind_intr(dev, sc->intr_res, 0); } sc->et.et_name = "RTC"; - sc->et.et_flags = ET_FLAGS_PERIODIC; + sc->et.et_flags = ET_FLAGS_PERIODIC | ET_FLAGS_POW2DIV; sc->et.et_quality = 0; sc->et.et_frequency = 32768; + sc->et.et_min_period.sec = 0; + sc->et.et_min_period.frac = 0x0008LL << 48; + sc->et.et_max_period.sec = 0; + sc->et.et_max_period.frac = 0x8000LL << 48; sc->et.et_start = rtc_start; sc->et.et_stop = rtc_stop; sc->et.et_priv = dev; diff --git a/sys/x86/isa/clock.c b/sys/x86/isa/clock.c index 6b5fede5cd9f..62869a602e2d 100644 --- a/sys/x86/isa/clock.c +++ b/sys/x86/isa/clock.c @@ -665,6 +665,11 @@ attimer_attach(device_t dev) sc->et.et_flags = ET_FLAGS_PERIODIC; sc->et.et_quality = 100; sc->et.et_frequency = i8254_freq; + sc->et.et_min_period.sec = 0; + sc->et.et_min_period.frac = ((1LL << 62) / i8254_freq) << 2; + sc->et.et_max_period.sec = 0xffff / i8254_freq; + sc->et.et_max_period.frac = + ((0xffffLL << 48) / i8254_freq) << 16; sc->et.et_start = attimer_start; sc->et.et_stop = attimer_stop; sc->et.et_priv = dev; diff --git a/sys/x86/x86/local_apic.c b/sys/x86/x86/local_apic.c index 4364fb1bb0c4..ffa34a5de8c9 100644 --- a/sys/x86/x86/local_apic.c +++ b/sys/x86/x86/local_apic.c @@ -263,6 +263,11 @@ lapic_init(vm_paddr_t addr) lapic_et.et_quality -= 100; } lapic_et.et_frequency = 0; + /* We don't know frequency yet, so trying to guess. */ + lapic_et.et_min_period.sec = 0; + lapic_et.et_min_period.frac = 0x00001000LL << 32; + lapic_et.et_max_period.sec = 1; + lapic_et.et_max_period.frac = 0; lapic_et.et_start = lapic_et_start; lapic_et.et_stop = lapic_et_stop; lapic_et.et_priv = NULL; @@ -493,6 +498,12 @@ lapic_et_start(struct eventtimer *et, printf("lapic: Divisor %lu, Frequency %lu Hz\n", lapic_timer_divisor, value); et->et_frequency = value; + et->et_min_period.sec = 0; + et->et_min_period.frac = + ((1LL << 63) / et->et_frequency) << 1; + et->et_max_period.sec = 0xffffffff / et->et_frequency; + et->et_max_period.frac = + ((0xffffffffLL << 32) / et->et_frequency) << 32; } la = &lapics[lapic_id()]; /*