274 lines
7.7 KiB
C
274 lines
7.7 KiB
C
/*
|
|
* 86Box A hypervisor and IBM PC system emulator that specializes in
|
|
* running old operating systems and software designed for IBM
|
|
* PC systems and compatibles from 1981 through fairly recent
|
|
* system designs based on the PCI bus.
|
|
*
|
|
* This file is part of the 86Box distribution.
|
|
*
|
|
* Fast, high-frequency, CPU-independent timer.
|
|
*
|
|
*
|
|
*
|
|
* Authors: Connor Hyde, <mario64crashed@gmail.com> I need a better email address ;^)
|
|
*
|
|
* Copyright 2024-2025 starfrost
|
|
*/
|
|
|
|
/* See vid_nv_rivatimer.h comments for rationale behind not using the regular timer system
|
|
|
|
Notes applicable to this file:
|
|
Since Windows XP, QueryPerformanceCounter and QueryPerformanceFrequency cannot fail so they are not checked.
|
|
|
|
*/
|
|
|
|
#include <86Box/nv/vid_nv_rivatimer.h>
|
|
|
|
#ifdef _WIN32
|
|
LARGE_INTEGER performance_frequency;
|
|
#endif
|
|
|
|
rivatimer_t* rivatimer_head; // The head of the rivatimer list.
|
|
rivatimer_t* rivatimer_tail; // The tail of the rivatimer list.
|
|
|
|
/* Functions only used in this translation unit */
|
|
bool rivatimer_really_exists(rivatimer_t* rivatimer); // Determine if a rivatimer really exists in the linked list.
|
|
|
|
void rivatimer_init()
|
|
{
|
|
// Destroy all the rivatimers.
|
|
rivatimer_t* rivatimer_ptr = rivatimer_head;
|
|
|
|
if (!rivatimer_ptr)
|
|
return;
|
|
|
|
while (rivatimer_ptr)
|
|
{
|
|
// since we are destroing it
|
|
rivatimer_t* old_next = rivatimer_ptr->next;
|
|
rivatimer_destroy(rivatimer_ptr);
|
|
|
|
rivatimer_ptr = old_next;
|
|
}
|
|
|
|
|
|
#ifdef _WIN32
|
|
// Query the performance frequency.
|
|
QueryPerformanceFrequency(&performance_frequency);
|
|
#endif
|
|
}
|
|
|
|
// Creates a rivatimer.
|
|
rivatimer_t* rivatimer_create(double period, void (*callback)(double real_time))
|
|
{
|
|
rivatimer_t* new_rivatimer = NULL;
|
|
|
|
// See i
|
|
if (period <= 0
|
|
|| !callback)
|
|
{
|
|
fatal("Invalid rivatimer_create call: period <= 0 or no callback");
|
|
}
|
|
|
|
// If there are no rivatimers, create one
|
|
if (!rivatimer_head)
|
|
{
|
|
rivatimer_head = calloc(1, sizeof(rivatimer_t));
|
|
rivatimer_head->prev = NULL; // indicate this is the first in the list even if we don't strictly need to
|
|
rivatimer_tail = rivatimer_head;
|
|
new_rivatimer = rivatimer_head;
|
|
}
|
|
else // Otherwise add a new one to the list
|
|
{
|
|
rivatimer_tail->next = calloc(1, sizeof(rivatimer_t));
|
|
rivatimer_tail = rivatimer_tail->next;
|
|
new_rivatimer = rivatimer_tail;
|
|
}
|
|
|
|
// sanity check
|
|
if (new_rivatimer)
|
|
{
|
|
new_rivatimer->running = false;
|
|
new_rivatimer->period = period;
|
|
new_rivatimer->next = NULL; // indicate this is the last in the list
|
|
new_rivatimer->callback = callback;
|
|
}
|
|
|
|
return new_rivatimer;
|
|
}
|
|
|
|
// Determines if a rivatimer really exists.
|
|
bool rivatimer_really_exists(rivatimer_t* rivatimer)
|
|
{
|
|
rivatimer_t* current = rivatimer_head;
|
|
|
|
if (!current)
|
|
return false;
|
|
|
|
while (current)
|
|
{
|
|
if (current == rivatimer)
|
|
return true;
|
|
|
|
current = current->next;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Destroy a rivatimer.
|
|
void rivatimer_destroy(rivatimer_t* rivatimer_ptr)
|
|
{
|
|
if (!rivatimer_really_exists(rivatimer_ptr))
|
|
fatal("rivatimer_destroy: The timer was already destroyed, or never existed in the first place.");
|
|
|
|
// Case: We are destroying the head
|
|
if (rivatimer_ptr == rivatimer_head)
|
|
{
|
|
// This is the only rivatimer
|
|
if (rivatimer_ptr->next == NULL)
|
|
{
|
|
rivatimer_head = NULL;
|
|
rivatimer_tail = NULL;
|
|
}
|
|
// This is not the only rivatimer
|
|
else
|
|
{
|
|
rivatimer_head = rivatimer_ptr->next;
|
|
rivatimer_head->prev = NULL;
|
|
// This is the only rivatimer and now there is only one
|
|
if (!rivatimer_head->next)
|
|
rivatimer_tail = rivatimer_head;
|
|
}
|
|
}
|
|
// Case: We are destroying the tail
|
|
else if (rivatimer_ptr == rivatimer_tail)
|
|
{
|
|
// We already covered the case where there is only one item above
|
|
rivatimer_tail = rivatimer_ptr->prev;
|
|
rivatimer_tail->next = NULL;
|
|
}
|
|
// Case: This is not the first or last rivatimer, so we don't need to set the head or tail
|
|
else
|
|
{
|
|
// Fix the break in the chain that this
|
|
if (rivatimer_ptr->next)
|
|
rivatimer_ptr->prev->next = rivatimer_ptr->next;
|
|
if (rivatimer_ptr->prev)
|
|
rivatimer_ptr->next->prev = rivatimer_ptr->prev;
|
|
}
|
|
|
|
free(rivatimer_ptr);
|
|
rivatimer_ptr = NULL; //explicitly set to null
|
|
}
|
|
|
|
void rivatimer_update_all()
|
|
{
|
|
rivatimer_t* rivatimer_ptr = rivatimer_head;
|
|
|
|
if (!rivatimer_ptr)
|
|
return;
|
|
|
|
while (rivatimer_ptr)
|
|
{
|
|
// if it's not running skip it
|
|
if (!rivatimer_ptr->running)
|
|
{
|
|
rivatimer_ptr = rivatimer_ptr->next;
|
|
continue;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
LARGE_INTEGER current_time;
|
|
|
|
QueryPerformanceCounter(¤t_time);
|
|
|
|
double microseconds = ((double)current_time.QuadPart / 1000000.0) - (rivatimer_ptr->starting_time.QuadPart / 1000000.0);
|
|
#else
|
|
struct timespec current_time;
|
|
|
|
clock_gettime(CLOCK_REALTIME, ¤t_time);
|
|
|
|
double microseconds = ((double)current_time.tv_sec * 1000000.0) + ((double)current_time.tv_nsec / 1000.0);
|
|
#endif
|
|
|
|
rivatimer_ptr->time += microseconds;
|
|
|
|
// Reset the current time so we can actually restart
|
|
#ifdef _WIN32
|
|
QueryPerformanceCounter(&rivatimer_ptr->starting_time);
|
|
#else
|
|
clock_gettime(CLOCK_REALTIME, &rivatimer_ptr->starting_time);
|
|
#endif
|
|
|
|
// Time to fire
|
|
if (microseconds > rivatimer_ptr->period)
|
|
{
|
|
if (!rivatimer_ptr->callback)
|
|
{
|
|
pclog("Eh? No callback in RivaTimer?");
|
|
continue;
|
|
}
|
|
|
|
rivatimer_ptr->callback(microseconds);
|
|
}
|
|
|
|
rivatimer_ptr = rivatimer_ptr->next;
|
|
}
|
|
|
|
}
|
|
|
|
void rivatimer_start(rivatimer_t* rivatimer_ptr)
|
|
{
|
|
if (!rivatimer_really_exists(rivatimer_ptr))
|
|
fatal("rivatimer_start: The timer has been destroyed, or never existed in the first place.");
|
|
|
|
if (rivatimer_ptr->period <= 0)
|
|
fatal("rivatimer_start: Zero period!");
|
|
|
|
rivatimer_ptr->running = true;
|
|
|
|
// Start off so rivatimer_update_all can actually update.
|
|
#ifdef _WIN32
|
|
QueryPerformanceCounter(&rivatimer_ptr->starting_time);
|
|
#else
|
|
clock_gettime(CLOCK_REALTIME, &rivatimer_ptr->starting_time);
|
|
#endif
|
|
}
|
|
|
|
void rivatimer_stop(rivatimer_t* rivatimer_ptr)
|
|
{
|
|
if (!rivatimer_really_exists(rivatimer_ptr))
|
|
fatal("rivatimer_stop: The timer has been destroyed, or never existed in the first place.");
|
|
|
|
rivatimer_ptr->running = false;
|
|
rivatimer_ptr->time = 0;
|
|
}
|
|
|
|
// Get the current time value of a rivatimer
|
|
double rivatimer_get_time(rivatimer_t* rivatimer_ptr)
|
|
{
|
|
if (!rivatimer_really_exists(rivatimer_ptr))
|
|
fatal("rivatimer_get_time: The timer has been destroyed, or never existed in the first place.");
|
|
|
|
return rivatimer_ptr->time;
|
|
}
|
|
|
|
void rivatimer_set_callback(rivatimer_t* rivatimer_ptr, void (*callback)(double real_time))
|
|
{
|
|
if (!rivatimer_really_exists(rivatimer_ptr))
|
|
fatal("rivatimer_set_callback: The timer has been destroyed, or never existed in the first place.");
|
|
|
|
if (!callback)
|
|
fatal("rivatimer_set_callback: No callback!");
|
|
|
|
rivatimer_ptr->callback = callback;
|
|
}
|
|
|
|
void rivatimer_set_period(rivatimer_t* rivatimer_ptr, double period)
|
|
{
|
|
if (!rivatimer_really_exists(rivatimer_ptr))
|
|
fatal("rivatimer_set_period: The timer has been destroyed, or never existed in the first place.");
|
|
|
|
rivatimer_ptr->period = period;
|
|
} |