mirror of
https://github.com/freebsd/freebsd-src
synced 2024-10-15 12:54:27 +00:00
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:
parent
e3bd5730ca
commit
014d7082a2
|
@ -1,5 +1,6 @@
|
|||
SRCS+= \
|
||||
fdt.c \
|
||||
rtc_pl031.c \
|
||||
uart_pl011.c
|
||||
|
||||
.PATH: ${BHYVE_SYSDIR}/sys/arm64/vmm
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
279
usr.sbin/bhyve/rtc_pl031.c
Normal 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);
|
||||
}
|
40
usr.sbin/bhyve/rtc_pl031.h
Normal file
40
usr.sbin/bhyve/rtc_pl031.h
Normal 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_ */
|
Loading…
Reference in a new issue