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