aboutsummaryrefslogtreecommitdiff
path: root/module/timer
diff options
context:
space:
mode:
authorRonald Cron <ronald.cron@arm.com>2018-06-05 09:31:39 +0200
committerRonald Cron <ronald.cron@arm.com>2018-06-08 11:46:47 +0200
commitb151958dbb2f37383f4d9a1f7802c36008d9fef2 (patch)
treefe20ebfb8c10facbfd028edefe601462ae3ee64c /module/timer
parentfd3027b6fd17a4a33a685adb73f2acfcae9a2ced (diff)
Add support for SGM-775
Co-authored-by: Filipe Rinaldi <filipe.rinaldi@arm.com> Co-authored-by: Paul Beesley <paul.beesley@arm.com> Co-authored-by: Chris Kay <chris.kay@arm.com> Co-authored-by: Elieva Pignat <elieva.pignat@arm.com> Co-authored-by: Pedro Custodio <pedro.krewinkelcustodio@arm.com> Change-Id: Ic7524ad58a7c15d5b055e88a9719b2feee437f1d Signed-off-by: Ronald Cron <ronald.cron@arm.com>
Diffstat (limited to 'module/timer')
-rw-r--r--module/timer/include/mod_timer.h294
-rw-r--r--module/timer/src/Makefile11
-rw-r--r--module/timer/src/mod_timer.c653
3 files changed, 958 insertions, 0 deletions
diff --git a/module/timer/include/mod_timer.h b/module/timer/include/mod_timer.h
new file mode 100644
index 00000000..7bf3567a
--- /dev/null
+++ b/module/timer/include/mod_timer.h
@@ -0,0 +1,294 @@
+/*
+ * Arm SCP/MCP Software
+ * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Description:
+ * Timer HAL
+ */
+
+#ifndef MOD_TIMER_H
+#define MOD_TIMER_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <fwk_id.h>
+#include <fwk_module_idx.h>
+
+/*!
+ * \addtogroup GroupModules Modules
+ * @{
+ */
+
+/*!
+ * \defgroup GroupModuleTimer Timer HAL
+ *
+ * \brief Hardware Abstraction Layer for Timers.
+ *
+ * \details Provides functionality for setting timer events, tracking elapsed
+ * time, and synchronously delaying execution.
+ *
+ * @{
+ */
+
+/*!
+ * \brief Timer module API indicies
+ */
+enum mod_timer_api_idx {
+ /*! Timer API index */
+ MOD_TIMER_API_IDX_TIMER,
+
+ /*! Alarm API index */
+ MOD_TIMER_API_IDX_ALARM,
+
+ /*! Number of APIs */
+ MOD_TIMER_API_COUNT,
+};
+
+/*!
+ * \brief Timer API ID
+ */
+#define MOD_TIMER_API_ID_TIMER FWK_ID_API(FWK_MODULE_IDX_TIMER, \
+ MOD_TIMER_API_IDX_TIMER)
+
+/*!
+ * \brief Alarm API ID
+ */
+#define MOD_TIMER_API_ID_ALARM FWK_ID_API(FWK_MODULE_IDX_TIMER, \
+ MOD_TIMER_API_IDX_ALARM)
+
+/*!
+ * \brief Alarm type.
+ */
+enum mod_timer_alarm_type {
+ /*! Alarm that will trigger once */
+ MOD_TIMER_ALARM_TYPE_ONCE,
+
+ /*! Alarm that will trigger at regular intervals */
+ MOD_TIMER_ALARM_TYPE_PERIODIC,
+
+ /*! Number of alarm types */
+ MOD_TIMER_ALARM_TYPE_COUNT,
+};
+
+/*!
+ * \brief Timer device descriptor
+ */
+struct mod_timer_dev_config {
+ /*! Element identifier for the device's associated driver */
+ fwk_id_t id;
+
+ /*! Timer device IRQ number */
+ unsigned int timer_irq;
+};
+
+/*!
+ * \brief Timer driver interface.
+ */
+struct mod_timer_driver_api {
+ /*! Name of the driver. */
+ const char *name;
+
+ /*! Enable timer events */
+ int (*enable)(fwk_id_t dev_id);
+
+ /*! Disable timer events */
+ int (*disable)(fwk_id_t dev_id);
+
+ /*! Set timer event for a specified timestamp */
+ int (*set_timer)(fwk_id_t dev_id, uint64_t timestamp);
+
+ /*! Get remaining time until the next pending timer event is due to fire */
+ int (*get_timer)(fwk_id_t dev_id, uint64_t *timestamp);
+
+ /*! Get current counter value */
+ int (*get_counter)(fwk_id_t dev_id, uint64_t *value);
+
+ /*! Get counter frequency */
+ int (*get_frequency)(fwk_id_t dev_id, uint32_t *value);
+};
+
+/*!
+ * \brief Timer HAL interface
+ */
+struct mod_timer_api {
+ /*!
+ * \brief Get the frequency of a given timer.
+ *
+ * \details Get the frequency in Hertz (Hz) that a timer is running at.
+ *
+ * \param dev_id Element identifier that identifies the timer device.
+ * \param frequency Pointer to storage for the timer frequency.
+ *
+ * \retval FWK_SUCCESS Operation succeeded.
+ * \retval FWK_E_PARAM The frequency pointer was invalid.
+ * \retval One of the other specific error codes described by the framework.
+ */
+ int (*get_frequency)(fwk_id_t dev_id, uint32_t *frequency);
+
+ /*!
+ * \brief Get a counter timestamp that represents a given time period in
+ * microseconds (µS).
+ *
+ * \note The value of the resulting timestamp is only valid for the given
+ * device, since other timer devices may operate at different rates.
+ *
+ * \param dev_id Element identifier that identifies the timer device.
+ * \param microseconds Period, in microseconds.
+ * \param timestamp Pointer to storage for the resulting counter timestamp.
+ *
+ * \retval FWK_SUCCESS Operation succeeded.
+ * \retval FWK_E_PARAM The timestamp pointer was invalid.
+ * \retval One of the other specific error codes described by the framework.
+ */
+ int (*time_to_timestamp)(fwk_id_t dev_id,
+ uint32_t microseconds,
+ uint64_t *timestamp);
+
+ /*!
+ * \brief Get the current counter value of a given timer.
+ *
+ * \details Directly returns the counter value of the timer at the present
+ * moment.
+ *
+ * \param dev_id Element identifier that identifies the timer device.
+ * \param counter Pointer to storage for the counter value.
+ *
+ * \retval FWK_SUCCESS Operation succeeded.
+ * \retval FWK_E_PARAM The counter pointer was invalid.
+ * \retval One of the other specific error codes described by the framework.
+ */
+ int (*get_counter)(fwk_id_t dev_id, uint64_t *counter);
+
+ /*!
+ * \brief Delay execution by synchronously waiting for a specified amount
+ * of time.
+ *
+ * \details Blocks the calling thread for the specified amount of time.
+ *
+ * \param dev_id Element identifier that identifies the timer device.
+ * \param microseconds The amount of time, given in microseconds, to delay.
+ *
+ * \retval FWK_SUCCESS Operation succeeded.
+ * \retval One of the other specific error codes described by the module.
+ */
+ int (*delay)(fwk_id_t dev_id, uint32_t microseconds);
+
+ /*!
+ * \brief Delay execution, waiting until a given condition is true or until
+ * a given timeout period has been exceeded, whichever occurs first.
+ *
+ * \note The calling thread is blocked until either condition has been met.
+ *
+ * \param dev_id Element identifier that identifies the timer device.
+ * \param microseconds Maximum amount of time, in microseconds, to wait for
+ * the given condition to be met.
+ * \param cond Pointer to the function that evaluates the condition and
+ * which returns a boolean value indicating if it has been met or not.
+ * The condition function is called repeatedly until it returns true,
+ * or until the timeout period has elapsed.
+ * \param data Pointer passed to the condition function when it is called.
+ *
+ * \retval FWK_SUCCESS The condition was met before the timeout period
+ * elapsed.
+ * \retval FWK_E_TIMEOUT The timeout period elapsed before the condition was
+ * met.
+ * \retval One of the other specific error codes described by the module.
+ */
+ int (*wait)(fwk_id_t dev_id,
+ uint32_t microseconds,
+ bool (*cond)(void*),
+ void *data);
+
+ /*!
+ * \brief Get the time difference, expressed in timer ticks, between the
+ * current timer counter value and the given timestamp. This represents
+ * the remaining number of ticks until the given timestamp is reached.
+ *
+ * \note If the given timestamp is in the past then the remaining_ticks is
+ * set to zero.
+ *
+ * \param dev_id Element identifier that identifies the timer device.
+ * \param timestamp Timestamp to compare to the current timer value.
+ * \param remaining_ticks Pointer to storage for the remaining number of
+ * ticks before the timer value reaches the given timestamp.
+ *
+ * \retval FWK_SUCCESS Operation succeeded.
+ * \retval FWK_E_PARAM The remaining_ticks pointer was invalid.
+ * \retval One of the other specific error codes described by the module.
+ *
+ * \note remaining_ticks is also a timestamp.
+ */
+ int (*remaining)(fwk_id_t dev_id,
+ uint64_t timestamp,
+ uint64_t *remaining_ticks);
+};
+
+/*!
+ * \brief Alarm interface
+ */
+struct mod_timer_alarm_api {
+ /*!
+ * \brief Start an alarm so it will trigger after a specified time.
+ *
+ * \details When an alarm is triggered, an event with identifier
+ * \p event_id will be sent to the entity that is bound to the alarm.
+ * The first word of the event's parameter will be set to \p param.
+ *
+ * If the alarm is periodic, it will automatically be started again
+ * with the same time delay after it triggers.
+ *
+ * An alarm can be started multiple times without being stopped. In this
+ * case, internally, the alarm will be stopped then started again with
+ * the new configuration.
+ *
+ * \param alarm_id Sub-element identifier of the alarm.
+ * \param event_id Identifier of the event the caller is expecting.
+ * \param milliseconds The time delay, given in milliseconds, until the
+ * alarm should trigger.
+ * \param type \ref MOD_TIMER_ALARM_TYPE_ONCE or
+ * \ref MOD_TIMER_ALARM_TYPE_PERIODIC.
+ * \param param Word-size parameter for the event.
+ *
+ * \pre \p alarm_id must be a valid sub-element alarm identifier that has
+ * previously been bound to.
+ *
+ * \retval FWK_SUCCESS The alarm was started.
+ */
+ int (*start)(fwk_id_t alarm_id,
+ unsigned int milliseconds,
+ enum mod_timer_alarm_type type,
+ fwk_id_t event_id,
+ uintptr_t param);
+
+ /*!
+ * \brief Stop a previously started alarm.
+ *
+ * \details Stop an alarm that was previously started. This will prevent the
+ * alarm from triggering. This does not undo the binding of the alarm
+ * and it can be started again afterwards.
+ *
+ * Any pending alarm events associated with the alarm are not cancelled
+ * or removed when the alarm is stopped.
+ *
+ * \param alarm_id Sub-element identifier of the alarm item.
+ *
+ * \pre \p alarm_id must be a valid sub-element alarm identifier that has
+ * previously been bound to.
+ *
+ * \retval FWK_SUCCESS The alarm was stopped.
+ * \retval FWK_E_STATE The alarm was already stopped.
+ */
+ int (*stop)(fwk_id_t alarm_id);
+};
+
+/*!
+ * @}
+ */
+
+/*!
+ * @}
+ */
+
+#endif /* MOD_TIMER_H */
diff --git a/module/timer/src/Makefile b/module/timer/src/Makefile
new file mode 100644
index 00000000..a89d8a96
--- /dev/null
+++ b/module/timer/src/Makefile
@@ -0,0 +1,11 @@
+#
+# Arm SCP/MCP Software
+# Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+BS_LIB_NAME := Timer
+BS_LIB_SOURCES = mod_timer.c
+
+include $(BS_DIR)/lib.mk
diff --git a/module/timer/src/mod_timer.c b/module/timer/src/mod_timer.c
new file mode 100644
index 00000000..2b61cd26
--- /dev/null
+++ b/module/timer/src/mod_timer.c
@@ -0,0 +1,653 @@
+/*
+ * Arm SCP/MCP Software
+ * Copyright (c) 2017-2018, Arm Limited and Contributors. All rights reserved.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Description:
+ * Implementation of Timer module
+ */
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <fwk_element.h>
+#include <fwk_errno.h>
+#include <fwk_event.h>
+#include <fwk_id.h>
+#include <fwk_interrupt.h>
+#include <fwk_list.h>
+#include <fwk_macros.h>
+#include <fwk_mm.h>
+#include <fwk_module.h>
+#include <fwk_thread.h>
+#include <mod_log.h>
+#include <mod_timer.h>
+#include <fwk_module_idx.h>
+
+/* Timer device context (element) */
+struct dev_ctx {
+ /* Pointer to the device's configuration */
+ const struct mod_timer_dev_config *config;
+ /* Pointer to an API provided by the driver that controls the device */
+ struct mod_timer_driver_api *driver;
+ /* Identifier of the driver that controls the device */
+ fwk_id_t driver_dev_id;
+ /* Storage for all alarms */
+ struct alarm_ctx *alarm_pool;
+ /* Queue of active alarms */
+ struct fwk_dlist alarms_active;
+};
+
+/* Alarm item context (sub-element) */
+struct alarm_ctx {
+ /* List node */
+ struct fwk_dlist_node node;
+ /* Time between starting this alarm and it triggering */
+ uint32_t microseconds;
+ /* Timestamp of the time this alarm will trigger */
+ uint64_t timestamp;
+ /* Identifier of the entity to send the alarm event to */
+ fwk_id_t listener;
+ /* Identifier of the event the listener is expecting to receive */
+ fwk_id_t event_id;
+ /* Parameter of the event */
+ uintptr_t param;
+ /* Flag indicating if this alarm if periodic */
+ bool periodic;
+ /* Flag indicating if this alarm is in the active queue */
+ bool started;
+ /* Flag indicating if this alarm has been bound to */
+ bool bound;
+};
+
+/* Table of timer device context structures */
+static struct dev_ctx *ctx_table;
+
+/* Log API */
+static const struct mod_log_api *log_api;
+
+/*
+ * Forward declarations
+ */
+
+static void timer_isr(uintptr_t ctx_ptr);
+
+/*
+ * Internal functions
+ */
+
+static int _time_to_timestamp(struct dev_ctx *ctx,
+ uint32_t microseconds,
+ uint64_t *timestamp)
+{
+ int status;
+ uint32_t frequency;
+
+ assert(ctx != NULL);
+ assert(timestamp != NULL);
+
+ status = ctx->driver->get_frequency(ctx->driver_dev_id, &frequency);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ *timestamp = ((uint64_t)frequency * microseconds) / 1000000;
+
+ return FWK_SUCCESS;
+}
+
+static int _timestamp_from_now(struct dev_ctx *ctx,
+ uint32_t microseconds,
+ uint64_t *timestamp)
+{
+ int status;
+ uint64_t counter;
+
+ assert(ctx != NULL);
+ assert(timestamp != NULL);
+
+ status = _time_to_timestamp(ctx, microseconds, timestamp);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ status = ctx->driver->get_counter(ctx->driver_dev_id, &counter);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ *timestamp += counter;
+
+ return FWK_SUCCESS;
+}
+
+static void _configure_timer_with_next_alarm(struct dev_ctx *ctx)
+{
+ struct alarm_ctx *alarm_head;
+ uint64_t counter = 0;
+ int status;
+
+ assert(ctx != NULL);
+
+ alarm_head = (struct alarm_ctx *)fwk_list_head(&ctx->alarms_active);
+ if (alarm_head != NULL) {
+ /*
+ * If an alarm's period is very small, the timer device could be
+ * configured to interrupt on a timestamp that is "in the past" by the
+ * time interrupts are enabled. In this case, the interrupt will not be
+ * generated due to a model bug. This code can be deleted once this bug
+ * has been fixed.
+ *
+ * If this alarm occurs very soon, process it immediately to avoid
+ * potentially missing the interrupt and waiting forever.
+ */
+ status = ctx->driver->get_counter(ctx->driver_dev_id, &counter);
+ if ((status == FWK_SUCCESS) &&
+ (counter + 2000 >= alarm_head->timestamp))
+ timer_isr((uintptr_t)ctx);
+
+ /* Configure timer device */
+ ctx->driver->set_timer(ctx->driver_dev_id, alarm_head->timestamp);
+ ctx->driver->enable(ctx->driver_dev_id);
+ }
+}
+
+static void _insert_alarm_ctx_into_active_queue(struct dev_ctx *ctx,
+ struct alarm_ctx *alarm_new)
+{
+ struct fwk_dlist_node *alarm_node;
+ struct alarm_ctx *alarm;
+
+ assert(ctx != NULL);
+ assert(alarm_new != NULL);
+
+ /*
+ * Search though the active queue to find the correct place to insert the
+ * new alarm item
+ */
+ alarm_node = fwk_list_head(&ctx->alarms_active);
+ alarm = FWK_LIST_GET(alarm_node, struct alarm_ctx, node);
+
+ while ((alarm_node != NULL) && (alarm_new->timestamp > alarm->timestamp)) {
+ alarm_node = fwk_list_next(&ctx->alarms_active, alarm_node);
+ alarm = FWK_LIST_GET(alarm_node, struct alarm_ctx, node);
+ }
+
+ /* Insert alarm_new just BEFORE the alarm that was found */
+ fwk_list_insert(&ctx->alarms_active,
+ &(alarm_new->node),
+ alarm_node);
+
+ alarm_new->started = true;
+}
+
+
+/*
+ * Functions fulfilling the timer API
+ */
+
+static int get_frequency(fwk_id_t dev_id, uint32_t *frequency)
+{
+ struct dev_ctx *ctx;
+ int status;
+
+ status = fwk_module_check_call(dev_id);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ ctx = &ctx_table[fwk_id_get_element_idx(dev_id)];
+
+ if (frequency == NULL)
+ return FWK_E_PARAM;
+
+ return ctx->driver->get_frequency(ctx->driver_dev_id, frequency);
+}
+
+static int time_to_timestamp(fwk_id_t dev_id,
+ uint32_t microseconds,
+ uint64_t *timestamp)
+{
+ int status;
+ struct dev_ctx *ctx;
+
+ status = fwk_module_check_call(dev_id);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ if (timestamp == NULL)
+ return FWK_E_PARAM;
+
+ ctx = &ctx_table[fwk_id_get_element_idx(dev_id)];
+
+ return _time_to_timestamp(ctx, microseconds, timestamp);
+}
+
+static int get_counter(fwk_id_t dev_id, uint64_t *counter)
+{
+ struct dev_ctx *ctx;
+ int status;
+
+ status = fwk_module_check_call(dev_id);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ ctx = &ctx_table[fwk_id_get_element_idx(dev_id)];
+
+ if (counter == NULL)
+ return FWK_E_PARAM;
+
+ /* Read counter */
+ return ctx->driver->get_counter(ctx->driver_dev_id, counter);
+}
+
+static int delay(fwk_id_t dev_id, uint32_t microseconds)
+{
+ int status;
+ struct dev_ctx *ctx;
+ uint64_t counter, counter_limit;
+
+ status = fwk_module_check_call(dev_id);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ ctx = &ctx_table[fwk_id_get_element_idx(dev_id)];
+
+ status = _timestamp_from_now(ctx, microseconds, &counter_limit);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ do {
+ status = ctx->driver->get_counter(ctx->driver_dev_id, &counter);
+ if (status != FWK_SUCCESS)
+ return status;
+ } while (counter < counter_limit);
+
+ return FWK_SUCCESS;
+}
+
+static int wait(fwk_id_t dev_id,
+ uint32_t microseconds,
+ bool (*cond)(void*),
+ void *data)
+{
+ struct dev_ctx *ctx;
+ int status;
+ uint64_t counter, counter_limit;
+
+ status = fwk_module_check_call(dev_id);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ ctx = &ctx_table[fwk_id_get_element_idx(dev_id)];
+
+ status = _timestamp_from_now(ctx, microseconds, &counter_limit);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ while (true) {
+
+ if (cond(data))
+ return FWK_SUCCESS;
+
+ status = ctx->driver->get_counter(ctx->driver_dev_id, &counter);
+ if (status != FWK_SUCCESS)
+ return FWK_E_DEVICE;
+
+ /*
+ * If the time to wait is over, check condition one last time.
+ */
+ if (counter > counter_limit) {
+ if (cond(data))
+ return FWK_SUCCESS;
+ else
+ return FWK_E_TIMEOUT;
+ }
+ }
+}
+
+static int remaining(fwk_id_t dev_id,
+ uint64_t timestamp,
+ uint64_t *remaining_ticks)
+{
+ struct dev_ctx *ctx;
+ int status;
+ uint64_t counter;
+
+ status = fwk_module_check_call(dev_id);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ ctx = &ctx_table[fwk_id_get_element_idx(dev_id)];
+
+ if (remaining_ticks == NULL)
+ return FWK_E_PARAM;
+
+ status = ctx->driver->get_counter(ctx->driver_dev_id, &counter);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ if (timestamp <= counter)
+ *remaining_ticks = 0;
+ else
+ *remaining_ticks = timestamp - counter;
+
+ return FWK_SUCCESS;
+}
+
+static const struct mod_timer_api timer_api = {
+ .get_frequency = get_frequency,
+ .time_to_timestamp = time_to_timestamp,
+ .get_counter = get_counter,
+ .delay = delay,
+ .wait = wait,
+ .remaining = remaining,
+};
+
+/*
+ * Functions fulfilling the alarm API
+ */
+
+static int alarm_stop(fwk_id_t alarm_id)
+{
+ int status;
+ struct dev_ctx *ctx;
+ struct alarm_ctx *alarm;
+
+ assert(fwk_module_is_valid_sub_element_id(alarm_id));
+
+ status = fwk_module_check_call(alarm_id);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ ctx = &ctx_table[fwk_id_get_element_idx(alarm_id)];
+ alarm = &ctx->alarm_pool[fwk_id_get_sub_element_idx(alarm_id)];
+
+ if (!alarm->started)
+ return FWK_E_STATE;
+
+ /* Disable timer interrupts to work with the active queue */
+ ctx->driver->disable(ctx->driver_dev_id);
+
+ fwk_list_remove(&ctx->alarms_active, (struct fwk_dlist_node *)alarm);
+ alarm->started = false;
+
+ _configure_timer_with_next_alarm(ctx);
+
+ return FWK_SUCCESS;
+}
+
+static int alarm_start(fwk_id_t alarm_id,
+ unsigned int milliseconds,
+ enum mod_timer_alarm_type type,
+ fwk_id_t event_id,
+ uintptr_t param)
+{
+ int status;
+ struct dev_ctx *ctx;
+ struct alarm_ctx *alarm;
+
+ assert(fwk_module_is_valid_sub_element_id(alarm_id));
+
+ status = fwk_module_check_call(alarm_id);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ ctx = ctx_table + fwk_id_get_element_idx(alarm_id);
+ alarm = &ctx->alarm_pool[fwk_id_get_sub_element_idx(alarm_id)];
+
+ if (alarm->started)
+ alarm_stop(alarm_id);
+
+ /* Cap to ensure value will not overflow when stored as microseconds */
+ milliseconds = FWK_MIN(milliseconds, UINT32_MAX / 1000);
+
+ /* Populate alarm item */
+ alarm->event_id = event_id;
+ alarm->param = param;
+ alarm->periodic =
+ (type == MOD_TIMER_ALARM_TYPE_PERIODIC ? true : false);
+ alarm->microseconds = milliseconds * 1000;
+ status = _timestamp_from_now(ctx,
+ alarm->microseconds,
+ &alarm->timestamp);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ /* Disable timer interrupts to work with the active queue */
+ ctx->driver->disable(ctx->driver_dev_id);
+
+ _insert_alarm_ctx_into_active_queue(ctx, alarm);
+
+ _configure_timer_with_next_alarm(ctx);
+
+ return FWK_SUCCESS;
+}
+
+static const struct mod_timer_alarm_api alarm_api = {
+ .start = alarm_start,
+ .stop = alarm_stop,
+};
+
+static void timer_isr(uintptr_t ctx_ptr)
+{
+ int status;
+ struct alarm_ctx *alarm;
+ struct alarm_ctx *alarm_head;
+ struct dev_ctx *ctx = (struct dev_ctx *)ctx_ptr;
+ uint64_t timestamp = 0;
+ uint64_t counter = 0;
+ struct fwk_event event;
+
+ assert(ctx != NULL);
+
+ /* Disable timer interrupts to work with the active queue */
+ ctx->driver->disable(ctx->driver_dev_id);
+ fwk_interrupt_clear_pending(ctx->config->timer_irq);
+
+process_alarm:
+ alarm = (struct alarm_ctx *)fwk_list_pop_head(&ctx->alarms_active);
+
+ if (alarm == NULL) {
+ /* Timer interrupt triggered without any alarm in the active queue */
+ assert(false);
+ return;
+ }
+
+ alarm->started = false;
+
+ event = (struct fwk_event) {
+ .source_id = fwk_module_id_timer,
+ .target_id = alarm->listener,
+ .id = alarm->event_id,
+ };
+ *(uintptr_t *)event.params = alarm->param; /* Word-size parameter */
+
+ status = fwk_thread_put_event(&event);
+ if (status != FWK_SUCCESS)
+ log_api->log(MOD_LOG_GROUP_WARNING,
+ "[Timer] Warning: Alarm was triggered but event could not "
+ "be pushed. Error code: %i\n", status);
+
+ if (alarm->periodic) {
+ /* Put this alarm back into the active queue */
+ status = _time_to_timestamp(ctx, alarm->microseconds, &timestamp);
+
+ if (status == FWK_SUCCESS) {
+ alarm->timestamp += timestamp;
+ _insert_alarm_ctx_into_active_queue(ctx, alarm);
+ } else
+ log_api->log(MOD_LOG_GROUP_ERROR,
+ "[Timer] Error: Periodic alarm could not be added "
+ "back into queue.\n");
+ }
+
+ alarm_head = FWK_LIST_GET(fwk_list_head(&ctx->alarms_active),
+ struct alarm_ctx,
+ node);
+ if (alarm_head != NULL) {
+ /*
+ * If successive alarm item timestamps are very close together, the
+ * timer device could be configured to interrupt on a timestamp that is
+ * "in the past". In this case, the interrupt will not be generated due
+ * to a model bug. This code can be deleted once this bug has been
+ * fixed.
+ *
+ * If the next alarm occurs very soon, process it immidiately to avoid
+ * potentially missing the interrupt and waiting forever.
+ */
+ status = ctx->driver->get_counter(ctx->driver_dev_id, &counter);
+ if ((status == FWK_SUCCESS) &&
+ (counter + 2000 >= alarm_head->timestamp))
+ goto process_alarm;
+
+ ctx->driver->set_timer(ctx->driver_dev_id, alarm_head->timestamp);
+ ctx->driver->enable(ctx->driver_dev_id);
+ }
+}
+
+/*
+ * Functions fulfilling the framework's module interface
+ */
+
+static int timer_init(fwk_id_t module_id,
+ unsigned int element_count,
+ const void *data)
+{
+ ctx_table = fwk_mm_calloc(element_count, sizeof(struct dev_ctx));
+
+ if (ctx_table == NULL)
+ return FWK_E_NOMEM;
+
+ return FWK_SUCCESS;
+}
+
+static int timer_device_init(fwk_id_t element_id, unsigned int alarm_count,
+ const void *data)
+{
+ struct dev_ctx *ctx;
+
+ assert(data != NULL);
+
+ ctx = ctx_table + fwk_id_get_element_idx(element_id);
+ ctx->config = data;
+
+ if (alarm_count > 0) {
+ ctx->alarm_pool = fwk_mm_calloc(alarm_count, sizeof(struct alarm_ctx));
+ if (ctx->alarm_pool == NULL) {
+ assert(false);
+ return FWK_E_NOMEM;
+ }
+ }
+
+ return FWK_SUCCESS;
+}
+
+static int timer_bind(fwk_id_t id, unsigned int round)
+{
+ int status;
+ struct dev_ctx *ctx;
+ struct mod_timer_driver_api *driver;
+ unsigned int driver_module_idx;
+
+ /* Nothing to do after the initial round. */
+ if (round > 0)
+ return FWK_SUCCESS;
+
+ /* Bind to log module */
+ if (fwk_module_is_valid_module_id(id)) {
+ return fwk_module_bind(fwk_module_id_log,
+ FWK_ID_API(FWK_MODULE_IDX_LOG, 0),
+ &log_api);
+ }
+
+ ctx = ctx_table + fwk_id_get_element_idx(id);
+ ctx->driver_dev_id = ctx->config->id;
+
+ /* Bind to the driver API for the current device */
+ driver_module_idx = fwk_id_get_module_idx(ctx->driver_dev_id);
+ status = fwk_module_bind(ctx->driver_dev_id,
+ FWK_ID_API(driver_module_idx, 0),
+ &driver);
+ if (status != FWK_SUCCESS)
+ return status;
+
+ /* Check that the driver API is completely fulfilled */
+ if (driver->enable == NULL ||
+ driver->disable == NULL ||
+ driver->get_counter == NULL ||
+ driver->get_frequency == NULL)
+ return FWK_E_DEVICE;
+
+ ctx->driver = driver;
+
+ return FWK_SUCCESS;
+}
+
+static int timer_process_bind_request(fwk_id_t requester_id,
+ fwk_id_t id,
+ fwk_id_t api_id,
+ const void **api)
+{
+ struct dev_ctx *ctx;
+ struct alarm_ctx *alarm_ctx;
+
+ if (fwk_id_is_equal(api_id, MOD_TIMER_API_ID_TIMER)) {
+ if (!fwk_module_is_valid_element_id(id)) {
+ assert(false);
+ return FWK_E_PARAM;
+ }
+
+ *api = &timer_api;
+ return FWK_SUCCESS;
+ }
+
+ /* Alarm API requested */
+
+ if (!fwk_module_is_valid_sub_element_id(id)) {
+ assert(false);
+ return FWK_E_PARAM;
+ }
+
+ ctx = ctx_table + fwk_id_get_element_idx(id);
+ alarm_ctx = &ctx->alarm_pool[fwk_id_get_sub_element_idx(id)];
+
+ if (alarm_ctx->bound) {
+ assert(false);
+ return FWK_E_STATE;
+ }
+
+ alarm_ctx->bound = true;
+ alarm_ctx->listener = requester_id;
+
+ *api = &alarm_api;
+ return FWK_SUCCESS;
+}
+
+static int timer_start(fwk_id_t id)
+{
+ struct dev_ctx *ctx;
+
+ if (!fwk_module_is_valid_element_id(id))
+ return FWK_SUCCESS;
+
+ ctx = ctx_table + fwk_id_get_element_idx(id);
+
+ fwk_list_init(&ctx->alarms_active);
+
+ fwk_interrupt_set_isr_param(ctx->config->timer_irq,
+ timer_isr,
+ (uintptr_t)ctx);
+ fwk_interrupt_enable(ctx->config->timer_irq);
+
+ return FWK_SUCCESS;
+}
+
+/* Module descriptor */
+const struct fwk_module module_timer = {
+ .name = "Timer HAL",
+ .api_count = MOD_TIMER_API_COUNT,
+ .type = FWK_MODULE_TYPE_HAL,
+ .init = timer_init,
+ .element_init = timer_device_init,
+ .bind = timer_bind,
+ .process_bind_request = timer_process_bind_request,
+ .start = timer_start,
+};