Skip to content

Commit 9452d47

Browse files
committed
Implement Goldfish RTC
The system date after boot is not synchronized with the host's wall clock, resulting in incorrect timestamps shown by commands such as date. When the system has the hwclock utility, it cannot function because the /dev/rtc? device file is missing. In addition, programs might rely on RTC alarm events for wake-up operations, which require access to /dev/rtc? device file. This commit implements an RTC device using the Goldfish RTC model, chosen for its simplicity and native support in the Linux kernel. By adding RTC support, the following capabilities are enabled: - System boot-up synchronization: The RTC driver initializes the system time from the at boot, ensuring the guestOS starts with an accurate and synchronized timestamp. - Wakeup events: The RTC can generate alarm interrupts to wake up the process. - The date command, hwclock utility, and alarm configuration via ioctl (see the rtc(4) man page) are now functional. - Provide accurate timestamps for filesystem operations such as when writing or modifying files on a virtio block device. The #605 hardcoded vblk_mmio_base_hi to 0x41, which limits adding new subnodes (e.g., RTC) under the SoC node. This value should instead be dynamically set during load_dtb(). Another limitation is in the next_addr probing logic, which currently uses subnode's name + 7. This assumes a fixed-length name and fails for variable-length names (e.g., rtc@4100000, should be name + 4). The fix is to locate the '@' character in the subnode name and use its position + 1 as the offset. Additionally, when no virtio block device is specified, virtio_mmio_base_hi will default to 0. In this case, use vblk_cnt for condition checking during MMIO handling to avoid incorrect behavior.
1 parent ccdcc30 commit 9452d47

File tree

9 files changed

+329
-95
lines changed

9 files changed

+329
-95
lines changed

src/common.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
3131

32-
#define MASK(n) (~((~0U << (n))))
32+
#define MASK(n) (~((~0UL << (n))))
3333

3434
#if defined(_MSC_VER)
3535
#include <intrin.h>

src/devices/minimal.dts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,14 +66,21 @@
6666
clock-frequency = <5000000>; /* the baudrate divisor is ignored */
6767
};
6868

69+
rtc0: rtc@4100000 {
70+
compatible = "google,goldfish-rtc";
71+
reg = <0x4100000 0x1000>;
72+
interrupts = <2>;
73+
interrupt-parent = <&plic0>;
74+
};
75+
6976
/*
7077
* Virtio block example subnode
7178
* The actual subnode are generated dynamically depends on the CLI -x vblk option
7279
*/
73-
/*blk0: virtio@4100000 {
80+
/*blk0: virtio@4200000 {
7481
compatible = "virtio,mmio";
75-
reg = <0x4100000 0x200>;
76-
interrupts = <2>;
82+
reg = <0x4200000 0x200>;
83+
interrupts = <3>;
7784
};*/
7885
};
7986
};

