1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
|
/* Rush.c
COPYRIGHT:
Copyright Denis Pelli, 1997. This file may be distributed freely as long
as this notice accompanies it and any changes are noted in the source.
However, it may not be sold, in source or compiled form, without permission.
It is distributed as is, without any warranty implied or provided. We
accept no liability for any damage or loss resulting from the use of
this software.
PURPOSE:
Run any critical user function with minimal interruption by Mac OS
system software. Without Rush, the Mac OS and device drivers steal
chunks of time whenever they like. E.g. the Zip disk driver, when no
disk is inserted, steals 2 ms once every 3 seconds. For a loop showing a
real-time movie, the possibility of losing 2 milliseconds, out of a
frame time of say 13 ms, substantially reduces the maximum number of
pixels that can be shown per frame if we insist on never missing a
frame. Rush implements two alternate solutions. Apple's guidelines
suggest that driver interrupt tasks should be very brief, passing any
lengthy tasks to the Deferred Task Manager to run later. When
priorityLevel is -1, Rush runs the user code as a deferred task, which
runs normally, except that all other deferred tasks are blocked until
our code finishes. When priorityLevel is 0 the user code runs normally.
When priorityLevel is >0 the user code runs at that raised processor
priority. The Deferred Task Manager doesn't run at all until the
processor priority goes back to zero. Thus any nonzero priorityLevel
will block all deferred tasks. A positive priorityLevel will also block
some primary interrupt tasks; more are blocked the higher the
priorityLevel. At priorityLevel 7 nearly all interrupts are blocked.
When priorityLevel is nonzero we block all the Mac's deferred tasks
while the user function is run. When priorityLevel is 1 (or more) we
block deferred tasks by raising the priority before calling the user
function. When priorityLevel is -1 we block deferred tasks by calling
the user function from within a deferred task.
The deferred-task solution is attractive. All deferred tasks are blocked
until ours finishes, and all urgent interrupt processes (including VBL
and Time Manager updates) occur normally. And keyboard and mouse still
work. This elegant solution was suggested by Bo3b Johnson at Apple
Developer Support on 4/19/97 (Follow-up: 418098).
A slight drawback is that because the deferred task is interrupt driven,
we must save and restore the 68881 floating point registers (if we're
compiled TARGET_RT_MAC_68881) to allow the user's task to use floating
point. Apple says the save/restore takes about 50 times as long as a
MOVE. This overhead might lessen the attractiveness of the deferred-task
solution (priorityLevel -1) when running on 68K machines, but I suspect
that the overhead will be acceptable in most applications.
The raised-priority solution has a different drawback. Raising priority
(to 1 or more) does block the deferred tasks, but also freezes the mouse
and keyboard, and results in abnormal operation of the Time Manager
(overflow and coarse steps) and. The loss of the Time Manager usually
doesn't matter on PCI PowerMacs, because Seconds.c and WaitSeconds.c
then use the excellent UpTime routine, which is immune to priorityLevel.
4/22/97 Testing with Matlab LoopTest.m indicates that Rush.mex runs fine
at all priorities. However, a 2 ms interruption due to the Zip driver
(when no disk is inserted) occurs about one every three seconds at
priorities -1, 0, and 1, and only disappears at priorities 2 and higher.
Contrary to Apple's rules, the Zip driver's 2 ms interruption is NOT a
deferred task. We're hoping that Iomega will fix their driver soon. For
news you might look here:
<http://www.macintouch.com/jazprobs.html>
A THOUGHT ON THE 68881:
Hmm. I wonder. In fact we know for certain that we're interrupting
our own wait loop:
while(!stuff.done) ; // wait until our deferredTask is done
Neither this loop nor the function that it's in have any floating point,
so we know that the interrupt hasn't interrupted any floating-point
code. So the usual assumptions of a subroutine about the prior state of
the FPU are probably valid. The only exception that occurs to me is if
our wait loop is interrupted and that interrupt process is itself
interrupted. However, even in that case we'd only get into trouble if
the interrupted interrupt process was using the 68881 fpu, which is
extremely unlikely because Apple discourages it so strongly (because of
the overhead of saving & restoring the fpu). Thus, we could probably
safely comment out the calls to Save68881 and Restore68881, but I don't
want to do that without testing, and I have little incentive at this
point. If you find that the save/restore overhead is not negligible in
your application, then you may want to try that. If you confirm that
it's safe to ride without these training wheels, please let me know.
EXAMPLE 1:
Let's rush the calculation of c=a+b. (This simple example is solely for
the sake of exposition. I can't think of any reason to rush this. In
real life you'll only want to rush code that must synchronize with
external events, e.g. displaying or recording.)
typedef struct {
int a,b,c;
} Abc;
void main(void)
{
Abc abc;
int priorityLevel=7;
abc.a=a;
abc.b=b;
Rush(priorityLevel,&Summer,&abc);
c=abc.c;
}
void Summer(void *argPtr)
{
Abc *abcPtr;
abcPtr=argPtr;
abcPtr->c = abcPtr->a + abcPtr->b;
}
EXAMPLE 2:
This example illustrates something closer to the use for which Rush.c
was created. Here we call back to the main application (i.e. Matlab) to
rush high-level code. The C subroutine called "mexFunction" is a MEX
function, callable from Matlab programs, e.g. Rush('c=a+b'). The
mexFunction receives a string of Matlab code, e.g. "c=a+b". Our rushed C
routine, fun(), asks the Matlab application to use "eval" to execute the
string, which has the effect of rushing this bit of Matlab code. Again,
in real life you'll only want to rush code that must synchronize with
external events. In my research, we now Rush all our Matlab display
loops, to produce frame-accurate real-time movies. The call to
mexSetTrapFlag (a Matlab internal routine) is significant. It tells
Matlab, if it encounters an error, to nevertheless return here to the
caller. It would be bad for Matlab to print an error message and stop
without returning, because the priorityLevel may have frozen the
keyboard and mouse, and a deferred task will hang forever if it tries to
access the disk. As implemented here, Matlab always returns, even if it
encounters an error, so everything's hunky dory.
typedef struct{
mxArray *mxString;
long error;
} Stuff;
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, CONSTmxArray *prhs[])
{
Stuff stuff;
stuff.mxString=prhs[0];
stuff.error=0;
Rush(priorityLevel,&fun,&stuff);
}
void fun(void *argPtr)
{
Stuff *stuffPtr;
mxArray *plhs[1],*prhs[1];
stuffPtr=(Stuff *)argPtr;
prhs[0]=stuffPtr->mxString;
mexSetTrapFlag(1);
stuffPtr->error=mexCallMATLAB(0,plhs,1,prhs,"eval");
mexSetTrapFlag(0);
}
HISTORY:
4/21/97 dgp Wrote it as a MEX file.
4/22/97 dgp Polished the code. Marked the done flag as "volatile",
since it's set by the deferred task at interrupt time.
5/2/97 dgp Fixed bug in definition of GetA1 declaration to fix 68K crash reported by dhb.
6/22/97 dgp Updated comments above.
7/1/97 dgp As requested by Josh Solomon, removed the mex-specific stuff to leave this
generic version that i'm adding to the VideoToolbox. Josh wants to use
it to create a Rush facility in Mathematica, like the one I created in Matlab.
7/9/97 dgp Now save and restore the 68K fpu registers (e.g. 68881) when necessary.
This allows the user's task to safely use floating point. The
saving and restoring is only necessary when TARGET_RT_MAC_68881 is true and the
priorityLevel is -1. The need for this is explained in Apple Technote hw22.
<http://developer.apple.com/technotes/hw/hw_22.html>
7/31/97 dgp Polished comments. Added conditionals for Matlab, which avoid calling GetPriority, by maintaining
our own copy of the priorityLevel. This is based on the impression I had, while debugging, that
calling GetPriority occassionally crashed. I'm not sure that impression was right, but this approach
worked well when I tested the previous incarnation of Rush, so i'm inclined to keep doing it this way.
David mentioned that the loops were running a bit slow on 68K, so I'm disabling the 68881 saving, since
I think it's superfluous because I know that the code being interrupted doesn't use floating point.
8/8/97 dhb Force include of Psychtoolbox.h if Matlab is defined.
1/6/98 dgp Include Psychtoolbox.h only if Matlab is true. (We need it for the call to SetPsychPriority.)
Fixed illegal goof that generated compiler error
when compiled with Matlab false (apparently I'd never tested that case before).
3/22/99 dgp Alerted by an Apple Tech Note, we now save and restore the PowerPC FP registers (see SaveFP.c) before running
our deferred task. <http://developer.apple.com/technotes/tn/tn1158.html> It is conceivable
that this isn't necessary for a deferred task, but I suspect it is.
12/9/99 dgp Updated for Universal Headers 3.3.
KNOWN BUGS:
*/
#define NEED_TO_SAVE_FPU 0
#if MATLAB
#include <Psychtoolbox.h>
#else
#include <VideoToolbox.h>
#endif
#ifndef __PROCESSES__
#include <Processes.h> // ProcessSerialNumber
#endif
#ifndef __TRAPS__
#include <Traps.h> // _DTInstall
#endif
#if TARGET_CPU_68K
/*
Here we decide whether we're being compiled
as an application or code resource.
In a code resource, A4 is used as the global pointer.
In an application, it's A5.
*/
#ifndef __A5__
// CW Pro 2 defines __A5__, but I don't know about earlier versions or other compilers.
#define __A5__ !MATLAB
#endif
#pragma parameter __D0 GetA1()
long GetA1(void)= 0x2009; // MOVE.L A1,D0
#pragma parameter __D0 GetA4()
long GetA4(void)= 0x200C; // MOVE.L A4,D0
#pragma parameter __D0 GetA5()
long GetA5(void)= 0x200D; // MOVE.L A5,D0
#if THINK_C
#pragma parameter __D0 SetA4(__D0)
long SetA4(long) = 0xC18C; // EXG D0,A4
#else
long SetA4(long:__D0):__D0 = 0xC18C; // EXG D0,A4
#endif
#else
#define GetA1() 0L
#define GetA4() 0L
#define GetA5() 0L
#define SetA4(x) 0L
#endif
// Inline code to save and restore FPU on 68881 etc.
// This is needed if we've arrived where we are by an interrupt
// that may be interrupting a floating point calculation.
// See Apple's Technote HW22 "Cooperating with the coprocessor"
// <http://developer.apple.com/technotes/hw/hw_22.html>
void Save68881(void);
void Restore68881(void);
#if TARGET_RT_MAC_68881
void Save68881(void)={0xF327,0xF227,0xE0FF}; // FSAVE -(A7) ; save FPU state
// FMOVEM FP0-FP7,-(A7) ; save FPU regs
void Restore68881(void)={0xF21F,0xD0FF,0xF35F}; // FMOVEM (A7)+,FP0-FP7 ; restore FPU regs
// FRESTORE (A7)+ ; restore FPU state
#endif
typedef struct{
ProcessSerialNumber psn;
DeferredTask *deferredTaskPtr;
volatile long A,error,failedAttempts;
void (*functionPtr)(void *argPtr);
void *argPtr;
volatile psych_bool done;
int priorityLevel;
} Stuff;
#if TARGET_CPU_PPC
static void OurDeferredTask(register Stuff *stuffPtr);
#else
static void OurDeferredTask(void);
#endif
static void CallFunction(Stuff *stuffPtr);
int Rush(int priorityLevel,void (*functionPtr)(void *argPtr),void *argPtr);
int Rush(int priorityLevel,void (*functionPtr)(void *argPtr),void *argPtr)
{
static psych_bool firstTime=1,deferAvailable;
static DeferredTaskUPP deferredTaskUPP;
int oldPriority;
DeferredTask deferredTask;
int error;
Stuff stuff;
if(firstTime){
deferAvailable=TrapAvailable(_DTInstall);
deferredTaskUPP=NewDeferredTaskProc(OurDeferredTask);
firstTime=0;
}
if(priorityLevel<-1 || priorityLevel>7)PrintfExit("%s: Illegal priorityLevel %d.",__FILE__,priorityLevel);
error=GetCurrentProcess(&stuff.psn);
stuff.functionPtr=functionPtr;
stuff.argPtr=argPtr;
stuff.done=0;
stuff.error=0;
stuff.failedAttempts=0;
stuff.deferredTaskPtr=NULL;
stuff.priorityLevel=priorityLevel;
switch(priorityLevel){
case -1:
// Run user's function as a deferred task.
if(!deferAvailable)PrintfExit("%s: Your System is too old: no Deferred Task Manager.",__FILE__);
#if MATLAB
oldPriority=GetPsychTable()->priority;
#else
oldPriority=GetPriority();
#endif
if(oldPriority>0){
#if MATLAB
SetPsychPriority(0);
#else
SetPriority(0);
#endif
PrintfExit("%s: Can't defer task while processor priority is above zero.",__FILE__);
}
#if !__A5__
stuff.A=GetA4();
#else
stuff.A=GetA5();
#endif
stuff.deferredTaskPtr=&deferredTask;
deferredTask.qType=dtQType;
deferredTask.dtAddr=deferredTaskUPP;
deferredTask.dtReserved=0;
deferredTask.dtParam=(long)&stuff;
error=DTInstall(stuff.deferredTaskPtr);
if(error)PrintfExit("%s: DTInstall error %d.",__FILE__,error);
while(!stuff.done) ; // wait until our deferredTask is done
break;
case 0:
// Run user's function normally.
CallFunction(&stuff);
break;
default:
// Run user's function at raised processor priority.
#if MATLAB
oldPriority=GetPsychTable()->priority;
SetPsychPriority(priorityLevel);
#else
oldPriority=GetPriority();
SetPriority(priorityLevel);
#endif
CallFunction(&stuff);
#if MATLAB
SetPsychPriority(oldPriority);
#else
SetPriority(oldPriority);
#endif
break;
}
if(stuff.error==1234)
PrintfExit("%s: user function never ran; always interrupted wrong process.",__FILE__);
if(stuff.failedAttempts)
printf("%s: WARNING: user function ran only after %ld attempts that interrupted other processes.\n",__FILE__,stuff.failedAttempts);
return stuff.failedAttempts;
}
#if (THINK_C || THINK_CPLUS || SYMANTEC_C)
#pragma options(!profile) // it would be dangerous to call the profiler from here
#pragma options(assign_registers,redundant_loads)
#endif
#if __MWERKS__ && __profile__
#pragma profile off // on 68k it would be dangerous to call the profiler from here
#endif
#if TARGET_CPU_PPC
static void OurDeferredTask(register Stuff *stuffPtr)
{
FPSaveRec fpSave;
SaveFP(&fpSave); // Only for PowerPC
CallFunction(stuffPtr);
RestoreFP(&fpSave);
}
#else
// The saving/restoring of 68881 wouldn't work if OurDeferredTask had any C use of floating point, because
// in that case the compiler would insert floating point instructions at the beginning of the routine,
// inevitably BEFORE we attempt to save the state, defeating our purpose. It's ok for the CallFunction() or the
// routine(s) that it calls to use floating point.
static void OurDeferredTask(void)
{
Stuff *stuffPtr;
long oldA;
stuffPtr=(void *)GetA1();
#if !__A5__
oldA=SetA4(stuffPtr->A);
#else
oldA=SetA5(stuffPtr->A);
#endif
#if TARGET_RT_MAC_68881 && NEED_TO_SAVE_FPU
Save68881();
#endif
CallFunction(stuffPtr);
#if TARGET_RT_MAC_68881 && NEED_TO_SAVE_FPU
Restore68881();
#endif
#if !__A5__
SetA4(oldA);
#else
SetA5(oldA);
#endif
}
#endif
static void CallFunction(Stuff *stuffPtr)
{
ProcessSerialNumber psn;
psych_bool isOurs;
int error;
error=GetCurrentProcess(&psn);
error=SameProcess(&stuffPtr->psn,&psn,&isOurs); // compare psns.
if(isOurs){
// It's safe. Let's do it.
(*stuffPtr->functionPtr)(stuffPtr->argPtr);
stuffPtr->error=0;
stuffPtr->done=1;
}else{
// Oops. We're interrupting some other process. It's unsafe to run now.
// Let's lie low, and reinstall ourselves to try again later.
stuffPtr->failedAttempts++;
if(stuffPtr->failedAttempts<10 && stuffPtr->deferredTaskPtr!=NULL)
stuffPtr->error=DTInstall(stuffPtr->deferredTaskPtr);
else stuffPtr->error=1234;
if(stuffPtr->error)stuffPtr->done=1; // Can't do it; give up.
}
}
#if 0
// This code is copied from Apple Technote HW22 "Cooperating with the Coprocessor"
// http://developer.apple.com/technotes/hw/hw_22.html
// DON'T try to run these. They're here solely to get a disassembly. The assembly
// code must be run as inline code, which the definitions at the top of this
// file accomplish, based on the THINK C disassemblies of these functions.
// THINK C knows floating point assembly directives; CodeWarrior 10 doesn't.
void Save68881(void);
void Restore68881(void);
void Save68881(void){
asm{
FSAVE -(SP) ; save the FP state
FMOVEM.X FP0-FP7,-(SP) ; save the FP regs we use
}
}
void Restore68881(void){
asm {
FMOVEM.X (SP)+,FP0-FP7 ; replace the FP regs we used
FRESTORE (SP)+ ; restore the FP state
}
}
#endif
/*
As disassembled by THINK C. Note that we got FMOVEM not FMOVEM.X. I suspect that
this difference doesn't matter here, but I don't really know. Maybe it's not a difference
at all.
Save68881:
00000000: F327 FSAVE -(A7)
00000002: F227 E0FF FMOVEM FP0-FP7,-(A7)
Restore68881:
00000000: F21F D0FF FMOVEM (A7)+,FP0-FP7
00000004: F35F FRESTORE (A7)+
*/
|