diff --git a/usr.sbin/bhyve/aarch64/Makefile.inc b/usr.sbin/bhyve/aarch64/Makefile.inc index 2c7a3cac105e..e2ea4414ca19 100644 --- a/usr.sbin/bhyve/aarch64/Makefile.inc +++ b/usr.sbin/bhyve/aarch64/Makefile.inc @@ -1,5 +1,6 @@ SRCS+= \ fdt.c \ + rtc_pl031.c \ uart_pl011.c .PATH: ${BHYVE_SYSDIR}/sys/arm64/vmm diff --git a/usr.sbin/bhyve/aarch64/bhyverun_machdep.c b/usr.sbin/bhyve/aarch64/bhyverun_machdep.c index 2aa7d2d9b4fd..a5fd3f054706 100644 --- a/usr.sbin/bhyve/aarch64/bhyverun_machdep.c +++ b/usr.sbin/bhyve/aarch64/bhyverun_machdep.c @@ -48,6 +48,7 @@ #include "mem.h" #include "pci_emul.h" #include "pci_irq.h" +#include "rtc_pl031.h" #include "uart_emul.h" /* Start of mem + 1M */ @@ -58,6 +59,9 @@ #define UART_MMIO_BASE 0x10000 #define UART_MMIO_SIZE 0x1000 #define UART_INTR 32 +#define RTC_MMIO_BASE 0x11000 +#define RTC_MMIO_SIZE 0x1000 +#define RTC_INTR 33 #define GIC_DIST_BASE 0x2f000000 #define GIC_DIST_SIZE 0x10000 @@ -287,6 +291,60 @@ init_mmio_uart(struct vmctx *ctx) return (true); } +static void +mmio_rtc_intr_assert(void *arg) +{ + struct vmctx *ctx = arg; + + vm_assert_irq(ctx, RTC_INTR); +} + +static void +mmio_rtc_intr_deassert(void *arg) +{ + struct vmctx *ctx = arg; + + vm_deassert_irq(ctx, RTC_INTR); +} + +static int +mmio_rtc_mem_handler(struct vcpu *vcpu __unused, int dir, + uint64_t addr, int size __unused, uint64_t *val, void *arg1, long arg2) +{ + struct rtc_pl031_softc *sc = arg1; + long reg; + + reg = addr - arg2; + if (dir == MEM_F_WRITE) + rtc_pl031_write(sc, reg, *val); + else + *val = rtc_pl031_read(sc, reg); + + return (0); +} + +static void +init_mmio_rtc(struct vmctx *ctx) +{ + struct rtc_pl031_softc *sc; + struct mem_range mr; + int error; + + sc = rtc_pl031_init(mmio_rtc_intr_assert, mmio_rtc_intr_deassert, + ctx); + + bzero(&mr, sizeof(struct mem_range)); + mr.name = "rtc"; + mr.base = RTC_MMIO_BASE; + mr.size = RTC_MMIO_SIZE; + mr.flags = MEM_F_RW; + mr.handler = mmio_rtc_mem_handler; + mr.arg1 = sc; + mr.arg2 = mr.base; + error = register_mem(&mr); + assert(error == 0); +} + static vm_paddr_t fdt_gpa(struct vmctx *ctx) { @@ -328,6 +386,8 @@ bhyve_init_platform(struct vmctx *ctx, struct vcpu *bsp) if (init_mmio_uart(ctx)) fdt_add_uart(UART_MMIO_BASE, UART_MMIO_SIZE, UART_INTR); + init_mmio_rtc(ctx); + fdt_add_rtc(RTC_MMIO_BASE, RTC_MMIO_SIZE, RTC_INTR); fdt_add_timer(); pci_irq_init(pcie_intrs); fdt_add_pcie(pcie_intrs); diff --git a/usr.sbin/bhyve/aarch64/fdt.c b/usr.sbin/bhyve/aarch64/fdt.c index e8c959a65f3b..3fb97a40c241 100644 --- a/usr.sbin/bhyve/aarch64/fdt.c +++ b/usr.sbin/bhyve/aarch64/fdt.c @@ -248,6 +248,37 @@ fdt_add_uart(uint64_t uart_base, uint64_t uart_size, int intr) fdt_end_node(fdt); } +void +fdt_add_rtc(uint64_t rtc_base, uint64_t rtc_size, int intr) +{ + void *fdt, *interrupts, *prop; + char node_name[32]; + + assert(gic_phandle != 0); + assert(apb_pclk_phandle != 0); + assert(intr >= GIC_FIRST_SPI); + + fdt = fdtroot; + + snprintf(node_name, sizeof(node_name), "rtc@%lx", rtc_base); + fdt_begin_node(fdt, node_name); +#define RTC_COMPAT "arm,pl031\0arm,primecell" + fdt_property(fdt, "compatible", RTC_COMPAT, sizeof(RTC_COMPAT)); +#undef RTC_COMPAT + set_single_reg(fdt, rtc_base, rtc_size); + fdt_property_u32(fdt, "interrupt-parent", gic_phandle); + fdt_property_placeholder(fdt, "interrupts", 3 * sizeof(uint32_t), + &interrupts); + SET_PROP_U32(interrupts, 0, GIC_SPI); + SET_PROP_U32(interrupts, 1, intr - GIC_FIRST_SPI); + SET_PROP_U32(interrupts, 2, IRQ_TYPE_LEVEL_HIGH); + fdt_property_placeholder(fdt, "clocks", sizeof(uint32_t), &prop); + SET_PROP_U32(prop, 0, apb_pclk_phandle); + fdt_property_string(fdt, "clock-names", "apb_pclk"); + + fdt_end_node(fdt); +} + void fdt_add_timer(void) { diff --git a/usr.sbin/bhyve/aarch64/fdt.h b/usr.sbin/bhyve/aarch64/fdt.h index 6534266173d0..c19d19d34a46 100644 --- a/usr.sbin/bhyve/aarch64/fdt.h +++ b/usr.sbin/bhyve/aarch64/fdt.h @@ -42,6 +42,7 @@ void fdt_add_gic(uint64_t dist_base, uint64_t dist_size, void fdt_add_timer(void); void fdt_add_pcie(int intrs[static 4]); void fdt_add_uart(uint64_t uart_base, uint64_t uart_size, int intr); +void fdt_add_rtc(uint64_t rtc_base, uint64_t rtc_size, int intr); void fdt_finalize(void); #endif /* _FDT_H_ */ diff --git a/usr.sbin/bhyve/rtc_pl031.c b/usr.sbin/bhyve/rtc_pl031.c new file mode 100644 index 000000000000..e334de6f92bb --- /dev/null +++ b/usr.sbin/bhyve/rtc_pl031.c @@ -0,0 +1,279 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2024 Jessica Clarke + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include +#include +#include +#include +#include + +#include "config.h" +#include "mevent.h" +#include "rtc_pl031.h" + +#define RTCDR 0x000 +#define RTCMR 0x004 +#define RTCLR 0x008 +#define RTCCR 0x00C +#define RTCIMSC 0x010 +#define RTCRIS 0x014 +#define RTCMIS 0x018 +#define RTCICR 0x01C + +#define RTCPeriphID0 0xFE0 +#define RTCPeriphID1 0xFE4 +#define RTCPeriphID2 0xFE8 +#define RTCPeriphID3 0xFEC +#define _RTCPeriphID_VAL 0x00141031 +#define RTCPeriphID_VAL(_n) ((_RTCPeriphID_VAL >> (8 * (_n))) & 0xff) + +#define RTCCellID0 0xFF0 +#define RTCCellID1 0xFF4 +#define RTCCellID2 0xFF8 +#define RTCCellID3 0xFFC +#define _RTCCellID_VAL 0xb105f00d +#define RTCCellID_VAL(_n) ((_RTCCellID_VAL >> (8 * (_n))) & 0xff) + +struct rtc_pl031_softc { + pthread_mutex_t mtx; + + time_t last_tick; + uint32_t dr; + uint32_t mr; + uint32_t lr; + uint8_t imsc; + uint8_t ris; + uint8_t prev_mis; + + struct mevent *mevp; + + void *arg; + rtc_pl031_intr_func_t intr_assert; + rtc_pl031_intr_func_t intr_deassert; +}; + +static void rtc_pl031_callback(int fd, enum ev_type type, void *param); + +/* + * Returns the current RTC time as number of seconds since 00:00:00 Jan 1, 1970 + */ +static time_t +rtc_pl031_time(void) +{ + struct tm tm; + time_t t; + + time(&t); + if (get_config_bool_default("rtc.use_localtime", false)) { + localtime_r(&t, &tm); + t = timegm(&tm); + } + return (t); +} + +static void +rtc_pl031_update_mis(struct rtc_pl031_softc *sc) +{ + uint8_t mis; + + mis = sc->ris & sc->imsc; + if (mis == sc->prev_mis) + return; + + sc->prev_mis = mis; + if (mis) + (*sc->intr_assert)(sc->arg); + else + (*sc->intr_deassert)(sc->arg); +} + +static uint64_t +rtc_pl031_next_match_ticks(struct rtc_pl031_softc *sc) +{ + uint32_t ticks; + + ticks = sc->mr - sc->dr; + if (ticks == 0) + return ((uint64_t)1 << 32); + + return (ticks); +} + +static int +rtc_pl031_next_timer_msecs(struct rtc_pl031_softc *sc) +{ + uint64_t ticks; + + ticks = rtc_pl031_next_match_ticks(sc); + return (MIN(ticks * 1000, INT_MAX)); +} + +static void +rtc_pl031_update_timer(struct rtc_pl031_softc *sc) +{ + mevent_timer_update(sc->mevp, rtc_pl031_next_timer_msecs(sc)); +} + +static void +rtc_pl031_tick(struct rtc_pl031_softc *sc, bool from_timer) +{ + bool match; + time_t now, ticks; + + now = rtc_pl031_time(); + ticks = now - sc->last_tick; + match = ticks >= 0 && + (uint64_t)ticks >= rtc_pl031_next_match_ticks(sc); + sc->dr += ticks; + sc->last_tick = now; + + if (match) { + sc->ris = 1; + rtc_pl031_update_mis(sc); + } + + if (match || from_timer || ticks < 0) + rtc_pl031_update_timer(sc); +} + +static void +rtc_pl031_callback(int fd __unused, enum ev_type type __unused, void *param) +{ + struct rtc_pl031_softc *sc = param; + + pthread_mutex_lock(&sc->mtx); + rtc_pl031_tick(sc, true); + pthread_mutex_unlock(&sc->mtx); +} + +void +rtc_pl031_write(struct rtc_pl031_softc *sc, int offset, uint32_t value) +{ + pthread_mutex_lock(&sc->mtx); + rtc_pl031_tick(sc, false); + switch (offset) { + case RTCMR: + sc->mr = value; + rtc_pl031_update_timer(sc); + break; + case RTCLR: + sc->lr = value; + sc->dr = sc->lr; + rtc_pl031_update_timer(sc); + break; + case RTCIMSC: + sc->imsc = value & 1; + rtc_pl031_update_mis(sc); + break; + case RTCICR: + sc->ris &= ~value; + rtc_pl031_update_mis(sc); + break; + default: + /* Ignore writes to read-only/unassigned/ID registers */ + break; + } + pthread_mutex_unlock(&sc->mtx); +} + +uint32_t +rtc_pl031_read(struct rtc_pl031_softc *sc, int offset) +{ + uint32_t reg; + + pthread_mutex_lock(&sc->mtx); + rtc_pl031_tick(sc, false); + switch (offset) { + case RTCDR: + reg = sc->dr; + break; + case RTCMR: + reg = sc->mr; + break; + case RTCLR: + reg = sc->lr; + break; + case RTCCR: + /* RTC enabled from reset */ + reg = 1; + break; + case RTCIMSC: + reg = sc->imsc; + break; + case RTCRIS: + reg = sc->ris; + break; + case RTCMIS: + reg = sc->ris & sc->imsc; + break; + case RTCPeriphID0: + case RTCPeriphID1: + case RTCPeriphID2: + case RTCPeriphID3: + reg = RTCPeriphID_VAL(offset - RTCPeriphID0); + break; + case RTCCellID0: + case RTCCellID1: + case RTCCellID2: + case RTCCellID3: + reg = RTCCellID_VAL(offset - RTCCellID0); + break; + default: + /* Return 0 in reads from unasigned registers */ + reg = 0; + break; + } + pthread_mutex_unlock(&sc->mtx); + + return (reg); +} + +struct rtc_pl031_softc * +rtc_pl031_init(rtc_pl031_intr_func_t intr_assert, + rtc_pl031_intr_func_t intr_deassert, void *arg) +{ + struct rtc_pl031_softc *sc; + time_t now; + + sc = calloc(1, sizeof(struct rtc_pl031_softc)); + + pthread_mutex_init(&sc->mtx, NULL); + + now = rtc_pl031_time(); + sc->dr = now; + sc->last_tick = now; + sc->arg = arg; + sc->intr_assert = intr_assert; + sc->intr_deassert = intr_deassert; + + sc->mevp = mevent_add(rtc_pl031_next_timer_msecs(sc), EVF_TIMER, + rtc_pl031_callback, sc); + + return (sc); +} diff --git a/usr.sbin/bhyve/rtc_pl031.h b/usr.sbin/bhyve/rtc_pl031.h new file mode 100644 index 000000000000..8e4ef685908a --- /dev/null +++ b/usr.sbin/bhyve/rtc_pl031.h @@ -0,0 +1,40 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2024 Jessica Clarke + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _RTC_PL031_H_ +#define _RTC_PL031_H_ + +struct rtc_pl031_softc; +typedef void (*rtc_pl031_intr_func_t)(void *arg); + +struct rtc_pl031_softc *rtc_pl031_init(rtc_pl031_intr_func_t intr_assert, + rtc_pl031_intr_func_t intr_deassert, void *arg); +void rtc_pl031_write(struct rtc_pl031_softc *sc, int offset, + uint32_t value); +uint32_t rtc_pl031_read(struct rtc_pl031_softc *sc, int offset); + +#endif /* _RTC_PL031_H_ */