-
-
Notifications
You must be signed in to change notification settings - Fork 382
nv2a: Implement PTIMER alarm interrupts #2568
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
365e0f0
20449ec
bce7f44
29ce639
b03d8a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,10 @@ | ||||||||||||||||||||||||||||||||||
| /* | ||||||||||||||||||||||||||||||||||
| * QEMU Geforce NV2A implementation | ||||||||||||||||||||||||||||||||||
| * PTIMER - time measurement and time-based alarms | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * Copyright (c) 2012 espes | ||||||||||||||||||||||||||||||||||
| * Copyright (c) 2015 Jannik Vogel | ||||||||||||||||||||||||||||||||||
| * Copyright (c) 2018-2021 Matt Borgerson | ||||||||||||||||||||||||||||||||||
| * Copyright (c) 2018-2025 Matt Borgerson | ||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||
| * This library is free software; you can redistribute it and/or | ||||||||||||||||||||||||||||||||||
| * modify it under the terms of the GNU Lesser General Public | ||||||||||||||||||||||||||||||||||
|
|
@@ -21,14 +22,87 @@ | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| #include "nv2a_int.h" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /* PTIMER - time measurement and time-based alarms */ | ||||||||||||||||||||||||||||||||||
| static uint64_t ptimer_get_clock(NV2AState *d) | ||||||||||||||||||||||||||||||||||
| #define CLOCK_HIGH_MASK 0x1fffffff | ||||||||||||||||||||||||||||||||||
| #define CLOCK_LOW_MASK 0x7ffffff | ||||||||||||||||||||||||||||||||||
| #define ALARM_MASK 0xffffffe0 | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| static void ptimer_alarm_fired(void *opaque); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| void ptimer_reset(NV2AState *d) | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| d->ptimer.alarm_time = 0; | ||||||||||||||||||||||||||||||||||
| d->ptimer.alarm_time_high = 0; | ||||||||||||||||||||||||||||||||||
| d->ptimer.time_offset = 0; | ||||||||||||||||||||||||||||||||||
| timer_del(&d->ptimer.timer); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| void ptimer_init(NV2AState *d) | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| timer_init_ns(&d->ptimer.timer, QEMU_CLOCK_VIRTUAL, ptimer_alarm_fired, d); | ||||||||||||||||||||||||||||||||||
| ptimer_reset(d); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| static uint64_t ptimer_get_absolute_clock(NV2AState *d) | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| return muldiv64(muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), | ||||||||||||||||||||||||||||||||||
| d->pramdac.core_clock_freq, | ||||||||||||||||||||||||||||||||||
| NANOSECONDS_PER_SECOND), | ||||||||||||||||||||||||||||||||||
| d->ptimer.denominator, | ||||||||||||||||||||||||||||||||||
| d->ptimer.numerator); | ||||||||||||||||||||||||||||||||||
| d->ptimer.denominator, d->ptimer.numerator); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| //! Returns the NV2A time adjusted by the last time register modification and | ||||||||||||||||||||||||||||||||||
| //! shifted into range. | ||||||||||||||||||||||||||||||||||
| static uint64_t get_ptimer_clock(NV2AState *d, uint64_t absolute_clock) | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| return ((absolute_clock + d->ptimer.time_offset) << 5) & | ||||||||||||||||||||||||||||||||||
| 0x1fffffffffffffffLL; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| static uint64_t ptimer_ticks_to_ns(NV2AState *d, uint64_t ticks) | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| uint64_t gpu_ticks = | ||||||||||||||||||||||||||||||||||
| muldiv64(ticks, d->ptimer.numerator, d->ptimer.denominator); | ||||||||||||||||||||||||||||||||||
| return muldiv64(gpu_ticks, NANOSECONDS_PER_SECOND, | ||||||||||||||||||||||||||||||||||
| d->pramdac.core_clock_freq); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| static void schedule_qemu_timer(NV2AState *d) | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| if (!(d->ptimer.enabled_interrupts & NV_PTIMER_INTR_EN_0_ALARM)) { | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| uint64_t now = get_ptimer_clock(d, ptimer_get_absolute_clock(d)); | ||||||||||||||||||||||||||||||||||
| uint64_t alarm_time = (uint64_t)d->ptimer.alarm_time + | ||||||||||||||||||||||||||||||||||
| ((uint64_t)d->ptimer.alarm_time_high << 32); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| uint64_t diff_ns = 0; | ||||||||||||||||||||||||||||||||||
| if (alarm_time > now) { | ||||||||||||||||||||||||||||||||||
| diff_ns = ptimer_ticks_to_ns(d, (alarm_time - now) >> 5); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| timer_mod(&d->ptimer.timer, | ||||||||||||||||||||||||||||||||||
| qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + diff_ns); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| static void ptimer_alarm_fired(void *opaque) | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| NV2AState *d = (NV2AState *)opaque; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (!(d->ptimer.enabled_interrupts & NV_PTIMER_INTR_EN_0_ALARM)) { | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| uint64_t now = get_ptimer_clock(d, ptimer_get_absolute_clock(d)); | ||||||||||||||||||||||||||||||||||
| uint64_t alarm_time = | ||||||||||||||||||||||||||||||||||
| d->ptimer.alarm_time + ((uint64_t)d->ptimer.alarm_time_high << 32); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (alarm_time <= now) { | ||||||||||||||||||||||||||||||||||
| d->ptimer.pending_interrupts |= NV_PTIMER_INTR_0_ALARM; | ||||||||||||||||||||||||||||||||||
| d->ptimer.alarm_time_high = now >> 32; | ||||||||||||||||||||||||||||||||||
| nv2a_update_irq(d); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| schedule_qemu_timer(d); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+99
to
106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation of The line A safer approach is to treat the QEMU timer as a one-shot event. Once it fires, the interrupt is raised, and the timer is not rescheduled. The guest is then responsible for clearing the interrupt and programming a new alarm, which will in turn schedule a new QEMU timer via I suggest modifying the logic to only reschedule the timer in case of a spurious wakeup (where
Suggested change
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| uint64_t ptimer_read(void *opaque, hwaddr addr, unsigned int size) | ||||||||||||||||||||||||||||||||||
|
|
@@ -49,11 +123,16 @@ uint64_t ptimer_read(void *opaque, hwaddr addr, unsigned int size) | |||||||||||||||||||||||||||||||||
| case NV_PTIMER_DENOMINATOR: | ||||||||||||||||||||||||||||||||||
| r = d->ptimer.denominator; | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| case NV_PTIMER_TIME_0: | ||||||||||||||||||||||||||||||||||
| r = (ptimer_get_clock(d) & 0x7ffffff) << 5; | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| case NV_PTIMER_TIME_1: | ||||||||||||||||||||||||||||||||||
| r = (ptimer_get_clock(d) >> 27) & 0x1fffffff; | ||||||||||||||||||||||||||||||||||
| case NV_PTIMER_TIME_0: { | ||||||||||||||||||||||||||||||||||
| uint64_t now = get_ptimer_clock(d, ptimer_get_absolute_clock(d)); | ||||||||||||||||||||||||||||||||||
| r = now & 0xffffffff; | ||||||||||||||||||||||||||||||||||
| } break; | ||||||||||||||||||||||||||||||||||
| case NV_PTIMER_TIME_1: { | ||||||||||||||||||||||||||||||||||
| uint64_t now = get_ptimer_clock(d, ptimer_get_absolute_clock(d)); | ||||||||||||||||||||||||||||||||||
| r = (now >> 32) & CLOCK_HIGH_MASK; | ||||||||||||||||||||||||||||||||||
| } break; | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+126
to
+133
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reading a 64-bit timer value from two separate 32-bit register reads can be racy. If the timer ticks over and crosses a 32-bit boundary between the read of the low part ( A common hardware pattern to solve this is to latch the high part of the timer when the low part is read. The subsequent read of the high part returns the latched value. To implement this, you could add a
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @gemini-code-assist: Latching could be the right approach, but we should review access patterns and confirm what hardware actually does. The proposed change might not be correct if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You raise a very valid point, @mborgerson, regarding the need to confirm actual hardware behavior and access patterns. My suggestion was based on a common hardware pattern to mitigate race conditions when a 64-bit timer is exposed via two separate 32-bit registers. The core issue is that in the current implementation, both My proposed solution addresses this specific race condition by latching the high 32 bits of the timer when the low 32 bits ( Your concern about Even if software can work around this by reading the high part multiple times until it stabilizes (a common but inefficient software workaround), the goal of emulation is often to present a consistent and predictable hardware interface. Providing an atomic 64-bit read through a latching mechanism is generally a more robust emulation of a 64-bit timer.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting response. My expectation is also that the HW isn't doing magic here and it really is left up to the SW to detect race-rollover if it actually cares to. When I was debugging the issue that precipitated this change I saw several places where the timer was being read in a tight loop, presumably to work around the potential race. |
||||||||||||||||||||||||||||||||||
| case NV_PTIMER_ALARM_0: | ||||||||||||||||||||||||||||||||||
| r = d->ptimer.alarm_time; | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
|
|
@@ -76,17 +155,47 @@ void ptimer_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size) | |||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| case NV_PTIMER_INTR_EN_0: | ||||||||||||||||||||||||||||||||||
| d->ptimer.enabled_interrupts = val; | ||||||||||||||||||||||||||||||||||
| if (val) { | ||||||||||||||||||||||||||||||||||
| schedule_qemu_timer(d); | ||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||
| timer_del(&d->ptimer.timer); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| nv2a_update_irq(d); | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| case NV_PTIMER_DENOMINATOR: | ||||||||||||||||||||||||||||||||||
| d->ptimer.denominator = val; | ||||||||||||||||||||||||||||||||||
| schedule_qemu_timer(d); | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| case NV_PTIMER_NUMERATOR: | ||||||||||||||||||||||||||||||||||
| d->ptimer.numerator = val; | ||||||||||||||||||||||||||||||||||
| schedule_qemu_timer(d); | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| case NV_PTIMER_ALARM_0: | ||||||||||||||||||||||||||||||||||
| d->ptimer.alarm_time = val; | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| case NV_PTIMER_ALARM_0: { | ||||||||||||||||||||||||||||||||||
| uint64_t now = get_ptimer_clock(d, ptimer_get_absolute_clock(d)); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| d->ptimer.alarm_time = val & ALARM_MASK; | ||||||||||||||||||||||||||||||||||
| d->ptimer.alarm_time_high = (now >> 32); | ||||||||||||||||||||||||||||||||||
| if (val <= (now & ALARM_MASK)) { | ||||||||||||||||||||||||||||||||||
| ++d->ptimer.alarm_time_high; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| schedule_qemu_timer(d); | ||||||||||||||||||||||||||||||||||
| } break; | ||||||||||||||||||||||||||||||||||
| case NV_PTIMER_TIME_0: { | ||||||||||||||||||||||||||||||||||
| uint64_t absolute_clock = ptimer_get_absolute_clock(d); | ||||||||||||||||||||||||||||||||||
| uint64_t now = get_ptimer_clock(d, absolute_clock); | ||||||||||||||||||||||||||||||||||
| uint64_t target_relative = | ||||||||||||||||||||||||||||||||||
| (now & 0xffffffff00000000LL) | (((val >> 5) & CLOCK_LOW_MASK) << 5); | ||||||||||||||||||||||||||||||||||
| d->ptimer.time_offset = target_relative - absolute_clock; | ||||||||||||||||||||||||||||||||||
| schedule_qemu_timer(d); | ||||||||||||||||||||||||||||||||||
| } break; | ||||||||||||||||||||||||||||||||||
| case NV_PTIMER_TIME_1: { | ||||||||||||||||||||||||||||||||||
| uint64_t absolute_clock = ptimer_get_absolute_clock(d); | ||||||||||||||||||||||||||||||||||
| uint64_t now = get_ptimer_clock(d, absolute_clock); | ||||||||||||||||||||||||||||||||||
| uint64_t target_relative = | ||||||||||||||||||||||||||||||||||
| (now & 0xffffffff) | ((val & CLOCK_HIGH_MASK) << 32); | ||||||||||||||||||||||||||||||||||
| d->ptimer.time_offset = target_relative - absolute_clock; | ||||||||||||||||||||||||||||||||||
| schedule_qemu_timer(d); | ||||||||||||||||||||||||||||||||||
| } break; | ||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit lost tracking the bit shifting. Is the left shifting in
get_ptimer_clockjust putting the clock in register format? We shift left inget_ptimer_clock, then shift right here to compute actual clock deltas. I wonder if this means we should really just do the shifting/masking at the register interface (e.g. inptimer_read)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's just moving in and out of the format used in the registers.
Aren't the read/write callbacks expected to act on data in HW format?
I wonder if some helper macros would be appropriate rather than messing with the guest-accessible callback behavior (but maybe I'm just not following your suggestion).