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 "hw/sysbus.h"
25 #include "target/riscv/cpu.h"
26 #include "hw/riscv/sifive_clint.h"
27 #include "qemu/timer.h"
29 static uint64_t cpu_riscv_read_rtc(void)
31 return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL
),
32 SIFIVE_CLINT_TIMEBASE_FREQ
, NANOSECONDS_PER_SECOND
);
36 * Called when timecmp is written to update the QEMU timer or immediately
37 * trigger timer interrupt if mtimecmp <= current timer value.
39 static void sifive_clint_write_timecmp(RISCVCPU
*cpu
, uint64_t value
)
44 uint64_t rtc_r
= cpu_riscv_read_rtc();
46 cpu
->env
.timecmp
= value
;
47 if (cpu
->env
.timecmp
<= rtc_r
) {
48 /* if we're setting an MTIMECMP value in the "past",
49 immediately raise the timer interrupt */
50 riscv_cpu_update_mip(cpu
, MIP_MTIP
, BOOL_TO_MASK(1));
54 /* otherwise, set up the future timer interrupt */
55 riscv_cpu_update_mip(cpu
, MIP_MTIP
, BOOL_TO_MASK(0));
56 diff
= cpu
->env
.timecmp
- rtc_r
;
57 /* back to ns (note args switched in muldiv64) */
58 next
= qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL
) +
59 muldiv64(diff
, NANOSECONDS_PER_SECOND
, SIFIVE_CLINT_TIMEBASE_FREQ
);
60 timer_mod(cpu
->env
.timer
, next
);
64 * Callback used when the timer set using timer_mod expires.
65 * Should raise the timer interrupt line
67 static void sifive_clint_timer_cb(void *opaque
)
69 RISCVCPU
*cpu
= opaque
;
70 riscv_cpu_update_mip(cpu
, MIP_MTIP
, BOOL_TO_MASK(1));
73 /* CPU wants to read rtc or timecmp register */
74 static uint64_t sifive_clint_read(void *opaque
, hwaddr addr
, unsigned size
)
76 SiFiveCLINTState
*clint
= opaque
;
77 if (addr
>= clint
->sip_base
&&
78 addr
< clint
->sip_base
+ (clint
->num_harts
<< 2)) {
79 size_t hartid
= (addr
- clint
->sip_base
) >> 2;
80 CPUState
*cpu
= qemu_get_cpu(hartid
);
81 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
83 error_report("clint: invalid timecmp hartid: %zu", hartid
);
84 } else if ((addr
& 0x3) == 0) {
85 return (env
->mip
& MIP_MSIP
) > 0;
87 error_report("clint: invalid read: %08x", (uint32_t)addr
);
90 } else if (addr
>= clint
->timecmp_base
&&
91 addr
< clint
->timecmp_base
+ (clint
->num_harts
<< 3)) {
92 size_t hartid
= (addr
- clint
->timecmp_base
) >> 3;
93 CPUState
*cpu
= qemu_get_cpu(hartid
);
94 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
96 error_report("clint: invalid timecmp hartid: %zu", hartid
);
97 } else if ((addr
& 0x7) == 0) {
99 uint64_t timecmp
= env
->timecmp
;
100 return timecmp
& 0xFFFFFFFF;
101 } else if ((addr
& 0x7) == 4) {
103 uint64_t timecmp
= env
->timecmp
;
104 return (timecmp
>> 32) & 0xFFFFFFFF;
106 error_report("clint: invalid read: %08x", (uint32_t)addr
);
109 } else if (addr
== clint
->time_base
) {
111 return cpu_riscv_read_rtc() & 0xFFFFFFFF;
112 } else if (addr
== clint
->time_base
+ 4) {
114 return (cpu_riscv_read_rtc() >> 32) & 0xFFFFFFFF;
117 error_report("clint: invalid read: %08x", (uint32_t)addr
);
121 /* CPU wrote to rtc or timecmp register */
122 static void sifive_clint_write(void *opaque
, hwaddr addr
, uint64_t value
,
125 SiFiveCLINTState
*clint
= opaque
;
127 if (addr
>= clint
->sip_base
&&
128 addr
< clint
->sip_base
+ (clint
->num_harts
<< 2)) {
129 size_t hartid
= (addr
- clint
->sip_base
) >> 2;
130 CPUState
*cpu
= qemu_get_cpu(hartid
);
131 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
133 error_report("clint: invalid timecmp hartid: %zu", hartid
);
134 } else if ((addr
& 0x3) == 0) {
135 riscv_cpu_update_mip(RISCV_CPU(cpu
), MIP_MSIP
, BOOL_TO_MASK(value
));
137 error_report("clint: invalid sip write: %08x", (uint32_t)addr
);
140 } else if (addr
>= clint
->timecmp_base
&&
141 addr
< clint
->timecmp_base
+ (clint
->num_harts
<< 3)) {
142 size_t hartid
= (addr
- clint
->timecmp_base
) >> 3;
143 CPUState
*cpu
= qemu_get_cpu(hartid
);
144 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
146 error_report("clint: invalid timecmp hartid: %zu", hartid
);
147 } else if ((addr
& 0x7) == 0) {
149 uint64_t timecmp_hi
= env
->timecmp
>> 32;
150 sifive_clint_write_timecmp(RISCV_CPU(cpu
),
151 timecmp_hi
<< 32 | (value
& 0xFFFFFFFF));
153 } else if ((addr
& 0x7) == 4) {
155 uint64_t timecmp_lo
= env
->timecmp
;
156 sifive_clint_write_timecmp(RISCV_CPU(cpu
),
157 value
<< 32 | (timecmp_lo
& 0xFFFFFFFF));
159 error_report("clint: invalid timecmp write: %08x", (uint32_t)addr
);
162 } else if (addr
== clint
->time_base
) {
164 error_report("clint: time_lo write not implemented");
166 } else if (addr
== clint
->time_base
+ 4) {
168 error_report("clint: time_hi write not implemented");
172 error_report("clint: invalid write: %08x", (uint32_t)addr
);
175 static const MemoryRegionOps sifive_clint_ops
= {
176 .read
= sifive_clint_read
,
177 .write
= sifive_clint_write
,
178 .endianness
= DEVICE_LITTLE_ENDIAN
,
180 .min_access_size
= 4,
185 static Property sifive_clint_properties
[] = {
186 DEFINE_PROP_UINT32("num-harts", SiFiveCLINTState
, num_harts
, 0),
187 DEFINE_PROP_UINT32("sip-base", SiFiveCLINTState
, sip_base
, 0),
188 DEFINE_PROP_UINT32("timecmp-base", SiFiveCLINTState
, timecmp_base
, 0),
189 DEFINE_PROP_UINT32("time-base", SiFiveCLINTState
, time_base
, 0),
190 DEFINE_PROP_UINT32("aperture-size", SiFiveCLINTState
, aperture_size
, 0),
191 DEFINE_PROP_END_OF_LIST(),
194 static void sifive_clint_realize(DeviceState
*dev
, Error
**errp
)
196 SiFiveCLINTState
*s
= SIFIVE_CLINT(dev
);
197 memory_region_init_io(&s
->mmio
, OBJECT(dev
), &sifive_clint_ops
, s
,
198 TYPE_SIFIVE_CLINT
, s
->aperture_size
);
199 sysbus_init_mmio(SYS_BUS_DEVICE(dev
), &s
->mmio
);
202 static void sifive_clint_class_init(ObjectClass
*klass
, void *data
)
204 DeviceClass
*dc
= DEVICE_CLASS(klass
);
205 dc
->realize
= sifive_clint_realize
;
206 dc
->props
= sifive_clint_properties
;
209 static const TypeInfo sifive_clint_info
= {
210 .name
= TYPE_SIFIVE_CLINT
,
211 .parent
= TYPE_SYS_BUS_DEVICE
,
212 .instance_size
= sizeof(SiFiveCLINTState
),
213 .class_init
= sifive_clint_class_init
,
216 static void sifive_clint_register_types(void)
218 type_register_static(&sifive_clint_info
);
221 type_init(sifive_clint_register_types
)
225 * Create CLINT device.
227 DeviceState
*sifive_clint_create(hwaddr addr
, hwaddr size
, uint32_t num_harts
,
228 uint32_t sip_base
, uint32_t timecmp_base
, uint32_t time_base
)
231 for (i
= 0; i
< num_harts
; i
++) {
232 CPUState
*cpu
= qemu_get_cpu(i
);
233 CPURISCVState
*env
= cpu
? cpu
->env_ptr
: NULL
;
237 env
->timer
= timer_new_ns(QEMU_CLOCK_VIRTUAL
,
238 &sifive_clint_timer_cb
, cpu
);
242 DeviceState
*dev
= qdev_create(NULL
, TYPE_SIFIVE_CLINT
);
243 qdev_prop_set_uint32(dev
, "num-harts", num_harts
);
244 qdev_prop_set_uint32(dev
, "sip-base", sip_base
);
245 qdev_prop_set_uint32(dev
, "timecmp-base", timecmp_base
);
246 qdev_prop_set_uint32(dev
, "time-base", time_base
);
247 qdev_prop_set_uint32(dev
, "aperture-size", size
);
248 qdev_init_nofail(dev
);
249 sysbus_mmio_map(SYS_BUS_DEVICE(dev
), 0, addr
);