pit: new timer implementation

Issue #5138
This commit is contained in:
Alexander Boettcher 2024-05-15 12:00:44 +02:00 committed by Christian Helmuth
parent 32bc1b14d4
commit 801fe272ca
4 changed files with 430 additions and 318 deletions

View File

@ -0,0 +1,426 @@
/*
* \brief Timer driver for the PIT
* \author Norman Feske
* \author Alexander Boettcher
* \date 2024-05-13
*/
/*
* Copyright (C) 2024 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 <base/component.h>
#include <base/heap.h>
#include <base/session_object.h>
#include <irq_session/connection.h>
#include <io_port_session/connection.h>
#include <root/component.h>
#include <timer_session/timer_session.h>
#include <trace/timestamp.h>
/* base-internal includes */
#include <base/internal/alarm_registry.h>
namespace Timer {
using namespace Genode;
struct Clock;
struct Device;
struct Alarm;
struct Root;
struct Session_component;
struct Main;
using Alarms = Alarm_registry<Alarm, Clock>;
}
struct Timer::Clock
{
uint64_t us;
static constexpr uint64_t MASK = uint64_t(-1);
uint64_t value() const { return us; }
void print(Output &out) const { Genode::print(out, us/1000); }
};
class Timer::Device : Noncopyable
{
private:
enum {
PIT_TICKS_PER_SECOND = 1193182,
PIT_MAX_COUNT = 65535,
PIT_MAX_USEC = (1000ull * 1000 * PIT_MAX_COUNT) /
(PIT_TICKS_PER_SECOND),
PIT_DATA_PORT_0 = 0x40, /* data port for PIT channel 0,
connected to the PIC */
PIT_CMD_PORT = 0x43, /* PIT command port */
IRQ_PIT = 0, /* timer interrupt at the PIC */
/*
* Bit definitions for accessing the PIT command port
*/
PIT_CMD_SELECT_CHANNEL_0 = 0 << 6,
PIT_CMD_ACCESS_LO = 1 << 4,
PIT_CMD_ACCESS_LO_HI = 3 << 4,
PIT_CMD_MODE_IRQ = 0 << 1,
PIT_CMD_MODE_RATE = 2 << 1,
PIT_CMD_READ_BACK = 3 << 6,
PIT_CMD_RB_COUNT = 0 << 5,
PIT_CMD_RB_STATUS = 0 << 4,
PIT_CMD_RB_CHANNEL_0 = 1 << 1,
/*
* Bit definitions of the PIT status byte
*/
PIT_STAT_INT_LINE = 1 << 7,
};
/* PIT counter */
struct Counter { uint16_t value; };
public:
struct Wakeup_dispatcher : Interface
{
virtual void dispatch_device_wakeup() = 0;
};
struct Deadline : Clock { };
static constexpr Deadline infinite_deadline { uint64_t(-1) };
private:
Env &_env;
Io_port_connection _io_port { _env, PIT_DATA_PORT_0,
PIT_CMD_PORT - PIT_DATA_PORT_0 + 1 };
Irq_connection _timer_irq { _env, unsigned(IRQ_PIT) };
uint64_t _max_timeout_us { PIT_MAX_USEC };
Wakeup_dispatcher &_dispatcher;
Signal_handler<Device> _handler { _env.ep(), *this, &Device::_handle_timeout };
uint64_t _curr_time_us { };
Counter _last_read { };
bool _wrap_handled { };
uint64_t _convert_counter_to_us(uint64_t counter)
{
/* round up to 1us in case of rest */
auto const mod = (counter * 1000 * 1000) % PIT_TICKS_PER_SECOND;
return (counter * 1000 * 1000 / PIT_TICKS_PER_SECOND)
+ (mod ? 1 : 0);
}
Counter _convert_relative_us_to_counter(uint64_t rel_us)
{
return { .value = uint16_t(min(rel_us * PIT_TICKS_PER_SECOND / 1000 / 1000,
uint64_t(PIT_MAX_COUNT))) };
}
void _handle_timeout()
{
_dispatcher.dispatch_device_wakeup();
_timer_irq.ack_irq();
}
void _set_counter(Counter const &cnt)
{
/* wrap status gets reset by re-programming counter */
_wrap_handled = false;
_io_port.outb(PIT_DATA_PORT_0, uint8_t( cnt.value & 0xff));
_io_port.outb(PIT_DATA_PORT_0, uint8_t((cnt.value >> 8) & 0xff));
}
void _with_counter(auto const &fn)
{
/* read-back count 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);
bool const wrapped = !!(status & PIT_STAT_INT_LINE);
fn(Counter(uint16_t((hi << 8) | lo)), wrapped && !_wrap_handled);
/* only handle wrap one time until next _set_counter */
if (wrapped)
_wrap_handled = true;
}
void _advance_current_time()
{
_with_counter([&](Counter const &pit, bool wrapped) {
auto diff = (!wrapped && (_last_read.value >= pit.value))
? _last_read.value - pit.value
: PIT_MAX_COUNT - pit.value + _last_read.value;
_curr_time_us += _convert_counter_to_us(diff);
_last_read = pit;
});
}
public:
Device(Env &env, Wakeup_dispatcher &dispatcher)
: _env(env), _dispatcher(dispatcher)
{
/* 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(_handler);
_handle_timeout();
}
Clock now()
{
_advance_current_time();
return Clock { .us = _curr_time_us };
}
void update_deadline(Deadline const deadline)
{
uint64_t const now_us = now().us;
uint64_t const rel_us = (deadline.us > now_us)
? min(_max_timeout_us, deadline.us - now_us)
: 1;
auto const pit_cnt = _convert_relative_us_to_counter(rel_us);
_last_read = pit_cnt;
_set_counter(pit_cnt);
}
};
struct Timer::Alarm : Alarms::Element
{
Session_component &session;
Alarm(Alarms &alarms, Session_component &session, Clock time)
:
Alarms::Element(alarms, *this, time), session(session)
{ }
void print(Output &out) const;
};
static Timer::Device::Deadline next_deadline(Timer::Alarms &alarms)
{
using namespace Timer;
return alarms.soonest(Clock { 0 }).convert<Device::Deadline>(
[&] (Clock soonest) -> Device::Deadline {
/* scan alarms for a cluster nearby the soonest */
uint64_t const MAX_DELAY_US = 250;
Device::Deadline result { soonest.us };
alarms.for_each_in_range(soonest, Clock { soonest.us + MAX_DELAY_US },
[&] (Alarm const &alarm) {
result.us = max(result.us, alarm.time.us); });
return result;
},
[&] (Alarms::None) { return Device::infinite_deadline; });
}
struct Timer::Session_component : Session_object<Timer::Session, Session_component>
{
Alarms &_alarms;
Device &_device;
Signal_context_capability _sigh { };
Clock const _creation_time = _device.now();
uint64_t _local_now_us() const { return _device.now().us - _creation_time.us; }
struct Period { uint64_t us; };
Constructible<Period> _period { };
Constructible<Alarm> _alarm { };
Session_component(Env &env,
Resources const &resources,
Label const &label,
Diag const &diag,
Alarms &alarms,
Device &device)
:
Session_object(env.ep(), resources, label, diag),
_alarms(alarms), _device(device)
{ }
/**
* Called by Device::Wakeup_dispatcher
*/
void handle_wakeup()
{
if (_sigh.valid())
Signal_transmitter(_sigh).submit();
if (_period.constructed()) {
Clock const next = _alarm.constructed()
? Clock { _alarm->time.us + _period->us }
: Clock { _device.now().us + _period->us };
_alarm.construct(_alarms, *this, next);
} else /* response of 'trigger_once' */ {
_alarm.destruct();
}
}
/******************************
** Timer::Session interface **
******************************/
void trigger_once(uint64_t rel_us) override
{
_period.destruct();
_alarm.destruct();
Clock const now = _device.now();
rel_us = max(rel_us, 250u);
_alarm.construct(_alarms, *this, Clock { now.us + rel_us });
_device.update_deadline(next_deadline(_alarms));
}
void trigger_periodic(uint64_t period_us) override
{
_period.destruct();
_alarm.destruct();
if (period_us) {
period_us = max(period_us, 1000u);
_period.construct(period_us);
handle_wakeup();
}
_device.update_deadline(next_deadline(_alarms));
}
void sigh(Signal_context_capability sigh) override { _sigh = sigh; }
uint64_t elapsed_ms() const override { return _local_now_us()/1000; }
uint64_t elapsed_us() const override { return _local_now_us(); }
void msleep(uint64_t) override { }
void usleep(uint64_t) override { }
};
struct Timer::Root : public Root_component<Session_component>
{
private:
Env &_env;
Alarms &_alarms;
Device &_device;
protected:
Session_component *_create_session(const char *args) override
{
return new (md_alloc())
Session_component(_env,
session_resources_from_args(args),
session_label_from_args(args),
session_diag_from_args(args),
_alarms, _device);
}
void _upgrade_session(Session_component *s, const char *args) override
{
s->upgrade(ram_quota_from_args(args));
s->upgrade(cap_quota_from_args(args));
}
void _destroy_session(Session_component *session) override
{
Genode::destroy(md_alloc(), session);
}
public:
Root(Env &env, Allocator &md_alloc, Alarms &alarms, Device &device)
:
Root_component<Session_component>(&env.ep().rpc_ep(), &md_alloc),
_env(env), _alarms(alarms), _device(device)
{ }
};
void Timer::Alarm::print(Output &out) const { Genode::print(out, session.label()); }
struct Timer::Main : Device::Wakeup_dispatcher
{
Env &_env;
Device _device { _env, *this };
Alarms _alarms { };
Sliced_heap _sliced_heap { _env.ram(), _env.rm() };
Root _root { _env, _sliced_heap, _alarms, _device };
/**
* Device::Wakeup_dispatcher
*/
void dispatch_device_wakeup() override
{
Clock const now = _device.now();
/* handle and remove pending alarms */
while (_alarms.with_any_in_range({ 0 }, now, [&] (Alarm &alarm) {
alarm.session.handle_wakeup(); }));
/* schedule next wakeup */
_device.update_deadline(next_deadline(_alarms));
}
Main(Genode::Env &env) : _env(env)
{
_env.parent().announce(_env.ep().manage(_root));
}
};
void Component::construct(Genode::Env &env) { static Timer::Main inst(env); }

