@@ -105,9 +105,33 @@ static void emu_update_timer_interrupt(hart_t *hart)
105105{
106106 emu_state_t * data = PRIV (hart );
107107
108- /* Sync global timer with local timer */
108+ /* Lazy timer checking. Only check timer interrupts when the current time
109+ * has reached the earliest scheduled interrupt time. This avoids expensive
110+ * semu_timer_get() calls and interrupt checks.
111+ *
112+ * Fast path: Skip if current time < next interrupt time
113+ * Slow path: Check all harts' timers and recalculate next interrupt time
114+ */
115+ uint64_t current_time = semu_timer_get (& data -> mtimer .mtime );
116+ if (current_time < data -> mtimer .next_interrupt_at ) {
117+ /* Fast path: No timer interrupt can fire yet, skip checking.
118+ * Still sync the timer for correctness (hart->time is used by CSR
119+ * reads).
120+ */
121+ hart -> time = data -> mtimer .mtime ;
122+ return ;
123+ }
124+
125+ /* Slow path: At least one timer might fire, check this hart */
109126 hart -> time = data -> mtimer .mtime ;
110127 aclint_mtimer_update_interrupts (hart , & data -> mtimer );
128+
129+ /* Recalculate next interrupt time after potential interrupt delivery.
130+ * The kernel likely updated mtimecmp in the interrupt handler, which
131+ * already called recalc, but we call it again to be safe in case
132+ * multiple harts share the same mtimecmp value.
133+ */
134+ aclint_mtimer_recalc_next_interrupt (& data -> mtimer );
111135}
112136
113137static void emu_update_swi_interrupt (hart_t * hart )
@@ -342,6 +366,8 @@ static inline sbi_ret_t handle_sbi_ecall_TIMER(hart_t *hart, int32_t fid)
342366 (((uint64_t ) hart -> x_regs [RV_R_A1 ]) << 32 ) |
343367 (uint64_t ) (hart -> x_regs [RV_R_A0 ]);
344368 hart -> sip &= ~RV_INT_STI_BIT ;
369+ /* Recalculate next interrupt time for lazy timer checking */
370+ aclint_mtimer_recalc_next_interrupt (& data -> mtimer );
345371 return (sbi_ret_t ){SBI_SUCCESS , 0 };
346372 default :
347373 return (sbi_ret_t ){SBI_ERR_NOT_SUPPORTED , 0 };
@@ -766,6 +792,11 @@ static int semu_init(emu_state_t *emu, int argc, char **argv)
766792 /* Set up ACLINT */
767793 semu_timer_init (& emu -> mtimer .mtime , CLOCK_FREQ , hart_count );
768794 emu -> mtimer .mtimecmp = calloc (vm -> n_hart , sizeof (uint64_t ));
795+ emu -> mtimer .n_harts = vm -> n_hart ;
796+ /* mtimecmp is initialized to 0 by calloc, so next_interrupt_at starts at 0.
797+ * It will be updated when the kernel writes mtimecmp via SBI or MMIO.
798+ */
799+ emu -> mtimer .next_interrupt_at = 0 ;
769800 emu -> mswi .msip = calloc (vm -> n_hart , sizeof (uint32_t ));
770801 emu -> sswi .ssip = calloc (vm -> n_hart , sizeof (uint32_t ));
771802#if SEMU_HAS (VIRTIOSND )
@@ -953,6 +984,34 @@ static void print_mmu_cache_stats(vm_t *vm)
953984}
954985#endif
955986
987+ /* Calculate nanoseconds until next timer interrupt.
988+ * Returns 0 if interrupt is already due, or capped at 100ms maximum.
989+ */
990+ static uint64_t calc_ns_until_next_interrupt (emu_state_t * emu )
991+ {
992+ uint64_t current_time = semu_timer_get (& emu -> mtimer .mtime );
993+ uint64_t next_int = emu -> mtimer .next_interrupt_at ;
994+
995+ /* If interrupt is already due or very close, return immediately */
996+ if (current_time >= next_int )
997+ return 0 ;
998+
999+ /* Calculate ticks until interrupt */
1000+ uint64_t ticks_remaining = next_int - current_time ;
1001+
1002+ /* Convert RISC-V timer ticks to nanoseconds:
1003+ * ns = ticks * (1e9 / CLOCK_FREQ)
1004+ */
1005+ uint64_t ns = (ticks_remaining * 1000000000ULL ) / emu -> mtimer .mtime .freq ;
1006+
1007+ /* Cap at 100ms to maintain responsiveness for UART and other events */
1008+ const uint64_t MAX_WAIT_NS = 100000000ULL ; /* 100ms */
1009+ if (ns > MAX_WAIT_NS )
1010+ ns = MAX_WAIT_NS ;
1011+
1012+ return ns ;
1013+ }
1014+
9561015static int semu_run (emu_state_t * emu )
9571016{
9581017 int ret ;
@@ -974,36 +1033,20 @@ static int semu_run(emu_state_t *emu)
9741033 return -1 ;
9751034 }
9761035
977- /* Add 1ms periodic timer */
978- struct kevent kev_timer ;
979- EV_SET (& kev_timer , 1 , EVFILT_TIMER , EV_ADD | EV_ENABLE , 0 , 1 , NULL );
980- if (kevent (kq , & kev_timer , 1 , NULL , 0 , NULL ) < 0 ) {
981- perror ("kevent timer setup" );
982- close (kq );
983- return -1 ;
984- }
985-
986- /* Note: UART input is polled via u8250_check_ready(), no need to
987- * monitor with kqueue. Timer events are sufficient to wake from WFI.
1036+ /* Note: Timer is configured dynamically in the event loop based on
1037+ * next_interrupt_at. UART input is polled via u8250_check_ready().
9881038 */
9891039#else
990- /* Linux: create timerfd for periodic wakeup */
1040+ /* Linux: create timerfd for dynamic timer wakeup */
9911041 int wfi_timer_fd = timerfd_create (CLOCK_MONOTONIC , TFD_NONBLOCK );
9921042 if (wfi_timer_fd < 0 ) {
9931043 perror ("timerfd_create" );
9941044 return -1 ;
9951045 }
9961046
997- /* Configure 1ms periodic timer */
998- struct itimerspec its = {
999- .it_interval = {.tv_sec = 0 , .tv_nsec = 1000000 },
1000- .it_value = {.tv_sec = 0 , .tv_nsec = 1000000 },
1001- };
1002- if (timerfd_settime (wfi_timer_fd , 0 , & its , NULL ) < 0 ) {
1003- perror ("timerfd_settime" );
1004- close (wfi_timer_fd );
1005- return -1 ;
1006- }
1047+ /* Timer is configured dynamically in the event loop based on
1048+ * next_interrupt_at to minimize unnecessary wakeups.
1049+ */
10071050#endif
10081051
10091052 while (!emu -> stopped ) {
@@ -1025,30 +1068,61 @@ static int semu_run(emu_state_t *emu)
10251068 }
10261069 if (all_waiting ) {
10271070 /* All harts waiting for interrupt - use event-driven wait
1028- * to reduce CPU usage while maintaining responsiveness
1071+ * to reduce CPU usage while maintaining responsiveness.
1072+ * Dynamically adjust timer based on next_interrupt_at.
10291073 */
1074+
1075+ /* Calculate how long to wait until next timer interrupt */
1076+ uint64_t wait_ns = calc_ns_until_next_interrupt (emu );
1077+
1078+ /* If interrupt is already due, don't wait - continue
1079+ * immediately
1080+ */
1081+ if (wait_ns > 0 ) {
10301082#ifdef __APPLE__
1031- /* macOS: wait for kqueue events (timer or UART) */
1032- struct kevent events [2 ];
1033- int nevents = kevent (kq , NULL , 0 , events , 2 , NULL );
1034- /* Events are automatically handled - timer fires every 1ms,
1035- * UART triggers on input. No need to explicitly consume. */
1036- (void ) nevents ;
1083+ /* configure one-shot kqueue timer with dynamic timeout */
1084+ struct kevent kev_timer ;
1085+ /* NOTE_USECONDS for microseconds, wait_ns/1000 converts
1086+ * ns to us
1087+ */
1088+ EV_SET (& kev_timer , 1 , EVFILT_TIMER ,
1089+ EV_ADD | EV_ENABLE | EV_ONESHOT , NOTE_USECONDS ,
1090+ wait_ns / 1000 , NULL );
1091+
1092+ struct kevent events [2 ];
1093+ int nevents = kevent (kq , & kev_timer , 1 , events , 2 , NULL );
1094+ /* Events are automatically handled. Wakeup occurs on:
1095+ * - Timer expiration (wait_ns elapsed)
1096+ * - UART input (if monitored)
1097+ */
1098+ (void ) nevents ;
10371099#else
1038- /* Linux: poll on timerfd and UART */
1039- struct pollfd pfds [2 ];
1040- pfds [0 ] = (struct pollfd ){wfi_timer_fd , POLLIN , 0 };
1041- pfds [1 ] = (struct pollfd ){emu -> uart .in_fd , POLLIN , 0 };
1042- poll (pfds , 2 , -1 );
1043-
1044- /* Consume timerfd event to prevent accumulation */
1045- if (pfds [0 ].revents & POLLIN ) {
1046- uint64_t expirations ;
1047- ssize_t ret =
1048- read (wfi_timer_fd , & expirations , sizeof (expirations ));
1049- (void ) ret ; /* Ignore read errors - timer will retry */
1050- }
1100+ /* Linux: configure timerfd with dynamic one-shot timeout */
1101+ struct itimerspec its = {
1102+ .it_interval = {0 , 0 }, /* One-shot, no repeat */
1103+ .it_value = {wait_ns / 1000000000 ,
1104+ wait_ns % 1000000000 },
1105+ };
1106+ if (timerfd_settime (wfi_timer_fd , 0 , & its , NULL ) < 0 ) {
1107+ perror ("timerfd_settime" );
1108+ /* Continue anyway - will retry next iteration */
1109+ }
1110+
1111+ /* Poll on timerfd and UART */
1112+ struct pollfd pfds [2 ];
1113+ pfds [0 ] = (struct pollfd ){wfi_timer_fd , POLLIN , 0 };
1114+ pfds [1 ] = (struct pollfd ){emu -> uart .in_fd , POLLIN , 0 };
1115+ poll (pfds , 2 , -1 );
1116+
1117+ /* Consume timerfd event to prevent accumulation */
1118+ if (pfds [0 ].revents & POLLIN ) {
1119+ uint64_t expirations ;
1120+ ssize_t ret = read (wfi_timer_fd , & expirations ,
1121+ sizeof (expirations ));
1122+ (void ) ret ; /* Ignore read errors - timer will retry */
1123+ }
10511124#endif
1125+ }
10521126 }
10531127 }
10541128
0 commit comments