diff options
author | Ronald Cron <ronald.cron@arm.com> | 2018-06-05 09:31:39 +0200 |
---|---|---|
committer | Ronald Cron <ronald.cron@arm.com> | 2018-06-08 11:46:47 +0200 |
commit | b151958dbb2f37383f4d9a1f7802c36008d9fef2 (patch) | |
tree | fe20ebfb8c10facbfd028edefe601462ae3ee64c /module/timer | |
parent | fd3027b6fd17a4a33a685adb73f2acfcae9a2ced (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.h | 294 | ||||
-rw-r--r-- | module/timer/src/Makefile | 11 | ||||
-rw-r--r-- | module/timer/src/mod_timer.c | 653 |
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, ×tamp); + + 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, +}; |