clock: Introduce clock_ticks_to_ns()

The clock_get_ns() API claims to return the period of a clock in
nanoseconds. Unfortunately since it returns an integer and a
clock's period is represented in units of 2^-32 nanoseconds,
the result is often an approximation, and calculating a clock
expiry deadline by multiplying clock_get_ns() by a number-of-ticks
is unacceptably inaccurate.

Introduce a new API clock_ticks_to_ns() which returns the number
of nanoseconds it takes the clock to make a given number of ticks.
This function can do the complete calculation internally and
will thus give a more accurate result.

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Tested-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
Reviewed-by: Luc Michel <luc@lmichel.fr>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
Message-Id: <20201215150929.30311-2-peter.maydell@linaro.org>
Signed-off-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
This commit is contained in:
Peter Maydell 2020-12-15 15:09:26 +00:00 committed by Philippe Mathieu-Daudé
parent 7886a674f1
commit 554d523785
2 changed files with 70 additions and 0 deletions

View File

@ -258,6 +258,35 @@ Here is an example:
clock_get_ns(dev->my_clk_input));
}
Calculating expiry deadlines
----------------------------
A commonly required operation for a clock is to calculate how long
it will take for the clock to tick N times; this can then be used
to set a timer expiry deadline. Use the function ``clock_ticks_to_ns()``,
which takes an unsigned 64-bit count of ticks and returns the length
of time in nanoseconds required for the clock to tick that many times.
It is important not to try to calculate expiry deadlines using a
shortcut like multiplying a "period of clock in nanoseconds" value
by the tick count, because clocks can have periods which are not a
whole number of nanoseconds, and the accumulated error in the
multiplication can be significant.
For a clock with a very long period and a large number of ticks,
the result of this function could in theory be too large to fit in
a 64-bit value. To avoid overflow in this case, ``clock_ticks_to_ns()``
saturates the result to INT64_MAX (because this is the largest valid
input to the QEMUTimer APIs). Since INT64_MAX nanoseconds is almost
300 years, anything with an expiry later than that is in the "will
never happen" category. Callers of ``clock_ticks_to_ns()`` should
therefore generally not special-case the possibility of a saturated
result but just allow the timer to be set to that far-future value.
(If you are performing further calculations on the returned value
rather than simply passing it to a QEMUTimer function like
``timer_mod_ns()`` then you should be careful to avoid overflow
in those calculations, of course.)
Changing a clock period
-----------------------

View File

@ -16,6 +16,8 @@
#include "qom/object.h"
#include "qemu/queue.h"
#include "qemu/host-utils.h"
#include "qemu/bitops.h"
#define TYPE_CLOCK "clock"
OBJECT_DECLARE_SIMPLE_TYPE(Clock, CLOCK)
@ -218,6 +220,45 @@ static inline unsigned clock_get_ns(Clock *clk)
return CLOCK_PERIOD_TO_NS(clock_get(clk));
}
/**
* clock_ticks_to_ns:
* @clk: the clock to query
* @ticks: number of ticks
*
* Returns the length of time in nanoseconds for this clock
* to tick @ticks times. Because a clock can have a period
* which is not a whole number of nanoseconds, it is important
* to use this function when calculating things like timer
* expiry deadlines, rather than attempting to obtain a "period
* in nanoseconds" value and then multiplying that by a number
* of ticks.
*
* The result could in theory be too large to fit in a 64-bit
* value if the number of ticks and the clock period are both
* large; to avoid overflow the result will be saturated to INT64_MAX
* (because this is the largest valid input to the QEMUTimer APIs).
* Since INT64_MAX nanoseconds is almost 300 years, anything with
* an expiry later than that is in the "will never happen" category
* and callers can reasonably not special-case the saturated result.
*/
static inline uint64_t clock_ticks_to_ns(const Clock *clk, uint64_t ticks)
{
uint64_t ns_low, ns_high;
/*
* clk->period is the period in units of 2^-32 ns, so
* (clk->period * ticks) is the required length of time in those
* units, and we can convert to nanoseconds by multiplying by
* 2^32, which is the same as shifting the 128-bit multiplication
* result right by 32.
*/
mulu64(&ns_low, &ns_high, clk->period, ticks);
if (ns_high & MAKE_64BIT_MASK(31, 33)) {
return INT64_MAX;
}
return ns_low >> 32 | ns_high << 32;
}
/**
* clock_is_enabled:
* @clk: a clock