diff --git a/.ci/boot-linux.sh b/.ci/boot-linux.sh index 9a91abd95..1a88f62fc 100755 --- a/.ci/boot-linux.sh +++ b/.ci/boot-linux.sh @@ -22,6 +22,9 @@ function ASSERT cleanup +# To test RTC clock +HOST_UTC_YEAR=$(LC_ALL=C date -u +%Y) + ENABLE_VBLK=1 VBLK_IMG=build/disk.img [ -f "${VBLK_IMG}" ] || ENABLE_VBLK=0 @@ -33,10 +36,93 @@ OPTS_BASE+=" -i build/linux-image/rootfs.cpio" TEST_OPTIONS=("base (${OPTS_BASE})") EXPECT_CMDS=(' expect "buildroot login:" { send "root\n" } timeout { exit 1 } + expect "# " { send "dmesg | grep rtc\n" } timeout { exit 2 } + expect "rtc0" { } timeout { exit 3 } + expect "# " { send "date -u +%Y\n" } timeout { exit 2 } + expect "${HOST_UTC_YEAR}" { } timeout { exit 3 } expect "# " { send "uname -a\n" } timeout { exit 2 } expect "riscv32 GNU/Linux" { send "\x01"; send "x" } timeout { exit 3 } ') +# RTC alarm and settime tests +TEST_OPTIONS+=("${OPTS_BASE}") +EXPECT_CMDS+=(' + expect "buildroot login:" { send "root\n" } timeout { exit 1 } + expect "# " { send "dmesg | grep rtc\n" } timeout { exit 2 } + expect "rtc0" { } timeout { exit 3 } + expect "# " { send "date -u +%Y\n" } timeout { exit 2 } + expect "${HOST_UTC_YEAR}" { } timeout { exit 3 } + expect "# " { send "uname -a\n" } timeout { exit 2 } + expect "riscv32 GNU/Linux" { } timeout { exit 3 } + expect "# " { send "rtc_alarm\n" } timeout { exit 3 } + expect "alarm_IRQ : no" { } timeout { exit 3 } + expect "alarm_IRQ : yes" { } timeout { exit 3 } + expect "alarm_IRQ : no" { } timeout { exit 3 } + expect "# " { send "\x01"; send "x" } timeout { exit 3 } +') +year1=1980 +year2=2030 +TEST_OPTIONS+=("${OPTS_BASE}") +EXPECT_CMDS+=(' + expect "buildroot login:" { send "root\n" } timeout { exit 1 } + expect "# " { send "dmesg | grep rtc\n" } timeout { exit 2 } + expect "rtc0" { } timeout { exit 3 } + expect "# " { send "date -u +%Y\n" } timeout { exit 2 } + expect "${HOST_UTC_YEAR}" { } timeout { exit 3 } + expect "# " { send "uname -a\n" } timeout { exit 2 } + expect "riscv32 GNU/Linux" { } timeout { exit 3 } + expect "# " { send "rtc_settime ${year1}\n" } timeout { exit 3 } + expect "rtc_date : ${year1}-01-01" { } timeout { exit 3 } + expect "# " { send "\x01"; send "x" } timeout { exit 3 } +') +TEST_OPTIONS+=("${OPTS_BASE}") +EXPECT_CMDS+=(' + expect "buildroot login:" { send "root\n" } timeout { exit 1 } + expect "# " { send "dmesg | grep rtc\n" } timeout { exit 2 } + expect "rtc0" { } timeout { exit 3 } + expect "# " { send "date -u +%Y\n" } timeout { exit 2 } + expect "${HOST_UTC_YEAR}" { } timeout { exit 3 } + expect "# " { send "uname -a\n" } timeout { exit 2 } + expect "riscv32 GNU/Linux" { } timeout { exit 3 } + expect "# " { send "rtc_settime ${year2}\n" } timeout { exit 3 } + expect "rtc_date : ${year2}-01-01" { } timeout { exit 3 } + expect "# " { send "\x01"; send "x" } timeout { exit 3 } +') +TEST_OPTIONS+=("${OPTS_BASE}") +EXPECT_CMDS+=(' + expect "buildroot login:" { send "root\n" } timeout { exit 1 } + expect "# " { send "dmesg | grep rtc\n" } timeout { exit 2 } + expect "rtc0" { } timeout { exit 3 } + expect "# " { send "date -u +%Y\n" } timeout { exit 2 } + expect "${HOST_UTC_YEAR}" { } timeout { exit 3 } + expect "# " { send "uname -a\n" } timeout { exit 2 } + expect "riscv32 GNU/Linux" { } timeout { exit 3 } + expect "# " { send "rtc_settime ${year1}\n" } timeout { exit 3 } + expect "rtc_date : ${year1}-01-01" { } timeout { exit 3 } + expect "# " { send "rtc_alarm\n" } timeout { exit 3 } + expect "alarm_IRQ : no" { } timeout { exit 3 } + expect "alarm_IRQ : yes" { } timeout { exit 3 } + expect "alarm_IRQ : no" { } timeout { exit 3 } + expect "# " { send "\x01"; send "x" } timeout { exit 3 } +') +TEST_OPTIONS+=("${OPTS_BASE}") +EXPECT_CMDS+=(' + expect "buildroot login:" { send "root\n" } timeout { exit 1 } + expect "# " { send "dmesg | grep rtc\n" } timeout { exit 2 } + expect "rtc0" { } timeout { exit 3 } + expect "# " { send "date -u +%Y\n" } timeout { exit 2 } + expect "${HOST_UTC_YEAR}" { } timeout { exit 3 } + expect "# " { send "uname -a\n" } timeout { exit 2 } + expect "riscv32 GNU/Linux" { } timeout { exit 3 } + expect "# " { send "rtc_settime ${year2}\n" } timeout { exit 3 } + expect "rtc_date : ${year2}-01-01" { } timeout { exit 3 } + expect "# " { send "rtc_alarm\n" } timeout { exit 3 } + expect "alarm_IRQ : no" { } timeout { exit 3 } + expect "alarm_IRQ : yes" { } timeout { exit 3 } + expect "alarm_IRQ : no" { } timeout { exit 3 } + expect "# " { send "\x01"; send "x" } timeout { exit 3 } +') + COLOR_G='\e[32;01m' # Green COLOR_R='\e[31;01m' # Red COLOR_Y='\e[33;01m' # Yellow @@ -54,6 +140,10 @@ if [ "${ENABLE_VBLK}" -eq "1" ]; then TEST_OPTIONS+=("${OPTS_BASE} -x vblk:${VBLK_IMG},readonly") EXPECT_CMDS+=(' expect "buildroot login:" { send "root\n" } timeout { exit 1 } + expect "# " { send "dmesg | grep rtc\n" } timeout { exit 2 } + expect "rtc0" { } timeout { exit 3 } + expect "# " { send "date -u +%Y\n" } timeout { exit 2 } + expect "${HOST_UTC_YEAR}" { } timeout { exit 3 } expect "# " { send "uname -a\n" } timeout { exit 2 } expect "riscv32 GNU/Linux" { send "mkdir mnt && mount /dev/vda mnt\n" } timeout { exit 3 } expect "# " { send "echo rv32emu > mnt/emu.txt\n" } timeout { exit 3 } @@ -65,6 +155,10 @@ if [ "${ENABLE_VBLK}" -eq "1" ]; then TEST_OPTIONS+=("${OPTS_BASE} -x vblk:${VBLK_IMG},readonly -x vblk:${BLK_DEV},readonly") EXPECT_CMDS+=(' expect "buildroot login:" { send "root\n" } timeout { exit 1 } + expect "# " { send "dmesg | grep rtc\n" } timeout { exit 2 } + expect "rtc0" { } timeout { exit 3 } + expect "# " { send "date -u +%Y\n" } timeout { exit 2 } + expect "${HOST_UTC_YEAR}" { } timeout { exit 3 } expect "# " { send "uname -a\n" } timeout { exit 2 } expect "riscv32 GNU/Linux" { send "mkdir mnt && mount /dev/vda mnt\n" } timeout { exit 3 } expect "# " { send "echo rv32emu > mnt/emu.txt\n" } timeout { exit 3 } @@ -79,6 +173,10 @@ if [ "${ENABLE_VBLK}" -eq "1" ]; then TEST_OPTIONS+=("${OPTS_BASE} -x vblk:${VBLK_IMG}") VBLK_EXPECT_CMDS=' expect "buildroot login:" { send "root\n" } timeout { exit 1 } + expect "# " { send "dmesg | grep rtc\n" } timeout { exit 2 } + expect "rtc0" { } timeout { exit 3 } + expect "# " { send "date -u +%Y\n" } timeout { exit 2 } + expect "${HOST_UTC_YEAR}" { } timeout { exit 3 } expect "# " { send "uname -a\n" } timeout { exit 2 } expect "riscv32 GNU/Linux" { send "mkdir mnt && mount /dev/vda mnt\n" } timeout { exit 3 } expect "# " { send "echo rv32emu > mnt/emu.txt\n" } timeout { exit 3 } @@ -96,6 +194,10 @@ if [ "${ENABLE_VBLK}" -eq "1" ]; then TEST_OPTIONS+=("${OPTS_BASE} -x vblk:${VBLK_IMG} -x vblk:${BLK_DEV}") VBLK_EXPECT_CMDS=' expect "buildroot login:" { send "root\n" } timeout { exit 1 } + expect "# " { send "dmesg | grep rtc\n" } timeout { exit 2 } + expect "rtc0" { } timeout { exit 3 } + expect "# " { send "date -u +%Y\n" } timeout { exit 2 } + expect "${HOST_UTC_YEAR}" { } timeout { exit 3 } expect "# " { send "uname -a\n" } timeout { exit 2 } expect "riscv32 GNU/Linux" { send "mkdir mnt && mount /dev/vda mnt\n" } timeout { exit 3 } expect "# " { send "echo rv32emu > mnt/emu.txt\n" } timeout { exit 3 } @@ -121,6 +223,9 @@ for i in "${!TEST_OPTIONS[@]}"; do RUN_LINUX="build/rv32emu ${OPTS}" ASSERT expect <<- DONE + set HOST_UTC_YEAR ${HOST_UTC_YEAR} + set year1 ${year1} + set year2 ${year2} set timeout ${TIMEOUT} spawn ${RUN_LINUX} ${EXPECT_CMDS[$i]} diff --git a/assets/system/configs/buildroot.config b/assets/system/configs/buildroot.config index df36a1759..65365a798 100644 --- a/assets/system/configs/buildroot.config +++ b/assets/system/configs/buildroot.config @@ -45,3 +45,8 @@ BR2_TARGET_ROOTFS_CPIO_NONE=y BR2_PACKAGE_BUSYBOX_CONFIG="busybox.config" BR2_PACKAGE_COREMARK=y BR2_PACKAGE_DHRYSTONE=y +BR2_PACKAGE_UTIL_LINUX=y +BR2_PACKAGE_UTIL_LINUX_HWCLOCK=y + +BR2_PACKAGE_RTC_ALARM=y +BR2_PACKAGE_RTC_SETTIME=y diff --git a/assets/system/configs/linux.config b/assets/system/configs/linux.config index fc3584e0e..1c6911809 100644 --- a/assets/system/configs/linux.config +++ b/assets/system/configs/linux.config @@ -1026,7 +1026,66 @@ CONFIG_USB_OHCI_LITTLE_ENDIAN=y # CONFIG_ACCESSIBILITY is not set # CONFIG_INFINIBAND is not set CONFIG_EDAC_SUPPORT=y -# CONFIG_RTC_CLASS is not set + +CONFIG_RTC_LIB=y +CONFIG_RTC_CLASS=y +CONFIG_RTC_HCTOSYS=y +CONFIG_RTC_HCTOSYS_DEVICE="rtc0" +# CONFIG_RTC_SYSTOHC is not set +# CONFIG_RTC_DEBUG is not set +# CONFIG_RTC_NVMEM is not set + +# +# RTC interfaces +# +CONFIG_RTC_INTF_SYSFS=y +CONFIG_RTC_INTF_PROC=y +CONFIG_RTC_INTF_DEV=y +# CONFIG_RTC_INTF_DEV_UIE_EMUL is not set +# CONFIG_RTC_DRV_TEST is not set + +# +# I2C RTC drivers +# + +# +# SPI RTC drivers +# + +# +# SPI and I2C RTC drivers +# + +# +# Platform RTC drivers +# +# CONFIG_RTC_DRV_DS1286 is not set +# CONFIG_RTC_DRV_DS1511 is not set +# CONFIG_RTC_DRV_DS1553 is not set +# CONFIG_RTC_DRV_DS1685_FAMILY is not set +# CONFIG_RTC_DRV_DS1742 is not set +# CONFIG_RTC_DRV_DS2404 is not set +# CONFIG_RTC_DRV_STK17TA8 is not set +# CONFIG_RTC_DRV_M48T86 is not set +# CONFIG_RTC_DRV_M48T35 is not set +# CONFIG_RTC_DRV_M48T59 is not set +# CONFIG_RTC_DRV_MSM6242 is not set +# CONFIG_RTC_DRV_BQ4802 is not set +# CONFIG_RTC_DRV_RP5C01 is not set +# CONFIG_RTC_DRV_V3020 is not set +# CONFIG_RTC_DRV_ZYNQMP is not set + +# +# on-CPU RTC drivers +# +# CONFIG_RTC_DRV_CADENCE is not set +# CONFIG_RTC_DRV_FTRTC010 is not set +# CONFIG_RTC_DRV_R7301 is not set + +# +# HID Sensor RTC drivers +# +CONFIG_RTC_DRV_GOLDFISH=y # CONFIG_DMADEVICES is not set # diff --git a/src/common.h b/src/common.h index bce9f0f24..d6719754d 100644 --- a/src/common.h +++ b/src/common.h @@ -29,7 +29,7 @@ #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0])) -#define MASK(n) (~((~0U << (n)))) +#define MASK(n) (~((~0UL << (n)))) #if defined(_MSC_VER) #include diff --git a/src/devices/minimal.dts b/src/devices/minimal.dts index 1b0b09572..ef4446cd5 100644 --- a/src/devices/minimal.dts +++ b/src/devices/minimal.dts @@ -66,14 +66,21 @@ clock-frequency = <5000000>; /* the baudrate divisor is ignored */ }; + rtc0: rtc@4100000 { + compatible = "google,goldfish-rtc"; + reg = <0x4100000 0x1000>; + interrupts = <2>; + interrupt-parent = <&plic0>; + }; + /* * Virtio block example subnode * The actual subnode are generated dynamically depends on the CLI -x vblk option */ - /*blk0: virtio@4100000 { + /*blk0: virtio@4200000 { compatible = "virtio,mmio"; - reg = <0x4100000 0x200>; - interrupts = <2>; + reg = <0x4200000 0x200>; + interrupts = <3>; };*/ }; }; diff --git a/src/devices/rtc.c b/src/devices/rtc.c new file mode 100644 index 000000000..0a87d7e6a --- /dev/null +++ b/src/devices/rtc.c @@ -0,0 +1,120 @@ +/* + * rv32emu is freely redistributable under the MIT License. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtc.h" + +static uint64_t now_nsec; + +uint64_t get_now_nsec(rtc_t *rtc) +{ + /* TODO: + * - detects timezone and use the correct UTC offset + * - a new CLI option should be added to main.c to let user to select + * [UTC] or [UTC + offset](localtime) time. E.g., -x rtc:utc or -x + * rtc:localtime + */ + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + return (uint64_t) (ts.tv_sec * 1e9) + ts.tv_nsec + rtc->clock_offset; +} + +uint32_t rtc_read(rtc_t *rtc, uint32_t addr) +{ + uint32_t rtc_read_val = 0; + + switch (addr) { + case RTC_TIME_LOW: + now_nsec = get_now_nsec(rtc); + rtc->time_low = (uint32_t) (now_nsec & MASK(32)); + rtc_read_val = rtc->time_low; + break; + case RTC_TIME_HIGH: + /* reuse the now_nsec when reading RTC_TIME_LOW */ + rtc->time_high = (uint32_t) (now_nsec >> 32); + rtc_read_val = rtc->time_high; + break; + case RTC_ALARM_LOW: + rtc_read_val = rtc->alarm_low; + break; + case RTC_ALARM_HIGH: + rtc_read_val = rtc->alarm_high; + break; + case RTC_ALARM_STATUS: + rtc_read_val = rtc->alarm_status; + break; + default: + rv_log_error("Unsupported RTC read operation, 0x%x", addr); + break; + } + + return rtc_read_val; +} + +void rtc_write(rtc_t *rtc, uint32_t addr, uint32_t value) +{ + switch (addr) { + case RTC_TIME_LOW: + now_nsec = get_now_nsec(rtc); + rtc->clock_offset += (uint64_t) (value) - (now_nsec & MASK(32)); + break; + case RTC_TIME_HIGH: + /* reuse the now_nsec when writing RTC_TIME_LOW */ + rtc->clock_offset += ((uint64_t) (value) << 32) - + (now_nsec & ((uint64_t) (MASK(32)) << 32)); + break; + case RTC_ALARM_LOW: + rtc->alarm_low = value; + break; + case RTC_ALARM_HIGH: + rtc->alarm_high = value; + break; + case RTC_IRQ_ENABLED: + rtc->irq_enabled = value; + break; + case RTC_CLEAR_ALARM: + rtc->clear_alarm = value; + break; + case RTC_CLEAR_INTERRUPT: + rtc->clear_interrupt = value; + rtc->interrupt_status = 0; + break; + default: + rv_log_error("Unsupported RTC write operation, 0x%x", addr); + break; + } + return; +} + +rtc_t *rtc_new() +{ + rtc_t *rtc = calloc(1, sizeof(rtc_t)); + assert(rtc); + + /* + * The rtc->time_low/high values can be updated through the RTC_SET_TIME + * ioctl operation. Therefore, they should be initialized to match the + * host OS time during initialization. + */ + now_nsec = get_now_nsec(rtc); + rtc->time_low = (uint32_t) (now_nsec & MASK(32)); + rtc->time_high = (uint32_t) (now_nsec >> 32); + + return rtc; +} + +void rtc_delete(rtc_t *rtc) +{ + free(rtc); +} diff --git a/src/devices/rtc.h b/src/devices/rtc.h new file mode 100644 index 000000000..f95e62c0b --- /dev/null +++ b/src/devices/rtc.h @@ -0,0 +1,58 @@ +/* + * rv32emu is freely redistributable under the MIT License. See the file + * "LICENSE" for information on usage and redistribution of this file. + */ + +#pragma once + +/* Check: https://github.com/torvalds/linux/blob/v6.1/drivers/rtc/rtc-goldfish.c + */ + +/* Google Goldfish RTC MMIO registers */ +#define RTC_REG_LIST \ + _(TIME_LOW, 0x00) /* R/W */ \ + _(TIME_HIGH, 0x04) /* R/W */ \ + _(ALARM_LOW, 0x08) /* R/W */ \ + _(ALARM_HIGH, 0x0c) /* R/W */ \ + _(IRQ_ENABLED, 0x10) /* W */ \ + _(CLEAR_ALARM, 0x14) /* W */ \ + _(ALARM_STATUS, 0x18) /* R */ \ + _(CLEAR_INTERRUPT, 0x1c) /* W */ + +enum { +#define _(reg, addr) RTC_##reg = addr, + RTC_REG_LIST +#undef _ +}; + +typedef struct { + uint32_t time_low; + uint32_t time_high; + uint32_t alarm_low; + uint32_t alarm_high; + uint32_t irq_enabled; + uint32_t clear_alarm; + uint32_t alarm_status; + uint32_t clear_interrupt; + + /* Ensure the clock always progresses so RTC_SET_TIME ioctl can set any + * arbitrary time */ + uint64_t clock_offset; + + uint32_t interrupt_status; +} rtc_t; + +#define IRQ_RTC_SHIFT 2 +#define IRQ_RTC_BIT (1 << IRQ_RTC_SHIFT) + +#define RTC_ALARM_FIRE(rtc, now_nsec) \ + rtc->irq_enabled && \ + (now_nsec) >= ((((uint64_t) rtc->alarm_high) << 32) | rtc->alarm_low) + +uint32_t rtc_read(rtc_t *rtc, uint32_t addr); + +void rtc_write(rtc_t *rtc, uint32_t addr, uint32_t value); + +rtc_t *rtc_new(); + +void rtc_delete(rtc_t *rtc); diff --git a/src/emulate.c b/src/emulate.c index d9c72c816..e92d38d84 100644 --- a/src/emulate.c +++ b/src/emulate.c @@ -370,6 +370,8 @@ static bool has_loops = false; #if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) extern void emu_update_uart_interrupts(riscv_t *rv); +extern void emu_update_rtc_interrupts(riscv_t *rv); +extern uint64_t get_now_nsec(rtc_t *rtc); static uint32_t peripheral_update_ctr = 64; #endif @@ -1015,6 +1017,13 @@ static void rv_check_interrupt(riscv_t *rv) u8250_check_ready(PRIV(rv)->uart); if (PRIV(rv)->uart->in_ready) emu_update_uart_interrupts(rv); + + uint64_t now_nsec = get_now_nsec(PRIV(rv)->rtc); + if (RTC_ALARM_FIRE(PRIV(rv)->rtc, now_nsec)) { + PRIV(rv)->rtc->alarm_status = 1; + PRIV(rv)->rtc->interrupt_status = 1; + emu_update_rtc_interrupts(rv); + } } if (rv->timer > attr->timer) diff --git a/src/riscv.c b/src/riscv.c index 5b43114d4..2fcde71f5 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -350,7 +350,10 @@ static void load_dtb(char **ram_loc, vm_attr_t *attr) const char *name = fdt_get_name(dtb_buf, subnode, NULL); assert(name); - uint32_t addr = strtoul(name + 7, NULL, 16); + char *at_pos = strchr(name, '@'); + assert(at_pos); + + uint32_t addr = strtoul(at_pos + 1, NULL, 16); if (addr == next_addr) next_addr = addr + addr_offset; @@ -365,6 +368,10 @@ static void load_dtb(char **ram_loc, vm_attr_t *attr) /* set IRQ for virtio block, see devices/virtio.h */ attr->vblk_irq_base = next_irq; + /* set the VBLK MMIO valid range */ + attr->vblk_mmio_base_hi = next_addr >> 20; + attr->vblk_mmio_max_hi = attr->vblk_mmio_base_hi + attr->vblk_cnt; + /* adding new virtio block nodes */ for (int i = 0; i < attr->vblk_cnt; i++) { uint32_t new_addr = next_addr + i * addr_offset; @@ -665,9 +672,8 @@ riscv_t *rv_create(riscv_user_t rv_attr) attr->uart->in_fd = attr->fd_stdin; attr->uart->out_fd = attr->fd_stdout; - /* setup virtio-blk */ - attr->vblk_mmio_base_hi = 0x41; - attr->vblk_mmio_max_hi = attr->vblk_mmio_base_hi + attr->vblk_cnt; + /* setup rtc */ + attr->rtc = rtc_new(); attr->vblk = malloc(sizeof(virtio_blk_state_t *) * attr->vblk_cnt); assert(attr->vblk); @@ -848,6 +854,7 @@ void rv_delete(riscv_t *rv) #if RV32_HAS(SYSTEM) && !RV32_HAS(ELF_LOADER) u8250_delete(attr->uart); plic_delete(attr->plic); + rtc_delete(attr->rtc); /* sync device, cleanup inside the callee */ rv_fsync_device(); #endif diff --git a/src/riscv.h b/src/riscv.h index 63f375be7..471e1f875 100644 --- a/src/riscv.h +++ b/src/riscv.h @@ -15,6 +15,7 @@ #if RV32_HAS(SYSTEM) #include "devices/plic.h" +#include "devices/rtc.h" #include "devices/uart.h" #include "devices/virtio.h" #endif /* RV32_HAS(SYSTEM) */ @@ -483,6 +484,9 @@ typedef struct { /* plic object */ plic_t *plic; + /* rtc object */ + rtc_t *rtc; + /* virtio-blk device */ uint32_t **disk; virtio_blk_state_t **vblk; diff --git a/src/system.c b/src/system.c index 4dd66f224..3d5550267 100644 --- a/src/system.c +++ b/src/system.c @@ -30,6 +30,16 @@ void emu_update_vblk_interrupts(riscv_t *rv) plic_update_interrupts(attr->plic); } } + +void emu_update_rtc_interrupts(riscv_t *rv) +{ + vm_attr_t *attr = PRIV(rv); + if (attr->rtc->interrupt_status) + attr->plic->active |= IRQ_RTC_BIT; + else + attr->plic->active &= ~IRQ_RTC_BIT; + plic_update_interrupts(attr->plic); +} #endif static bool ppn_is_valid(riscv_t *rv, uint32_t ppn) diff --git a/src/system.h b/src/system.h index ace503599..529ffa531 100644 --- a/src/system.h +++ b/src/system.h @@ -25,107 +25,126 @@ enum SUPPORTED_MMIO { MMIO_PLIC, MMIO_UART, MMIO_VIRTIOBLK, + MMIO_RTC, }; /* clang-format off */ -#define MMIO_OP(io, rw) \ - switch(io){ \ - case MMIO_PLIC: \ - IIF(rw)( /* read */ \ - mmio_read_val = plic_read(PRIV(rv)->plic, addr & 0x3FFFFFF); \ - plic_update_interrupts(PRIV(rv)->plic); \ - return mmio_read_val; \ - , /* write */ \ - plic_write(PRIV(rv)->plic, addr & 0x3FFFFFF, val); \ - plic_update_interrupts(PRIV(rv)->plic); \ - return; \ - ) \ - break; \ - case MMIO_UART: \ - IIF(rw)( /* read */ \ - mmio_read_val = u8250_read(PRIV(rv)->uart, addr & 0xFFFFF); \ - emu_update_uart_interrupts(rv); \ - return mmio_read_val; \ - , /* write */ \ - u8250_write(PRIV(rv)->uart, addr & 0xFFFFF, val); \ - emu_update_uart_interrupts(rv); \ - return; \ - ) \ - break; \ - case MMIO_VIRTIOBLK: \ - IIF(rw)( /* read */ \ +#define MMIO_OP(io, rw) \ + switch(io){ \ + case MMIO_PLIC: \ + IIF(rw)( /* read */ \ + mmio_read_val = plic_read(PRIV(rv)->plic, addr & 0x3FFFFFF); \ + plic_update_interrupts(PRIV(rv)->plic); \ + return mmio_read_val; \ + , /* write */ \ + plic_write(PRIV(rv)->plic, addr & 0x3FFFFFF, val); \ + plic_update_interrupts(PRIV(rv)->plic); \ + return; \ + ) \ + break; \ + case MMIO_UART: \ + IIF(rw)( /* read */ \ + mmio_read_val = u8250_read(PRIV(rv)->uart, addr & 0xFFFFF); \ + emu_update_uart_interrupts(rv); \ + return mmio_read_val; \ + , /* write */ \ + u8250_write(PRIV(rv)->uart, addr & 0xFFFFF, val); \ + emu_update_uart_interrupts(rv); \ + return; \ + ) \ + break; \ + case MMIO_VIRTIOBLK: \ + IIF(rw)( /* read */ \ mmio_read_val = virtio_blk_read(PRIV(rv)->vblk_curr, addr & 0xFFFFF); \ - emu_update_vblk_interrupts(rv); \ - return mmio_read_val; \ - , /* write */ \ + emu_update_vblk_interrupts(rv); \ + return mmio_read_val; \ + , /* write */ \ virtio_blk_write(PRIV(rv)->vblk_curr, addr & 0xFFFFF, val); \ - emu_update_vblk_interrupts(rv); \ - return; \ - ) \ - break; \ - default: \ - rv_log_error("unknown MMIO type %d\n", io); \ - break; \ + emu_update_vblk_interrupts(rv); \ + return; \ + ) \ + break; \ + case MMIO_RTC: \ + IIF(rw)( /* read */ \ + mmio_read_val = rtc_read(PRIV(rv)->rtc, addr & 0xFFFFF); \ + emu_update_rtc_interrupts(rv); \ + return mmio_read_val; \ + , /* write */ \ + rtc_write(PRIV(rv)->rtc, addr & 0xFFFFF, val); \ + emu_update_rtc_interrupts(rv); \ + return; \ + ) \ + break; \ + default: \ + rv_log_error("unknown MMIO type %d\n", io); \ + break; \ } /* clang-format on */ -#define MMIO_READ() \ - do { \ - uint32_t mmio_read_val; \ - if ((addr >> 28) == 0xF) { /* MMIO at 0xF_______ */ \ - /* 256 regions of 1MiB */ \ - uint32_t hi = (addr >> 20) & MASK(8); \ - if (hi >= PRIV(rv)->vblk_mmio_base_hi && \ - hi <= PRIV(rv)->vblk_mmio_max_hi) { \ - PRIV(rv)->vblk_curr = \ - PRIV(rv)->vblk[hi - PRIV(rv)->vblk_mmio_base_hi]; \ - MMIO_OP(MMIO_VIRTIOBLK, MMIO_R); \ - } else { \ - switch (hi) { \ - case 0x0: \ - case 0x2: /* PLIC (0 - 0x3F) */ \ - MMIO_OP(MMIO_PLIC, MMIO_R); \ - break; \ - case 0x40: /* UART */ \ - MMIO_OP(MMIO_UART, MMIO_R); \ - break; \ - default: \ - __UNREACHABLE; \ - break; \ - } \ - } \ - } \ +#define MMIO_READ() \ + do { \ + uint32_t mmio_read_val; \ + if ((addr >> 28) == 0xF) { /* MMIO at 0xF_______ */ \ + /* 256 regions of 1MiB */ \ + uint32_t hi = (addr >> 20) & MASK(8); \ + if (PRIV(rv)->vblk_cnt && hi >= PRIV(rv)->vblk_mmio_base_hi && \ + hi <= PRIV(rv)->vblk_mmio_max_hi) { \ + PRIV(rv)->vblk_curr = \ + PRIV(rv)->vblk[hi - PRIV(rv)->vblk_mmio_base_hi]; \ + MMIO_OP(MMIO_VIRTIOBLK, MMIO_R); \ + } else { \ + switch (hi) { \ + case 0x0: \ + case 0x2: /* PLIC (0 - 0x3F) */ \ + MMIO_OP(MMIO_PLIC, MMIO_R); \ + break; \ + case 0x40: /* UART */ \ + MMIO_OP(MMIO_UART, MMIO_R); \ + break; \ + case 0x41: /* RTC */ \ + MMIO_OP(MMIO_RTC, MMIO_R); \ + break; \ + default: \ + __UNREACHABLE; \ + break; \ + } \ + } \ + } \ } while (0) -#define MMIO_WRITE() \ - do { \ - if ((addr >> 28) == 0xF) { /* MMIO at 0xF_______ */ \ - /* 256 regions of 1MiB */ \ - uint32_t hi = (addr >> 20) & MASK(8); \ - if (hi >= PRIV(rv)->vblk_mmio_base_hi && \ - hi <= PRIV(rv)->vblk_mmio_max_hi) { \ - PRIV(rv)->vblk_curr = \ - PRIV(rv)->vblk[hi - PRIV(rv)->vblk_mmio_base_hi]; \ - MMIO_OP(MMIO_VIRTIOBLK, MMIO_W); \ - } else { \ - switch (hi) { \ - case 0x0: \ - case 0x2: /* PLIC (0 - 0x3F) */ \ - MMIO_OP(MMIO_PLIC, MMIO_W); \ - break; \ - case 0x40: /* UART */ \ - MMIO_OP(MMIO_UART, MMIO_W); \ - break; \ - default: \ - __UNREACHABLE; \ - break; \ - } \ - } \ - } \ +#define MMIO_WRITE() \ + do { \ + if ((addr >> 28) == 0xF) { /* MMIO at 0xF_______ */ \ + /* 256 regions of 1MiB */ \ + uint32_t hi = (addr >> 20) & MASK(8); \ + if (PRIV(rv)->vblk_cnt && hi >= PRIV(rv)->vblk_mmio_base_hi && \ + hi <= PRIV(rv)->vblk_mmio_max_hi) { \ + PRIV(rv)->vblk_curr = \ + PRIV(rv)->vblk[hi - PRIV(rv)->vblk_mmio_base_hi]; \ + MMIO_OP(MMIO_VIRTIOBLK, MMIO_W); \ + } else { \ + switch (hi) { \ + case 0x0: \ + case 0x2: /* PLIC (0 - 0x3F) */ \ + MMIO_OP(MMIO_PLIC, MMIO_W); \ + break; \ + case 0x40: /* UART */ \ + MMIO_OP(MMIO_UART, MMIO_W); \ + break; \ + case 0x41: /* RTC */ \ + MMIO_OP(MMIO_RTC, MMIO_W); \ + break; \ + default: \ + __UNREACHABLE; \ + break; \ + } \ + } \ + } \ } while (0) void emu_update_uart_interrupts(riscv_t *rv); void emu_update_vblk_interrupts(riscv_t *rv); +void emu_update_rtc_interrupts(riscv_t *rv); /* * Linux kernel might create signal frame when returning from trap diff --git a/tests/system/br_pkgs/rtc/rtc_alarm.c b/tests/system/br_pkgs/rtc/rtc_alarm.c new file mode 100644 index 000000000..92ed02080 --- /dev/null +++ b/tests/system/br_pkgs/rtc/rtc_alarm.c @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define PROC_RTC_PATH "/proc/driver/rtc" + +/* Helper function to dump /proc/driver/rtc */ +void read_proc_rtc(const char *msg) +{ + FILE *fp = fopen(PROC_RTC_PATH, "r"); + if (!fp) { + perror("Failed to open /proc/driver/rtc"); + return; + } + + printf("\n=== %s ===\n", msg); + char line[256]; + while (fgets(line, sizeof(line), fp)) { + printf("%s", line); + } + printf("===========================\n\n"); + + fclose(fp); +} + +int main() +{ + int fd; + struct rtc_time rtc_tm; + unsigned long data; + + printf("Opening /dev/rtc0...\n"); + fd = open("/dev/rtc0", O_RDONLY); + if (fd == -1) { + perror("Failed to open /dev/rtc0"); + return 1; + } + + /* Step 1. Read /proc/driver/rtc before setting alarm */ + read_proc_rtc("Initial /proc/driver/rtc"); + + /* Step 2. Read current RTC time via ioctl */ + if (ioctl(fd, RTC_RD_TIME, &rtc_tm) == -1) { + perror("RTC_RD_TIME ioctl failed"); + close(fd); + return 1; + } + + printf("Current RTC time: %04d-%02d-%02d %02d:%02d:%02d (UTC)\n", + rtc_tm.tm_year + 1900, rtc_tm.tm_mon + 1, rtc_tm.tm_mday, + rtc_tm.tm_hour, rtc_tm.tm_min, rtc_tm.tm_sec); + + /* Step 3. Set alarm 5 seconds from now */ + int delay = 5; + rtc_tm.tm_sec += delay; + if (rtc_tm.tm_sec >= 60) { + rtc_tm.tm_sec -= 60; + rtc_tm.tm_min++; + if (rtc_tm.tm_min >= 60) { + rtc_tm.tm_min = 0; + rtc_tm.tm_hour = (rtc_tm.tm_hour + 1) % 24; + } + } + + printf("Setting alarm for %d seconds later...\n", delay); + if (ioctl(fd, RTC_ALM_SET, &rtc_tm) == -1) { + perror("RTC_ALM_SET ioctl failed"); + close(fd); + return 1; + } + + /* Step 4. Enable alarm interrupt */ + if (ioctl(fd, RTC_AIE_ON, 0) == -1) { + perror("RTC_AIE_ON ioctl failed"); + close(fd); + return 1; + } + + /* Step 5. Read /proc/driver/rtc right after enabling alarm */ + read_proc_rtc("After enabling alarm"); + + printf("Alarm enabled. Waiting for it to fire...\n"); + + /* Step 6. Block until the alarm interrupt occurs */ + if (read(fd, &data, sizeof(unsigned long)) == -1) { + perror("read() failed"); + close(fd); + return 1; + } + + printf(">>> Alarm Fired! <<<\n"); + + /* Step 7. Read /proc/driver/rtc after alarm fired */ + read_proc_rtc("After alarm fired"); + + /* Step 8. Disable the alarm interrupt */ + if (ioctl(fd, RTC_AIE_OFF, 0) == -1) { + perror("RTC_AIE_OFF ioctl failed"); + close(fd); + return 1; + } + + close(fd); + return 0; +} diff --git a/tests/system/br_pkgs/rtc/rtc_settime.c b/tests/system/br_pkgs/rtc/rtc_settime.c new file mode 100644 index 000000000..06ea4deda --- /dev/null +++ b/tests/system/br_pkgs/rtc/rtc_settime.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char *argv[]) +{ + int fd; + struct rtc_time new_time; + int year = 0; + + /* TODO: support month and date */ + /* Handle CLI argument */ + if (argc == 2) { + year = atoi(argv[1]); + } else if (argc > 2) { + fprintf(stderr, "Usage: %s [year]\n", argv[0]); + fprintf(stderr, "Example: %s 1972\n", argv[0]); + return 1; + } + + printf("Opening /dev/rtc0...\n"); + fd = open("/dev/rtc0", O_RDWR); + if (fd == -1) { + perror("Failed to open /dev/rtc0"); + return 1; + } + + if (year > 0) { + /* Manually set RTC to -01-01 00:00:00 */ + new_time.tm_sec = 0; + new_time.tm_min = 0; + new_time.tm_hour = 0; + new_time.tm_mday = 1; + new_time.tm_mon = 0; /* January = 0 */ + new_time.tm_year = year - 1900; /* tm_year is years since 1900 */ + new_time.tm_wday = 0; + new_time.tm_yday = 0; + new_time.tm_isdst = 0; + + printf("Setting RTC time to: %04d-01-01 00:00:00 (UTC)\n", year); + } else { + /* No year provided, set RTC to current UTC time */ + time_t now = time(NULL); + struct tm *utc_tm = gmtime(&now); + + new_time.tm_sec = utc_tm->tm_sec; + new_time.tm_min = utc_tm->tm_min; + new_time.tm_hour = utc_tm->tm_hour; + new_time.tm_mday = utc_tm->tm_mday; + new_time.tm_mon = utc_tm->tm_mon; /* 0-11 */ + new_time.tm_year = utc_tm->tm_year; /* years since 1900 */ + new_time.tm_wday = utc_tm->tm_wday; + new_time.tm_yday = utc_tm->tm_yday; + new_time.tm_isdst = 0; + + printf( + "Setting RTC time to current UTC: %04d-%02d-%02d %02d:%02d:%02d\n", + new_time.tm_year + 1900, new_time.tm_mon + 1, new_time.tm_mday, + new_time.tm_hour, new_time.tm_min, new_time.tm_sec); + } + + /* Trigger the goldfish_rtc_set_time kernel driver */ + if (ioctl(fd, RTC_SET_TIME, &new_time) == -1) { + perror("RTC_SET_TIME ioctl failed"); + close(fd); + return 1; + } + + printf("RTC time successfully updated!\n\n"); + close(fd); + + /* Immediately read and print /proc/driver/rtc */ + printf("Reading /proc/driver/rtc to verify...\n\n"); + FILE *proc_file = fopen("/proc/driver/rtc", "r"); + if (!proc_file) { + perror("Failed to open /proc/driver/rtc"); + return 1; + } + + char buffer[256]; + while (fgets(buffer, sizeof(buffer), proc_file)) { + printf("%s", buffer); + } + + fclose(proc_file); + return 0; +} diff --git a/tools/build-linux-image.sh b/tools/build-linux-image.sh index b6ee4e203..0ac6c340c 100755 --- a/tools/build-linux-image.sh +++ b/tools/build-linux-image.sh @@ -23,32 +23,127 @@ PARALLEL="-j$(nproc)" OUTPUT_DIR=./build/linux-image/ +BR_PKG_DIR=./tests/system/br_pkgs + +function create_br_pkg_config() +{ + local pkg_name=$1 + local output_path=$2 + + cat << EOF > "${output_path}" +config BR2_PACKAGE_${pkg_name^^} + bool "${pkg_name}" + help + ${pkg_name} package. +EOF +} + +function create_br_pkg_makefile() +{ + local pkg_name=$1 + local output_path=$2 + + cat << EOF > "${output_path}" +################################################################################ +# +# ${pkg_name} package +# +################################################################################ + +${pkg_name^^}_VERSION = 1.0 +${pkg_name^^}_SITE = package/${pkg_name}/src +${pkg_name^^}_SITE_METHOD = local + +define ${pkg_name^^}_BUILD_CMDS + \$(MAKE) CC="\$(TARGET_CC)" LD="\$(TARGET_LD)" -C \$(@D) +endef + +define ${pkg_name^^}_INSTALL_TARGET_CMDS + \$(INSTALL) -D -m 0755 \$(@D)/${pkg_name} \$(TARGET_DIR)/usr/bin +endef + +\$(eval \$(generic-package)) +EOF +} + +function create_br_pkg_src() +{ + local pkg_name=$1 + local src_c_file=$2 + local output_path=$3 + local mk_output_path=$3/Makefile + local src_output_path=$3/$pkg_name.c + + # Create src directory + mkdir -p ${output_path} + + # Create makefile + # the output binary is in lowercase + cat << EOF > "${mk_output_path}" +all: + \$(CC) ${pkg_name}.c -o ${pkg_name} +EOF + + # moving C source file + cp -f ${src_c_file} ${src_output_path} +} + +function update_br_pkg_config() +{ + local pkg_name=$1 + + cat << EOF >> "${SRC_DIR}/buildroot/package/Config.in" +menu "${pkg_name}" + source "package/${pkg_name}/Config.in" +endmenu + +EOF +} + +# This function patches the packages when building the rootfs.cpio from scratch +function do_patch_buildroot +{ + for c in $(find ${BR_PKG_DIR} -type f); do + local basename="$(basename ${c})" + local pkg_name="${basename%.*}" + + mkdir -p ${SRC_DIR}/buildroot/package/${pkg_name} + + create_br_pkg_config ${pkg_name} ${SRC_DIR}/buildroot/package/${pkg_name}/Config.in + create_br_pkg_makefile ${pkg_name} ${SRC_DIR}/buildroot/package/${pkg_name}/${pkg_name}.mk + create_br_pkg_src ${pkg_name} ${c} ${SRC_DIR}/buildroot/package/${pkg_name}/src + + update_br_pkg_config ${pkg_name} + done +} + function do_buildroot { - cp -f assets/system/configs/buildroot.config $SRC_DIR/buildroot/.config - cp -f assets/system/configs/busybox.config $SRC_DIR/buildroot/busybox.config + cp -f assets/system/configs/buildroot.config ${SRC_DIR}/buildroot/.config + cp -f assets/system/configs/busybox.config ${SRC_DIR}/buildroot/busybox.config # Otherwise, the error below raises: # You seem to have the current working directory in your # LD_LIBRARY_PATH environment variable. This doesn't work. unset LD_LIBRARY_PATH - pushd $SRC_DIR/buildroot + do_patch_buildroot + pushd ${SRC_DIR}/buildroot ASSERT make olddefconfig - ASSERT make $PARALLEL + ASSERT make ${PARALLEL} popd - cp -f $SRC_DIR/buildroot/output/images/rootfs.cpio $OUTPUT_DIR + cp -f ${SRC_DIR}/buildroot/output/images/rootfs.cpio ${OUTPUT_DIR} } function do_linux { - cp -f assets/system/configs/linux.config $SRC_DIR/linux/.config - export PATH="$SRC_DIR/buildroot/output/host/bin:$PATH" + cp -f assets/system/configs/linux.config ${SRC_DIR}/linux/.config + export PATH="${SRC_DIR}/buildroot/output/host/bin:${PATH}" export CROSS_COMPILE=riscv32-buildroot-linux-gnu- export ARCH=riscv - pushd $SRC_DIR/linux + pushd ${SRC_DIR}/linux ASSERT make olddefconfig - ASSERT make $PARALLEL + ASSERT make ${PARALLEL} popd - cp -f $SRC_DIR/linux/arch/riscv/boot/Image $OUTPUT_DIR + cp -f ${SRC_DIR}/linux/arch/riscv/boot/Image ${OUTPUT_DIR} } do_buildroot && OK