 bbb9fc2591
			
		
	
	
		bbb9fc2591
		
	
	
	
	
		
			
			Use CPURISCVState as argument directly in riscv_cpu_update_mip and riscv_timer_write_timecmp, since type converts from CPURISCVState to RISCVCPU in many caller of them and then back to CPURISCVState in them. Signed-off-by: Weiwei Li <liweiwei@iscas.ac.cn> Signed-off-by: Junqiang Wang <wangjunqiang@iscas.ac.cn> Reviewed-by: Alistair Francis <alistair.francis@wdc.com> Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com> Message-Id: <20230309071329.45932-4-liweiwei@iscas.ac.cn> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
		
			
				
	
	
		
			142 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			142 lines
		
	
	
		
			4.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * RISC-V timer helper implementation.
 | |
|  *
 | |
|  * Copyright (c) 2022 Rivos Inc.
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or modify it
 | |
|  * under the terms and conditions of the GNU General Public License,
 | |
|  * version 2 or later, as published by the Free Software Foundation.
 | |
|  *
 | |
|  * This program is distributed in the hope it will be useful, but WITHOUT
 | |
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | |
|  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 | |
|  * more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License along with
 | |
|  * this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| #include "qemu/osdep.h"
 | |
| #include "qemu/log.h"
 | |
| #include "cpu_bits.h"
 | |
| #include "time_helper.h"
 | |
| #include "hw/intc/riscv_aclint.h"
 | |
| 
 | |
| static void riscv_vstimer_cb(void *opaque)
 | |
| {
 | |
|     RISCVCPU *cpu = opaque;
 | |
|     CPURISCVState *env = &cpu->env;
 | |
|     env->vstime_irq = 1;
 | |
|     riscv_cpu_update_mip(env, 0, BOOL_TO_MASK(1));
 | |
| }
 | |
| 
 | |
| static void riscv_stimer_cb(void *opaque)
 | |
| {
 | |
|     RISCVCPU *cpu = opaque;
 | |
|     riscv_cpu_update_mip(&cpu->env, MIP_STIP, BOOL_TO_MASK(1));
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Called when timecmp is written to update the QEMU timer or immediately
 | |
|  * trigger timer interrupt if mtimecmp <= current timer value.
 | |
|  */
 | |
| void riscv_timer_write_timecmp(CPURISCVState *env, QEMUTimer *timer,
 | |
|                                uint64_t timecmp, uint64_t delta,
 | |
|                                uint32_t timer_irq)
 | |
| {
 | |
|     uint64_t diff, ns_diff, next;
 | |
|     RISCVAclintMTimerState *mtimer = env->rdtime_fn_arg;
 | |
|     uint32_t timebase_freq = mtimer->timebase_freq;
 | |
|     uint64_t rtc_r = env->rdtime_fn(env->rdtime_fn_arg) + delta;
 | |
| 
 | |
|     if (timecmp <= rtc_r) {
 | |
|         /*
 | |
|          * If we're setting an stimecmp value in the "past",
 | |
|          * immediately raise the timer interrupt
 | |
|          */
 | |
|         if (timer_irq == MIP_VSTIP) {
 | |
|             env->vstime_irq = 1;
 | |
|             riscv_cpu_update_mip(env, 0, BOOL_TO_MASK(1));
 | |
|         } else {
 | |
|             riscv_cpu_update_mip(env, MIP_STIP, BOOL_TO_MASK(1));
 | |
|         }
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     /* Clear the [VS|S]TIP bit in mip */
 | |
|     if (timer_irq == MIP_VSTIP) {
 | |
|         env->vstime_irq = 0;
 | |
|         riscv_cpu_update_mip(env, 0, BOOL_TO_MASK(0));
 | |
|     } else {
 | |
|         riscv_cpu_update_mip(env, timer_irq, BOOL_TO_MASK(0));
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * Sstc specification says the following about timer interrupt:
 | |
|      * "A supervisor timer interrupt becomes pending - as reflected in
 | |
|      * the STIP bit in the mip and sip registers - whenever time contains
 | |
|      * a value greater than or equal to stimecmp, treating the values
 | |
|      * as unsigned integers. Writes to stimecmp are guaranteed to be
 | |
|      * reflected in STIP eventually, but not necessarily immediately.
 | |
|      * The interrupt remains posted until stimecmp becomes greater
 | |
|      * than time - typically as a result of writing stimecmp."
 | |
|      *
 | |
|      * When timecmp = UINT64_MAX, the time CSR will eventually reach
 | |
|      * timecmp value but on next timer tick the time CSR will wrap-around
 | |
|      * and become zero which is less than UINT64_MAX. Now, the timer
 | |
|      * interrupt behaves like a level triggered interrupt so it will
 | |
|      * become 1 when time = timecmp = UINT64_MAX and next timer tick
 | |
|      * it will become 0 again because time = 0 < timecmp = UINT64_MAX.
 | |
|      *
 | |
|      * Based on above, we don't re-start the QEMU timer when timecmp
 | |
|      * equals UINT64_MAX.
 | |
|      */
 | |
|     if (timecmp == UINT64_MAX) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     /* otherwise, set up the future timer interrupt */
 | |
|     diff = timecmp - rtc_r;
 | |
|     /* back to ns (note args switched in muldiv64) */
 | |
|     ns_diff = muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq);
 | |
| 
 | |
|     /*
 | |
|      * check if ns_diff overflowed and check if the addition would potentially
 | |
|      * overflow
 | |
|      */
 | |
|     if ((NANOSECONDS_PER_SECOND > timebase_freq && ns_diff < diff) ||
 | |
|         ns_diff > INT64_MAX) {
 | |
|         next = INT64_MAX;
 | |
|     } else {
 | |
|         /*
 | |
|          * as it is very unlikely qemu_clock_get_ns will return a value
 | |
|          * greater than INT64_MAX, no additional check is needed for an
 | |
|          * unsigned integer overflow.
 | |
|          */
 | |
|         next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns_diff;
 | |
|         /*
 | |
|          * if ns_diff is INT64_MAX next may still be outside the range
 | |
|          * of a signed integer.
 | |
|          */
 | |
|         next = MIN(next, INT64_MAX);
 | |
|     }
 | |
| 
 | |
|     timer_mod(timer, next);
 | |
| }
 | |
| 
 | |
| void riscv_timer_init(RISCVCPU *cpu)
 | |
| {
 | |
|     CPURISCVState *env;
 | |
| 
 | |
|     if (!cpu) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     env = &cpu->env;
 | |
|     env->stimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_stimer_cb, cpu);
 | |
|     env->stimecmp = 0;
 | |
| 
 | |
|     env->vstimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_vstimer_cb, cpu);
 | |
|     env->vstimecmp = 0;
 | |
| }
 |