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