diff --git a/repos/base/src/timer/pit/main.cc b/repos/base/src/timer/pit/main.cc
new file mode 100644
index 0000000000..17e37ccbf2
--- /dev/null
+++ b/repos/base/src/timer/pit/main.cc
@@ -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
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* base-internal includes */
+#include
+
+namespace Timer {
+
+ using namespace Genode;
+
+ struct Clock;
+ struct Device;
+ struct Alarm;
+ struct Root;
+ struct Session_component;
+ struct Main;
+
+ using Alarms = Alarm_registry;
+}
+
+
+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 _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(
+ [&] (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
+{
+ 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 { };
+ Constructible _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
+{
+ 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(&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); }
diff --git a/repos/base/src/timer/pit/target.inc b/repos/base/src/timer/pit/target.inc
index ae785572df..1bbe43065f 100644
--- a/repos/base/src/timer/pit/target.inc
+++ b/repos/base/src/timer/pit/target.inc
@@ -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))
diff --git a/repos/base/src/timer/pit/time_source.cc b/repos/base/src/timer/pit/time_source.cc
deleted file mode 100644
index 86458070d4..0000000000
--- a/repos/base/src/timer/pit/time_source.cc
+++ /dev/null
@@ -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
-
-/* local includes */
-#include
-
-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);
-}
diff --git a/repos/base/src/timer/pit/time_source.h b/repos/base/src/timer/pit/time_source.h
deleted file mode 100644
index 2e0646168a..0000000000
--- a/repos/base/src/timer/pit/time_source.h
+++ /dev/null
@@ -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
-#include
-#include
-
-/* local includes */
-#include
-
-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_ */