src/devices/rtc.c

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
/*
2+
* rv32emu is freely redistributable under the MIT License. See the file
3+
* "LICENSE" for information on usage and redistribution of this file.
4+
*/
5+
6+
#include <assert.h>
7+
#include <errno.h>
8+
#include <fcntl.h>
9+
#include <stdbool.h>
10+
#include <stdio.h>
11+
#include <stdlib.h>
12+
#include <string.h>
13+
#include <time.h>
14+
#include <unistd.h>
15+
16+
#include "rtc.h"
17+
18+
static uint64_t now_nsec;
19+
20+
uint64_t get_now_nsec(rtc_t *rtc)
21+
{
22+
/* TODO:
23+
* - detects timezone and use the correct UTC offset
24+
* - a new CLI option should be added to main.c to let user to select
25+
* [UTC] or [UTC + offset](localtime) time. E.g., -x rtc:utc or -x
26+
* rtc:localtime
27+
*/
28+
struct timespec ts;
29+
clock_gettime(CLOCK_REALTIME, &ts);
30+
return (uint64_t) (ts.tv_sec * 1e9) + ts.tv_nsec + rtc->clock_offset;
31+
}
32+
33+
uint32_t rtc_read(rtc_t *rtc, uint32_t addr)
34+
{
35+
uint32_t rtc_read_val = 0;
36+
37+
switch (addr) {
38+
case RTC_TIME_LOW:
39+
now_nsec = get_now_nsec(rtc);
40+
rtc->time_low = (uint32_t) (now_nsec & MASK(32));
41+
rtc_read_val = rtc->time_low;
42+
break;
43+
case RTC_TIME_HIGH:
44+
/* reuse the now_nsec when reading RTC_TIME_LOW */
45+
rtc->time_high = (uint32_t) (now_nsec >> 32);
46+
rtc_read_val = rtc->time_high;
47+
break;
48+
case RTC_ALARM_LOW:
49+
rtc_read_val = rtc->alarm_low;
50+
break;
51+
case RTC_ALARM_HIGH:
52+
rtc_read_val = rtc->alarm_high;
53+
break;
54+
case RTC_ALARM_STATUS:
55+
rtc_read_val = rtc->alarm_status;
56+
break;
57+
default:
58+
rv_log_error("Unsupported RTC read operation, 0x%x", addr);
59+
break;
60+
}
61+
62+
return rtc_read_val;
63+
}
64+
65+
void rtc_write(rtc_t *rtc, uint32_t addr, uint32_t value)
66+
{
67+
switch (addr) {
68+
case RTC_TIME_LOW:
69+
now_nsec = get_now_nsec(rtc);
70+
rtc->clock_offset += (uint64_t) (value) - (now_nsec & MASK(32));
71+
break;
72+
case RTC_TIME_HIGH:
73+
/* reuse the now_nsec when writing RTC_TIME_LOW */
74+
rtc->clock_offset += ((uint64_t) (value) << 32) -
75+
(now_nsec & ((uint64_t) (MASK(32)) << 32));
76+
break;
77+
case RTC_ALARM_LOW:
78+
rtc->alarm_low = value;
79+
break;
80+
case RTC_ALARM_HIGH:
81+
rtc->alarm_high = value;
82+
break;
83+
case RTC_IRQ_ENABLED:
84+
rtc->irq_enabled = value;
85+
break;
86+
case RTC_CLEAR_ALARM:
87+
rtc->clear_alarm = value;
88+
break;
89+
case RTC_CLEAR_INTERRUPT:
90+
rtc->clear_interrupt = value;
91+
rtc->interrupt_status = 0;
92+
break;
93+
default:
94+
rv_log_error("Unsupported RTC write operation, 0x%x", addr);
95+
break;
96+
}
97+
return;
98+
}
99+
100+
rtc_t *rtc_new()
101+
{
102+
rtc_t *rtc = calloc(1, sizeof(rtc_t));
103+
assert(rtc);
104+
105+
/*
106+
* The rtc->time_low/high values can be updated through the RTC_SET_TIME
107+
* ioctl operation. Therefore, they should be initialized to match the
108+
* host OS time during initialization.
109+
*/
110+
now_nsec = get_now_nsec(rtc);
111+
rtc->time_low = (uint32_t) (now_nsec & MASK(32));
112+
rtc->time_high = (uint32_t) (now_nsec >> 32);
113+
114+
return rtc;
115+
}
116+
117+
void rtc_delete(rtc_t *rtc)
118+
{
119+
free(rtc);
120+
}

src/devices/rtc.h

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* rv32emu is freely redistributable under the MIT License. See the file
3+
* "LICENSE" for information on usage and redistribution of this file.
4+
*/
5+
6+
#pragma once
7+
8+
/* Check: https://github.com/torvalds/linux/blob/v6.1/drivers/rtc/rtc-goldfish.c
9+
*/
10+
11+
/* Google Goldfish RTC MMIO registers */
12+
#define RTC_REG_LIST \
13+
_(TIME_LOW, 0x00) /* R/W */ \
14+
_(TIME_HIGH, 0x04) /* R/W */ \
15+
_(ALARM_LOW, 0x08) /* R/W */ \
16+
_(ALARM_HIGH, 0x0c) /* R/W */ \
17+
_(IRQ_ENABLED, 0x10) /* W */ \
18+
_(CLEAR_ALARM, 0x14) /* W */ \
19+
_(ALARM_STATUS, 0x18) /* R */ \
20+
_(CLEAR_INTERRUPT, 0x1c) /* W */
21+
22+
enum {
23+
#define _(reg, addr) RTC_##reg = addr,
24+
RTC_REG_LIST
25+
#undef _
26+
};
27+
28+
typedef struct {
29+
uint32_t time_low;
30+
uint32_t time_high;
31+
uint32_t alarm_low;
32+
uint32_t alarm_high;
33+
uint32_t irq_enabled;
34+
uint32_t clear_alarm;
35+
uint32_t alarm_status;
36+
uint32_t clear_interrupt;
37+
38+
/* Ensure the clock always progresses so RTC_SET_TIME ioctl can set any
39+
* arbitrary time */
40+
uint64_t clock_offset;
41+
42+
uint32_t interrupt_status;
43+
} rtc_t;
44+
45+
#define IRQ_RTC_SHIFT 2
46+
#define IRQ_RTC_BIT (1 << IRQ_RTC_SHIFT)
47+
48+
#define RTC_ALARM_FIRE(rtc, now_nsec) \
49+
rtc->irq_enabled && \
50+
(now_nsec) >= ((((uint64_t) rtc->alarm_high) << 32) | rtc->alarm_low)
51+
52+
uint32_t rtc_read(rtc_t *rtc, uint32_t addr);
53+
54+
void rtc_write(rtc_t *rtc, uint32_t addr, uint32_t value);
55+
56+
rtc_t *rtc_new();
57+
58+
void rtc_delete(rtc_t *rtc);

