mirror of
https://github.com/freebsd/freebsd-src
synced 2024-07-22 18:56:38 +00:00
014d7082a2
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
280 lines
6 KiB
C
280 lines
6 KiB
C
/*-
|
|
* 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);
|
|
}
|