View File

@ -1,9 +1,8 @@
TARGET = pit_timer_drv
REQUIRES = x86
GEN_DIR := $(dir $(call select_from_repositories,src/timer/main.cc))
INC_DIR += $(GEN_DIR)/pit
SRC_CC += time_source.cc
SRC_CC += main.cc
LIBS += base
include $(GEN_DIR)/target.inc
REP_INC_DIR += src/include
vpath time_source.cc $(GEN_DIR)/pit
vpath main.cc $(dir $(call select_from_repositories,src/timer/pit/main.cc))

View File

@ -1,210 +0,0 @@
/*
* \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, (uint8_t)(value & 0xff));
_io_port.outb(PIT_DATA_PORT_0, (uint8_t)((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 (uint16_t)((hi << 8) | lo);
}
void Timer::Time_source::set_timeout(Microseconds duration,
Timeout_handler &handler)
{
_handler = &handler;
uint64_t duration_us = duration.value;
/* timeout '0' is trigger to cancel the current pending, if required */
if (!duration.value) {
duration_us = max_timeout().value;
Signal_transmitter(_signal_handler).submit();
} else {
/* limit timer-interrupt rate */
enum { MAX_TIMER_IRQS_PER_SECOND = 4*1000 };
if (duration_us < (uint64_t)1000 * 1000 / MAX_TIMER_IRQS_PER_SECOND)
duration_us = (uint64_t)1000 * 1000 / MAX_TIMER_IRQS_PER_SECOND;
if (duration_us > max_timeout().value)
duration_us = max_timeout().value;
}
_counter_init_value = (uint16_t)((PIT_TICKS_PER_MSEC * duration_us) / 1000);
_set_counter(_counter_init_value);
if (duration.value)
_timer_irq.ack_irq();
}
uint32_t Timer::Time_source::_ticks_since_update_no_wrap(uint16_t curr_counter)
{
/*
* The counter did not wrap since the last update of _counter_init_value.
* This means that _counter_init_value is equal to or greater than
* curr_counter and that the time that passed is simply the difference
* between the two.
*/
return _counter_init_value - curr_counter;
}
uint32_t Timer::Time_source::_ticks_since_update_one_wrap(uint16_t curr_counter)
{
/*
* The counter wrapped since the last update of _counter_init_value.
* This means that the time that passed is the whole _counter_init_value
* plus the time that passed since the counter wrapped.
*/
return _counter_init_value + PIT_MAX_COUNT - curr_counter;
}
Duration Timer::Time_source::curr_time()
{
/* read out and update curr time solely if running in context of irq */
if (_irq)
_curr_time();
return Duration(Microseconds(_curr_time_us));
}
Duration Timer::Time_source::_curr_time()
{
/* read PIT counter and wrapped status */
uint32_t ticks;
bool wrapped;
uint16_t const curr_counter = _read_counter(&wrapped);
if (!wrapped) {
/*
* The counter did not wrap since the last call to scheduled_timeout
* which means that it did not wrap since the last update of
* _counter_init_time.
*/
ticks = _ticks_since_update_no_wrap(curr_counter);
}
else if (wrapped && !_handled_wrap) {
/*
* The counter wrapped at least once since the last call to
* schedule_timeout (wrapped) and curr_time (!_handled_wrap) which
* means that it definitely did wrap since the last update of
* _counter_init_time. We cannot determine whether it wrapped only
* once but we have to assume it. Even if it wrapped multiple times,
* the error that results from the assumption that it did not is pretty
* innocuous ((nr_of_wraps - 1) * 53 ms at a max).
*/
ticks = _ticks_since_update_one_wrap(curr_counter);
_handled_wrap = true;
}
else { /* wrapped && _handled_wrap */
/*
* The counter wrapped at least once since the last call to
* schedule_timeout (wrapped) but may not have wrapped since the last
* call to curr_time (_handled_wrap).
*/
if (_counter_init_value >= curr_counter) {
/*
* We cannot determine whether the counter wrapped since the last
* call to curr_time but we have to assume that it did not. Even if
* it wrapped, the error that results from the assumption that it
* did not is pretty innocuous as long as _counter_init_value is
* not greater than curr_counter (nr_of_wraps * 53 ms at a max).
*/
ticks = _ticks_since_update_no_wrap(curr_counter);
} else {
/*
* The counter definitely wrapped multiple times since the last
* call to schedule_timeout and at least once since the last call
* to curr_time. It is the only explanation for the fact that
* curr_counter became greater than _counter_init_value again
* after _counter_init_value was updated with a wrapped counter
* by curr_time (_handled_wrap). This means two things:
*
* First, the counter wrapped at least once since the last update
* of _counter_init_value. We cannot determine whether it wrapped
* only once but we have to assume it. Even if it wrapped multiple
* times, the error that results from the assumption that it
* did not is pretty innocuous ((nr_of_wraps - 1) * 53 ms at a max).
*
* Second, we have to warn the user as it is a sure indication of
* insufficient activation latency if the counter wraps multiple
* times between two schedule_timeout calls.
*/
warning("PIT wrapped multiple times, timer-driver latency too big");
ticks = _ticks_since_update_one_wrap(curr_counter);
}
}
/* use current counter as the reference for the next update */
_counter_init_value = curr_counter;
/* translate counter to microseconds and update time value */
static_assert(PIT_TICKS_PER_MSEC >= (unsigned)TIMER_MIN_TICKS_PER_MS,
"Bad TICS_PER_MS value");
_curr_time_us += timer_ticks_to_us(ticks, PIT_TICKS_PER_MSEC);
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, unsigned(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);
}

