Skip to content

Commit

Permalink
timers: add goldfish timer support
Browse files Browse the repository at this point in the history
Signed-off-by: ligd <[email protected]>
  • Loading branch information
GUIDINGLI authored and xiaoxiang781216 committed Oct 8, 2024
1 parent 0b38595 commit 39bfdb9
Show file tree
Hide file tree
Showing 6 changed files with 455 additions and 0 deletions.
143 changes: 143 additions & 0 deletions Documentation/platforms/arm/goldfish/goldfish_timer.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
===============
GOLDFISH TIMER
===============

Introduction
============
This device is used to return the current host time as a high-precision signed 64-bit nanosecond value to the kernel, with the starting point being an arbitrary loose reference point. The value should correspond to the QEMU "vm_clock"; that is, it should not be updated when the emulated system is not running, hence it cannot be directly based on the host clock.

Timer Registers
===============

::

#define GOLDFISH_TIMER_TIME_LOW 0x0 /* Get current time, then return low-order 32-bits. */
#define GOLDFISH_TIMER_TIME_HIGH 0x4 /* Return high 32-bits from previous TIME_LOW read. */
#define GOLDFISH_TIMER_ALARM_LOW 0x8 /* Set low 32-bit value of alarm, then arm it. */
#define GOLDFISH_TIMER_ALARM_HIGH 0xc /* Set high 32-bit value of alarm. */
#define GOLDFISH_TIMER_IRQ_ENABLED 0x10 /* Enable interrupts. */
#define GOLDFISH_TIMER_CLEAR_ALARM 0x14 /* Clear alarm. */
#define GOLDFISH_TIMER_ALARM_STATUS 0x18 /* Return 1 if alarm is armed, 0 if not. */
#define GOLDFISH_TIMER_CLEAR_INTERRUPT 0x1c /* Clear interrupt. */

Timer Read
==========
To read the current time, the kernel must execute an IO_READ(TIME_LOW), which returns an unsigned 32-bit value, followed by an IO_READ(TIME_HIGH), which returns a signed 32-bit value corresponding to the high half of the complete value.

::

static int goldfish_timer_current(FAR struct oneshot_lowerhalf_s *lower_,
FAR struct timespec *ts)
{
FAR struct goldfish_timer_lowerhalf_s *lower =
(FAR struct goldfish_timer_lowerhalf_s *)lower_;
irqstate_t flags;
uint32_t l32;
uint32_t h32;
uint64_t nsec;

DEBUGASSERT(lower != NULL);

flags = spin_lock_irqsave(&lower->lock);

l32 = getreg32(lower->base + GOLDFISH_TIMER_TIME_LOW);
h32 = getreg32(lower->base + GOLDFISH_TIMER_TIME_HIGH);
nsec = ((uint64_t)h32 << 32) | l32;

ts->tv_sec = nsec / NSEC_PER_SEC;
ts->tv_nsec = nsec % NSEC_PER_SEC;

spin_unlock_irqrestore(&lower->lock, flags);

return 0;
}

Timer Set Alarm
===============
This device can also be used to set an alarm, as follows:

::

IO_WRITE(ALARM_HIGH, <high-value>) // Must be executed first.
IO_WRITE(ALARM_LOW, <low-value>) // Must be executed second.

When the corresponding value is reached, the device will raise its IRQ. Note that if the alarm value is already older than the current time, the IRQ will be triggered immediately after the second IO_WRITE().

::

static int goldfish_timer_start(FAR struct oneshot_lowerhalf_s *lower_,
FAR oneshot_callback_t callback,
FAR void *arg,
FAR const struct timespec *ts)
{
FAR struct goldfish_timer_lowerhalf_s *lower =
(FAR struct goldfish_timer_lowerhalf_s *)lower_;
irqstate_t flags;
uint64_t nsec;
uint32_t l32;
uint32_t h32;

DEBUGASSERT(lower != NULL);

flags = spin_lock_irqsave(&lower->lock);

lower->callback = callback;
lower->arg = arg;

nsec = ts->tv_sec * 1000000000 + ts->tv_nsec;
l32 = getreg32(lower->base + GOLDFISH_TIMER_TIME_LOW);
h32 = getreg32(lower->base + GOLDFISH_TIMER_TIME_HIGH);
nsec += ((uint64_t)h32 << 32) | l32;

putreg32(1, lower->base + GOLDFISH_TIMER_IRQ_ENABLED);
putreg32(nsec >> 32, lower->base + GOLDFISH_TIMER_ALARM_HIGH);
putreg32(nsec, lower->base + GOLDFISH_TIMER_ALARM_LOW);

spin_unlock_irqrestore(&lower->lock, flags);

return 0;
}

Timer Interrupt
===============
IO_WRITE(CLEAR_INTERRUPT, <any>) can be used to lower the IRQ level after the kernel has processed the alarm.
IO_WRITE(CLEAR_ALARM, <any>) can be used to disarm the current alarm (if one exists).

Note: Currently, the alarm is used only on ARM-based systems. On MIPS-based systems, only TIME_LOW / TIME_HIGH are used.

::

static int goldfish_timer_interrupt(int irq,
FAR void *context,
FAR void *arg)
{
FAR struct goldfish_timer_lowerhalf_s *lower = arg;
oneshot_callback_t callback = NULL;
irqstate_t flags;
void *cbarg;

DEBUGASSERT(lower != NULL);

flags = spin_lock_irqsave(&lower->lock);

putreg32(1, lower->base + GOLDFISH_TIMER_CLEAR_ALARM);

if (lower->callback != NULL)
{
callback = lower->callback;
cbarg = lower->arg;
lower->callback = NULL;
lower->arg = NULL;
}

spin_unlock_irqrestore(&lower->lock, flags);

/* Then perform the callback */

if (callback)
{
callback(&lower->lh, cbarg);
}

return 0;
}
5 changes: 5 additions & 0 deletions Documentation/platforms/arm/goldfish/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@
GOLDFISH
========

.. toctree::
:maxdepth: 1

goldfish_timer.rst

Supported Boards
================
9 changes: 9 additions & 0 deletions drivers/timers/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -515,4 +515,13 @@ config CS2100CP_REGDEBUG
depends on DEBUG_FEATURES

endif # TIMERS_CS2100CP

config GOLDFISH_TIMER
bool "Enable GOLDFISH_TIMER"
default n
---help---
Goldfish timer is a virtual timer device that is used in the Android
emulator. It is used to provide a virtual timer to the guest OS.
See: https://android.googlesource.com/platform/external/qemu/+/master/docs/GOLDFISH-VIRTUAL-HARDWARE.TXT

endmenu # Timer Driver Support
6 changes: 6 additions & 0 deletions drivers/timers/Make.defs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ ifeq ($(CONFIG_CAPTURE),y)
TMRVPATH = :timers
endif

ifeq ($(CONFIG_GOLDFISH_TIMER),y)
CSRCS += goldfish_timer.c
TMRDEPPATH = --dep-path timers
TMRVPATH = :timers
endif

# Include timer build support (if any were selected)

DEPPATH += $(TMRDEPPATH)
Expand Down
Loading

0 comments on commit 39bfdb9

Please sign in to comment.