tech-kern archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
broken src/sys/arch/x86/isa/clock.c (i8254_get_timecount)
hi-
while debugging my 4.0/i386 auich_calibrate() problem, I've
come to the conclusion that i8254_get_timecount() has been broken
since rev 1.98 of the old src/sys/arch/i386/isa/clock.c (2006/09/03)
when "clkintr_pending" was removed.
the way I understand it, the i8254 provides a countdown timer
that drops by one at 1193182 Hz (TIMER_FREQ). the driver programs
it to count down from 11932 to zero to produce an interrupt at
rate "hz" (100 Hz). The initial value of the timer (11932) is
"rtclock_tval" in the driver.
the function i8254_get_timecount() is part of the new timecounter
framework. it is supposed to be a u_int counting up at TIMER_FREQ Hz
(wrapping when it gets to the end, I think).
the base value for i8254_get_timecount() is "i8254_offset" (a static
uint32_t). each time the clock interrupts clockintr() calls tickle_tc()
which would normally just add "rtclock_tval" to "i8254_offset" ...
/* from hw interrupt func, i8254_ticked normally not true(?) */
if (i8254_ticked)
i8254_ticked = 0;
else {
i8254_offset += rtclock_tval;
i8254_lastcount = 0;
}
however, if the i8254 counter is read using i8254_get_timecount(),
then that function attempts to add in the between-interrupt ticks while
watching for the i8254 counter to "wrap" around like this:
/* high and low come from the countdown hardware register */
/* convert countdown to count up to match the timecounter framework */
count = rtclock_tval - ((high << 8) | low);
/* normal case: rtclock_tval != 0 */
/* count < i8254_lastcount is true if i8254 counter wrapped */
/* i8254_ticked is cleared by the hardware interrupt */
if (rtclock_tval && (count < i8254_lastcount || !i8254_ticked)) {
i8254_ticked = 1;
i8254_offset += rtclock_tval;
}
i8254_lastcount = count; /* save old val for wrap detection */
count += i8254_offset;
/* ... unlock */
return (count);
the problem with that is that i8254_ticked is 0 to start, so the very
first time you hit the "if" statement it is going to set i8254_ticked to
1 and advance the i8254_offset irrespective of if a wrap is detected
(e.g. count < i8254_lastcount).
The earlier version of the if statement was:
if (rtclock_tval &&
(count < i8254_lastcount || (!i8254_ticked && clkintr_pending))) { ... }
where "clkintr_pending" was an int that was always zero. So this if
only fired when the wrap detection (count < i8254_lastcount) was true.
I looked at the FreeBSD version of this code and it looks like this:
if (count < i8254_lastcount ||
(!i8254_ticked && (clkintr_pending ||
((count < 20 || (!(eflags & PSL_I) && count < timer0_max_count / 2u)
) &&
i8254_pending != NULL && i8254_pending(i8254_intsrc))))) { ... }
not easy to understand (4 line "if" with 4 levels of parens: YUCK!), but
I believe the idea of the extra stuff in the if statement is to only fire
the !i8254_ticked case if !i8254_ticked AND an interrupt is pending. the
NetBSD version clearly doesn't have the "AND an interrupt is pending" part.
That is what is causing auich_calibrate() to fail!
To fix the problem, I was thinking one could either ignore i8254_ticked
like we used to:
if (rtclock_tval && count < i8254_lastcount)
or alternately, maybe change the "||" to an "&&" (assuming that the
interrupt will run before the counter wraps more than once?).
if (rtclock_tval && (count < i8254_lastcount && !i8254_ticked))
either way makes auich_calibrate() work properly once again! any
suggestions on what to go with?
chuck
Home |
Main Index |
Thread Index |
Old Index