src/emulate.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,8 @@ static bool has_loops = false;
370370

371371
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
372372
extern void emu_update_uart_interrupts(riscv_t *rv);
373+
extern void emu_update_rtc_interrupts(riscv_t *rv);
374+
extern uint64_t get_now_nsec(rtc_t *rtc);
373375
static uint32_t peripheral_update_ctr = 64;
374376
#endif
375377

@@ -1015,6 +1017,13 @@ static void rv_check_interrupt(riscv_t *rv)
10151017
u8250_check_ready(PRIV(rv)->uart);
10161018
if (PRIV(rv)->uart->in_ready)
10171019
emu_update_uart_interrupts(rv);
1020+
1021+
uint64_t now_nsec = get_now_nsec(PRIV(rv)->rtc);
1022+
if (RTC_ALARM_FIRE(PRIV(rv)->rtc, now_nsec)) {
1023+
PRIV(rv)->rtc->alarm_status = 1;
1024+
PRIV(rv)->rtc->interrupt_status = 1;
1025+
emu_update_rtc_interrupts(rv);
1026+
}
10181027
}
10191028

10201029
if (rv->timer > attr->timer)

src/riscv.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,10 @@ static void load_dtb(char **ram_loc, vm_attr_t *attr)
350350
const char *name = fdt_get_name(dtb_buf, subnode, NULL);
351351
assert(name);
352352

353-
uint32_t addr = strtoul(name + 7, NULL, 16);
353+
char *at_pos = strchr(name, '@');
354+
assert(at_pos);
355+
356+
uint32_t addr = strtoul(at_pos + 1, NULL, 16);
354357
if (addr == next_addr)
355358
next_addr = addr + addr_offset;
356359

@@ -365,6 +368,10 @@ static void load_dtb(char **ram_loc, vm_attr_t *attr)
365368
/* set IRQ for virtio block, see devices/virtio.h */
366369
attr->vblk_irq_base = next_irq;
367370

371+
/* set the VBLK MMIO valid range */
372+
attr->vblk_mmio_base_hi = next_addr >> 20;
373+
attr->vblk_mmio_max_hi = attr->vblk_mmio_base_hi + attr->vblk_cnt;
374+
368375
/* adding new virtio block nodes */
369376
for (int i = 0; i < attr->vblk_cnt; i++) {
370377
uint32_t new_addr = next_addr + i * addr_offset;
@@ -665,9 +672,8 @@ riscv_t *rv_create(riscv_user_t rv_attr)
665672
attr->uart->in_fd = attr->fd_stdin;
666673
attr->uart->out_fd = attr->fd_stdout;
667674

668-
/* setup virtio-blk */
669-
attr->vblk_mmio_base_hi = 0x41;
670-
attr->vblk_mmio_max_hi = attr->vblk_mmio_base_hi + attr->vblk_cnt;
675+
/* setup rtc */
676+
attr->rtc = rtc_new();
671677

672678
attr->vblk = malloc(sizeof(virtio_blk_state_t *) * attr->vblk_cnt);
673679
assert(attr->vblk);
@@ -848,6 +854,7 @@ void rv_delete(riscv_t *rv)
848854
#if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER)
849855
u8250_delete(attr->uart);
850856
plic_delete(attr->plic);
857+
rtc_delete(attr->rtc);
851858
/* sync device, cleanup inside the callee */
852859
rv_fsync_device();
853860
#endif

src/riscv.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
#if RV32_HAS(SYSTEM)
1717
#include "devices/plic.h"
18+
#include "devices/rtc.h"
1819
#include "devices/uart.h"
1920
#include "devices/virtio.h"
2021
#endif /* RV32_HAS(SYSTEM) */
@@ -483,6 +484,9 @@ typedef struct {
483484
/* plic object */
484485
plic_t *plic;
485486

487+
/* rtc object */
488+
rtc_t *rtc;
489+
486490
/* virtio-blk device */
487491
uint32_t **disk;
488492
virtio_blk_state_t **vblk;

src/system.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ void emu_update_vblk_interrupts(riscv_t *rv)
3030
plic_update_interrupts(attr->plic);
3131
}
3232
}
33+
34+
void emu_update_rtc_interrupts(riscv_t *rv)
35+
{
36+
vm_attr_t *attr = PRIV(rv);
37+
if (attr->rtc->interrupt_status)
38+
attr->plic->active |= IRQ_RTC_BIT;
39+
else
40+
attr->plic->active &= ~IRQ_RTC_BIT;
41+
plic_update_interrupts(attr->plic);
42+
}
3343
#endif
3444

3545
static bool ppn_is_valid(riscv_t *rv, uint32_t ppn)

0 commit comments

Comments
 (0)