View File

@ -1,103 +0,0 @@
/*
* \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.
*/
#ifndef _TIME_SOURCE_H_
#define _TIME_SOURCE_H_
/* Genode includes */
#include <io_port_session/connection.h>
#include <irq_session/connection.h>
#include <base/duration.h>
/* local includes */
#include <signalled_time_source.h>
namespace Timer {
using Genode::uint64_t;
using Microseconds = Genode::Microseconds;
using Duration = Genode::Duration;
class Time_source;
}
class Timer::Time_source : public Genode::Signalled_time_source
{
private:
enum {
PIT_TICKS_PER_SECOND = 1193182,
PIT_TICKS_PER_MSEC = PIT_TICKS_PER_SECOND/1000,
PIT_MAX_COUNT = 65535,
PIT_DATA_PORT_0 = 0x40, /* data port for PIT channel 0,
connected to the PIC */
PIT_CMD_PORT = 0x43, /* PIT command port */
PIT_MAX_USEC = (PIT_MAX_COUNT*1000)/(PIT_TICKS_PER_MSEC),
IRQ_PIT = 0, /* timer interrupt at the PIC */
/*
* Bit definitions for accessing the PIT command port
*/
PIT_CMD_SELECT_CHANNEL_0 = 0 << 6,
PIT_CMD_ACCESS_LO = 1 << 4,
PIT_CMD_ACCESS_LO_HI = 3 << 4,
PIT_CMD_MODE_IRQ = 0 << 1,
PIT_CMD_MODE_RATE = 2 << 1,
PIT_CMD_READ_BACK = 3 << 6,
PIT_CMD_RB_COUNT = 0 << 5,
PIT_CMD_RB_STATUS = 0 << 4,
PIT_CMD_RB_CHANNEL_0 = 1 << 1,
/*
* Bit definitions of the PIT status byte
*/
PIT_STAT_INT_LINE = 1 << 7,
};
Genode::Io_port_connection _io_port;
Genode::Irq_connection _timer_irq;
uint64_t mutable _curr_time_us = 0;
Genode::uint16_t mutable _counter_init_value = 0;
bool mutable _handled_wrap = false;
void _set_counter(Genode::uint16_t value);
Genode::uint16_t _read_counter(bool *wrapped);
Genode::uint32_t _ticks_since_update_one_wrap(Genode::uint16_t curr_counter);
Genode::uint32_t _ticks_since_update_no_wrap(Genode::uint16_t curr_counter);
Duration _curr_time();
public:
Time_source(Genode::Env &env);
/*************************
** Genode::Time_source **
*************************/
Duration curr_time() override;
void set_timeout(Microseconds duration, Genode::Timeout_handler &handler) override;
Microseconds max_timeout() const override {
return Microseconds(PIT_MAX_USEC); }
};
#endif /* _TIME_SOURCE_H_ */