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/qdev-properties.h"
28 #include "hw/riscv/sifive_clint.h"
29 #include "qemu/timer.h"
31 static uint64_t cpu_riscv_read_rtc(void)
33 return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL
),
34 SIFIVE_CLINT_TIMEBASE_FREQ
, NANOSECONDS_PER_SECOND
);
38 * Called when timecmp is written to update the QEMU timer or immediately
39 * trigger timer interrupt if mtimecmp <= current timer value.
41 static void sifive_clint_write_timecmp(RISCVCPU
*cpu
, uint64_t value
)
46 uint64_t rtc_r
= cpu_riscv_read_rtc();
48 cpu
->env
.timecmp
= value
;
49 if (cpu
->env
.timecmp
<= rtc_r
) {
50 /* if we're setting an MTIMECMP value in the "past",
51 immediately raise the timer interrupt */
52 riscv_cpu_update_mip(cpu
, MIP_MTIP
, BOOL_TO_MASK(1));
56 /* otherwise, set up the future timer interrupt */
57 riscv_cpu_update_mip(cpu
, MIP_MTIP
, BOOL_TO_MASK(0));
58 diff
= cpu
->env
.timecmp
- rtc_r
;
59 /* back to ns (note args switched in muldiv64) */
60 next
= qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL
) +
61 muldiv64(diff
, NANOSECONDS_PER_SECOND
, SIFIVE_CLINT_TIMEBASE_FREQ
);
62 timer_mod(cpu
->env
.timer
, next
);
66 * Callback used when the timer set using timer_mod expires.
67 * Should raise the timer interrupt line
69 static void sifive_clint_timer_cb(void *opaque
)
71 RISCVCPU
*cpu
= opaque
;
72 riscv_cpu_update_mip(cpu
, MIP_MTIP
, BOOL_TO_MASK(1));
75 /* CPU wants to read rtc or timecmp register */
76 static uint64_t sifive_clint_read(void *opaque
, hwaddr addr
, unsigned size
)
78 SiFiveCLINTState
*clint
= opaque
;
79 if (addr
>= clint
->sip_base
&&
80 addr
< clint
->sip_base
+ (clint
->num_harts
<< 2)) {
81 size_t hartid
= (addr
- clint
->sip_base
) >> 2;
82 CPUState
*cpu
= qemu_get_cpu(hartid
);
83 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
85 error_report("clint: invalid timecmp hartid: %zu", hartid
);
86 } else if ((addr
& 0x3) == 0) {
87 return (env
->mip
& MIP_MSIP
) > 0;
89 error_report("clint: invalid read: %08x", (uint32_t)addr
);
92 } else if (addr
>= clint
->timecmp_base
&&
93 addr
< clint
->timecmp_base
+ (clint
->num_harts
<< 3)) {
94 size_t hartid
= (addr
- clint
->timecmp_base
) >> 3;
95 CPUState
*cpu
= qemu_get_cpu(hartid
);
96 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
98 error_report("clint: invalid timecmp hartid: %zu", hartid
);
99 } else if ((addr
& 0x7) == 0) {
101 uint64_t timecmp
= env
->timecmp
;
102 return timecmp
& 0xFFFFFFFF;
103 } else if ((addr
& 0x7) == 4) {
105 uint64_t timecmp
= env
->timecmp
;
106 return (timecmp
>> 32) & 0xFFFFFFFF;
108 error_report("clint: invalid read: %08x", (uint32_t)addr
);
111 } else if (addr
== clint
->time_base
) {
113 return cpu_riscv_read_rtc() & 0xFFFFFFFF;
114 } else if (addr
== clint
->time_base
+ 4) {
116 return (cpu_riscv_read_rtc() >> 32) & 0xFFFFFFFF;
119 error_report("clint: invalid read: %08x", (uint32_t)addr
);
123 /* CPU wrote to rtc or timecmp register */
124 static void sifive_clint_write(void *opaque
, hwaddr addr
, uint64_t value
,
127 SiFiveCLINTState
*clint
= opaque
;
129 if (addr
>= clint
->sip_base
&&
130 addr
< clint
->sip_base
+ (clint
->num_harts
<< 2)) {
131 size_t hartid
= (addr
- clint
->sip_base
) >> 2;
132 CPUState
*cpu
= qemu_get_cpu(hartid
);
133 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
135 error_report("clint: invalid timecmp hartid: %zu", hartid
);
136 } else if ((addr
& 0x3) == 0) {
137 riscv_cpu_update_mip(RISCV_CPU(cpu
), MIP_MSIP
, BOOL_TO_MASK(value
));
139 error_report("clint: invalid sip write: %08x", (uint32_t)addr
);
142 } else if (addr
>= clint
->timecmp_base
&&
143 addr
< clint
->timecmp_base
+ (clint
->num_harts
<< 3)) {
144 size_t hartid
= (addr
- clint
->timecmp_base
) >> 3;
145 CPUState
*cpu
= qemu_get_cpu(hartid
);
146 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
148 error_report("clint: invalid timecmp hartid: %zu", hartid
);
149 } else if ((addr
& 0x7) == 0) {
151 uint64_t timecmp_hi
= env
->timecmp
>> 32;
152 sifive_clint_write_timecmp(RISCV_CPU(cpu
),
153 timecmp_hi
<< 32 | (value
& 0xFFFFFFFF));
155 } else if ((addr
& 0x7) == 4) {
157 uint64_t timecmp_lo
= env
->timecmp
;
158 sifive_clint_write_timecmp(RISCV_CPU(cpu
),
159 value
<< 32 | (timecmp_lo
& 0xFFFFFFFF));
161 error_report("clint: invalid timecmp write: %08x", (uint32_t)addr
);
164 } else if (addr
== clint
->time_base
) {
166 error_report("clint: time_lo write not implemented");
168 } else if (addr
== clint
->time_base
+ 4) {
170 error_report("clint: time_hi write not implemented");
174 error_report("clint: invalid write: %08x", (uint32_t)addr
);
177 static const MemoryRegionOps sifive_clint_ops
= {
178 .read
= sifive_clint_read
,
179 .write
= sifive_clint_write
,
180 .endianness
= DEVICE_LITTLE_ENDIAN
,
182 .min_access_size
= 4,
187 static Property sifive_clint_properties
[] = {
188 DEFINE_PROP_UINT32("num-harts", SiFiveCLINTState
, num_harts
, 0),
189 DEFINE_PROP_UINT32("sip-base", SiFiveCLINTState
, sip_base
, 0),
190 DEFINE_PROP_UINT32("timecmp-base", SiFiveCLINTState
, timecmp_base
, 0),
191 DEFINE_PROP_UINT32("time-base", SiFiveCLINTState
, time_base
, 0),
192 DEFINE_PROP_UINT32("aperture-size", SiFiveCLINTState
, aperture_size
, 0),
193 DEFINE_PROP_END_OF_LIST(),
196 static void sifive_clint_realize(DeviceState
*dev
, Error
**errp
)
198 SiFiveCLINTState
*s
= SIFIVE_CLINT(dev
);
199 memory_region_init_io(&s
->mmio
, OBJECT(dev
), &sifive_clint_ops
, s
,
200 TYPE_SIFIVE_CLINT
, s
->aperture_size
);
201 sysbus_init_mmio(SYS_BUS_DEVICE(dev
), &s
->mmio
);
204 static void sifive_clint_class_init(ObjectClass
*klass
, void *data
)
206 DeviceClass
*dc
= DEVICE_CLASS(klass
);
207 dc
->realize
= sifive_clint_realize
;
208 device_class_set_props(dc
, sifive_clint_properties
);
211 static const TypeInfo sifive_clint_info
= {
212 .name
= TYPE_SIFIVE_CLINT
,
213 .parent
= TYPE_SYS_BUS_DEVICE
,
214 .instance_size
= sizeof(SiFiveCLINTState
),
215 .class_init
= sifive_clint_class_init
,
218 static void sifive_clint_register_types(void)
220 type_register_static(&sifive_clint_info
);
223 type_init(sifive_clint_register_types
)
227 * Create CLINT device.
229 DeviceState
*sifive_clint_create(hwaddr addr
, hwaddr size
, uint32_t num_harts
,
230 uint32_t sip_base
, uint32_t timecmp_base
, uint32_t time_base
)
233 for (i
= 0; i
< num_harts
; i
++) {
234 CPUState
*cpu
= qemu_get_cpu(i
);
235 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
239 env
->timer
= timer_new_ns(QEMU_CLOCK_VIRTUAL
,
240 &sifive_clint_timer_cb
, cpu
);
244 DeviceState
*dev
= qdev_create(NULL
, TYPE_SIFIVE_CLINT
);
245 qdev_prop_set_uint32(dev
, "num-harts", num_harts
);
246 qdev_prop_set_uint32(dev
, "sip-base", sip_base
);
247 qdev_prop_set_uint32(dev
, "timecmp-base", timecmp_base
);
248 qdev_prop_set_uint32(dev
, "time-base", time_base
);
249 qdev_prop_set_uint32(dev
, "aperture-size", size
);
250 qdev_init_nofail(dev
);
251 sysbus_mmio_map(SYS_BUS_DEVICE(dev
), 0, addr
);