genode/repos/os/src/drivers/timer/pit/time_source.cc
Martin Stein 399e1586be timer: generic timer_ticks_to_us implementation
There are hardware timers whose frequency can't be expressed as
ticks-per-microsecond integer-value because only a ticks-per-millisecond
integer-value is precise enough. We don't want to use expensive
floating-point values here but nonetheless want to translate from ticks
to time with microseconds precision. Thus, we split the input in two and
translate both parts separately. This way, we can raise precision by
shifting the values to their optimal bit position. Afterwards, the results
are shifted back and merged together again.

As this algorithm is not so trivial anymore and used by at least three
timer drivers (base-hw/x86_64, base-hw/cortex_a9, timer/pit), move it to a
generic header to avoid redundancy.

Ref #2400
2017-08-28 16:49:49 +02:00

116 lines
3.0 KiB
C++

/*
* \brief Time source that uses the Programmable Interval Timer (PIT)
* \author Norman Feske
* \author Martin Stein
* \date 2009-06-16
*/
/*
* Copyright (C) 2009-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <drivers/timer/util.h>
/* local includes */
#include <time_source.h>
using namespace Genode;
void Timer::Time_source::_set_counter(uint16_t value)
{
_handled_wrap = false;
_io_port.outb(PIT_DATA_PORT_0, value & 0xff);
_io_port.outb(PIT_DATA_PORT_0, (value >> 8) & 0xff);
}
uint16_t Timer::Time_source::_read_counter(bool *wrapped)
{
/* read-back count and status of counter 0 */
_io_port.outb(PIT_CMD_PORT, PIT_CMD_READ_BACK |
PIT_CMD_RB_COUNT |
PIT_CMD_RB_STATUS |
PIT_CMD_RB_CHANNEL_0);
/* read status byte from latch register */
uint8_t status = _io_port.inb(PIT_DATA_PORT_0);
/* read low and high bytes from latch register */
uint16_t lo = _io_port.inb(PIT_DATA_PORT_0);
uint16_t hi = _io_port.inb(PIT_DATA_PORT_0);
*wrapped = status & PIT_STAT_INT_LINE ? true : false;
return (hi << 8) | lo;
}
void Timer::Time_source::schedule_timeout(Microseconds duration,
Timeout_handler &handler)
{
_handler = &handler;
_timer_irq.ack_irq();
unsigned long duration_us = duration.value;
/* limit timer-interrupt rate */
enum { MAX_TIMER_IRQS_PER_SECOND = 4*1000 };
if (duration_us < 1000 * 1000 / MAX_TIMER_IRQS_PER_SECOND)
duration_us = 1000 * 1000 / MAX_TIMER_IRQS_PER_SECOND;
if (duration_us > max_timeout().value)
duration_us = max_timeout().value;
_counter_init_value = (PIT_TICKS_PER_MSEC * duration_us) / 1000;
_set_counter(_counter_init_value);
}
Duration Timer::Time_source::curr_time()
{
uint32_t ticks;
/* read PIT count and status */
bool wrapped;
uint16_t const curr_counter = _read_counter(&wrapped);
/* determine the time since we looked at the counter */
if (wrapped && !_handled_wrap) {
ticks = _counter_init_value;
/* the counter really wrapped around */
if (curr_counter)
ticks += PIT_MAX_COUNT + 1 - curr_counter;
_handled_wrap = true;
} else {
if (_counter_init_value)
ticks = _counter_init_value - curr_counter;
else
ticks = PIT_MAX_COUNT + 1 - curr_counter;
}
static_assert(PIT_TICKS_PER_MSEC >= 1000, "Bad TICS_PER_MS value");
_curr_time_us += timer_ticks_to_us(ticks, PIT_TICKS_PER_MSEC);
/* use current counter as the reference for the next update */
_counter_init_value = curr_counter;
return Duration(Microseconds(_curr_time_us));
}
Timer::Time_source::Time_source(Env &env)
:
Signalled_time_source(env),
_io_port(env, PIT_DATA_PORT_0, PIT_CMD_PORT - PIT_DATA_PORT_0 + 1),
_timer_irq(env, IRQ_PIT)
{
/* operate PIT in one-shot mode */
_io_port.outb(PIT_CMD_PORT, PIT_CMD_SELECT_CHANNEL_0 |
PIT_CMD_ACCESS_LO_HI | PIT_CMD_MODE_IRQ);
_timer_irq.sigh(_signal_handler);
}