--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eventq.c Sun Jan 28 11:36:00 2007 +0000 @@ -0,0 +1,341 @@ +/** + * $Id: eventq.c,v 1.1 2007-01-06 04:06:36 nkeynes Exp $ + * + * Simple implementation of one-shot timers. Effectively this allows IO + * devices to wait until a particular time before completing. We expect + * there to be at least half a dozen or so continually scheduled events + * (TMU and PVR2), peaking around 20+. + * + * Copyright (c) 2005 Nathan Keynes. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that 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. + */ + +#include +#include "dreamcast.h" +#include "eventq.h" +#include "sh4core.h" + +#define LONG_SCAN_PERIOD 1000000000 /* 1 second */ + +typedef struct event { + uint32_t id; + uint32_t seconds; + uint32_t nanosecs; + event_func_t func; + + struct event *next; +} *event_t; + +static struct event events[MAX_EVENT_ID]; + +/** + * Countdown to the next scan of the long-duration list (greater than 1 second). + */ +static int long_scan_time_remaining; + +static event_t event_head; +static event_t long_event_head; + +void event_reset(); +void event_init(); +uint32_t event_run_slice( uint32_t nanosecs ); +void event_save_state( FILE *f ); +int event_load_state( FILE * f ); + +struct dreamcast_module eventq_module = { "EVENTQ", event_init, event_reset, NULL, event_run_slice, + NULL, event_save_state, event_load_state }; + +static void event_update_pending( ) +{ + if( event_head == NULL ) { + if( !(sh4r.event_types & PENDING_IRQ) ) { + sh4r.event_pending = NOT_SCHEDULED; + } + sh4r.event_types &= (~PENDING_EVENT); + } else { + if( !(sh4r.event_types & PENDING_IRQ) ) { + sh4r.event_pending = event_head->nanosecs; + } + sh4r.event_types |= PENDING_EVENT; + } +} + +uint32_t event_get_next_time( ) +{ + if( event_head == NULL ) { + return NOT_SCHEDULED; + } else { + return event_head->nanosecs; + } +} + +/** + * Add the event to the short queue. + */ +static void event_enqueue( event_t event ) +{ + if( event_head == NULL || event->nanosecs < event_head->nanosecs ) { + event->next = event_head; + event_head = event; + event_update_pending(); + } else { + event_t cur = event_head; + event_t next = cur->next; + while( next != NULL && event->nanosecs >= next->nanosecs ) { + cur = next; + next = cur->next; + } + event->next = next; + cur->next = event; + } +} + +static void event_dequeue( event_t event ) +{ + if( event_head == NULL ) { + ERROR( "Empty event queue but should contain event %d", event->id ); + } else if( event_head == event ) { + /* removing queue head */ + event_head = event_head->next; + event_update_pending(); + } else { + event_t cur = event_head; + event_t next = cur->next; + while( next != NULL ) { + if( next == event ) { + cur->next = next->next; + break; + } + cur = next; + next = cur->next; + } + } +} + +static void event_dequeue_long( event_t event ) +{ + if( long_event_head == NULL ) { + ERROR( "Empty long event queue but should contain event %d", event->id ); + } else if( long_event_head == event ) { + /* removing queue head */ + long_event_head = long_event_head->next; + } else { + event_t cur = long_event_head; + event_t next = cur->next; + while( next != NULL ) { + if( next == event ) { + cur->next = next->next; + break; + } + cur = next; + next = cur->next; + } + } +} + +void register_event_callback( int eventid, event_func_t func ) +{ + events[eventid].func = func; +} + +void event_schedule( int eventid, uint32_t nanosecs ) +{ + int i; + + nanosecs += sh4r.slice_cycle; + + event_t event = &events[eventid]; + + if( event->nanosecs != NOT_SCHEDULED ) { + /* Event is already scheduled. Remove it from the list first */ + event_cancel(eventid); + } + + event->id = eventid; + event->seconds = 0; + event->nanosecs = nanosecs; + + event_enqueue( event ); +} + +void event_schedule_long( int eventid, uint32_t seconds, uint32_t nanosecs ) { + if( seconds == 0 ) { + event_schedule( eventid, nanosecs ); + } else { + event_t event = &events[eventid]; + + if( event->nanosecs != NOT_SCHEDULED ) { + /* Event is already scheduled. Remove it from the list first */ + event_cancel(eventid); + } + + event->id = eventid; + event->seconds = seconds; + event->nanosecs = nanosecs; + event->next = long_event_head; + long_event_head = event; + } + +} + +void event_cancel( int eventid ) +{ + event_t event = &events[eventid]; + if( event->nanosecs == NOT_SCHEDULED ) { + return; /* not scheduled */ + } else { + event->nanosecs = NOT_SCHEDULED; + if( event->seconds != 0 ) { /* long term event */ + event_dequeue_long( event ); + } else { + event_dequeue( event ); + } + } +} + + +void event_execute() +{ + /* Loop in case we missed some or got a couple scheduled for the same time */ + while( event_head != NULL && event_head->nanosecs <= sh4r.slice_cycle ) { + event_t event = event_head; + event_head = event->next; + event->nanosecs = NOT_SCHEDULED; + // Note: Make sure the internal state is consistent before calling the + // user function, as it will (quite likely) enqueue another event. + event->func( event->id ); + } + + event_update_pending(); +} + +void event_asic_callback( int eventid ) +{ + asic_event( eventid ); +} + +void event_init() +{ + int i; + for( i=0; iid; + fwrite( &id, sizeof(id), 1, f ); + id = long_event_head == NULL ? -1 : long_event_head->id; + fwrite( &id, sizeof(id), 1, f ); + fwrite( &long_scan_time_remaining, sizeof(long_scan_time_remaining), 1, f ); + for( i=0; iid; + fwrite( &id, sizeof(id), 1, f ); + } +} + +int event_load_state( FILE *f ) +{ + int id, i; + fread( &id, sizeof(id), 1, f ); + event_head = id == -1 ? NULL : &events[id]; + fread( &id, sizeof(id), 1, f ); + long_event_head = id == -1 ? NULL : &events[id]; + fread( &long_scan_time_remaining, sizeof(long_scan_time_remaining), 1, f ); + for( i=0; iseconds == 0 ) { + event_t event = long_event_head; + long_event_head = event->next; + event_enqueue(event); + } + + if( long_event_head != NULL ) { + event_t last = long_event_head; + event_t cur = last->next; + while( cur != NULL ) { + if( --cur->seconds == 0 ) { + last->next = cur->next; + event_enqueue(cur); + } else { + last = cur; + } + cur = last->next; + } + } +} + +/** + * Decrement the event time on all pending events by the supplied nanoseconds. + * It may or may not be faster to wrap around instead, but this has the benefit + * of simplicity. + */ +uint32_t event_run_slice( uint32_t nanosecs ) +{ + event_t event = event_head; + while( event != NULL ) { + if( event->nanosecs <= nanosecs ) { + event->nanosecs = 0; + } else { + event->nanosecs -= nanosecs; + } + event = event->next; + } + + long_scan_time_remaining -= nanosecs; + if( long_scan_time_remaining <= 0 ) { + long_scan_time_remaining += LONG_SCAN_PERIOD; + event_scan_long(); + } + + event_update_pending(); + return nanosecs; +} +