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_ */