2 * SiFive CLINT (Core Local Interruptor)
4 * Copyright (c) 2016-2017 Sagar Karandikar, sagark@eecs.berkeley.edu
5 * Copyright (c) 2017 SiFive, Inc.
7 * This provides real-time clock, timer and interprocessor interrupts.
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms and conditions of the GNU General Public License,
11 * version 2 or later, as published by the Free Software Foundation.
13 * This program is distributed in the hope it will be useful, but WITHOUT
14 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
18 * You should have received a copy of the GNU General Public License along with
19 * this program. If not, see <http://www.gnu.org/licenses/>.
22 #include "qemu/osdep.h"
23 #include "qapi/error.h"
24 #include "qemu/error-report.h"
25 #include "qemu/module.h"
26 #include "hw/sysbus.h"
27 #include "target/riscv/cpu.h"
28 #include "hw/qdev-properties.h"
29 #include "hw/riscv/sifive_clint.h"
30 #include "qemu/timer.h"
32 static uint64_t cpu_riscv_read_rtc(void)
34 return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL
),
35 SIFIVE_CLINT_TIMEBASE_FREQ
, NANOSECONDS_PER_SECOND
);
39 * Called when timecmp is written to update the QEMU timer or immediately
40 * trigger timer interrupt if mtimecmp <= current timer value.
42 static void sifive_clint_write_timecmp(RISCVCPU
*cpu
, uint64_t value
)
47 uint64_t rtc_r
= cpu_riscv_read_rtc();
49 cpu
->env
.timecmp
= value
;
50 if (cpu
->env
.timecmp
<= rtc_r
) {
51 /* if we're setting an MTIMECMP value in the "past",
52 immediately raise the timer interrupt */
53 riscv_cpu_update_mip(cpu
, MIP_MTIP
, BOOL_TO_MASK(1));
57 /* otherwise, set up the future timer interrupt */
58 riscv_cpu_update_mip(cpu
, MIP_MTIP
, BOOL_TO_MASK(0));
59 diff
= cpu
->env
.timecmp
- rtc_r
;
60 /* back to ns (note args switched in muldiv64) */
61 next
= qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL
) +
62 muldiv64(diff
, NANOSECONDS_PER_SECOND
, SIFIVE_CLINT_TIMEBASE_FREQ
);
63 timer_mod(cpu
->env
.timer
, next
);
67 * Callback used when the timer set using timer_mod expires.
68 * Should raise the timer interrupt line
70 static void sifive_clint_timer_cb(void *opaque
)
72 RISCVCPU
*cpu
= opaque
;
73 riscv_cpu_update_mip(cpu
, MIP_MTIP
, BOOL_TO_MASK(1));
76 /* CPU wants to read rtc or timecmp register */
77 static uint64_t sifive_clint_read(void *opaque
, hwaddr addr
, unsigned size
)
79 SiFiveCLINTState
*clint
= opaque
;
80 if (addr
>= clint
->sip_base
&&
81 addr
< clint
->sip_base
+ (clint
->num_harts
<< 2)) {
82 size_t hartid
= (addr
- clint
->sip_base
) >> 2;
83 CPUState
*cpu
= qemu_get_cpu(hartid
);
84 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
86 error_report("clint: invalid timecmp hartid: %zu", hartid
);
87 } else if ((addr
& 0x3) == 0) {
88 return (env
->mip
& MIP_MSIP
) > 0;
90 error_report("clint: invalid read: %08x", (uint32_t)addr
);
93 } else if (addr
>= clint
->timecmp_base
&&
94 addr
< clint
->timecmp_base
+ (clint
->num_harts
<< 3)) {
95 size_t hartid
= (addr
- clint
->timecmp_base
) >> 3;
96 CPUState
*cpu
= qemu_get_cpu(hartid
);
97 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
99 error_report("clint: invalid timecmp hartid: %zu", hartid
);
100 } else if ((addr
& 0x7) == 0) {
102 uint64_t timecmp
= env
->timecmp
;
103 return timecmp
& 0xFFFFFFFF;
104 } else if ((addr
& 0x7) == 4) {
106 uint64_t timecmp
= env
->timecmp
;
107 return (timecmp
>> 32) & 0xFFFFFFFF;
109 error_report("clint: invalid read: %08x", (uint32_t)addr
);
112 } else if (addr
== clint
->time_base
) {
114 return cpu_riscv_read_rtc() & 0xFFFFFFFF;
115 } else if (addr
== clint
->time_base
+ 4) {
117 return (cpu_riscv_read_rtc() >> 32) & 0xFFFFFFFF;
120 error_report("clint: invalid read: %08x", (uint32_t)addr
);
124 /* CPU wrote to rtc or timecmp register */
125 static void sifive_clint_write(void *opaque
, hwaddr addr
, uint64_t value
,
128 SiFiveCLINTState
*clint
= opaque
;
130 if (addr
>= clint
->sip_base
&&
131 addr
< clint
->sip_base
+ (clint
->num_harts
<< 2)) {
132 size_t hartid
= (addr
- clint
->sip_base
) >> 2;
133 CPUState
*cpu
= qemu_get_cpu(hartid
);
134 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
136 error_report("clint: invalid timecmp hartid: %zu", hartid
);
137 } else if ((addr
& 0x3) == 0) {
138 riscv_cpu_update_mip(RISCV_CPU(cpu
), MIP_MSIP
, BOOL_TO_MASK(value
));
140 error_report("clint: invalid sip write: %08x", (uint32_t)addr
);
143 } else if (addr
>= clint
->timecmp_base
&&
144 addr
< clint
->timecmp_base
+ (clint
->num_harts
<< 3)) {
145 size_t hartid
= (addr
- clint
->timecmp_base
) >> 3;
146 CPUState
*cpu
= qemu_get_cpu(hartid
);
147 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
149 error_report("clint: invalid timecmp hartid: %zu", hartid
);
150 } else if ((addr
& 0x7) == 0) {
152 uint64_t timecmp_hi
= env
->timecmp
>> 32;
153 sifive_clint_write_timecmp(RISCV_CPU(cpu
),
154 timecmp_hi
<< 32 | (value
& 0xFFFFFFFF));
156 } else if ((addr
& 0x7) == 4) {
158 uint64_t timecmp_lo
= env
->timecmp
;
159 sifive_clint_write_timecmp(RISCV_CPU(cpu
),
160 value
<< 32 | (timecmp_lo
& 0xFFFFFFFF));
162 error_report("clint: invalid timecmp write: %08x", (uint32_t)addr
);
165 } else if (addr
== clint
->time_base
) {
167 error_report("clint: time_lo write not implemented");
169 } else if (addr
== clint
->time_base
+ 4) {
171 error_report("clint: time_hi write not implemented");
175 error_report("clint: invalid write: %08x", (uint32_t)addr
);
178 static const MemoryRegionOps sifive_clint_ops
= {
179 .read
= sifive_clint_read
,
180 .write
= sifive_clint_write
,
181 .endianness
= DEVICE_LITTLE_ENDIAN
,
183 .min_access_size
= 4,
188 static Property sifive_clint_properties
[] = {
189 DEFINE_PROP_UINT32("num-harts", SiFiveCLINTState
, num_harts
, 0),
190 DEFINE_PROP_UINT32("sip-base", SiFiveCLINTState
, sip_base
, 0),
191 DEFINE_PROP_UINT32("timecmp-base", SiFiveCLINTState
, timecmp_base
, 0),
192 DEFINE_PROP_UINT32("time-base", SiFiveCLINTState
, time_base
, 0),
193 DEFINE_PROP_UINT32("aperture-size", SiFiveCLINTState
, aperture_size
, 0),
194 DEFINE_PROP_END_OF_LIST(),
197 static void sifive_clint_realize(DeviceState
*dev
, Error
**errp
)
199 SiFiveCLINTState
*s
= SIFIVE_CLINT(dev
);
200 memory_region_init_io(&s
->mmio
, OBJECT(dev
), &sifive_clint_ops
, s
,
201 TYPE_SIFIVE_CLINT
, s
->aperture_size
);
202 sysbus_init_mmio(SYS_BUS_DEVICE(dev
), &s
->mmio
);
205 static void sifive_clint_class_init(ObjectClass
*klass
, void *data
)
207 DeviceClass
*dc
= DEVICE_CLASS(klass
);
208 dc
->realize
= sifive_clint_realize
;
209 device_class_set_props(dc
, sifive_clint_properties
);
212 static const TypeInfo sifive_clint_info
= {
213 .name
= TYPE_SIFIVE_CLINT
,
214 .parent
= TYPE_SYS_BUS_DEVICE
,
215 .instance_size
= sizeof(SiFiveCLINTState
),
216 .class_init
= sifive_clint_class_init
,
219 static void sifive_clint_register_types(void)
221 type_register_static(&sifive_clint_info
);
224 type_init(sifive_clint_register_types
)
228 * Create CLINT device.
230 DeviceState
*sifive_clint_create(hwaddr addr
, hwaddr size
, uint32_t num_harts
,
231 uint32_t sip_base
, uint32_t timecmp_base
, uint32_t time_base
,
235 for (i
= 0; i
< num_harts
; i
++) {
236 CPUState
*cpu
= qemu_get_cpu(i
);
237 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
241 if (provide_rdtime
) {
242 riscv_cpu_set_rdtime_fn(env
, cpu_riscv_read_rtc
);
244 env
->timer
= timer_new_ns(QEMU_CLOCK_VIRTUAL
,
245 &sifive_clint_timer_cb
, cpu
);
249 DeviceState
*dev
= qdev_new(TYPE_SIFIVE_CLINT
);
250 qdev_prop_set_uint32(dev
, "num-harts", num_harts
);
251 qdev_prop_set_uint32(dev
, "sip-base", sip_base
);
252 qdev_prop_set_uint32(dev
, "timecmp-base", timecmp_base
);
253 qdev_prop_set_uint32(dev
, "time-base", time_base
);
254 qdev_prop_set_uint32(dev
, "aperture-size", size
);
255 sysbus_realize_and_unref(SYS_BUS_DEVICE(dev
), &error_fatal
);
256 sysbus_mmio_map(SYS_BUS_DEVICE(dev
), 0, addr
);