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
This commit is contained in:
Jessica Clarke 2024-02-21 22:57:04 +00:00 committed by Mark Johnston
parent e3bd5730ca
commit 014d7082a2
6 changed files with 412 additions and 0 deletions

View file

@ -1,5 +1,6 @@
SRCS+= \
fdt.c \
rtc_pl031.c \
uart_pl011.c
.PATH: ${BHYVE_SYSDIR}/sys/arm64/vmm

View file

@ -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);

View file

@ -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)
{

View file

@ -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_ */

279
usr.sbin/bhyve/rtc_pl031.c Normal file
View file

@ -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);
}

View file

@ -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_ */