git: 014d7082a239 - main - bhyve: Implement a PL031 RTC on arm64

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Wed, 10 Apr 2024 15:19:29 UTC
The branch main has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=014d7082a2398ec39e76b5f7b1f842fc9be6c51e

commit 014d7082a2398ec39e76b5f7b1f842fc9be6c51e
Author:     Jessica Clarke <jrtc27@jrtc27.com>
AuthorDate: 2024-02-21 22:57:04 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2024-04-10 15:17:56 +0000

    bhyve: Implement a PL031 RTC on arm64
    
    Unlike amd64's, this RTC is implemented entirely in userspace. This is
    the same RTC as is provided by QEMU's virt machine.
    
    Reviewed by:    jhb
    MFC after:      2 weeks
    Obtained from:  CheriBSD
---
 usr.sbin/bhyve/aarch64/Makefile.inc       |   1 +
 usr.sbin/bhyve/aarch64/bhyverun_machdep.c |  60 +++++++
 usr.sbin/bhyve/aarch64/fdt.c              |  31 ++++
 usr.sbin/bhyve/aarch64/fdt.h              |   1 +
 usr.sbin/bhyve/rtc_pl031.c                | 279 ++++++++++++++++++++++++++++++
 usr.sbin/bhyve/rtc_pl031.h                |  40 +++++
 6 files changed, 412 insertions(+)

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 <jrtc27@FreeBSD.org>
+ *
+ * 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 <sys/param.h>
+
+#include <assert.h>
+#include <limits.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <time.h>
+
+#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 <jrtc27@FreeBSD.org>
+ *
+ * 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_ */