mirror of
https://github.com/apache/nuttx.git
synced 2026-08-01 20:28:58 +00:00
While attempting to create architecture-specific implementation of up_udelay, it was discovered that the overriding function is not included in the final binary, the weak implementation was used instead. Further investigation and experimentation showed that the linker only overrides the weak implementation with the custom one if the custom one is present in a .c source file that contains at least one other function that is called from somewhere. Some additional testing revealed that at least one other already present up_udelay override (rv32m1-vega:nsh) is affected by this. In a short mailing list discussion it was determined that this is a likely result of using static libraries during the build process and it was suggested to introduce configuration option that will exclude weak implementations of the function from the build altogether. This patch does that. This patch does not enable this configuration option for any existing board/chip because doing so would change its behaviour and needs to be tested by users of the hardware. Also changed is the static assertion in sched/clock/clock_delay.c to not prevent building the code when architecture declares that it does not use BOARD_LOOPSPERMSEC to determine required loop count. BOARD_LOOPSPERMSEC is made undefined in such case. Patch was tested by building breadxavr:nsh (identical binary by SHA256), rv32m1-vega:nsh (identical text section) and rv-virt:nsh (text section differs because of different ordering of functions in the binary, ostest passed though.) Signed-off-by: Kerogit <kr.git@kerogit.eu>
522 lines
14 KiB
C
522 lines
14 KiB
C
/****************************************************************************
|
|
* drivers/timers/arch_timer.c
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/clock.h>
|
|
#include <nuttx/timers/arch_timer.h>
|
|
|
|
/****************************************************************************
|
|
* Pre-processor Definitions
|
|
****************************************************************************/
|
|
|
|
/* If no value is given, we proceed with 0 since a timer is used for accurate
|
|
* delays. A runtime DEBUGASSERT catches the case where the timer lower-half
|
|
* isn't registered in time.
|
|
*
|
|
* Value is unset if ARCH_HAVE_DYNAMIC_UDELAY is set. In that case,
|
|
* ARCH_HAVE_UDELAY is also set and the only user of these values
|
|
* (udelay_coarse) is excluded from the build.
|
|
*/
|
|
|
|
#ifndef CONFIG_ARCH_HAVE_UDELAY
|
|
# if CONFIG_BOARD_LOOPSPERMSEC == -1
|
|
# undef CONFIG_BOARD_LOOPSPERMSEC
|
|
# define CONFIG_BOARD_LOOPSPERMSEC 0
|
|
# endif
|
|
|
|
# define CONFIG_BOARD_LOOPSPER100USEC ((CONFIG_BOARD_LOOPSPERMSEC+5)/10)
|
|
# define CONFIG_BOARD_LOOPSPER10USEC ((CONFIG_BOARD_LOOPSPERMSEC+50)/100)
|
|
# define CONFIG_BOARD_LOOPSPERUSEC ((CONFIG_BOARD_LOOPSPERMSEC+500)/1000)
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct arch_timer_s
|
|
{
|
|
FAR struct timer_lowerhalf_s *lower;
|
|
uint32_t *next_interval;
|
|
clock_t timebase;
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static struct arch_timer_s g_timer;
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_SCHED_TICKLESS
|
|
|
|
static uint32_t update_timeout(uint32_t timeout)
|
|
{
|
|
struct timer_status_s status;
|
|
|
|
/* Don't need critical section here
|
|
* since caller already do it for us
|
|
*/
|
|
|
|
TIMER_TICK_GETSTATUS(g_timer.lower, &status);
|
|
if (g_timer.next_interval)
|
|
{
|
|
/* If the timer interrupt is in the process,
|
|
* let the callback return the right interval.
|
|
*/
|
|
|
|
*g_timer.next_interval = timeout;
|
|
}
|
|
else if (timeout != status.timeleft)
|
|
{
|
|
/* Otherwise, update the timeout directly. */
|
|
|
|
TIMER_TICK_SETTIMEOUT(g_timer.lower, timeout);
|
|
g_timer.timebase += status.timeout - status.timeleft;
|
|
}
|
|
|
|
return status.timeleft;
|
|
}
|
|
#endif
|
|
|
|
static uint64_t current_usec(void)
|
|
{
|
|
struct timer_status_s status;
|
|
clock_t timebase;
|
|
|
|
do
|
|
{
|
|
timebase = g_timer.timebase;
|
|
TIMER_GETSTATUS(g_timer.lower, &status);
|
|
}
|
|
while (timebase != g_timer.timebase);
|
|
|
|
return TICK2USEC(timebase) +
|
|
(status.timeout - status.timeleft);
|
|
}
|
|
|
|
static void udelay_accurate(useconds_t microseconds)
|
|
{
|
|
uint64_t start = current_usec();
|
|
while (current_usec() - start < microseconds)
|
|
{
|
|
; /* Wait until the timeout reach */
|
|
}
|
|
}
|
|
|
|
#ifndef CONFIG_ARCH_HAVE_UDELAY
|
|
|
|
/****************************************************************************
|
|
* Name: udelay_coarse
|
|
*
|
|
* Description:
|
|
* Wait loop called (only) by up_udelay if udelay_accurate
|
|
* is not available. (Excluded from the build if up_udelay is also
|
|
* excluded from the build.)
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void udelay_coarse(useconds_t microseconds)
|
|
{
|
|
volatile int i;
|
|
|
|
DEBUGASSERT(CONFIG_BOARD_LOOPSPERMSEC != 0);
|
|
|
|
/* We'll do this a little at a time because we expect that the
|
|
* CONFIG_BOARD_LOOPSPERUSEC is very inaccurate during to truncation in
|
|
* the divisions of its calculation. We'll use the largest values that
|
|
* we can in order to prevent significant error buildup in the loops.
|
|
*/
|
|
|
|
while (microseconds > 1000)
|
|
{
|
|
for (i = 0; i < CONFIG_BOARD_LOOPSPERMSEC; i++)
|
|
{
|
|
}
|
|
|
|
microseconds -= 1000;
|
|
}
|
|
|
|
while (microseconds > 100)
|
|
{
|
|
for (i = 0; i < CONFIG_BOARD_LOOPSPER100USEC; i++)
|
|
{
|
|
}
|
|
|
|
microseconds -= 100;
|
|
}
|
|
|
|
while (microseconds > 10)
|
|
{
|
|
for (i = 0; i < CONFIG_BOARD_LOOPSPER10USEC; i++)
|
|
{
|
|
}
|
|
|
|
microseconds -= 10;
|
|
}
|
|
|
|
while (microseconds > 0)
|
|
{
|
|
for (i = 0; i < CONFIG_BOARD_LOOPSPERUSEC; i++)
|
|
{
|
|
}
|
|
|
|
microseconds--;
|
|
}
|
|
}
|
|
|
|
#endif /* ifndef CONFIG_ARCH_HAVE_UDELAY */
|
|
|
|
static bool timer_callback(FAR uint32_t *next_interval, FAR void *arg)
|
|
{
|
|
#ifdef CONFIG_SCHED_TICKLESS
|
|
struct timer_status_s status;
|
|
uint32_t temp_interval;
|
|
|
|
g_timer.timebase += *next_interval;
|
|
temp_interval = g_oneshot_maxticks;
|
|
g_timer.next_interval = &temp_interval;
|
|
nxsched_process_timer();
|
|
g_timer.next_interval = NULL;
|
|
|
|
TIMER_TICK_GETSTATUS(g_timer.lower, &status);
|
|
if (temp_interval != status.timeleft)
|
|
{
|
|
g_timer.timebase += status.timeout - status.timeleft;
|
|
*next_interval = temp_interval;
|
|
}
|
|
#else
|
|
g_timer.timebase++;
|
|
nxsched_process_timer();
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
void up_timer_set_lowerhalf(FAR struct timer_lowerhalf_s *lower)
|
|
{
|
|
#ifdef CONFIG_SCHED_TICKLESS
|
|
TIMER_TICK_MAXTIMEOUT(lower, &g_oneshot_maxticks);
|
|
TIMER_TICK_SETTIMEOUT(lower, g_oneshot_maxticks);
|
|
#else
|
|
TIMER_TICK_SETTIMEOUT(lower, 1);
|
|
#endif
|
|
|
|
TIMER_SETCALLBACK(lower, timer_callback, NULL);
|
|
TIMER_START(lower);
|
|
|
|
g_timer.lower = lower;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: up_timer_gettime
|
|
*
|
|
* Description:
|
|
* Return the elapsed time since power-up (or, more correctly, since
|
|
* the architecture-specific timer was initialized). This function is
|
|
* functionally equivalent to:
|
|
*
|
|
* int clock_gettime(clockid_t clockid, FAR struct timespec *ts);
|
|
*
|
|
* when clockid is CLOCK_MONOTONIC.
|
|
*
|
|
* This function provides the basis for reporting the current time and
|
|
* also is used to eliminate error build-up from small errors in interval
|
|
* time calculations.
|
|
*
|
|
* Provided by platform-specific code and called from the RTOS base code.
|
|
*
|
|
* Input Parameters:
|
|
* ts - Provides the location in which to return the up-time.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success; a negated errno value is returned on
|
|
* any failure.
|
|
*
|
|
* Assumptions:
|
|
* Called from the normal tasking context. The implementation must
|
|
* provide whatever mutual exclusion is necessary for correct operation.
|
|
* This can include disabling interrupts in order to assure atomic register
|
|
* operations.
|
|
*
|
|
****************************************************************************/
|
|
|
|
void weak_function up_timer_getmask(FAR clock_t *mask)
|
|
{
|
|
uint32_t maxticks;
|
|
|
|
TIMER_TICK_MAXTIMEOUT(g_timer.lower, &maxticks);
|
|
|
|
*mask = 0;
|
|
while (1)
|
|
{
|
|
clock_t next = (*mask << 1) | 1;
|
|
if (next > maxticks)
|
|
{
|
|
break;
|
|
}
|
|
|
|
*mask = next;
|
|
}
|
|
}
|
|
|
|
int weak_function up_timer_gettick(FAR clock_t *ticks)
|
|
{
|
|
int ret = -EAGAIN;
|
|
|
|
if (g_timer.lower != NULL)
|
|
{
|
|
*ticks = current_usec() / USEC_PER_TICK;
|
|
ret = OK;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int weak_function up_timer_gettime(struct timespec *ts)
|
|
{
|
|
int ret = -EAGAIN;
|
|
uint64_t usec;
|
|
|
|
if (g_timer.lower != NULL)
|
|
{
|
|
usec = current_usec();
|
|
|
|
ts->tv_sec = usec / USEC_PER_SEC;
|
|
ts->tv_nsec = (usec % USEC_PER_SEC) * NSEC_PER_USEC;
|
|
ret = OK;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: up_timer_cancel
|
|
*
|
|
* Description:
|
|
* Cancel the interval timer and return the time remaining on the timer.
|
|
* These two steps need to be as nearly atomic as possible.
|
|
* nxsched_process_timer() will not be called unless the timer is
|
|
* restarted with up_timer_start().
|
|
*
|
|
* If, as a race condition, the timer has already expired when this
|
|
* function is called, then that pending interrupt must be cleared so
|
|
* that up_timer_start() and the remaining time of zero should be
|
|
* returned.
|
|
*
|
|
* NOTE: This function may execute at a high rate with no timer running (as
|
|
* when pre-emption is enabled and disabled).
|
|
*
|
|
* Provided by platform-specific code and called from the RTOS base code.
|
|
*
|
|
* Input Parameters:
|
|
* ts - Location to return the remaining time. Zero should be returned
|
|
* if the timer is not active. ts may be zero in which case the
|
|
* time remaining is not returned.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success. A call to up_timer_cancel() when
|
|
* the timer is not active should also return success; a negated errno
|
|
* value is returned on any failure.
|
|
*
|
|
* Assumptions:
|
|
* May be called from interrupt level handling or from the normal tasking
|
|
* level. Interrupts may need to be disabled internally to assure
|
|
* non-reentrancy.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_SCHED_TICKLESS
|
|
int weak_function up_timer_tick_cancel(FAR clock_t *ticks)
|
|
{
|
|
int ret = -EAGAIN;
|
|
|
|
if (g_timer.lower != NULL)
|
|
{
|
|
*ticks = update_timeout(g_oneshot_maxticks);
|
|
ret = OK;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: up_timer_start
|
|
*
|
|
* Description:
|
|
* Start the interval timer. nxsched_process_timer() will be called at
|
|
* the completion of the timeout (unless up_timer_cancel is called to stop
|
|
* the timing.
|
|
*
|
|
* Provided by platform-specific code and called from the RTOS base code.
|
|
*
|
|
* Input Parameters:
|
|
* ts - Provides the time interval until nxsched_process_timer() is
|
|
* called.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) is returned on success; a negated errno value is returned on
|
|
* any failure.
|
|
*
|
|
* Assumptions:
|
|
* May be called from interrupt level handling or from the normal tasking
|
|
* level. Interrupts may need to be disabled internally to assure
|
|
* non-reentrancy.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_SCHED_TICKLESS
|
|
int weak_function up_timer_tick_start(clock_t ticks)
|
|
{
|
|
int ret = -EAGAIN;
|
|
|
|
if (g_timer.lower != NULL)
|
|
{
|
|
update_timeout(ticks);
|
|
ret = OK;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: up_perf_*
|
|
*
|
|
* Description:
|
|
* The first interface simply provides the current time value in unknown
|
|
* units. NOTE: This function may be called early before the timer has
|
|
* been initialized. In that event, the function should just return a
|
|
* start time of zero.
|
|
*
|
|
* Nothing is assumed about the units of this time value. The following
|
|
* are assumed, however: (1) The time is an unsigned integer value, (2)
|
|
* the time is monotonically increasing, and (3) the elapsed time (also
|
|
* in unknown units) can be obtained by subtracting a start time from
|
|
* the current time.
|
|
*
|
|
* The second interface simple converts an elapsed time into well known
|
|
* units.
|
|
****************************************************************************/
|
|
|
|
#ifndef CONFIG_ARCH_HAVE_PERF_EVENTS
|
|
void up_perf_init(FAR void *arg)
|
|
{
|
|
UNUSED(arg);
|
|
}
|
|
|
|
clock_t up_perf_gettime(void)
|
|
{
|
|
clock_t ret = 0;
|
|
|
|
if (g_timer.lower != NULL)
|
|
{
|
|
ret = current_usec();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
unsigned long up_perf_getfreq(void)
|
|
{
|
|
return USEC_PER_SEC;
|
|
}
|
|
|
|
void up_perf_convert(clock_t elapsed, FAR struct timespec *ts)
|
|
{
|
|
clock_usec2time(ts, elapsed);
|
|
}
|
|
#endif /* CONFIG_ARCH_PERF_EVENTS */
|
|
|
|
/****************************************************************************
|
|
* Name: up_mdelay
|
|
*
|
|
* Description:
|
|
* Delay inline for the requested number of milliseconds.
|
|
* *** NOT multi-tasking friendly ***
|
|
*
|
|
****************************************************************************/
|
|
|
|
void weak_function up_mdelay(unsigned int milliseconds)
|
|
{
|
|
up_udelay(USEC_PER_MSEC * milliseconds);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: up_udelay
|
|
*
|
|
* Description:
|
|
* Delay inline for the requested number of microseconds.
|
|
*
|
|
* *** NOT multi-tasking friendly ***
|
|
*
|
|
* This function is both compiled optionally based on ARCH_HAVE_UDELAY
|
|
* and declared with weak attribute. See comment of up_udelay
|
|
* implementation in sched/clock/clock_delay.c for explanation.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifndef CONFIG_ARCH_HAVE_UDELAY
|
|
|
|
void weak_function up_udelay(useconds_t microseconds)
|
|
{
|
|
if (g_timer.lower != NULL)
|
|
{
|
|
udelay_accurate(microseconds);
|
|
}
|
|
else /* Period timer hasn't been initialized yet */
|
|
{
|
|
udelay_coarse(microseconds);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: up_ndelay
|
|
*
|
|
* Description:
|
|
* Delay inline for the requested number of nanoseconds.
|
|
*
|
|
* *** NOT multi-tasking friendly ***
|
|
*
|
|
****************************************************************************/
|
|
|
|
void weak_function up_ndelay(unsigned long nanoseconds)
|
|
{
|
|
up_udelay((nanoseconds + NSEC_PER_USEC - 1) / NSEC_PER_USEC);
|
|
}
|