81b3ddaf87
The read_tcnt() function calculates the TCNT register values for the two channels of the timer module; it sets these up in the local tcnt[] array, and eventually returns either one or both of them, depending on whether the access is 8 or 16 bits. However, not all of the code paths through this function set both elements of this array: if the guest has programmed the TCCR.CSS register fields to values which are either documented as not to be used or which QEMU does not implement, then the function will return uninitialized data. (This was spotted by Coverity.) Add the missing CSS cases to this code, so that we return a consistent value instead of uninitialized data, and so the code structure indicates what's happening. Fixes: CID 1429976 Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org> Message-id: 20210219223241.16344-3-peter.maydell@linaro.org
490 lines
14 KiB
C
490 lines
14 KiB
C
/*
|
|
* Renesas 8bit timer
|
|
*
|
|
* Datasheet: RX62N Group, RX621 Group User's Manual: Hardware
|
|
* (Rev.1.40 R01UH0033EJ0140)
|
|
*
|
|
* Copyright (c) 2019 Yoshinori Sato
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2 or later, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/log.h"
|
|
#include "hw/irq.h"
|
|
#include "hw/registerfields.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "hw/timer/renesas_tmr.h"
|
|
#include "migration/vmstate.h"
|
|
|
|
REG8(TCR, 0)
|
|
FIELD(TCR, CCLR, 3, 2)
|
|
FIELD(TCR, OVIE, 5, 1)
|
|
FIELD(TCR, CMIEA, 6, 1)
|
|
FIELD(TCR, CMIEB, 7, 1)
|
|
REG8(TCSR, 2)
|
|
FIELD(TCSR, OSA, 0, 2)
|
|
FIELD(TCSR, OSB, 2, 2)
|
|
FIELD(TCSR, ADTE, 4, 2)
|
|
REG8(TCORA, 4)
|
|
REG8(TCORB, 6)
|
|
REG8(TCNT, 8)
|
|
REG8(TCCR, 10)
|
|
FIELD(TCCR, CKS, 0, 3)
|
|
FIELD(TCCR, CSS, 3, 2)
|
|
FIELD(TCCR, TMRIS, 7, 1)
|
|
|
|
#define CSS_EXTERNAL 0x00
|
|
#define CSS_INTERNAL 0x01
|
|
#define CSS_INVALID 0x02
|
|
#define CSS_CASCADING 0x03
|
|
#define CCLR_A 0x01
|
|
#define CCLR_B 0x02
|
|
|
|
static const int clkdiv[] = {0, 1, 2, 8, 32, 64, 1024, 8192};
|
|
|
|
static uint8_t concat_reg(uint8_t *reg)
|
|
{
|
|
return (reg[0] << 8) | reg[1];
|
|
}
|
|
|
|
static void update_events(RTMRState *tmr, int ch)
|
|
{
|
|
uint16_t diff[TMR_NR_EVENTS], min;
|
|
int64_t next_time;
|
|
int i, event;
|
|
|
|
if (tmr->tccr[ch] == 0) {
|
|
return ;
|
|
}
|
|
if (FIELD_EX8(tmr->tccr[ch], TCCR, CSS) == 0) {
|
|
/* external clock mode */
|
|
/* event not happened */
|
|
return ;
|
|
}
|
|
if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) == CSS_CASCADING) {
|
|
/* cascading mode */
|
|
if (ch == 1) {
|
|
tmr->next[ch] = none;
|
|
return ;
|
|
}
|
|
diff[cmia] = concat_reg(tmr->tcora) - concat_reg(tmr->tcnt);
|
|
diff[cmib] = concat_reg(tmr->tcorb) - concat_reg(tmr->tcnt);
|
|
diff[ovi] = 0x10000 - concat_reg(tmr->tcnt);
|
|
} else {
|
|
/* separate mode */
|
|
diff[cmia] = tmr->tcora[ch] - tmr->tcnt[ch];
|
|
diff[cmib] = tmr->tcorb[ch] - tmr->tcnt[ch];
|
|
diff[ovi] = 0x100 - tmr->tcnt[ch];
|
|
}
|
|
/* Search for the most recently occurring event. */
|
|
for (event = 0, min = diff[0], i = 1; i < none; i++) {
|
|
if (min > diff[i]) {
|
|
event = i;
|
|
min = diff[i];
|
|
}
|
|
}
|
|
tmr->next[ch] = event;
|
|
next_time = diff[event];
|
|
next_time *= clkdiv[FIELD_EX8(tmr->tccr[ch], TCCR, CKS)];
|
|
next_time *= NANOSECONDS_PER_SECOND;
|
|
next_time /= tmr->input_freq;
|
|
next_time += qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
|
timer_mod(&tmr->timer[ch], next_time);
|
|
}
|
|
|
|
static int elapsed_time(RTMRState *tmr, int ch, int64_t delta)
|
|
{
|
|
int divrate = clkdiv[FIELD_EX8(tmr->tccr[ch], TCCR, CKS)];
|
|
int et;
|
|
|
|
tmr->div_round[ch] += delta;
|
|
if (divrate > 0) {
|
|
et = tmr->div_round[ch] / divrate;
|
|
tmr->div_round[ch] %= divrate;
|
|
} else {
|
|
/* disble clock. so no update */
|
|
et = 0;
|
|
}
|
|
return et;
|
|
}
|
|
|
|
static uint16_t read_tcnt(RTMRState *tmr, unsigned size, int ch)
|
|
{
|
|
int64_t delta, now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
|
int elapsed, ovf = 0;
|
|
uint16_t tcnt[2];
|
|
uint32_t ret;
|
|
|
|
delta = (now - tmr->tick) * NANOSECONDS_PER_SECOND / tmr->input_freq;
|
|
if (delta > 0) {
|
|
tmr->tick = now;
|
|
|
|
switch (FIELD_EX8(tmr->tccr[1], TCCR, CSS)) {
|
|
case CSS_INTERNAL:
|
|
/* timer1 count update */
|
|
elapsed = elapsed_time(tmr, 1, delta);
|
|
if (elapsed >= 0x100) {
|
|
ovf = elapsed >> 8;
|
|
}
|
|
tcnt[1] = tmr->tcnt[1] + (elapsed & 0xff);
|
|
break;
|
|
case CSS_INVALID: /* guest error to have set this */
|
|
case CSS_EXTERNAL: /* QEMU doesn't implement these */
|
|
case CSS_CASCADING:
|
|
tcnt[1] = tmr->tcnt[1];
|
|
break;
|
|
}
|
|
switch (FIELD_EX8(tmr->tccr[0], TCCR, CSS)) {
|
|
case CSS_INTERNAL:
|
|
elapsed = elapsed_time(tmr, 0, delta);
|
|
tcnt[0] = tmr->tcnt[0] + elapsed;
|
|
break;
|
|
case CSS_CASCADING:
|
|
tcnt[0] = tmr->tcnt[0] + ovf;
|
|
break;
|
|
case CSS_INVALID: /* guest error to have set this */
|
|
case CSS_EXTERNAL: /* QEMU doesn't implement this */
|
|
tcnt[0] = tmr->tcnt[0];
|
|
break;
|
|
}
|
|
} else {
|
|
tcnt[0] = tmr->tcnt[0];
|
|
tcnt[1] = tmr->tcnt[1];
|
|
}
|
|
if (size == 1) {
|
|
return tcnt[ch];
|
|
} else {
|
|
ret = 0;
|
|
ret = deposit32(ret, 0, 8, tcnt[1]);
|
|
ret = deposit32(ret, 8, 8, tcnt[0]);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
static uint8_t read_tccr(uint8_t r)
|
|
{
|
|
uint8_t tccr = 0;
|
|
tccr = FIELD_DP8(tccr, TCCR, TMRIS,
|
|
FIELD_EX8(r, TCCR, TMRIS));
|
|
tccr = FIELD_DP8(tccr, TCCR, CSS,
|
|
FIELD_EX8(r, TCCR, CSS));
|
|
tccr = FIELD_DP8(tccr, TCCR, CKS,
|
|
FIELD_EX8(r, TCCR, CKS));
|
|
return tccr;
|
|
}
|
|
|
|
static uint64_t tmr_read(void *opaque, hwaddr addr, unsigned size)
|
|
{
|
|
RTMRState *tmr = opaque;
|
|
int ch = addr & 1;
|
|
uint64_t ret;
|
|
|
|
if (size == 2 && (ch != 0 || addr == A_TCR || addr == A_TCSR)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR, "renesas_tmr: Invalid read size 0x%"
|
|
HWADDR_PRIX "\n",
|
|
addr);
|
|
return UINT64_MAX;
|
|
}
|
|
switch (addr & 0x0e) {
|
|
case A_TCR:
|
|
ret = 0;
|
|
ret = FIELD_DP8(ret, TCR, CCLR,
|
|
FIELD_EX8(tmr->tcr[ch], TCR, CCLR));
|
|
ret = FIELD_DP8(ret, TCR, OVIE,
|
|
FIELD_EX8(tmr->tcr[ch], TCR, OVIE));
|
|
ret = FIELD_DP8(ret, TCR, CMIEA,
|
|
FIELD_EX8(tmr->tcr[ch], TCR, CMIEA));
|
|
ret = FIELD_DP8(ret, TCR, CMIEB,
|
|
FIELD_EX8(tmr->tcr[ch], TCR, CMIEB));
|
|
return ret;
|
|
case A_TCSR:
|
|
ret = 0;
|
|
ret = FIELD_DP8(ret, TCSR, OSA,
|
|
FIELD_EX8(tmr->tcsr[ch], TCSR, OSA));
|
|
ret = FIELD_DP8(ret, TCSR, OSB,
|
|
FIELD_EX8(tmr->tcsr[ch], TCSR, OSB));
|
|
switch (ch) {
|
|
case 0:
|
|
ret = FIELD_DP8(ret, TCSR, ADTE,
|
|
FIELD_EX8(tmr->tcsr[ch], TCSR, ADTE));
|
|
break;
|
|
case 1: /* CH1 ADTE unimplement always 1 */
|
|
ret = FIELD_DP8(ret, TCSR, ADTE, 1);
|
|
break;
|
|
}
|
|
return ret;
|
|
case A_TCORA:
|
|
if (size == 1) {
|
|
return tmr->tcora[ch];
|
|
} else if (ch == 0) {
|
|
return concat_reg(tmr->tcora);
|
|
}
|
|
/* fall through */
|
|
case A_TCORB:
|
|
if (size == 1) {
|
|
return tmr->tcorb[ch];
|
|
} else {
|
|
return concat_reg(tmr->tcorb);
|
|
}
|
|
case A_TCNT:
|
|
return read_tcnt(tmr, size, ch);
|
|
case A_TCCR:
|
|
if (size == 1) {
|
|
return read_tccr(tmr->tccr[ch]);
|
|
} else {
|
|
return read_tccr(tmr->tccr[0]) << 8 | read_tccr(tmr->tccr[1]);
|
|
}
|
|
default:
|
|
qemu_log_mask(LOG_UNIMP, "renesas_tmr: Register 0x%" HWADDR_PRIX
|
|
" not implemented\n",
|
|
addr);
|
|
break;
|
|
}
|
|
return UINT64_MAX;
|
|
}
|
|
|
|
static void tmr_write_count(RTMRState *tmr, int ch, unsigned size,
|
|
uint8_t *reg, uint64_t val)
|
|
{
|
|
if (size == 1) {
|
|
reg[ch] = val;
|
|
update_events(tmr, ch);
|
|
} else {
|
|
reg[0] = extract32(val, 8, 8);
|
|
reg[1] = extract32(val, 0, 8);
|
|
update_events(tmr, 0);
|
|
update_events(tmr, 1);
|
|
}
|
|
}
|
|
|
|
static void tmr_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
|
|
{
|
|
RTMRState *tmr = opaque;
|
|
int ch = addr & 1;
|
|
|
|
if (size == 2 && (ch != 0 || addr == A_TCR || addr == A_TCSR)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"renesas_tmr: Invalid write size 0x%" HWADDR_PRIX "\n",
|
|
addr);
|
|
return;
|
|
}
|
|
switch (addr & 0x0e) {
|
|
case A_TCR:
|
|
tmr->tcr[ch] = val;
|
|
break;
|
|
case A_TCSR:
|
|
tmr->tcsr[ch] = val;
|
|
break;
|
|
case A_TCORA:
|
|
tmr_write_count(tmr, ch, size, tmr->tcora, val);
|
|
break;
|
|
case A_TCORB:
|
|
tmr_write_count(tmr, ch, size, tmr->tcorb, val);
|
|
break;
|
|
case A_TCNT:
|
|
tmr_write_count(tmr, ch, size, tmr->tcnt, val);
|
|
break;
|
|
case A_TCCR:
|
|
tmr_write_count(tmr, ch, size, tmr->tccr, val);
|
|
break;
|
|
default:
|
|
qemu_log_mask(LOG_UNIMP, "renesas_tmr: Register 0x%" HWADDR_PRIX
|
|
" not implemented\n",
|
|
addr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static const MemoryRegionOps tmr_ops = {
|
|
.write = tmr_write,
|
|
.read = tmr_read,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.impl = {
|
|
.min_access_size = 1,
|
|
.max_access_size = 2,
|
|
},
|
|
.valid = {
|
|
.min_access_size = 1,
|
|
.max_access_size = 2,
|
|
},
|
|
};
|
|
|
|
static void timer_events(RTMRState *tmr, int ch);
|
|
|
|
static uint16_t issue_event(RTMRState *tmr, int ch, int sz,
|
|
uint16_t tcnt, uint16_t tcora, uint16_t tcorb)
|
|
{
|
|
uint16_t ret = tcnt;
|
|
|
|
switch (tmr->next[ch]) {
|
|
case none:
|
|
break;
|
|
case cmia:
|
|
if (tcnt >= tcora) {
|
|
if (FIELD_EX8(tmr->tcr[ch], TCR, CCLR) == CCLR_A) {
|
|
ret = tcnt - tcora;
|
|
}
|
|
if (FIELD_EX8(tmr->tcr[ch], TCR, CMIEA)) {
|
|
qemu_irq_pulse(tmr->cmia[ch]);
|
|
}
|
|
if (sz == 8 && ch == 0 &&
|
|
FIELD_EX8(tmr->tccr[1], TCCR, CSS) == CSS_CASCADING) {
|
|
tmr->tcnt[1]++;
|
|
timer_events(tmr, 1);
|
|
}
|
|
}
|
|
break;
|
|
case cmib:
|
|
if (tcnt >= tcorb) {
|
|
if (FIELD_EX8(tmr->tcr[ch], TCR, CCLR) == CCLR_B) {
|
|
ret = tcnt - tcorb;
|
|
}
|
|
if (FIELD_EX8(tmr->tcr[ch], TCR, CMIEB)) {
|
|
qemu_irq_pulse(tmr->cmib[ch]);
|
|
}
|
|
}
|
|
break;
|
|
case ovi:
|
|
if ((tcnt >= (1 << sz)) && FIELD_EX8(tmr->tcr[ch], TCR, OVIE)) {
|
|
qemu_irq_pulse(tmr->ovi[ch]);
|
|
}
|
|
break;
|
|
default:
|
|
g_assert_not_reached();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void timer_events(RTMRState *tmr, int ch)
|
|
{
|
|
uint16_t tcnt;
|
|
|
|
tmr->tcnt[ch] = read_tcnt(tmr, 1, ch);
|
|
if (FIELD_EX8(tmr->tccr[0], TCCR, CSS) != CSS_CASCADING) {
|
|
tmr->tcnt[ch] = issue_event(tmr, ch, 8,
|
|
tmr->tcnt[ch],
|
|
tmr->tcora[ch],
|
|
tmr->tcorb[ch]) & 0xff;
|
|
} else {
|
|
if (ch == 1) {
|
|
return ;
|
|
}
|
|
tcnt = issue_event(tmr, ch, 16,
|
|
concat_reg(tmr->tcnt),
|
|
concat_reg(tmr->tcora),
|
|
concat_reg(tmr->tcorb));
|
|
tmr->tcnt[0] = (tcnt >> 8) & 0xff;
|
|
tmr->tcnt[1] = tcnt & 0xff;
|
|
}
|
|
update_events(tmr, ch);
|
|
}
|
|
|
|
static void timer_event0(void *opaque)
|
|
{
|
|
RTMRState *tmr = opaque;
|
|
|
|
timer_events(tmr, 0);
|
|
}
|
|
|
|
static void timer_event1(void *opaque)
|
|
{
|
|
RTMRState *tmr = opaque;
|
|
|
|
timer_events(tmr, 1);
|
|
}
|
|
|
|
static void rtmr_reset(DeviceState *dev)
|
|
{
|
|
RTMRState *tmr = RTMR(dev);
|
|
tmr->tcr[0] = tmr->tcr[1] = 0x00;
|
|
tmr->tcsr[0] = 0x00;
|
|
tmr->tcsr[1] = 0x10;
|
|
tmr->tcnt[0] = tmr->tcnt[1] = 0x00;
|
|
tmr->tcora[0] = tmr->tcora[1] = 0xff;
|
|
tmr->tcorb[0] = tmr->tcorb[1] = 0xff;
|
|
tmr->tccr[0] = tmr->tccr[1] = 0x00;
|
|
tmr->next[0] = tmr->next[1] = none;
|
|
tmr->tick = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
|
}
|
|
|
|
static void rtmr_init(Object *obj)
|
|
{
|
|
SysBusDevice *d = SYS_BUS_DEVICE(obj);
|
|
RTMRState *tmr = RTMR(obj);
|
|
int i;
|
|
|
|
memory_region_init_io(&tmr->memory, OBJECT(tmr), &tmr_ops,
|
|
tmr, "renesas-tmr", 0x10);
|
|
sysbus_init_mmio(d, &tmr->memory);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(tmr->ovi); i++) {
|
|
sysbus_init_irq(d, &tmr->cmia[i]);
|
|
sysbus_init_irq(d, &tmr->cmib[i]);
|
|
sysbus_init_irq(d, &tmr->ovi[i]);
|
|
}
|
|
timer_init_ns(&tmr->timer[0], QEMU_CLOCK_VIRTUAL, timer_event0, tmr);
|
|
timer_init_ns(&tmr->timer[1], QEMU_CLOCK_VIRTUAL, timer_event1, tmr);
|
|
}
|
|
|
|
static const VMStateDescription vmstate_rtmr = {
|
|
.name = "rx-tmr",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_INT64(tick, RTMRState),
|
|
VMSTATE_UINT8_ARRAY(tcnt, RTMRState, TMR_CH),
|
|
VMSTATE_UINT8_ARRAY(tcora, RTMRState, TMR_CH),
|
|
VMSTATE_UINT8_ARRAY(tcorb, RTMRState, TMR_CH),
|
|
VMSTATE_UINT8_ARRAY(tcr, RTMRState, TMR_CH),
|
|
VMSTATE_UINT8_ARRAY(tccr, RTMRState, TMR_CH),
|
|
VMSTATE_UINT8_ARRAY(tcor, RTMRState, TMR_CH),
|
|
VMSTATE_UINT8_ARRAY(tcsr, RTMRState, TMR_CH),
|
|
VMSTATE_INT64_ARRAY(div_round, RTMRState, TMR_CH),
|
|
VMSTATE_UINT8_ARRAY(next, RTMRState, TMR_CH),
|
|
VMSTATE_TIMER_ARRAY(timer, RTMRState, TMR_CH),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static Property rtmr_properties[] = {
|
|
DEFINE_PROP_UINT64("input-freq", RTMRState, input_freq, 0),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void rtmr_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->vmsd = &vmstate_rtmr;
|
|
dc->reset = rtmr_reset;
|
|
device_class_set_props(dc, rtmr_properties);
|
|
}
|
|
|
|
static const TypeInfo rtmr_info = {
|
|
.name = TYPE_RENESAS_TMR,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(RTMRState),
|
|
.instance_init = rtmr_init,
|
|
.class_init = rtmr_class_init,
|
|
};
|
|
|
|
static void rtmr_register_types(void)
|
|
{
|
|
type_register_static(&rtmr_info);
|
|
}
|
|
|
|
type_init(rtmr_register_types)
|