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 "qemu/error-report.h"
24 #include "qemu/module.h"
25 #include "hw/sysbus.h"
26 #include "target/riscv/cpu.h"
27 #include "hw/riscv/sifive_clint.h"
28 #include "qemu/timer.h"
30 static uint64_t cpu_riscv_read_rtc(void)
32 return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL
),
33 SIFIVE_CLINT_TIMEBASE_FREQ
, NANOSECONDS_PER_SECOND
);
37 * Called when timecmp is written to update the QEMU timer or immediately
38 * trigger timer interrupt if mtimecmp <= current timer value.
40 static void sifive_clint_write_timecmp(RISCVCPU
*cpu
, uint64_t value
)
45 uint64_t rtc_r
= cpu_riscv_read_rtc();
47 cpu
->env
.timecmp
= value
;
48 if (cpu
->env
.timecmp
<= rtc_r
) {
49 /* if we're setting an MTIMECMP value in the "past",
50 immediately raise the timer interrupt */
51 riscv_cpu_update_mip(cpu
, MIP_MTIP
, BOOL_TO_MASK(1));
55 /* otherwise, set up the future timer interrupt */
56 riscv_cpu_update_mip(cpu
, MIP_MTIP
, BOOL_TO_MASK(0));
57 diff
= cpu
->env
.timecmp
- rtc_r
;
58 /* back to ns (note args switched in muldiv64) */
59 next
= qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL
) +
60 muldiv64(diff
, NANOSECONDS_PER_SECOND
, SIFIVE_CLINT_TIMEBASE_FREQ
);
61 timer_mod(cpu
->env
.timer
, next
);
65 * Callback used when the timer set using timer_mod expires.
66 * Should raise the timer interrupt line
68 static void sifive_clint_timer_cb(void *opaque
)
70 RISCVCPU
*cpu
= opaque
;
71 riscv_cpu_update_mip(cpu
, MIP_MTIP
, BOOL_TO_MASK(1));
74 /* CPU wants to read rtc or timecmp register */
75 static uint64_t sifive_clint_read(void *opaque
, hwaddr addr
, unsigned size
)
77 SiFiveCLINTState
*clint
= opaque
;
78 if (addr
>= clint
->sip_base
&&
79 addr
< clint
->sip_base
+ (clint
->num_harts
<< 2)) {
80 size_t hartid
= (addr
- clint
->sip_base
) >> 2;
81 CPUState
*cpu
= qemu_get_cpu(hartid
);
82 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
84 error_report("clint: invalid timecmp hartid: %zu", hartid
);
85 } else if ((addr
& 0x3) == 0) {
86 return (env
->mip
& MIP_MSIP
) > 0;
88 error_report("clint: invalid read: %08x", (uint32_t)addr
);
91 } else if (addr
>= clint
->timecmp_base
&&
92 addr
< clint
->timecmp_base
+ (clint
->num_harts
<< 3)) {
93 size_t hartid
= (addr
- clint
->timecmp_base
) >> 3;
94 CPUState
*cpu
= qemu_get_cpu(hartid
);
95 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
97 error_report("clint: invalid timecmp hartid: %zu", hartid
);
98 } else if ((addr
& 0x7) == 0) {
100 uint64_t timecmp
= env
->timecmp
;
101 return timecmp
& 0xFFFFFFFF;
102 } else if ((addr
& 0x7) == 4) {
104 uint64_t timecmp
= env
->timecmp
;
105 return (timecmp
>> 32) & 0xFFFFFFFF;
107 error_report("clint: invalid read: %08x", (uint32_t)addr
);
110 } else if (addr
== clint
->time_base
) {
112 return cpu_riscv_read_rtc() & 0xFFFFFFFF;
113 } else if (addr
== clint
->time_base
+ 4) {
115 return (cpu_riscv_read_rtc() >> 32) & 0xFFFFFFFF;
118 error_report("clint: invalid read: %08x", (uint32_t)addr
);
122 /* CPU wrote to rtc or timecmp register */
123 static void sifive_clint_write(void *opaque
, hwaddr addr
, uint64_t value
,
126 SiFiveCLINTState
*clint
= opaque
;
128 if (addr
>= clint
->sip_base
&&
129 addr
< clint
->sip_base
+ (clint
->num_harts
<< 2)) {
130 size_t hartid
= (addr
- clint
->sip_base
) >> 2;
131 CPUState
*cpu
= qemu_get_cpu(hartid
);
132 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
134 error_report("clint: invalid timecmp hartid: %zu", hartid
);
135 } else if ((addr
& 0x3) == 0) {
136 riscv_cpu_update_mip(RISCV_CPU(cpu
), MIP_MSIP
, BOOL_TO_MASK(value
));
138 error_report("clint: invalid sip write: %08x", (uint32_t)addr
);
141 } else if (addr
>= clint
->timecmp_base
&&
142 addr
< clint
->timecmp_base
+ (clint
->num_harts
<< 3)) {
143 size_t hartid
= (addr
- clint
->timecmp_base
) >> 3;
144 CPUState
*cpu
= qemu_get_cpu(hartid
);
145 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
147 error_report("clint: invalid timecmp hartid: %zu", hartid
);
148 } else if ((addr
& 0x7) == 0) {
150 uint64_t timecmp_hi
= env
->timecmp
>> 32;
151 sifive_clint_write_timecmp(RISCV_CPU(cpu
),
152 timecmp_hi
<< 32 | (value
& 0xFFFFFFFF));
154 } else if ((addr
& 0x7) == 4) {
156 uint64_t timecmp_lo
= env
->timecmp
;
157 sifive_clint_write_timecmp(RISCV_CPU(cpu
),
158 value
<< 32 | (timecmp_lo
& 0xFFFFFFFF));
160 error_report("clint: invalid timecmp write: %08x", (uint32_t)addr
);
163 } else if (addr
== clint
->time_base
) {
165 error_report("clint: time_lo write not implemented");
167 } else if (addr
== clint
->time_base
+ 4) {
169 error_report("clint: time_hi write not implemented");
173 error_report("clint: invalid write: %08x", (uint32_t)addr
);
176 static const MemoryRegionOps sifive_clint_ops
= {
177 .read
= sifive_clint_read
,
178 .write
= sifive_clint_write
,
179 .endianness
= DEVICE_LITTLE_ENDIAN
,
181 .min_access_size
= 4,
186 static Property sifive_clint_properties
[] = {
187 DEFINE_PROP_UINT32("num-harts", SiFiveCLINTState
, num_harts
, 0),
188 DEFINE_PROP_UINT32("sip-base", SiFiveCLINTState
, sip_base
, 0),
189 DEFINE_PROP_UINT32("timecmp-base", SiFiveCLINTState
, timecmp_base
, 0),
190 DEFINE_PROP_UINT32("time-base", SiFiveCLINTState
, time_base
, 0),
191 DEFINE_PROP_UINT32("aperture-size", SiFiveCLINTState
, aperture_size
, 0),
192 DEFINE_PROP_END_OF_LIST(),
195 static void sifive_clint_realize(DeviceState
*dev
, Error
**errp
)
197 SiFiveCLINTState
*s
= SIFIVE_CLINT(dev
);
198 memory_region_init_io(&s
->mmio
, OBJECT(dev
), &sifive_clint_ops
, s
,
199 TYPE_SIFIVE_CLINT
, s
->aperture_size
);
200 sysbus_init_mmio(SYS_BUS_DEVICE(dev
), &s
->mmio
);
203 static void sifive_clint_class_init(ObjectClass
*klass
, void *data
)
205 DeviceClass
*dc
= DEVICE_CLASS(klass
);
206 dc
->realize
= sifive_clint_realize
;
207 dc
->props
= sifive_clint_properties
;
210 static const TypeInfo sifive_clint_info
= {
211 .name
= TYPE_SIFIVE_CLINT
,
212 .parent
= TYPE_SYS_BUS_DEVICE
,
213 .instance_size
= sizeof(SiFiveCLINTState
),
214 .class_init
= sifive_clint_class_init
,
217 static void sifive_clint_register_types(void)
219 type_register_static(&sifive_clint_info
);
222 type_init(sifive_clint_register_types
)
226 * Create CLINT device.
228 DeviceState
*sifive_clint_create(hwaddr addr
, hwaddr size
, uint32_t num_harts
,
229 uint32_t sip_base
, uint32_t timecmp_base
, uint32_t time_base
)
232 for (i
= 0; i
< num_harts
; i
++) {
233 CPUState
*cpu
= qemu_get_cpu(i
);
234 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
238 env
->timer
= timer_new_ns(QEMU_CLOCK_VIRTUAL
,
239 &sifive_clint_timer_cb
, cpu
);
243 DeviceState
*dev
= qdev_create(NULL
, TYPE_SIFIVE_CLINT
);
244 qdev_prop_set_uint32(dev
, "num-harts", num_harts
);
245 qdev_prop_set_uint32(dev
, "sip-base", sip_base
);
246 qdev_prop_set_uint32(dev
, "timecmp-base", timecmp_base
);
247 qdev_prop_set_uint32(dev
, "time-base", time_base
);
248 qdev_prop_set_uint32(dev
, "aperture-size", size
);
249 qdev_init_nofail(dev
);
250 sysbus_mmio_map(SYS_BUS_DEVICE(dev
), 0, addr
);