mirror of
https://github.com/qemu/qemu.git
synced 2024-12-05 01:33:41 +08:00
9598c1bb39
The traditional ptimer behaviour includes a collection of weird edge case behaviours. In 2016 we improved the ptimer implementation to fix these and generally make the behaviour more flexible, with ptimers opting in to the new behaviour by passing an appropriate set of policy flags to ptimer_init(). For backwards-compatibility, we defined PTIMER_POLICY_DEFAULT (which sets no flags) to give the old weird behaviour. This turns out to be a poor choice of name, because people writing new devices which use ptimers are misled into thinking that the default is probably a sensible choice of flags, when in fact it is almost always not what you want. Rename PTIMER_POLICY_DEFAULT to PTIMER_POLICY_LEGACY and beef up the comment to more clearly say that new devices should not be using it. The code-change part of this commit was produced by sed -i -e 's/PTIMER_POLICY_DEFAULT/PTIMER_POLICY_LEGACY/g' $(git grep -l PTIMER_POLICY_DEFAULT) with the exception of a test name string change in tests/unit/ptimer-test.c which was added manually. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Francisco Iglesias <francisco.iglesias@amd.com> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Message-id: 20220516103058.162280-1-peter.maydell@linaro.org
433 lines
12 KiB
C
433 lines
12 KiB
C
/*
|
|
* QEMU GRLIB GPTimer Emulator
|
|
*
|
|
* Copyright (c) 2010-2019 AdaCore
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "hw/sparc/grlib.h"
|
|
#include "hw/sysbus.h"
|
|
#include "qemu/timer.h"
|
|
#include "hw/irq.h"
|
|
#include "hw/ptimer.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "qemu/module.h"
|
|
|
|
#include "trace.h"
|
|
#include "qom/object.h"
|
|
|
|
#define UNIT_REG_SIZE 16 /* Size of memory mapped regs for the unit */
|
|
#define GPTIMER_REG_SIZE 16 /* Size of memory mapped regs for a GPTimer */
|
|
|
|
#define GPTIMER_MAX_TIMERS 8
|
|
|
|
/* GPTimer Config register fields */
|
|
#define GPTIMER_ENABLE (1 << 0)
|
|
#define GPTIMER_RESTART (1 << 1)
|
|
#define GPTIMER_LOAD (1 << 2)
|
|
#define GPTIMER_INT_ENABLE (1 << 3)
|
|
#define GPTIMER_INT_PENDING (1 << 4)
|
|
#define GPTIMER_CHAIN (1 << 5) /* Not supported */
|
|
#define GPTIMER_DEBUG_HALT (1 << 6) /* Not supported */
|
|
|
|
/* Memory mapped register offsets */
|
|
#define SCALER_OFFSET 0x00
|
|
#define SCALER_RELOAD_OFFSET 0x04
|
|
#define CONFIG_OFFSET 0x08
|
|
#define COUNTER_OFFSET 0x00
|
|
#define COUNTER_RELOAD_OFFSET 0x04
|
|
#define TIMER_BASE 0x10
|
|
|
|
OBJECT_DECLARE_SIMPLE_TYPE(GPTimerUnit, GRLIB_GPTIMER)
|
|
|
|
typedef struct GPTimer GPTimer;
|
|
|
|
struct GPTimer {
|
|
struct ptimer_state *ptimer;
|
|
|
|
qemu_irq irq;
|
|
int id;
|
|
GPTimerUnit *unit;
|
|
|
|
/* registers */
|
|
uint32_t counter;
|
|
uint32_t reload;
|
|
uint32_t config;
|
|
};
|
|
|
|
struct GPTimerUnit {
|
|
SysBusDevice parent_obj;
|
|
|
|
MemoryRegion iomem;
|
|
|
|
uint32_t nr_timers; /* Number of timers available */
|
|
uint32_t freq_hz; /* System frequency */
|
|
uint32_t irq_line; /* Base irq line */
|
|
|
|
GPTimer *timers;
|
|
|
|
/* registers */
|
|
uint32_t scaler;
|
|
uint32_t reload;
|
|
uint32_t config;
|
|
};
|
|
|
|
static void grlib_gptimer_tx_begin(GPTimer *timer)
|
|
{
|
|
ptimer_transaction_begin(timer->ptimer);
|
|
}
|
|
|
|
static void grlib_gptimer_tx_commit(GPTimer *timer)
|
|
{
|
|
ptimer_transaction_commit(timer->ptimer);
|
|
}
|
|
|
|
/* Must be called within grlib_gptimer_tx_begin/commit block */
|
|
static void grlib_gptimer_enable(GPTimer *timer)
|
|
{
|
|
assert(timer != NULL);
|
|
|
|
|
|
ptimer_stop(timer->ptimer);
|
|
|
|
if (!(timer->config & GPTIMER_ENABLE)) {
|
|
/* Timer disabled */
|
|
trace_grlib_gptimer_disabled(timer->id, timer->config);
|
|
return;
|
|
}
|
|
|
|
/* ptimer is triggered when the counter reach 0 but GPTimer is triggered at
|
|
underflow. Set count + 1 to simulate the GPTimer behavior. */
|
|
|
|
trace_grlib_gptimer_enable(timer->id, timer->counter);
|
|
|
|
ptimer_set_count(timer->ptimer, (uint64_t)timer->counter + 1);
|
|
ptimer_run(timer->ptimer, 1);
|
|
}
|
|
|
|
/* Must be called within grlib_gptimer_tx_begin/commit block */
|
|
static void grlib_gptimer_restart(GPTimer *timer)
|
|
{
|
|
assert(timer != NULL);
|
|
|
|
trace_grlib_gptimer_restart(timer->id, timer->reload);
|
|
|
|
timer->counter = timer->reload;
|
|
grlib_gptimer_enable(timer);
|
|
}
|
|
|
|
static void grlib_gptimer_set_scaler(GPTimerUnit *unit, uint32_t scaler)
|
|
{
|
|
int i = 0;
|
|
uint32_t value = 0;
|
|
|
|
assert(unit != NULL);
|
|
|
|
if (scaler > 0) {
|
|
value = unit->freq_hz / (scaler + 1);
|
|
} else {
|
|
value = unit->freq_hz;
|
|
}
|
|
|
|
trace_grlib_gptimer_set_scaler(scaler, value);
|
|
|
|
for (i = 0; i < unit->nr_timers; i++) {
|
|
ptimer_transaction_begin(unit->timers[i].ptimer);
|
|
ptimer_set_freq(unit->timers[i].ptimer, value);
|
|
ptimer_transaction_commit(unit->timers[i].ptimer);
|
|
}
|
|
}
|
|
|
|
static void grlib_gptimer_hit(void *opaque)
|
|
{
|
|
GPTimer *timer = opaque;
|
|
assert(timer != NULL);
|
|
|
|
trace_grlib_gptimer_hit(timer->id);
|
|
|
|
/* Timer expired */
|
|
|
|
if (timer->config & GPTIMER_INT_ENABLE) {
|
|
/* Set the pending bit (only unset by write in the config register) */
|
|
timer->config |= GPTIMER_INT_PENDING;
|
|
qemu_irq_pulse(timer->irq);
|
|
}
|
|
|
|
if (timer->config & GPTIMER_RESTART) {
|
|
grlib_gptimer_restart(timer);
|
|
}
|
|
}
|
|
|
|
static uint64_t grlib_gptimer_read(void *opaque, hwaddr addr,
|
|
unsigned size)
|
|
{
|
|
GPTimerUnit *unit = opaque;
|
|
hwaddr timer_addr;
|
|
int id;
|
|
uint32_t value = 0;
|
|
|
|
addr &= 0xff;
|
|
|
|
/* Unit registers */
|
|
switch (addr) {
|
|
case SCALER_OFFSET:
|
|
trace_grlib_gptimer_readl(-1, addr, unit->scaler);
|
|
return unit->scaler;
|
|
|
|
case SCALER_RELOAD_OFFSET:
|
|
trace_grlib_gptimer_readl(-1, addr, unit->reload);
|
|
return unit->reload;
|
|
|
|
case CONFIG_OFFSET:
|
|
trace_grlib_gptimer_readl(-1, addr, unit->config);
|
|
return unit->config;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
timer_addr = (addr % TIMER_BASE);
|
|
id = (addr - TIMER_BASE) / TIMER_BASE;
|
|
|
|
if (id >= 0 && id < unit->nr_timers) {
|
|
|
|
/* GPTimer registers */
|
|
switch (timer_addr) {
|
|
case COUNTER_OFFSET:
|
|
value = ptimer_get_count(unit->timers[id].ptimer);
|
|
trace_grlib_gptimer_readl(id, addr, value);
|
|
return value;
|
|
|
|
case COUNTER_RELOAD_OFFSET:
|
|
value = unit->timers[id].reload;
|
|
trace_grlib_gptimer_readl(id, addr, value);
|
|
return value;
|
|
|
|
case CONFIG_OFFSET:
|
|
trace_grlib_gptimer_readl(id, addr, unit->timers[id].config);
|
|
return unit->timers[id].config;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
trace_grlib_gptimer_readl(-1, addr, 0);
|
|
return 0;
|
|
}
|
|
|
|
static void grlib_gptimer_write(void *opaque, hwaddr addr,
|
|
uint64_t value, unsigned size)
|
|
{
|
|
GPTimerUnit *unit = opaque;
|
|
hwaddr timer_addr;
|
|
int id;
|
|
|
|
addr &= 0xff;
|
|
|
|
/* Unit registers */
|
|
switch (addr) {
|
|
case SCALER_OFFSET:
|
|
value &= 0xFFFF; /* clean up the value */
|
|
unit->scaler = value;
|
|
trace_grlib_gptimer_writel(-1, addr, unit->scaler);
|
|
return;
|
|
|
|
case SCALER_RELOAD_OFFSET:
|
|
value &= 0xFFFF; /* clean up the value */
|
|
unit->reload = value;
|
|
trace_grlib_gptimer_writel(-1, addr, unit->reload);
|
|
grlib_gptimer_set_scaler(unit, value);
|
|
return;
|
|
|
|
case CONFIG_OFFSET:
|
|
/* Read Only (disable timer freeze not supported) */
|
|
trace_grlib_gptimer_writel(-1, addr, 0);
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
timer_addr = (addr % TIMER_BASE);
|
|
id = (addr - TIMER_BASE) / TIMER_BASE;
|
|
|
|
if (id >= 0 && id < unit->nr_timers) {
|
|
|
|
/* GPTimer registers */
|
|
switch (timer_addr) {
|
|
case COUNTER_OFFSET:
|
|
trace_grlib_gptimer_writel(id, addr, value);
|
|
grlib_gptimer_tx_begin(&unit->timers[id]);
|
|
unit->timers[id].counter = value;
|
|
grlib_gptimer_enable(&unit->timers[id]);
|
|
grlib_gptimer_tx_commit(&unit->timers[id]);
|
|
return;
|
|
|
|
case COUNTER_RELOAD_OFFSET:
|
|
trace_grlib_gptimer_writel(id, addr, value);
|
|
unit->timers[id].reload = value;
|
|
return;
|
|
|
|
case CONFIG_OFFSET:
|
|
trace_grlib_gptimer_writel(id, addr, value);
|
|
|
|
if (value & GPTIMER_INT_PENDING) {
|
|
/* clear pending bit */
|
|
value &= ~GPTIMER_INT_PENDING;
|
|
} else {
|
|
/* keep pending bit */
|
|
value |= unit->timers[id].config & GPTIMER_INT_PENDING;
|
|
}
|
|
|
|
unit->timers[id].config = value;
|
|
|
|
/* gptimer_restart calls gptimer_enable, so if "enable" and "load"
|
|
bits are present, we just have to call restart. */
|
|
|
|
grlib_gptimer_tx_begin(&unit->timers[id]);
|
|
if (value & GPTIMER_LOAD) {
|
|
grlib_gptimer_restart(&unit->timers[id]);
|
|
} else if (value & GPTIMER_ENABLE) {
|
|
grlib_gptimer_enable(&unit->timers[id]);
|
|
}
|
|
|
|
/* These fields must always be read as 0 */
|
|
value &= ~(GPTIMER_LOAD & GPTIMER_DEBUG_HALT);
|
|
|
|
unit->timers[id].config = value;
|
|
grlib_gptimer_tx_commit(&unit->timers[id]);
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
trace_grlib_gptimer_writel(-1, addr, value);
|
|
}
|
|
|
|
static const MemoryRegionOps grlib_gptimer_ops = {
|
|
.read = grlib_gptimer_read,
|
|
.write = grlib_gptimer_write,
|
|
.endianness = DEVICE_NATIVE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 4,
|
|
},
|
|
};
|
|
|
|
static void grlib_gptimer_reset(DeviceState *d)
|
|
{
|
|
GPTimerUnit *unit = GRLIB_GPTIMER(d);
|
|
int i = 0;
|
|
|
|
assert(unit != NULL);
|
|
|
|
unit->scaler = 0;
|
|
unit->reload = 0;
|
|
|
|
unit->config = unit->nr_timers;
|
|
unit->config |= unit->irq_line << 3;
|
|
unit->config |= 1 << 8; /* separate interrupt */
|
|
unit->config |= 1 << 9; /* Disable timer freeze */
|
|
|
|
|
|
for (i = 0; i < unit->nr_timers; i++) {
|
|
GPTimer *timer = &unit->timers[i];
|
|
|
|
timer->counter = 0;
|
|
timer->reload = 0;
|
|
timer->config = 0;
|
|
ptimer_transaction_begin(timer->ptimer);
|
|
ptimer_stop(timer->ptimer);
|
|
ptimer_set_count(timer->ptimer, 0);
|
|
ptimer_set_freq(timer->ptimer, unit->freq_hz);
|
|
ptimer_transaction_commit(timer->ptimer);
|
|
}
|
|
}
|
|
|
|
static void grlib_gptimer_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
GPTimerUnit *unit = GRLIB_GPTIMER(dev);
|
|
unsigned int i;
|
|
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
|
|
|
|
assert(unit->nr_timers > 0);
|
|
assert(unit->nr_timers <= GPTIMER_MAX_TIMERS);
|
|
|
|
unit->timers = g_malloc0(sizeof unit->timers[0] * unit->nr_timers);
|
|
|
|
for (i = 0; i < unit->nr_timers; i++) {
|
|
GPTimer *timer = &unit->timers[i];
|
|
|
|
timer->unit = unit;
|
|
timer->ptimer = ptimer_init(grlib_gptimer_hit, timer,
|
|
PTIMER_POLICY_LEGACY);
|
|
timer->id = i;
|
|
|
|
/* One IRQ line for each timer */
|
|
sysbus_init_irq(sbd, &timer->irq);
|
|
|
|
ptimer_transaction_begin(timer->ptimer);
|
|
ptimer_set_freq(timer->ptimer, unit->freq_hz);
|
|
ptimer_transaction_commit(timer->ptimer);
|
|
}
|
|
|
|
memory_region_init_io(&unit->iomem, OBJECT(unit), &grlib_gptimer_ops,
|
|
unit, "gptimer",
|
|
UNIT_REG_SIZE + GPTIMER_REG_SIZE * unit->nr_timers);
|
|
|
|
sysbus_init_mmio(sbd, &unit->iomem);
|
|
}
|
|
|
|
static Property grlib_gptimer_properties[] = {
|
|
DEFINE_PROP_UINT32("frequency", GPTimerUnit, freq_hz, 40000000),
|
|
DEFINE_PROP_UINT32("irq-line", GPTimerUnit, irq_line, 8),
|
|
DEFINE_PROP_UINT32("nr-timers", GPTimerUnit, nr_timers, 2),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static void grlib_gptimer_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->realize = grlib_gptimer_realize;
|
|
dc->reset = grlib_gptimer_reset;
|
|
device_class_set_props(dc, grlib_gptimer_properties);
|
|
}
|
|
|
|
static const TypeInfo grlib_gptimer_info = {
|
|
.name = TYPE_GRLIB_GPTIMER,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(GPTimerUnit),
|
|
.class_init = grlib_gptimer_class_init,
|
|
};
|
|
|
|
static void grlib_gptimer_register_types(void)
|
|
{
|
|
type_register_static(&grlib_gptimer_info);
|
|
}
|
|
|
|
type_init(grlib_gptimer_register_types)
|