nkeynes@265 | 1 | /**
|
nkeynes@422 | 2 | * $Id: eventq.c,v 1.2 2007-10-06 08:59:42 nkeynes Exp $
|
nkeynes@265 | 3 | *
|
nkeynes@265 | 4 | * Simple implementation of one-shot timers. Effectively this allows IO
|
nkeynes@265 | 5 | * devices to wait until a particular time before completing. We expect
|
nkeynes@265 | 6 | * there to be at least half a dozen or so continually scheduled events
|
nkeynes@265 | 7 | * (TMU and PVR2), peaking around 20+.
|
nkeynes@265 | 8 | *
|
nkeynes@265 | 9 | * Copyright (c) 2005 Nathan Keynes.
|
nkeynes@265 | 10 | *
|
nkeynes@265 | 11 | * This program is free software; you can redistribute it and/or modify
|
nkeynes@265 | 12 | * it under the terms of the GNU General Public License as published by
|
nkeynes@265 | 13 | * the Free Software Foundation; either version 2 of the License, or
|
nkeynes@265 | 14 | * (at your option) any later version.
|
nkeynes@265 | 15 | *
|
nkeynes@265 | 16 | * This program is distributed in the hope that it will be useful,
|
nkeynes@265 | 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
nkeynes@265 | 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
nkeynes@265 | 19 | * GNU General Public License for more details.
|
nkeynes@265 | 20 | */
|
nkeynes@265 | 21 |
|
nkeynes@265 | 22 | #include <assert.h>
|
nkeynes@265 | 23 | #include "dreamcast.h"
|
nkeynes@265 | 24 | #include "eventq.h"
|
nkeynes@422 | 25 | #include "asic.h"
|
nkeynes@265 | 26 | #include "sh4core.h"
|
nkeynes@265 | 27 |
|
nkeynes@265 | 28 | #define LONG_SCAN_PERIOD 1000000000 /* 1 second */
|
nkeynes@265 | 29 |
|
nkeynes@265 | 30 | typedef struct event {
|
nkeynes@265 | 31 | uint32_t id;
|
nkeynes@265 | 32 | uint32_t seconds;
|
nkeynes@265 | 33 | uint32_t nanosecs;
|
nkeynes@265 | 34 | event_func_t func;
|
nkeynes@265 | 35 |
|
nkeynes@265 | 36 | struct event *next;
|
nkeynes@265 | 37 | } *event_t;
|
nkeynes@265 | 38 |
|
nkeynes@265 | 39 | static struct event events[MAX_EVENT_ID];
|
nkeynes@265 | 40 |
|
nkeynes@265 | 41 | /**
|
nkeynes@265 | 42 | * Countdown to the next scan of the long-duration list (greater than 1 second).
|
nkeynes@265 | 43 | */
|
nkeynes@265 | 44 | static int long_scan_time_remaining;
|
nkeynes@265 | 45 |
|
nkeynes@265 | 46 | static event_t event_head;
|
nkeynes@265 | 47 | static event_t long_event_head;
|
nkeynes@265 | 48 |
|
nkeynes@265 | 49 | void event_reset();
|
nkeynes@265 | 50 | void event_init();
|
nkeynes@265 | 51 | uint32_t event_run_slice( uint32_t nanosecs );
|
nkeynes@265 | 52 | void event_save_state( FILE *f );
|
nkeynes@265 | 53 | int event_load_state( FILE * f );
|
nkeynes@265 | 54 |
|
nkeynes@265 | 55 | struct dreamcast_module eventq_module = { "EVENTQ", event_init, event_reset, NULL, event_run_slice,
|
nkeynes@265 | 56 | NULL, event_save_state, event_load_state };
|
nkeynes@265 | 57 |
|
nkeynes@265 | 58 | static void event_update_pending( )
|
nkeynes@265 | 59 | {
|
nkeynes@265 | 60 | if( event_head == NULL ) {
|
nkeynes@265 | 61 | if( !(sh4r.event_types & PENDING_IRQ) ) {
|
nkeynes@265 | 62 | sh4r.event_pending = NOT_SCHEDULED;
|
nkeynes@265 | 63 | }
|
nkeynes@265 | 64 | sh4r.event_types &= (~PENDING_EVENT);
|
nkeynes@265 | 65 | } else {
|
nkeynes@265 | 66 | if( !(sh4r.event_types & PENDING_IRQ) ) {
|
nkeynes@265 | 67 | sh4r.event_pending = event_head->nanosecs;
|
nkeynes@265 | 68 | }
|
nkeynes@265 | 69 | sh4r.event_types |= PENDING_EVENT;
|
nkeynes@265 | 70 | }
|
nkeynes@265 | 71 | }
|
nkeynes@265 | 72 |
|
nkeynes@265 | 73 | uint32_t event_get_next_time( )
|
nkeynes@265 | 74 | {
|
nkeynes@265 | 75 | if( event_head == NULL ) {
|
nkeynes@265 | 76 | return NOT_SCHEDULED;
|
nkeynes@265 | 77 | } else {
|
nkeynes@265 | 78 | return event_head->nanosecs;
|
nkeynes@265 | 79 | }
|
nkeynes@265 | 80 | }
|
nkeynes@265 | 81 |
|
nkeynes@265 | 82 | /**
|
nkeynes@265 | 83 | * Add the event to the short queue.
|
nkeynes@265 | 84 | */
|
nkeynes@265 | 85 | static void event_enqueue( event_t event )
|
nkeynes@265 | 86 | {
|
nkeynes@265 | 87 | if( event_head == NULL || event->nanosecs < event_head->nanosecs ) {
|
nkeynes@265 | 88 | event->next = event_head;
|
nkeynes@265 | 89 | event_head = event;
|
nkeynes@265 | 90 | event_update_pending();
|
nkeynes@265 | 91 | } else {
|
nkeynes@265 | 92 | event_t cur = event_head;
|
nkeynes@265 | 93 | event_t next = cur->next;
|
nkeynes@265 | 94 | while( next != NULL && event->nanosecs >= next->nanosecs ) {
|
nkeynes@265 | 95 | cur = next;
|
nkeynes@265 | 96 | next = cur->next;
|
nkeynes@265 | 97 | }
|
nkeynes@265 | 98 | event->next = next;
|
nkeynes@265 | 99 | cur->next = event;
|
nkeynes@265 | 100 | }
|
nkeynes@265 | 101 | }
|
nkeynes@265 | 102 |
|
nkeynes@265 | 103 | static void event_dequeue( event_t event )
|
nkeynes@265 | 104 | {
|
nkeynes@265 | 105 | if( event_head == NULL ) {
|
nkeynes@265 | 106 | ERROR( "Empty event queue but should contain event %d", event->id );
|
nkeynes@265 | 107 | } else if( event_head == event ) {
|
nkeynes@265 | 108 | /* removing queue head */
|
nkeynes@265 | 109 | event_head = event_head->next;
|
nkeynes@265 | 110 | event_update_pending();
|
nkeynes@265 | 111 | } else {
|
nkeynes@265 | 112 | event_t cur = event_head;
|
nkeynes@265 | 113 | event_t next = cur->next;
|
nkeynes@265 | 114 | while( next != NULL ) {
|
nkeynes@265 | 115 | if( next == event ) {
|
nkeynes@265 | 116 | cur->next = next->next;
|
nkeynes@265 | 117 | break;
|
nkeynes@265 | 118 | }
|
nkeynes@265 | 119 | cur = next;
|
nkeynes@265 | 120 | next = cur->next;
|
nkeynes@265 | 121 | }
|
nkeynes@265 | 122 | }
|
nkeynes@265 | 123 | }
|
nkeynes@265 | 124 |
|
nkeynes@265 | 125 | static void event_dequeue_long( event_t event )
|
nkeynes@265 | 126 | {
|
nkeynes@265 | 127 | if( long_event_head == NULL ) {
|
nkeynes@265 | 128 | ERROR( "Empty long event queue but should contain event %d", event->id );
|
nkeynes@265 | 129 | } else if( long_event_head == event ) {
|
nkeynes@265 | 130 | /* removing queue head */
|
nkeynes@265 | 131 | long_event_head = long_event_head->next;
|
nkeynes@265 | 132 | } else {
|
nkeynes@265 | 133 | event_t cur = long_event_head;
|
nkeynes@265 | 134 | event_t next = cur->next;
|
nkeynes@265 | 135 | while( next != NULL ) {
|
nkeynes@265 | 136 | if( next == event ) {
|
nkeynes@265 | 137 | cur->next = next->next;
|
nkeynes@265 | 138 | break;
|
nkeynes@265 | 139 | }
|
nkeynes@265 | 140 | cur = next;
|
nkeynes@265 | 141 | next = cur->next;
|
nkeynes@265 | 142 | }
|
nkeynes@265 | 143 | }
|
nkeynes@265 | 144 | }
|
nkeynes@265 | 145 |
|
nkeynes@265 | 146 | void register_event_callback( int eventid, event_func_t func )
|
nkeynes@265 | 147 | {
|
nkeynes@265 | 148 | events[eventid].func = func;
|
nkeynes@265 | 149 | }
|
nkeynes@265 | 150 |
|
nkeynes@265 | 151 | void event_schedule( int eventid, uint32_t nanosecs )
|
nkeynes@265 | 152 | {
|
nkeynes@265 | 153 | nanosecs += sh4r.slice_cycle;
|
nkeynes@265 | 154 |
|
nkeynes@265 | 155 | event_t event = &events[eventid];
|
nkeynes@265 | 156 |
|
nkeynes@265 | 157 | if( event->nanosecs != NOT_SCHEDULED ) {
|
nkeynes@265 | 158 | /* Event is already scheduled. Remove it from the list first */
|
nkeynes@265 | 159 | event_cancel(eventid);
|
nkeynes@265 | 160 | }
|
nkeynes@265 | 161 |
|
nkeynes@265 | 162 | event->id = eventid;
|
nkeynes@265 | 163 | event->seconds = 0;
|
nkeynes@265 | 164 | event->nanosecs = nanosecs;
|
nkeynes@265 | 165 |
|
nkeynes@265 | 166 | event_enqueue( event );
|
nkeynes@265 | 167 | }
|
nkeynes@265 | 168 |
|
nkeynes@265 | 169 | void event_schedule_long( int eventid, uint32_t seconds, uint32_t nanosecs ) {
|
nkeynes@265 | 170 | if( seconds == 0 ) {
|
nkeynes@265 | 171 | event_schedule( eventid, nanosecs );
|
nkeynes@265 | 172 | } else {
|
nkeynes@265 | 173 | event_t event = &events[eventid];
|
nkeynes@265 | 174 |
|
nkeynes@265 | 175 | if( event->nanosecs != NOT_SCHEDULED ) {
|
nkeynes@265 | 176 | /* Event is already scheduled. Remove it from the list first */
|
nkeynes@265 | 177 | event_cancel(eventid);
|
nkeynes@265 | 178 | }
|
nkeynes@265 | 179 |
|
nkeynes@265 | 180 | event->id = eventid;
|
nkeynes@265 | 181 | event->seconds = seconds;
|
nkeynes@265 | 182 | event->nanosecs = nanosecs;
|
nkeynes@265 | 183 | event->next = long_event_head;
|
nkeynes@265 | 184 | long_event_head = event;
|
nkeynes@265 | 185 | }
|
nkeynes@265 | 186 |
|
nkeynes@265 | 187 | }
|
nkeynes@265 | 188 |
|
nkeynes@265 | 189 | void event_cancel( int eventid )
|
nkeynes@265 | 190 | {
|
nkeynes@265 | 191 | event_t event = &events[eventid];
|
nkeynes@265 | 192 | if( event->nanosecs == NOT_SCHEDULED ) {
|
nkeynes@265 | 193 | return; /* not scheduled */
|
nkeynes@265 | 194 | } else {
|
nkeynes@265 | 195 | event->nanosecs = NOT_SCHEDULED;
|
nkeynes@265 | 196 | if( event->seconds != 0 ) { /* long term event */
|
nkeynes@265 | 197 | event_dequeue_long( event );
|
nkeynes@265 | 198 | } else {
|
nkeynes@265 | 199 | event_dequeue( event );
|
nkeynes@265 | 200 | }
|
nkeynes@265 | 201 | }
|
nkeynes@265 | 202 | }
|
nkeynes@265 | 203 |
|
nkeynes@265 | 204 |
|
nkeynes@265 | 205 | void event_execute()
|
nkeynes@265 | 206 | {
|
nkeynes@265 | 207 | /* Loop in case we missed some or got a couple scheduled for the same time */
|
nkeynes@265 | 208 | while( event_head != NULL && event_head->nanosecs <= sh4r.slice_cycle ) {
|
nkeynes@265 | 209 | event_t event = event_head;
|
nkeynes@265 | 210 | event_head = event->next;
|
nkeynes@265 | 211 | event->nanosecs = NOT_SCHEDULED;
|
nkeynes@265 | 212 | // Note: Make sure the internal state is consistent before calling the
|
nkeynes@265 | 213 | // user function, as it will (quite likely) enqueue another event.
|
nkeynes@265 | 214 | event->func( event->id );
|
nkeynes@265 | 215 | }
|
nkeynes@265 | 216 |
|
nkeynes@265 | 217 | event_update_pending();
|
nkeynes@265 | 218 | }
|
nkeynes@265 | 219 |
|
nkeynes@265 | 220 | void event_asic_callback( int eventid )
|
nkeynes@265 | 221 | {
|
nkeynes@265 | 222 | asic_event( eventid );
|
nkeynes@265 | 223 | }
|
nkeynes@265 | 224 |
|
nkeynes@265 | 225 | void event_init()
|
nkeynes@265 | 226 | {
|
nkeynes@265 | 227 | int i;
|
nkeynes@265 | 228 | for( i=0; i<MAX_EVENT_ID; i++ ) {
|
nkeynes@265 | 229 | events[i].id = i;
|
nkeynes@265 | 230 | events[i].nanosecs = NOT_SCHEDULED;
|
nkeynes@265 | 231 | if( i < 96 ) {
|
nkeynes@265 | 232 | events[i].func = event_asic_callback;
|
nkeynes@265 | 233 | } else {
|
nkeynes@265 | 234 | events[i].func = NULL;
|
nkeynes@265 | 235 | }
|
nkeynes@265 | 236 | events[i].next = NULL;
|
nkeynes@265 | 237 | }
|
nkeynes@265 | 238 | event_head = NULL;
|
nkeynes@265 | 239 | long_event_head = NULL;
|
nkeynes@265 | 240 | long_scan_time_remaining = LONG_SCAN_PERIOD;
|
nkeynes@265 | 241 | }
|
nkeynes@265 | 242 |
|
nkeynes@265 | 243 |
|
nkeynes@265 | 244 |
|
nkeynes@265 | 245 | void event_reset()
|
nkeynes@265 | 246 | {
|
nkeynes@265 | 247 | int i;
|
nkeynes@265 | 248 | event_head = NULL;
|
nkeynes@265 | 249 | long_event_head = NULL;
|
nkeynes@265 | 250 | long_scan_time_remaining = LONG_SCAN_PERIOD;
|
nkeynes@265 | 251 | for( i=0; i<MAX_EVENT_ID; i++ ) {
|
nkeynes@265 | 252 | events[i].nanosecs = NOT_SCHEDULED;
|
nkeynes@265 | 253 | }
|
nkeynes@265 | 254 | }
|
nkeynes@265 | 255 |
|
nkeynes@265 | 256 | void event_save_state( FILE *f )
|
nkeynes@265 | 257 | {
|
nkeynes@265 | 258 | int id, i;
|
nkeynes@265 | 259 | id = event_head == NULL ? -1 : event_head->id;
|
nkeynes@265 | 260 | fwrite( &id, sizeof(id), 1, f );
|
nkeynes@265 | 261 | id = long_event_head == NULL ? -1 : long_event_head->id;
|
nkeynes@265 | 262 | fwrite( &id, sizeof(id), 1, f );
|
nkeynes@265 | 263 | fwrite( &long_scan_time_remaining, sizeof(long_scan_time_remaining), 1, f );
|
nkeynes@265 | 264 | for( i=0; i<MAX_EVENT_ID; i++ ) {
|
nkeynes@265 | 265 | fwrite( &events[i].id, sizeof(uint32_t), 3, f ); /* First 3 words from structure */
|
nkeynes@265 | 266 | id = events[i].next == NULL ? -1 : events[i].next->id;
|
nkeynes@265 | 267 | fwrite( &id, sizeof(id), 1, f );
|
nkeynes@265 | 268 | }
|
nkeynes@265 | 269 | }
|
nkeynes@265 | 270 |
|
nkeynes@265 | 271 | int event_load_state( FILE *f )
|
nkeynes@265 | 272 | {
|
nkeynes@265 | 273 | int id, i;
|
nkeynes@265 | 274 | fread( &id, sizeof(id), 1, f );
|
nkeynes@265 | 275 | event_head = id == -1 ? NULL : &events[id];
|
nkeynes@265 | 276 | fread( &id, sizeof(id), 1, f );
|
nkeynes@265 | 277 | long_event_head = id == -1 ? NULL : &events[id];
|
nkeynes@265 | 278 | fread( &long_scan_time_remaining, sizeof(long_scan_time_remaining), 1, f );
|
nkeynes@265 | 279 | for( i=0; i<MAX_EVENT_ID; i++ ) {
|
nkeynes@265 | 280 | fread( &events[i].id, sizeof(uint32_t), 3, f );
|
nkeynes@265 | 281 | fread( &id, sizeof(id), 1, f );
|
nkeynes@265 | 282 | events[i].next = id == -1 ? NULL : &events[id];
|
nkeynes@265 | 283 | }
|
nkeynes@265 | 284 | return 0;
|
nkeynes@265 | 285 | }
|
nkeynes@265 | 286 |
|
nkeynes@265 | 287 | /**
|
nkeynes@265 | 288 | * Scan all entries in the long queue, decrementing each by 1 second. Entries
|
nkeynes@265 | 289 | * that are now < 1 second are moved to the short queue.
|
nkeynes@265 | 290 | */
|
nkeynes@265 | 291 | static void event_scan_long()
|
nkeynes@265 | 292 | {
|
nkeynes@265 | 293 | while( long_event_head != NULL && --long_event_head->seconds == 0 ) {
|
nkeynes@265 | 294 | event_t event = long_event_head;
|
nkeynes@265 | 295 | long_event_head = event->next;
|
nkeynes@265 | 296 | event_enqueue(event);
|
nkeynes@265 | 297 | }
|
nkeynes@265 | 298 |
|
nkeynes@265 | 299 | if( long_event_head != NULL ) {
|
nkeynes@265 | 300 | event_t last = long_event_head;
|
nkeynes@265 | 301 | event_t cur = last->next;
|
nkeynes@265 | 302 | while( cur != NULL ) {
|
nkeynes@265 | 303 | if( --cur->seconds == 0 ) {
|
nkeynes@265 | 304 | last->next = cur->next;
|
nkeynes@265 | 305 | event_enqueue(cur);
|
nkeynes@265 | 306 | } else {
|
nkeynes@265 | 307 | last = cur;
|
nkeynes@265 | 308 | }
|
nkeynes@265 | 309 | cur = last->next;
|
nkeynes@265 | 310 | }
|
nkeynes@265 | 311 | }
|
nkeynes@265 | 312 | }
|
nkeynes@265 | 313 |
|
nkeynes@265 | 314 | /**
|
nkeynes@265 | 315 | * Decrement the event time on all pending events by the supplied nanoseconds.
|
nkeynes@265 | 316 | * It may or may not be faster to wrap around instead, but this has the benefit
|
nkeynes@265 | 317 | * of simplicity.
|
nkeynes@265 | 318 | */
|
nkeynes@265 | 319 | uint32_t event_run_slice( uint32_t nanosecs )
|
nkeynes@265 | 320 | {
|
nkeynes@265 | 321 | event_t event = event_head;
|
nkeynes@265 | 322 | while( event != NULL ) {
|
nkeynes@265 | 323 | if( event->nanosecs <= nanosecs ) {
|
nkeynes@265 | 324 | event->nanosecs = 0;
|
nkeynes@265 | 325 | } else {
|
nkeynes@265 | 326 | event->nanosecs -= nanosecs;
|
nkeynes@265 | 327 | }
|
nkeynes@265 | 328 | event = event->next;
|
nkeynes@265 | 329 | }
|
nkeynes@265 | 330 |
|
nkeynes@265 | 331 | long_scan_time_remaining -= nanosecs;
|
nkeynes@265 | 332 | if( long_scan_time_remaining <= 0 ) {
|
nkeynes@265 | 333 | long_scan_time_remaining += LONG_SCAN_PERIOD;
|
nkeynes@265 | 334 | event_scan_long();
|
nkeynes@265 | 335 | }
|
nkeynes@265 | 336 |
|
nkeynes@265 | 337 | event_update_pending();
|
nkeynes@265 | 338 | return nanosecs;
|
nkeynes@265 | 339 | }
|
nkeynes@265 | 340 |
|