/*
******* Enforcer **************************************************************
*
*   NAME
*	Enforcer V37 - An advanced version of Enforcer - Requires V37
*
*   SYNOPSIS
*	Enforcer - A tool to watch for illegal memory accesses
*
*   FUNCTION
*	Enforcer will use the MMU in the advanced 680x0 processors
*	to set up MMU tables to watch for illegal accesses to memory
*	such as the low-page and non-existent pages.
*
*	To use, run Enforcer (plus any options you may wish)
*	If you wish to detach, just use RUN >NIL: <NIL: to start it.
*	You can also start it from the Workbench.  When started from Workbench,
*	Enforcer will read the tooltypes of its icon or selected project icon
*	for its options.  (See the sample project icons)
*
*	Enforcer should only be run *after* SetPatch.
*
*	If SegTracker is running in the system when Enforcer is started,
*	Enforcer will use the public SegTracker seglist tracking for
*	identifying the hits.
*
*   INPUTS
*	The options for Enforcer are as follows:
*
*	QUIET/S        - This tells Enforcer to not complain about any invalid
*	                 access and to just build MMU tables for cache setting
*	                 reasons -- mainly used in conjunction with an
*	                 Amiga BridgeBoard in a 68030 environment so that
*	                 the system can run with the data cache turned on.
*	                 In this case,
*	                                RUN >NIL: Enforcer QUIET
*	                 should be placed into the startup-sequence right
*	                 after SetPatch.
*
*	TINY/S         - This tells Enforcer to output a minimal hit.  The
*	                 output is basically the first line of the Enforcer
*	                 hit.  (see below)
*
*	SMALL/S        - This tells Enforcer to output the hit line, the
*	                 USP: line, and the Name: line.  (This means that
*	                 no register or stack display will be output)
*
*	SHOWPC/S       - This tells Enforcer to also output the two lines
*	                 that contain the memory area around the PC where
*	                 the hit happened.  Useful for disassembly.
*	                 This option will not do anything if QUIET, SMALL or
*	                 TINY output modes are selected.
*
*	STACKLINES/K/N - This lets you pick the number of lines of stack
*	                 backtrace to display.  The default is 2.  If set
*	                 to 0, no stack backtrace will be displayed.  There
*	                 is NO ENFORCED LIMIT on the number of lines.
*
*	STACKCHECK/S   - This option tells Enforcer that you wish all of
*	                 the long words displayed in the stack to be checked
*	                 against the global seglists via SegTracker.
*	                 This will tell you what seglist various return
*	                 addresses are on the stack.  If you are not
*	                 displaying stack information in the Enforcer hit
*	                 then STACKCHECK will have nothing to check.
*	                 If you are displaying stack information, then
*	                 each long word will be check and only those which
*	                 are in one of the tracked seglists will be
*	                 displayed in a SegTracker line.
*	                 The output will show the PC address first and
*	                 then work its way back on the stack such that you
*	                 can read it from bottom up as the order of calling
*	                 or from top down as the stack-frame backtrace.
*
*	AREGCHECK/S    - This option tells Enforcer that you wish all of
*	                 the values in the Address Registers checked via
*	                 SegTracker, much like STACKCHECK.
*
*	DREGCHECK/S    - This option tells Enforcer that you wish all of
*	                 the values in the Data Registers checked via
*	                 SegTracker, much like STACKCHECK.
*
*	DATESTAMP/S    - This makes Enforcer output a date and time with each
*	                 hit.  Due to the nature of the way Enforcer must
*	                 work, the time can not be read during the Enforcer
*	                 hit itself so the time output will be the last time
*	                 value the main Enforcer task set up.  Enforcer will
*	                 update this value every second as to try to not
*	                 use any real CPU time.  The time displayed in the
*	                 hit will thus be exact.
*	                 (Assuming the system clock is correct.)
*	                 The date is output before anything from the hit
*	                 other than the optional introduction string.
*
*	DEADLY/S       - This makes Enforcer be a bit more nasty.  Normally,
*	                 when an illegal read happens, Enforcer returns 0
*	                 as the result of this read.  With this option,
*	                 Enforcer will return $ABADFEED as the read data.
*	                 This option can make programs with Enforcer hits
*	                 cause even more hits.
*
*	FSPACE/S       - This option will make the special $00F00000 address
*	                 space available for writing to.  This is useful for
*	                 those people with $00F00000 boards.  Mainly Commodore
*	                 internal development work -- should only be used
*	                 in that enviroment.
*
*	VERBOSE/S      - This option will make Enforcer display information
*	                 as to the mapping of the I/O boards and other
*	                 technical information.  This information maybe useful
*	                 in specialized debugging.
*
*	LED/K/N        - This option lets you specify the speed at which
*	                 the LED will be toggled for each Enforcer hit.
*	                 The default is 1 (which is like it always was)
*	                 Setting it to 0 will make Enforcer not touch
*	                 the LED.  Using a larger value will make the
*	                 flash take longer (such that it can be noticed
*	                 when doing I/O models other than the default
*	                 serial output)  The time that the flash will
*	                 take is a bit more than 1.3 microseconds times
*	                 the number.  So 1000 will be a bit more than
*	                 1.3 milliseconds.  (Or 1000000 is a bit more than
*	                 1.3 seconds.)
*
*	PARALLEL/S     - This option will make Enforcer use the parallel port
*	                 hardware rather than the serial port for output.
*
*	RAWIO/S        - This option will make Enforcer stuff the hit report
*	(special IO)     into an internal buffer and then from the main
*	                 Enforcer process output the results via the
*	                 RawPutChar() EXEC debugging LVO.  Since the output
*	                 happens on the Enforcer task it is possible for a
*	                 hit that ends in a system crash to not be able to
*	                 be reported.  This option is here such that tools
*	                 which can redirect debugging output can redirect
*	                 the Enforcer output too.
*
*	FILE/K         - This option will make Enforcer output the hit report
*	(special IO)     but to a file insted of sending it to the hardware
*	                 directly or using the RAWIO LVO.  A good example of
*	                 such a file is CON:0/0/640/100/HIT/AUTO/WAIT.
*	                 Another thing that can be done is to have a program
*	                 sit on a named pipe and have Enforcer output to it.
*	                 This program can then do whatever it feels like with
*	                 the Enforcer hits.  (Such as decode them, etc.)
*	                 *NOTE*  It is not a good idea to have Enforcer hits
*	                 go to a file on a disk as if the system crashes
*	                 during/after the Enforcer hit, the disk may
*	                 become corrupt.
*
*	STDIO/S        - This option will make Enforcer output the hit report
*	(special IO)     to STDOUT.  This option only works from the CLI as it
*	                 requires STDOUT.  It is best used with redirection or
*	                 pipes.
*
*	BUFFERSIZE/K/N - This lets you set Enforcer's internal output buffer
*	                 for the special I/O options.  This option is only
*	                 valid with the RAWIO, FILE, or STDIO options.
*	                 The minimum setting is 8000.  The default is 8000.
*	                 Having the right amount of buffer is rather
*	                 important for the special I/O modes.  The reason
*	                 is due to the fact that no operating system calls
*	                 can be made from a bus error.  Thus, in the
*	                 special I/O mode, Enforcer must store the output
*	                 in this buffer and, via some special magic,
*	                 wake up the Enforcer task to read the buffer and
*	                 write it out as needed.  However, if a task is
*	                 in Forbid() or Disable() when the Enforcer hit
*	                 happens, the Enforcer task will not be able to
*	                 output the results of the hit.  This buffer lets
*	                 a number of hits happen even if the Enforcer task
*	                 was unable to do the I/O.  If the number of
*	                 hits that happen before the I/O was able to
*	                 run gets too large, the last few hits will either
*	                 be cut off completely or contain only partial
*	                 information.
*
*	INTRO/K        - This optional introduction string will be output
*	                 at the start of every Enforcer hit.  For example:
*	                 INTRO="*NBad Program!"   The default is no string.
*
*	PRIORITY/K/N   - This lets you set Enforcer's I/O task priority.
*	                 The default for this priority is 99.  In some
*	                 special cases, you may wish to adjust this.
*	                 It is, however, recommended that if you are using
*	                 one of the special I/O options (RAWIO, FILE, or
*	                 STDIO) that you keep the priority rather high.
*	                 If the priority you supply is outside of the
*	                 valid task priority range (-127 to 127) Enforcer
*	                 will use the default priority.
*
*	NOALERTPATCH/S - This option disables the patching of the EXEC
*	                 Alert() function.  Normally Enforcer will patch
*	                 this function to provide information as to what
*	                 called Alert() and to prevent the Enforcer hits
*	                 that a call to Alert() would cause.
*
*	ON/S           - Mainly for completeness.  If not specified, it
*	                 is assumed you want to turn ON Enforcer.
*
*	QUIT=OFF/S     - Tells Enforcer to turn off.  Enforcer can also be
*	                 stopped by sending a CTRL-C to its process.
*
*   RESULTS
*	When running, a set of MMU tables that map addresses that are not
*	in the system's address map as invalid such that any access to them
*	will cause an access fault.  Enforcer will then display this fact
*	and generate a diagnostic message as to what the illegal access
*	was.  The first memory page (the one starting at location 0) is
*	also marked as invalid as many programming errors cause invalid
*	access to these addresses.  These addresses are completely off
*	limits to applications.
*
*	When an	access violation happens, a report such as the following
*	is output.
*
*03-Apr-93  21:26:18
*WORD-WRITE to  00000000        data=4444       PC: 07895CA4
*USP:  078D692C SR: 0000 SW: 0729  (U0)(-)(-)  TCB: 078A2690
*Data: DDDD0000 DDDD1111 DDDD2222 DDDD3333 DDDD4444 DDDD5555 DDDD6666 DDDD7777
*Addr: AAAA0000 AAAA1111 AAAA2222 AAAA3333 AAAA4444 AAAA5555 07800804 --------
*Stck: 00000000 07848E1C 00009C40 078A30B4 BBBBBBBB BBBBBBBB BBBBBBBB BBBBBBBB
*Stck: BBBBBBBB BBBBBBBB BBBBBBBB BBBBBBBB BBBBBBBB 078E9048 00011DA8 DEADBEEF
*----> 07895CA4 - "lawbreaker"  Hunk 0000 Offset 0000007C
*PC-8: AAAA1111 247CAAAA 2222267C AAAA3333 287CAAAA 44442A7C AAAA5555 31C40000
*PC *: 522E0127 201433FC 400000DF F09A522E 012611C7 00CE4EAE FF7642B8 0324532E
*Name: "New_Shell"  CLI: "lawbreaker"  Hunk 0000 Offset 0000007C
*
*LONG-READ from AAAA4444                        PC: 07895CA8
*USP:  078D692C SR: 0015 SW: 0749  (U0)(F)(-)  TCB: 078A2690
*Data: DDDD0000 DDDD1111 DDDD2222 DDDD3333 DDDD4444 DDDD5555 DDDD6666 DDDD7777
*Addr: AAAA0000 AAAA1111 AAAA2222 AAAA3333 AAAA4444 AAAA5555 07800804 --------
*Stck: 00000000 07848E1C 00009C40 078A30B4 BBBBBBBB BBBBBBBB BBBBBBBB BBBBBBBB
*Stck: BBBBBBBB BBBBBBBB BBBBBBBB BBBBBBBB BBBBBBBB 078E9048 00011DA8 DEADBEEF
*----> 07895CA8 - "lawbreaker"  Hunk 0000 Offset 00000080
*PC-8: 247CAAAA 2222267C AAAA3333 287CAAAA 44442A7C AAAA5555 31C40000 522E0127
*PC *: 201433FC 400000DF F09A522E 012611C7 00CE4EAE FF7642B8 0324532E 01266C08
*Name: "New_Shell"  CLI: "lawbreaker"  Hunk 0000 Offset 00000080
*
*25-Jul-93  17:15:06
*Alert !! Alert 35000000     TCB: 07642F70     USP: 07657C10
*Data: 00000000 DDDD1111 DDDD2222 DDDD3333 0763852A DDDD5555 DDDD6666 35000000
*Addr: AAAA0000 AAAA1111 AAAA2222 AAAA3333 AAAA4444 0763852A 07400810 --------
*Stck: 076385A0 00000000 0752EE9A 00002800 07643994 00000000 0762F710 076305F0
*----> 076385A0 - "lawbreaker"  Hunk 0000 Offset 00000098
*
*	Here is a breakdown of what these reports are saying:
*
*	In the first report, the first line is the date stamp.
*
*	The first line of each report describes the access violation
*	and where it happened from.  In the case of a WRITE, the data
*	that was being written will be displayed as well.  If an instruction
*	mode access caused the fault, there will be an (INST) in the line.
*
*	The first line may also contain the BUS ERROR message.  This will
*	be displayed when an address that is valid in the system lists
*	causes a physical bus fault during the access.  This usually
*	will happen with plug-in cards or when a hardware problem causes
*	some form of system fault.  Watch out, if this does show up, your
*	system may be unstable and/or unreliable.
*
*	The second line (starts USP:) displays the USER stack pointer (USP),
*	the status register (SR:), the special status word (SW:).  It then
*	displays the supervisor/user state and the interrupt level.  This
*	will be from (U0) to (U7) or (S0) to (S7)  (S=Supervisor)  Next
*	is the forbid state (F=forbid, -=not) and the disable state (D or -)
*	of the task that was running when the access fault took place.
*	Finally, the task control block address is displayed (TCB:)
*
*	The next two lines contain the data and address register dumps from
*	when the access fault happened.  Note that A7 is not listed here.
*	It is the stack pointer and is listed as USP: in the line above.
*
*	Then come the lines of stack backtrace.  These lines show the
*	data on the stack.  If the stack is in invalid memory, Enforcer will
*	display a message to that fact.
*
*	If SegTracker was installed before Enforcer, the "---->" lines
*	will display in which seglist the given addresses are in based on the
*	global tracking that SegTracker does.  (See docs on SegTracker)
*	If no seglist match is found, no lines will be displayed.
*	One line will be displayed for each of the stack longwords asked
*	for (see the STACKCHECK option) and one line for the PC address of
*	the Enforcer hit.  (The PC line is always checked for is SegTracker
*	is installed.)  The lines are in order: hit, first stack find,
*	second stack find, etc.  This is useful for tracking down who
*	called the routine that caused the Enforcer hit.
*
*	Next, optionally, comes the data around the program counter when the
*	access fault happened.  The first line (PC-8:) is the 8 long-words
*	before the program counter.  The second line starts at the program
*	counter and goes for 8 long words.
*
*	The last line displays the name of the task that was running when
*	the access fault took place.  If the task was a CLI, it will display
*	the name of the CLI command that was running.  If the access fault
*	was found to have happened within the seglist of a loaded program,
*	the segment number and the offset from the start of the segment will
*	be displayed.  (Note that this works for any LoadSeg()'ed process)
*
*	Note that the name will display as "Processor Interrupt Level x"
*	if the access happened in an interrupt.
*
*	The other output that could happen is when a program or the OS
*	calls the EXEC Alert function.  Enforcer catches these calls
*	and will display the alert information as seen above.  (With the
*	data and time if needed)
*
*   WARNING
*	Enforcer is for software testing.  In this role it is vital.
*	Software that causes Enforcer hits may well not be able to run on
*	newer hardware.  (Enforcer hits of high addresses on systems not
*	running Enforcer but with a 68040 will most likely crash the system)
*	Future systems and hardware will make this even more important.  The
*	system can NOT survive software that causes Enforcer hits.
*
*	However, Enforcer is NOT a system protector.  As a side effect, it
*	may well keep a system from crashing when Enforcer hits happen, but
*	it may just as well make the software crash earlier.  Enforcer is
*	mainly a development and testing tool.
*
*	Enforcer causes	no ill effects with correctly working software.
*	If a program fails to work while Enforcer is active, you should
*	contact the developer of that program.
*
*   NOTES
*	This is Enforcer V37.  Bryce Nesbitt came up with the original
*	"Enforcer" that has been instrumental to the improvement in the
*	quality of software on the Amiga.  The Amiga users and developers
*	owe him a great deal for this.  Thank you Bryce!  Enforcer V37,
*	however, is a greatly enhanced and more advanced tool.
*
*	Enforcer V37 came about due to a number of needs.  These included
*	the need for more output options and better performance.  It also
*	marks the removal of all kludges that were in the older versions.
*	Also, some future plans required some of these changes...
*
*	In addition, the complete redesign was needed in order to also
*	support the 68040.  The internal design of Enforcer is now set up
*	such that CPU/MMU specific code can be cleanly accessed from the
*	general house keeping aspect of the code.  The MMU bus error
*	handling is, however, 100% CPU specific.
*
*	Since AbsExecBase is in low memory, reads of this address are slower
*	with Enforcer running.  Caching AbsExecBase locally is highly
*	recommended since it is in CHIP memory and on systems with FAST
*	memory, it will be faster to access the local cached value. (In
*	addition to the performance increase when running Enforcer) Note
*	that doing many reads of location 4 will hurt interrupt performance.
*
*	When the Amiga produces an ALERT, EXEC places some magic numbers
*	into some special locations in low memory.  The exact pattern
*	changes between versions of the operating system.
*
*	Enforcer will patch the EXEC function ColdReboot() in an attempt to
*	"get out of the way" when someone tries to reboot the system.
*	Enforcer will clean up as much as possible the MMU tables and then
*	call the original LVO.  When Enforcer is asked to quit, it will
*	check to make sure it can remove itself from this LVO. If it can
*	not, it will not quit at that time.  If run from the shell, it will
*	display a message saying that it tried but could not exit.  Enforcer
*	will continue to be active and you can try later to deactivate it.
*
*	Enforcer will also patch the EXEC function Alert() in an attempt to
*	provide better tracking of other events in the system.  It is also
*	patched such that dead-end alerts will correctly reset the system
*	and be displayed.  With this patch in place, the normal alerts will
*	not be seen but will be replaced by the Enforcer output shown
*	above.  See LawBreaker for a more complete example of this.
*
*   68020 NOTES
*	The 68020 does not have a built-in MMU but has a co-processor
*	feature that lets an external MMU be connected.  Enforcer MMU code
*	is designed for use with 68851 MMU.  This is the some-what 68030
*	compatible MMU by Motorola.  Enforcer uses the same code for both
*	the 68030 and the 68020/68851.  For this reason, 68020/68851 users
*	should see the 68030 NOTES section.
*
*   68030 NOTES
*	The 68030 uses cycle/instruction continuation and will
*	supply the data on reads and ignore writes during an access
*	fault rather than let the real bus cycle happen.  This means
*	that on a fault caused by MMU tables, no bus cycle to the
*	fault address will be generated.  (For those of you with analyzers)
*
*	In some cases, the 68030 will have advanced the Program Counter
*	past the instruction by the time the access fault happens.
*	This is usually only on WRITE faults.  For this reason, the PC
*	may either point at the instruction that caused the fault or
*	just after the instruction that caused the fault.  (Which could
*	mean that it is pointing to the middle of the instruction
*	that caused the fault.)
*
*	Note that there is a processor called 68EC030.  This processor
*	has a disabled or defective MMU.  However, it may function well
*	enough for Enforcer to think it has a fully functional MMU and
*	thus Enforcer will attempt to run.  However, even if it looks like
*	the MMU is functioning, it is not fully operational and thus may
*	cause strange system activity and even crashes.  Do not assume
*	that Enforcer is safe to use on 68EC030 systems.
*
*   68040 NOTES
*	Enforcer, on the 68040, *requires* that the 68040.library be
*	installed and it requires an MMU 68040 CPU.  The 68EC040 does not
*	have a MMU.  The 68LC040 does have an MMU and is supported. Enforcer
*	will work best in a system with the 68040.library 37.10 or better
*	but it does know how to deal with systems that do not have that
*	version.
*
*	Due to the design of the 68040, Enforcer is required to do a number
*	of things differently.  For example, the MMU page size can only be
*	either 8K or 4K.  This means that to protect the low 1K of memory,
*	Enforcer will end up having to mark the first 4K of memory as
*	invalid and emulate the access to the 3K of that memory that is
*	valid. For this reason Enforcer moves a number of possible
*	structures from the first 4K of memory to higher addresses.  This
*	means that the system will continue to run at a reasonable speed.
*	The first time Enforcer is run it may need to allocate memory for
*	these structures that it will move.  Enforcer can never return this
*	memory to the system.
*
*	In addition to the fact that the 68040 MMU table size is different,
*	the address fault handling is also different.  Namely, the 68040 can
*	only rerun the cycle and not continue it like the 68030. This means
*	that on a 68040, the page must be made available first and then made
*	unavailable.  Make this work, Enforcer will switch the instruction
*	that caused the error into trace mode and let it run with a special
*	MMU setup.  When the trace exception comes in, the MMU is set up
*	back to the way it was.  Enforcer does its best to keep debuggers
*	working.  Note, however, that the interrupt level during a trace of
*	a READ will end up being set to 7.  This is to prevent interrupts
*	from changing the order of trace/MMU table execution.  The level
*	will be restored to the original state before continuing.  Since T0
*	mode tracing is also supported, there are also some changes in the
*	way it operates.  T0 mode tracing is defined, on the 68040, to cause
*	a trace whenever the instruction pipeline needed to be reloaded.
*	While on the 68020/030 processors this was normally only for the
*	branch instructions, in the 68040 this includes a large number of
*	other instructions.  (Including NOP!)  Anyway, if an Enforcer hit
*	happens while in T0 tracing mode, the trace will happen even on
*	instructions that normally would not cause a T0 mode trace.  Since
*	this may actually help in debugging and because it was not possible
*	to do anything else, this method of operation is deemed acceptable.
*
*	Another issue with the 68040 is that WRITE faults happen *after* the
*	instruction has executed.  (Except for MOVEM)  In fact, it is common
*	for the 68040 to execute one or more extra instructions before the
*	WRITE fault is executed.  This design makes the 68040 much faster,
*	but it also makes the Program Counter value that Enforcer can report
*	for the fault much less likely to be pointing to the instruction
*	that caused it.  The worst cases are sequences such as a write fault
*	followed by a branch instruction.  In these cases, the branch is
*	usually already executed before the write fault happens and thus the
*	PC will be pointing to the target of the branch.  There is nothing
*	that can be done within Enforcer to help out here.  You will just
*	need to be aware of this and deal with it as best as possible.
*
*	Along with the above issue, is the fact that since a write fault may
*	be delayed, a read fault may happen before the write fault shows up.
*	Internally, enforcer does not do special processing for these and
*	they will not show up.  Since another hit was happening anyway, it
*	is felt that it is best to just not report the hit.  Along the same
*	lines, the hit generated from a MOVEM instruction may only show as a
*	single hit rather than 1 for each register moved.
*
*	On the Amiga, MOVE16 is not supported 100%.  Causing an Enforcer hit
*	with a MOVE16 will cause major problems and maybe cause Enforcer or
*	your task to lock.  Since MOVE16 is not supported, this is not a
*	major issue.  Just watch out if you are using this 68040
*	instruction.  (Also, watch out for the 68040 CPU bug with MOVE16)
*
*	The functions CachePreDMA(), CachePostDMA(), and CacheControl() are
*	patched when the 68040 MMU is turned on by Enforcer.  These
*	functions are patched such the issues with DMA and the 68040
*	COPYBACK data caches are addressed.  The 68040.library normally
*	deals with this, however since Enforcer turns on the MMU, the method
*	of dealing with it in the 68040.library will not work. For this
*	reason, Enforcer will patch these and implement the required fix for
*	when the MMU is on.  When Enforcer is asked to exit, it will check
*	if it can remove itself from these functions.  If it can not, it
*	will ignore the request to exit.  If Enforcer was run from the CLI,
*	it will print a message saying that it can not exit when the attempt
*	is made.
*
*   WRITING DEBUGGERS
*	If you wish to make a debugger that works with Enforcer to help
*	pinpoint Enforcer hits in the application and not cause Enforcer
*	hits itself, here are some simple tips and a bit of code.
*
*   DEBUGGERS:  TRAPPING A HIT
*	To trap a hit requires a number of things to work.
*
*	First, the debugger itself must never cause an Enforcer hit.
*	For help on that, see the "DEBUGGERS: NOT CAUSING A HIT"
*
*	Second, the debugger must be global.  That is, you must be
*	able to deal with a task getting a hit that is not the task
*	under test.  There are a number of simple ways to deal with
*	this, and I will leave this up to the debugger writer.
*	(One method will be shown below)
*
*	Third, the debugger must start *AFTER* Enforcer starts.
*	If it is started before Enforcer, the hits will not be
*	trapped.  (Note that this is not a problem)
*
*	A very important point:  The code needs to be fast for
*	the special case of location 4.  This is shown in the
*	code below.  It is very important that this be fast.
*
*	Note that it is much prefered that debuggers use the
*	method described below for trapping hits.  It should
*	be much more supportable this way as any of the tricky
*	work that may need to be done in the hit processing
*	will be handled by Enforcer itself.  If you wish the
*	hit decoded, you can capture the Enforcer output via a
*	pipe or some other method (such as RAWIO) or you can
*	leave that issue up to the user.
*
*	Now, given the above, the following bits of code can be
*	used to get the debugger to switch into single-step mode
*	at the point of the Enforcer hit.  You can also set some
*	data value here to tell your debugger about this.
*
*	;
*	; The following code is inserted into the bus error vector.
*	; Make sure you follow the VBR to find the vector.
*	; Store the old vector in the address OldVector
*	; Make sure you already have the single-step trap vector
*	; installed before you install this.  Note that any extra
*	; code you add in the comment area *MUST NOT* cause a bus
*	; fault of any kind, including reading of location 4.
*	;
*	; This is the 68020 and 68030 version...
*	;
*	EnforcerHit:    ds.l    1                       ; Some private flag
*	MyTask:         ds.l    1                       ; Task under test
*	MyExecBase:     ds.l    1                       ; The local copy
*	OldVector:      ds.l    1                       ; One long word
*	NewVector:      cmp.l   #4,$10(sp)              ; 68020 and 68030
*	                beq.s   TraceSkip               ; If AbsExecBase, OK
*	                ;
*	                ; Now, if you wish to only trap a specific task,
*	                ; do the check at this point.  For example, a
*	                ; simple single-task debugger would do something
*	                ; like this:
*	                move.l  a0,-(sp)                ; Save this...
*	                move.l  MyExecBase(pc),a0       ; Get ExecBase...
*	                move.l  ThisTask(a0),a0         ; Get ThisTask
*	                cmp.l   MyTask(pc),a0           ; Are they the same?
*	                move.l  (sp)+,a0                ; Restore A0 (no flags)
*	                bne.s   TraceSkip               ; If not my task, skip
*	                ;
*	                bset.b  #7,(sp)                 ; Set trace bit...
*	                ; If you have any other data to set, do it now...
*	                ; Set as setting the EnforcerHit bit in your data...
*	                addq.l	#1,EnforcerHit          : Count the hit...
*	                ;
*	TraceSkip:      move.l  OldVector(pc),-(sp)     ; Ready to return
*	                rts
*	;
*	; This is the 68040 version...
*	;
*	NewVector040:   cmp.l   #4,$14(sp)              ; 68040
*	                beq.s   TraceSkip040            ; If AbsExecBase, OK
*	                ;
*	                ; Now, if you wish to only trap a specific task,
*	                ; do the check at this point.  For example, a
*	                ; simple single-task debugger would do something
*	                ; like this:
*	                move.l  a0,-(sp)                ; Save this...
*	                move.l  MyExecBase(pc),a0       ; Get ExecBase...
*	                move.l  ThisTask(a0),a0         ; Get ThisTask
*	                cmp.l   MyTask(pc),a0           ; Are they the same?
*	                move.l  (sp)+,a0                ; Restore A0 (no flags)
*	                bne.s   TraceSkip               ; If not my task, skip
*	                ;
*	                bset.b  #7,(sp)                 ; Set trace bit...
*	                ; If you have any other data to set, do it now...
*	                ; Set as setting the EnforcerHit bit in your data...
*	                addq.l	#1,EnforcerHit          : Count the hit...
*	                ;
*	TraceSkip040:   move.l  OldVector(pc),-(sp)     ; Ready to return
*	                rts
*
*   DEBUGGERS:  NOT CAUSING A HIT
*	In order not to cause Enforcer hits, you can do a number
*	of things.  The easiest is to test the address with the TypeOfMem()
*	EXEC function.  If TypeOfMem() returns 0, the address is not
*	in the memory lists.  However, this does not mean it is not a
*	valid address in all cases.  (ROM, chip registers, I/O boards)
*	For those cases, you can build a "valid memory access table"
*	much like Enforcer does.  Here is the code from Enforcer for
*	the base memory tables:
*
*	\*
*	 * Mark_Address(mmu,start address,length,type)
*	 *\
*
*	\*
*	 * Special case the first page of CHIP RAM
*	 *\
*	mmu=Mark_Address(mmu,0,0x1000,INVALID | NONCACHEABLE);
*
*	\*
*	 * Map in the free memory
*	 *\
*	Forbid();
*	mem=(struct MemHeader *)SysBase->MemList.lh_Head;
*	while (mem->mh_Node.ln_Succ)
*	{
*	  mmu=Mark_Address(mmu,
*	                   (ULONG)(mem->mh_Lower),
*	                   (ULONG)(mem->mh_Upper)-(ULONG)(mem->mh_Lower),
*	                   ((MEMF_CHIP & TypeOfMem(mem->mh_Lower)) ?
*	                     (NONCACHEABLE | VALID) : (CACHEABLE | VALID)));
*	  mem=(struct MemHeader *)(mem->mh_Node.ln_Succ);
*	}
*	Permit();
*
*	\*
*	 * Map in the autoconfig boards
*	 *\
*	if (ExpansionBase=OpenLibrary("expansion.library",0))
*	{
*	struct	ConfigDev	*cd=NULL;
*
*	  while (cd=FindConfigDev(cd,-1L,-1L))
*	  {
*	    \* Skip memory boards... *\
*	    if (!(cd->cd_Rom.er_Type & ERTF_MEMLIST))
*	    {
*	      mmu=Mark_Address(mmu,
*	                       (ULONG)(cd->cd_BoardAddr),
*	                       cd->cd_BoardSize,
*	                       VALID | NONCACHEABLE);
*	    }
*	  }
*	  CloseLibrary(ExpansionBase);
*	}
*
*	\*
*	 * Now for the control areas...
*	 *\
*	mmu=Mark_Address(mmu,0x00BC0000,0x00040000,VALID | NONCACHEABLE);
*	mmu=Mark_Address(mmu,0x00D80000,0x00080000,VALID | NONCACHEABLE);
*
*	\*
*	 * and the ROM...
*	 *\
*	mmu=Mark_Address(mmu,
*	                 0x00F80000,
*	                 0x00080000,
*	                 VALID | CACHEABLE | WRITEPROTECT);
*
*	\*
*	 * If the credit card resource, make the addresses valid...
*	 *\
*	if (OpenResource("card.resource"))
*	{
*	  mmu=Mark_Address(mmu,0x00600000,0x00440002,VALID | NONCACHEABLE);
*	}
*
*	\*
*	 * If CD-based Amiga (CDTV, A570, etc.)
*	 *\
*	if (FindResident("cdstrap"))
*	{
*	  mmu=Mark_Address(mmu,0x00E00000,0x00080000,VALID | NONCACHEABLE);
*	  mmu=Mark_Address(mmu,0x00B80000,0x00040000,VALID | NONCACHEABLE);
*	}
*
*	\*
*	 * Check for ReKick/ZKick/KickIt
*	 *\
*	if ((((ULONG)(SysBase->LibNode.lib_Node.ln_Name)) >> 16) == 0x20)
*	{
*	  mmu=Mark_Address(mmu,
*	                   0x00200000,
*	                   0x00080000,
*	                   VALID | CACHEABLE | WRITEPROTECT);
*	}
*
*   SEE ALSO
*	"A master's secrets are only as good as the
*	 master's ability to explain them to others."  -  Michael Sinz
*
*   BUGS
*	None?
*
*******************************************************************************
*/

#include	<exec/types.h>
#include	<exec/execbase.h>
#include	<exec/tasks.h>
#include	<exec/memory.h>
#include	<exec/alerts.h>
#include        <exec/ports.h>
#include        <exec/libraries.h>
#include	<exec/semaphores.h>
#include        <dos/dos.h>
#include        <dos/dosextens.h>
#include	<dos/rdargs.h>
#include	<devices/timer.h>
#include	<workbench/startup.h>
#include	<workbench/workbench.h>
#include	<libraries/configvars.h>

#include	<clib/exec_protos.h>
#include	<pragmas/exec_pragmas.h>

#include	<clib/dos_protos.h>
#include	<pragmas/dos_pragmas.h>

#include	<clib/icon_protos.h>
#include	<pragmas/icon_pragmas.h>

#include	<clib/expansion_protos.h>
#include	<pragmas/expansion_pragmas.h>

#include	<string.h>

/*
 * A private LVO that I will use called RawPutChar...
 * I need to do the prototype myself...
 */
VOID RawPutChar(UBYTE);
#pragma libcall SysBase RawPutChar 204 001

#define EXECBASE (*(struct ExecBase **)4)

/*
 * Minimum buffer size of the output buffer when using one
 * of the special I/O modes.
 */
#define	MIN_BUFFER	(8000)

/*
 * The MMU Frame we will use...
 */
struct MMU_Frame
{
	ULONG	mmu_Flag;	/* The mmu type flag */

	ULONG	mmu_CRP[2];	/* CRP for 030 is 2 longs...  For the 040 it is the root pointer and only 1 long used */
	ULONG	mmu_TC;		/* TC for both 030 and 040.  Accessed as a long, but only part of it is used on 040 */

	ULONG	mmu_CRP_OLD[2];
	ULONG	mmu_TC_OLD;

	ULONG	*mmu_LevelA;	/* This is the base table.  The root pointer will contain this one... */
	ULONG	*mmu_LevelB;	/* Note that on the 68040, these are just the invalid pages and the full pages are elsewhere */
	ULONG	*mmu_LevelC;	/* " */

	ULONG	mmu_Indirect;	/* The indirect MMU error descriptor ;^)   For the 68040 slimmy trick... */
	ULONG	*mmu_Magic;	/* Special memory area for magic code (such as the 68040 bus error remapping magic) */

	ULONG	*mmu_Page0;	/* For 68040 page 0 work */

	ULONG	mmu_InvalidA;	/* The invalid value at LevelA  (68040 only) */
	ULONG	mmu_InvalidB;	/* The invalid value at LevelB  (68040 only) */
	ULONG	mmu_InvalidC;	/* The invalid value at LevelC  (68040 only) */

	ULONG	mmu_TT[4];	/* Storage for TT regs (68040 only) */
};

/* Values for MMU_Frame.mmu_Flag */
#define	MMU_030		1	/* MMU setup 68030 or 68020+68851 */
#define	MMU_040		2	/* MMU setup 68040 */

/*
 * General flags for setting up the MMU
 */
#define	VALID		(0x01)
#define	NONCACHEABLE	(0x40)
#define	CACHEABLE	(0x00)
#define	INVALID		(0x00)
#define	WRITEPROTECT	(0x04)

/******************************************************************************/

/* External data from assembly */
extern	char	Copyright[];
extern	char	MyTask[];
extern	char	DateStr[];
extern	char	TimeStr[];
extern	ULONG	ROM_Addr;	/* The physical address of the ROM */
extern	ULONG	Bad_ReadValue;
extern	ULONG	Quiet_Flag;
extern	ULONG	ShowPC_Flag;
extern	ULONG	Small_Flag;
extern	ULONG	Tiny_Flag;
extern	ULONG	Parallel_Flag;
extern	ULONG	LED_Count;
extern	ULONG	StackLines;
extern	ULONG	SegLines;
extern	ULONG	ARegCheck;
extern	ULONG	DRegCheck;
extern	ULONG	AlertOFF;

extern	UBYTE	*Intro;
extern	UBYTE	*OutputBuffer;
extern	ULONG	DoDateStamp;
extern	ULONG	WriteOffset;
extern	ULONG	ReadOffset;
extern	ULONG	BufferSize;

extern	struct	ExecBase	*SysBase;
extern	struct	Library		*DOSBase;
extern	struct	Library		*Lib68040;

extern	ULONG	SegTracker;

/* External functions from assembly... */
ULONG __asm Test_MMU(void);
BOOL __asm MMU_On(register __a0 struct MMU_Frame *);
BOOL __asm MMU_Off(register __a0 struct MMU_Frame *);

/******************************************************************************/

/* Internal functions... */
static void Free_MMU_Frame(struct MMU_Frame *mmu);
static struct MMU_Frame *Mark_Address(struct MMU_Frame *mmu,ULONG addr,ULONG size,ULONG orBits);
static struct MMU_Frame *Map_ROM(struct MMU_Frame *mmu,ULONG addr);
static struct MMU_Frame *Mark_Invalid(struct MMU_Frame *mmu);
static struct MMU_Frame *Init_MMU_Frame(ULONG MMU_Flag);

/******************************************************************************/

#define TEMPLATE  "QUIET/S,TINY/S,SMALL/S,SHOWPC/S,STACKLINES/K/N,STACKCHECK/S,AREGCHECK/S,DREGCHECK/S,DATESTAMP/S,DEADLY/S,FSPACE/S,VERBOSE/S,LED/K/N,PARALLEL/S,RAWIO/S,FILE/K,STDIO/S,BUFFERSIZE/K/N,INTRO/K,PRIORITY/K/N,NOALERTPATCH/S,ON/S,QUIT=OFF/S"

#define	OPT_QUIET	0
#define	OPT_TINY	1
#define	OPT_SMALL	2
#define	OPT_SHOWPC	3
#define	OPT_STACKLINES	4
#define	OPT_STACKCHECK	5
#define	OPT_AREGCHECK	6
#define	OPT_DREGCHECK	7
#define	OPT_DATESTAMP	8
#define	OPT_DEADLY	9
#define	OPT_FSPACE	10
#define	OPT_VERBOSE	11
#define	OPT_LED		12
#define	OPT_PARALLEL	13
#define	OPT_RAWIO	14
#define	OPT_FILE	15
#define	OPT_STDIO	16
#define	OPT_BUFFERSIZE	17
#define	OPT_INTRO	18
#define	OPT_PRIORITY	19
#define	OPT_ALERTPATCH	20
#define	OPT_ON		21
#define	OPT_QUIT	22
#define	OPT_COUNT	23

ULONG cmd(void)
{
struct	Library		*ExpansionBase;
struct	Process		*proc;
	ULONG		temp1;
	BPTR		OutputFile=NULL;
struct	RDArgs		*rdargs=NULL;
struct	WBStartup	*msg=NULL;
struct	RDArgs		*myRDArgs=NULL;
	ULONG		rc=RETURN_FAIL;
struct	MMU_Frame	*mmu;
struct	MsgPort		*timerPort=NULL;
struct	timerequest	*timerIO=NULL;
	ULONG		timerOpen=FALSE;
	LONG		opts[OPT_COUNT];

	SysBase = EXECBASE;

	proc=(struct Process *)FindTask(NULL);

	if (!(proc->pr_CLI))
	{
		WaitPort(&(proc->pr_MsgPort));
		msg=(struct WBStartup *)GetMsg(&(proc->pr_MsgPort));
	}

	if (DOSBase = OpenLibrary("dos.library",37))
	{
	char	*oldName=proc->pr_Task.tc_Node.ln_Name;

		memset((char *)opts, 0, sizeof(opts));

		/*
		 * Open the 68040.library
		 */
		Lib68040=OpenLibrary("68040.library",0);

		/*
		 * Do the option parsing
		 * If from Workbench, use icon tool types
		 * If from CLI, use ReadArgs
		 */
		if (msg)
		{
		struct	Library	*IconBase;
		struct	WBArg	*wbArg;

			/*
			 * Started from Workbench so do icon magic...
			 *
			 * What we will do here is try all of the tooltypes
			 * in the icon and keep only those which do not cause
			 * errors in the RDArgs.
			 */
			if (wbArg=msg->sm_ArgList) if (IconBase=OpenLibrary("icon.library",0))
			{
			struct	DiskObject	*diskObj;
				BPTR		tmplock;

				/*
				 * Use project icon if it is there...
				 */
				if (msg->sm_NumArgs > 1) wbArg++;

				tmplock=CurrentDir(wbArg->wa_Lock);
				if (diskObj=GetDiskObject(wbArg->wa_Name))
				{
				char	**ToolTypes;

					if (ToolTypes=diskObj->do_ToolTypes)
					{
					char	*TotalString;
					ULONG	totalSize=3;

						while (*ToolTypes)
						{
							totalSize+=strlen(*ToolTypes)+1;
							ToolTypes++;
						}

						if (TotalString=AllocVec(totalSize,MEMF_PUBLIC))
						{
						char	*CurrentPos=TotalString;
						ULONG	currentLength;

							ToolTypes=diskObj->do_ToolTypes;
							do
							{
								*CurrentPos='\0';
								if (*ToolTypes) strcpy(CurrentPos,*ToolTypes);
								currentLength=strlen(CurrentPos);
								CurrentPos[currentLength+0]='\n';
								CurrentPos[currentLength+1]='\0';

								if (rdargs) FreeArgs(rdargs);
								rdargs=NULL;
								memset((char *)opts, 0, sizeof(opts));

								if (myRDArgs) FreeDosObject(DOS_RDARGS,myRDArgs);
								if (myRDArgs=AllocDosObject(DOS_RDARGS,NULL))
								{
									myRDArgs->RDA_Source.CS_Buffer=TotalString;
									myRDArgs->RDA_Source.CS_Length=strlen(TotalString);

									if (rdargs=ReadArgs(TEMPLATE, opts, myRDArgs))
									{
										CurrentPos[currentLength]=' ';
										CurrentPos+=currentLength+1;
									}
								}
							} while (*ToolTypes++);
							FreeVec(TotalString);
						}
					}

					FreeDiskObject(diskObj);
				}

				CurrentDir(tmplock);
				CloseLibrary(IconBase);
			}
			rc=RETURN_OK;
		}
		else
		{
			/*
			 * Started from CLI so do standard ReadArgs
			 */
			PutStr(Copyright);
			if (!(rdargs = ReadArgs(TEMPLATE, opts, NULL))) PrintFault(IoErr(),NULL);
			else if (CheckSignal(SIGBREAKF_CTRL_C)) PrintFault(ERROR_BREAK,NULL);
			else rc=RETURN_OK;
		}

		/*
		 * Check if we are running already...
		 */
		if (RETURN_OK==rc)
		{
		struct	Task	*tsk;

			Forbid();
			if (!(tsk=FindTask(MyTask)))
			{
				proc->pr_Task.tc_Node.ln_Name=MyTask;
				if (opts[OPT_QUIT]) if (!msg) PutStr("Enforcer is not running.\n");
			}
			else if (opts[OPT_QUIT])
			{
				Signal(tsk,SIGBREAKF_CTRL_C);
				if (!msg) PutStr("Signalled Enforcer to unload.\n");
			}
			else
			{
				if (!msg) PutStr("Enforcer already running.\n");
				rc=RETURN_FAIL;
			}
			Permit();
		}


		/*
		 * If all is OK, start up the system as needed...
		 */
		if ((RETURN_OK==rc) && (!opts[OPT_QUIT]))
		{
			/* Check for I/O options */
			if (opts[OPT_FILE])
			{
				if (!(OutputFile=Open((char *)opts[OPT_FILE],MODE_NEWFILE)))
				{
					rc=RETURN_FAIL;
					if (!msg) PutStr("Could not open output file.\n");
				}
			}

			if (RETURN_OK==rc)
			{
				if (opts[OPT_BUFFERSIZE]) BufferSize=(*(ULONG *)opts[OPT_BUFFERSIZE]);

				if ((opts[OPT_RAWIO]) || opts[OPT_STDIO] || (OutputFile))
				{
					if (BufferSize<MIN_BUFFER) BufferSize=MIN_BUFFER;
					if (!(OutputBuffer=AllocVec(BufferSize,MEMF_PUBLIC)))
					{
						rc=RETURN_FAIL;
						if (!msg) PutStr("Could not allocate I/O buffer.\n");
					}
				}
				else if (BufferSize)
				{
					rc=RETURN_FAIL;
					if (!msg) PutStr("BufferSize option invalid without an I/O option.\n");
				}
			}
		}

		/*
		 * If all is OK and we are asking for DateStamps...
		 */
		if ((RETURN_OK==rc) && (!opts[OPT_QUIT]) && (opts[OPT_DATESTAMP]))
		{
			rc=RETURN_FAIL;

			if (timerPort=CreateMsgPort())
			{
				if (timerIO=(struct timerequest *)CreateIORequest(timerPort,sizeof(struct timerequest)))
				{
					if (!OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)timerIO,0L))
					{
						rc=RETURN_OK;
						timerOpen=TRUE;
						timerIO->tr_time.tv_secs=0;
						timerIO->tr_time.tv_micro=1;
						timerIO->tr_node.io_Command=TR_ADDREQUEST;
						SendIO((struct IORequest *)timerIO);
					}
				}
			}

			if (RETURN_FAIL==rc) if (!msg) PutStr("Could not initialize timer.\n");
		}

		/*
		 * If all is OK, start up the system as needed...
		 */
		if ((RETURN_OK==rc) && (!opts[OPT_QUIT]))
		{
			ULONG		MMU_Flag;
			ULONG		Verbose=FALSE;
		struct	SignalSemaphore	*sem;

			if (sem=FindSemaphore("SegTracker"))
			{
				sem++;
				SegTracker=*(ULONG *)sem;
			}

			/*
			 * If enforcer is to be deadly, we use this
			 * value as the return from bad READ requests
			 */
			if (opts[OPT_DEADLY]) Bad_ReadValue=0xABADFEED;

			/* Various flags... */
			Quiet_Flag=opts[OPT_QUIET];
			Tiny_Flag=opts[OPT_TINY];
			Small_Flag=opts[OPT_SMALL];
			ShowPC_Flag=opts[OPT_SHOWPC];
			Parallel_Flag=opts[OPT_PARALLEL];
			Intro=(UBYTE *)opts[OPT_INTRO];
			DoDateStamp=opts[OPT_DATESTAMP];
			ARegCheck=opts[OPT_AREGCHECK];
			DRegCheck=opts[OPT_DREGCHECK];

			/* If alert patch is not to be done, set AlertOFF to NULL */
			if (opts[OPT_ALERTPATCH]) AlertOFF=NULL;

			/* Set the number of LED flashes */
			LED_Count=1;	/* Default is 1 */
			if (opts[OPT_LED]) LED_Count=(*(ULONG *)opts[OPT_LED]);

			/* Set the stack lines... */
			StackLines=2;	/* Default to 2... */
			if (opts[OPT_STACKLINES]) StackLines=(*(ULONG *)opts[OPT_STACKLINES]);

			/* Set the number of longwords to check for segments */
			if (opts[OPT_STACKCHECK]) SegLines=StackLines << 3;

			/* Check for verbose */
			if (!msg) if (opts[OPT_VERBOSE]) Verbose=TRUE;

			/*
			 * MMU_Flag is:
			 *
			 *	1 - For standard 68020/68030
			 *	2 - For 68040
			 */
			MMU_Flag=Test_MMU();
			if (mmu=Init_MMU_Frame(MMU_Flag))
			{
			struct	MemHeader	*mem;
				LONG		oldPri=99; /* Default */

				if (opts[OPT_PRIORITY]) oldPri=(*(LONG *)opts[OPT_PRIORITY]);
				if ((oldPri>127) || (oldPri<-127))
				{
					if (!msg) PutStr("Invalid PRIORITY...  Using default.\n");
					oldPri=99;
				}

				/* We really need to be high priority... */
				oldPri=SetTaskPri((struct Task *)proc,oldPri);

				/*
				 * Special case the first page of CHIP RAM
				 */
				mmu=Mark_Address(mmu,0,0x1000,VALID | NONCACHEABLE);

				/*
				 * Map in the free memory
				 */
				Forbid();
				mem=(struct MemHeader *)SysBase->MemList.lh_Head;
				while (mem->mh_Node.ln_Succ)
				{
					mmu=Mark_Address(mmu,(ULONG)(mem->mh_Lower),(ULONG)(mem->mh_Upper)-(ULONG)(mem->mh_Lower),((MEMF_CHIP & TypeOfMem(mem->mh_Lower)) ? (NONCACHEABLE | VALID) : (CACHEABLE | VALID)));
					mem=(struct MemHeader *)(mem->mh_Node.ln_Succ);
				}
				Permit();

				/*
				 * Map in the autoconfig boards
				 */
				if (ExpansionBase=OpenLibrary("expansion.library",0))
				{
				struct	ConfigDev	*cd=NULL;

					while (cd=FindConfigDev(cd,-1L,-1L))
					{
						/* Skip memory boards... */
						if (!(cd->cd_Rom.er_Type & ERTF_MEMLIST))
						{
							if (Verbose)
							{
							ULONG	tmp[4];

								tmp[0]=(ULONG)(cd->cd_BoardAddr);
								tmp[1]=(ULONG)(cd->cd_BoardSize);
								tmp[2]=tmp[1] / 1024;
								tmp[3]=cd->cd_Rom.er_Type;

								VPrintf("Board Address: $%08lx   Size: $%08lx (%ldK)\tType=$%02lx CACHE DISABLED\n",tmp);
							}

							mmu=Mark_Address(mmu,(ULONG)(cd->cd_BoardAddr),cd->cd_BoardSize,VALID | NONCACHEABLE);
						}
					}
					CloseLibrary(ExpansionBase);
				}

				/*
				 * Now for the control areas...
				 */
				mmu=Mark_Address(mmu,0x00BC0000,0x00040000,VALID | NONCACHEABLE);
				mmu=Mark_Address(mmu,0x00D80000,0x00080000,VALID | NONCACHEABLE);
				mmu=Mark_Address(mmu,0x00F00000,0x00080000,VALID | CACHEABLE);
				mmu=Map_ROM(mmu,ROM_Addr);

				if (Verbose) VPrintf("ROM Physical Address: $%08lx\n",&ROM_Addr);

				/*
				 * If the credit card resource, make the addresses valid...
				 */
				if (OpenResource("card.resource"))
				{
					if (Verbose) PutStr("CARD Detected: $00600000   Size: $00440002 (4352K)\t\t CACHE DISABLED\n");
					mmu=Mark_Address(mmu,0x00600000,0x00440002,VALID | NONCACHEABLE);
				}

				/*
				 * If CDTV, make CDTV hardware valid...
				 */
				if (FindResident("cdstrap"))
				{
					if (Verbose) PutStr("CDTV Detected: $00E00000   Size: $00080000 (512K)\t\t CACHE DISABLED\n");
					mmu=Mark_Address(mmu,0x00E00000,0x00080000,VALID | NONCACHEABLE);
					mmu=Mark_Address(mmu,0x00B80000,0x00040000,VALID | NONCACHEABLE);
				}

				/*
				 * Protect $00F00000 card memory unless told not to...
				 */
				if (!opts[OPT_FSPACE])
				{
					mmu=Mark_Address(mmu,0x00F00000,0x00080000,WRITEPROTECT);
				}

				/*
				 * Check for ReKick/ZKick/KickIt
				 */
				if ((((ULONG)(SysBase->LibNode.lib_Node.ln_Name)) >> 16) == 0x20)
				{
					mmu=Mark_Address(mmu,0x00200000,0x00080000,VALID | CACHEABLE | WRITEPROTECT);
				}

				/*
				 * Check if we should mark low memory invalid...
				 */
				if (!opts[OPT_QUIET]) mmu=Mark_Invalid(mmu);

				/*
				 * If all OK still, we go and install ourselves...
				 */
				if (MMU_On(mmu))
				{
				ULONG	WaitSigs=SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_F;
				BOOL	Done=FALSE;

					/*
					 * If we also have a timer to look at, add that
					 * signal bit to our mask...
					 */
					if (timerPort) WaitSigs |= (1L << timerPort->mp_SigBit);

					if (!msg)
					{
						if (Verbose) PutStr("\n");
						PutStr("Enforcer is on the job");
						if (opts[OPT_QUIET]) PutStr(" and silent");
						PutStr(".\n\n");
					}

/**************************************************************************************************************************************************************/
/**************************************************************************************************************************************************************/
					while (!Done)
					{
						/*
						 * So, we got a signal other that CTRL-C...  Lets do what we have to...
						 */
						while (ReadOffset != (temp1=WriteOffset))
						{
							/*
							 * Default change in position...
							 */
							if (ReadOffset>temp1) temp1=BufferSize;
							temp1-=ReadOffset;

							if (opts[OPT_RAWIO])
							{
								RawPutChar(OutputBuffer[ReadOffset]);
								temp1=1;
							}
							else if (OutputFile)
							{
								Write(OutputFile,&OutputBuffer[ReadOffset],temp1);
							}
							else if ((!msg) && opts[OPT_STDIO])
							{
								Write(Output(),&OutputBuffer[ReadOffset],temp1);
							}

							/*
							 * Now, adjust the read pointer
							 */
							temp1+=ReadOffset;
							if (temp1==BufferSize) temp1=0;
							ReadOffset=temp1;
						}

						/*
						 * Check if our timer went off...
						 * (We only have timer requests here)
						 */
						if (timerPort) if (GetMsg(timerPort))
						{
						struct	DateTime	ds;

							/*
							 * Ok, so we need to get a new date
							 * string put together...
							 */
							DateStamp(&(ds.dat_Stamp));
							ds.dat_Format=FORMAT_DOS;
							ds.dat_Flags=0;
							ds.dat_StrDay=NULL;
							ds.dat_StrDate=DateStr;
							ds.dat_StrTime=TimeStr;
							DateToStr(&ds);

							timerIO->tr_time.tv_secs=1;
							timerIO->tr_time.tv_micro=0;
							timerIO->tr_node.io_Command=TR_ADDREQUEST;
							SendIO((struct IORequest *)timerIO);
						}

						if (Wait(WaitSigs) & SIGBREAKF_CTRL_C)
						{
							if (MMU_Off(mmu)) Done=TRUE;
							else if (!msg) PutStr("Error - Can not exit at this time.\n");
						}
					}
/**************************************************************************************************************************************************************/
/**************************************************************************************************************************************************************/

					if (!msg) PutStr("Enforcer is no longer active.\n");
				}
				else if (!msg) PutStr("Error while building the MMU tables.\n");

				/* We go now...  So restore priority... */
				SetTaskPri((struct Task *)proc,oldPri);

				/*
				 * Free up the tables now...
				 */
				Free_MMU_Frame(mmu);
			}
			else if (!msg)
			{
				/*
				 * Maybe tell the user why...  ;^)
				 */
				PutStr("Sorry, but Enforcer can not run at this time, MMU is not available.\n");
			}
		}

		/*
		 * Release the timer resources
		 */
		if (timerOpen)
		{
			AbortIO((struct IORequest *)timerIO);
			WaitIO((struct IORequest *)timerIO);
			CloseDevice((struct IORequest *)timerIO);
		}
		DeleteIORequest((struct IORequest *)timerIO);
		DeleteMsgPort(timerPort);

		/*
		 * Free up the output buffer (if we have one)
		 */
		FreeVec(OutputBuffer);

		/*
		 * Close the output file if needed...
		 */
		if (OutputFile) Close(OutputFile);

		if (rdargs) FreeArgs(rdargs);
		if (myRDArgs) FreeDosObject(DOS_RDARGS,myRDArgs);

		proc->pr_Task.tc_Node.ln_Name=oldName;

		CloseLibrary(Lib68040);
		CloseLibrary(DOSBase);
	}
	else
	{
		if (!msg) if (DOSBase=OpenLibrary("dos.library",0))
		{
			Write(Output(),"Requires Kickstart 2.04 (37.175) or later.\n",43);
			CloseLibrary(DOSBase);
		}
	}

	if (msg)
	{
		Forbid();
		ReplyMsg((struct Message *)msg);
	}

	return(0);
}

static void *AllocAligned(ULONG size,ULONG chunk)
{
void	*data;

	if (data=AllocMem(size+chunk,MEMF_PUBLIC))
	{
		Forbid();
		FreeMem(data,size+chunk);
		data=AllocAbs(size,(APTR)((((ULONG)data)+chunk)&(~chunk)));
		Permit();
	}
	return(data);
}

/* Constants */
#define LEVELA_BITS_030		8	/* Increment in init loop is manually calculated */
#define LEVELB_BITS_030		6
#define LEVELC_BITS_030		8
#define LEVELA_SIZE_030		(1<<LEVELA_BITS_030)
#define LEVELB_SIZE_030		(1<<LEVELB_BITS_030)
#define LEVELC_SIZE_030		(1<<LEVELC_BITS_030)
#define LEVELA_ALLOC_030	(LEVELA_SIZE_030*4)
#define LEVELB_ALLOC_030	(LEVELB_SIZE_030*4)
#define LEVELC_ALLOC_030	(LEVELC_SIZE_030*4)

#define PAGE_SIZE_040	(0x1000)
#define	ALLOC_GRAN_040	(0x01FF)
#define	LEVELA_SIZE_040	(128)
#define	LEVELB_SIZE_040	(128)
#define	LEVELC_SIZE_040	(64)
#define	LEVELA_ALLOC_040	(4*LEVELA_SIZE_040)
#define	LEVELB_ALLOC_040	(4*LEVELB_SIZE_040)
#define	LEVELC_ALLOC_040	(4*LEVELC_SIZE_040)

#define	TABLE_MASK	(0xFFFFFFF3)

/*
 * Clean up the MMU Frame as needed...
 */
static void Free_MMU_Frame(struct MMU_Frame *mmu)
{
ULONG	i;
ULONG	j;
ULONG	*temp1;

	if (mmu)
	{
		switch (mmu->mmu_Flag)
		{
			/* 68030 MMU Frame Cleanup */
		case MMU_030:	if (mmu->mmu_LevelA) FreeMem(mmu->mmu_LevelA,LEVELA_ALLOC_030);
				if (mmu->mmu_LevelB) FreeMem(mmu->mmu_LevelB,LEVELB_ALLOC_030);
				if (mmu->mmu_LevelC) FreeMem(mmu->mmu_LevelC,LEVELC_ALLOC_030);
				break;

			/* 68040 MMU Frame Cleanup */
		case MMU_040:	if ((mmu->mmu_LevelA) && (mmu->mmu_LevelB) && (mmu->mmu_LevelC) && (mmu->mmu_Magic))
				{
					for (i=0;i<LEVELA_SIZE_040;i++) if ((mmu->mmu_LevelA[i]&TABLE_MASK) != mmu->mmu_InvalidA)
					{
						temp1=(ULONG *)(mmu->mmu_LevelA[i] & ~ALLOC_GRAN_040);
						for (j=0;j<LEVELB_SIZE_040;j++) if ((temp1[j]&TABLE_MASK) != mmu->mmu_InvalidB)
						{
							FreeMem((ULONG *)(temp1[j] & ~ALLOC_GRAN_040),LEVELC_ALLOC_040);
						}
						FreeMem(temp1,LEVELB_ALLOC_040);
					}
				}
				if (mmu->mmu_LevelA) FreeMem(mmu->mmu_LevelA,LEVELA_ALLOC_040);
				if (mmu->mmu_LevelB) FreeMem(mmu->mmu_LevelB,LEVELB_ALLOC_040);
				if (mmu->mmu_LevelC) FreeMem(mmu->mmu_LevelC,LEVELC_ALLOC_040);
				if (mmu->mmu_Magic)  FreeMem(mmu->mmu_Magic,PAGE_SIZE_040);
				break;
		}

		/* Free the MMU Frame */
		FreeVec(mmu);
	}
}

static struct MMU_Frame *Mark_Address(struct MMU_Frame *mmu,ULONG addr,ULONG size,ULONG orBits)
{
BOOL	rc=FALSE;	/* Assume failure */
ULONG	i;
ULONG	temp1;
ULONG	temp2;
ULONG	indexa;
ULONG	indexb;
ULONG	indexc;
ULONG	*levelb;
ULONG	*levelc;

	if (mmu)
	{
		switch (mmu->mmu_Flag)
		{
		case MMU_030:	temp1=addr >> 18;
				/* Crude hack routine here for Enforcer-like MMU tables...  Not a full general-perpose MMU thingy yet... */
				if (temp1 < (0xFF / 4))
				{
					temp2=(addr+size-1) >> 18;
					while (temp1 <= temp2)
					{
						if (!(mmu->mmu_LevelB[temp1] & 0x02)) mmu->mmu_LevelB[temp1] |= orBits;
						temp1++;
					}
				}
				else mmu->mmu_LevelA[temp1 >> 6] |= orBits;
				rc=TRUE;
				break;
			/*
			 * For the 68040 we need to maybe allocate another table
			 * if the address falls within that new table...
			 */
		case MMU_040:	/* First, figure out the replacement orBits */
				temp1=0x20;	/* Copyback, cacheable, writeable, invalid */
				if (orBits & VALID) temp1 |= 0x01;	/* Resident */
				if (orBits & NONCACHEABLE) temp1 += 0x20;	/* Make the 0x20 into a 0x40 */
				if (orBits & WRITEPROTECT) temp1 |= 0x04;	/* Write protected */
				orBits=temp1 | 0x400;	/* New orBits plus the global flag ;^)  (trick) */

				temp1=addr >> 12;	/* Remove page offset... */
				temp2=(addr+size-1) >> 12;

				if (temp1>temp2) rc=TRUE;	/* Small size is OK */

				while (temp1 <= temp2)
				{
					rc=FALSE;			/* Assume we failed first... */
					indexa=temp1 >> 13;		/* Top 7 bits */
					indexb=(temp1 >> 6) & 0x7F;	/* Middle 7 bits */
					indexc=temp1 & 0x3F;		/* Bottom 6 bits */

					if (mmu->mmu_LevelA[indexa] == mmu->mmu_InvalidA)
					{
						/* We need a new LevelB here... */
						if (levelb=AllocAligned(LEVELB_ALLOC_040,ALLOC_GRAN_040))
						{
							mmu->mmu_LevelA[indexa]=(((ULONG)levelb) | 0x03);	/* Link it in */
							for (i=0;i<LEVELB_SIZE_040;i++) levelb[i]=mmu->mmu_InvalidB;
						}
					}

					if (mmu->mmu_LevelA[indexa] != mmu->mmu_InvalidA)
					{
						levelb=(ULONG *)(mmu->mmu_LevelA[indexa] & ~ALLOC_GRAN_040);	/* Get table pointer */

						if (levelb[indexb] == mmu->mmu_InvalidB)
						{
							/* We need a new LevelC here... */
							if (levelc=AllocAligned(LEVELC_ALLOC_040,ALLOC_GRAN_040))
							{
								levelb[indexb]=((ULONG)levelc) | 0x03;	/* Link it in */
								for (i=0;i<LEVELC_SIZE_040;i++) levelc[i]=mmu->mmu_InvalidC;
							}
						}

						if (levelb[indexb] != mmu->mmu_InvalidB)
						{
							levelc=(ULONG *)(levelb[indexb] & ~ALLOC_GRAN_040);	/* Get table pointer */

							if (levelc[indexc] == mmu->mmu_InvalidC) levelc[indexc]=(temp1 << 12);
							levelc[indexc] |= orBits;

							/* Special case:  Check if we marked both cache and non-cache */
							/* non-cache wins every time, so force it to win! */
							if ((levelc[indexc] & 0x60) == 0x60) levelc[indexc]-=0x20;

							rc=TRUE;	/* Operation worked... */
						}
					}

					temp1++;
				}
				break;
		}
	}

	if (!rc)
	{
		Free_MMU_Frame(mmu);
		mmu=NULL;
	}

	return(mmu);
}

/*
 * This routine will map the ROM addresses to the physical
 * addresses given here...
 */
static struct MMU_Frame *Map_ROM(struct MMU_Frame *mmu,ULONG addr)
{
ULONG	temp1;
ULONG	*level;

	mmu=Mark_Address(mmu,0x00F80000,0x00080000,VALID | CACHEABLE | WRITEPROTECT);

	/*
	 * Check if we need to do a special mapping...
	 */
	if (mmu) if (0x00F80000 != addr)
	{
		switch (mmu->mmu_Flag)
		{
			/*
			 * The 68030 case is rather simple...  ;^)
			 */
		case MMU_030:	mmu->mmu_LevelB[0xF8 / 4] = addr | 0x1D;		/* Cacheable, Writeprotected, Resident */
				mmu->mmu_LevelB[0xFC / 4] = (addr+0x00040000) | 0x1D;	/* Cacheable, Writeprotected, Resident */
				break;

			/*
			 * The 68040 case is a bit harder...
			 */
		case MMU_040:	temp1=0x00F80000 >> 12;
				/* Starting at the ROM, do the full 512K space mapping... */
				while (temp1 <= (0x00FFFFFF >> 12))
				{
					level=(ULONG *)(mmu->mmu_LevelA[temp1 >> 13] & ~ALLOC_GRAN_040);	/* LevelB */
					level=(ULONG *)(level[(temp1 >> 6) & 0x7F] & ~ALLOC_GRAN_040);		/* LevelC */
					level[temp1 & 0x3F] = addr | 0x425;	/* Global, Cacheable, Writeprotected, Resident */

					temp1++;	addr+=0x00001000;	/* Next page:  4K at a shot... */
				}
				break;
		}
	}

	return(mmu);
}

/*
 * This routine will mark the invalid addresses as invalid
 * The whole address page is marked, not just the single address
 * since this would be impossible on the current MMUs
 * It will also make the indirect frame marked as invalid.
 */
static struct MMU_Frame *Mark_Invalid(struct MMU_Frame *mmu)
{
	if (mmu)
	{
		switch (mmu->mmu_Flag)
		{
			/*
			 * The 68030 case is rather simple...  ;^)
			 */
		case MMU_030:	mmu->mmu_LevelC[0]=0xBADF00D0;
				break;

			/*
			 * The 68040 case is a bit harder...
			 */
		case MMU_040:	/*
				 * First make sure the table is build
				 */
				mmu->mmu_Indirect=((ULONG)(mmu->mmu_Magic)) | 0x044; /* invalid, write protected, non-cached, serialized */
				if (mmu=Mark_Address(mmu,0,1,0))
				{
				ULONG	*level;

					/* Now we know the tree down to the address is there... */
					level=(ULONG *)(mmu->mmu_LevelA[0] & ~ALLOC_GRAN_040);	/* Get LevelB */
					level=(ULONG *)(level[0] & ~ALLOC_GRAN_040);		/* Get LevelC */
					level[0]=0x00000044;					/* Non-cached, serialized, write protected, invalid */
					mmu->mmu_Page0=level;					/* Store Page 0 address */
				}
				break;
		}
	}
	return(mmu);
}

static struct MMU_Frame *Init_MMU_Frame(ULONG MMU_Flag)
{
struct	MMU_Frame	*mmu;
	ULONG		i;
	BOOL		rc=FALSE;

	if (mmu=AllocVec(sizeof(struct MMU_Frame),MEMF_PUBLIC|MEMF_CLEAR))
	{
		mmu->mmu_Flag=MMU_Flag;
		if (MMU_Flag==MMU_030)
		{
			mmu->mmu_LevelA=AllocAligned(LEVELA_ALLOC_030,0x0000000F);
			mmu->mmu_LevelB=AllocAligned(LEVELB_ALLOC_030,0x0000000F);
			mmu->mmu_LevelC=AllocAligned(LEVELC_ALLOC_030,0x0000000F);

			if ((mmu->mmu_LevelA) && (mmu->mmu_LevelB) && (mmu->mmu_LevelC))
			{
				/*
				 * Now build the base MMU table...
				 */
				for (i=0;i<LEVELA_SIZE_030;i++) mmu->mmu_LevelA[i]=(i << 24);
				for (i=0;i<LEVELB_SIZE_030;i++) mmu->mmu_LevelB[i]=(i << 18);
				for (i=0;i<LEVELC_SIZE_030;i++) mmu->mmu_LevelC[i]=(i << 10) | VALID | NONCACHEABLE;

				/*
				 * Connect up the tables.
				 */
				mmu->mmu_TC	=0x80A08680;
				mmu->mmu_CRP[0]	=0x80000002;
				mmu->mmu_CRP[1]	=(ULONG)mmu->mmu_LevelA;
				mmu->mmu_LevelA[0]=((ULONG)mmu->mmu_LevelB) | 0x02;
				mmu->mmu_LevelB[0]=((ULONG)mmu->mmu_LevelC) | 0x02;

				rc=TRUE;
			}
		}
		else if (MMU_Flag==MMU_040)
		{
			mmu->mmu_LevelA=AllocAligned(LEVELA_ALLOC_040,ALLOC_GRAN_040);
			mmu->mmu_LevelB=AllocAligned(LEVELB_ALLOC_040,ALLOC_GRAN_040);
			mmu->mmu_LevelC=AllocAligned(LEVELC_ALLOC_040,ALLOC_GRAN_040);
			mmu->mmu_Magic=AllocAligned(PAGE_SIZE_040,PAGE_SIZE_040-1);

			if ((mmu->mmu_LevelA) && (mmu->mmu_LevelB) && (mmu->mmu_LevelC) && (mmu->mmu_Magic))
			{
				/* First, set up the fake page memory to be what I want */
				for (i=0;i<(PAGE_SIZE_040/4);i++) mmu->mmu_Magic[i]=Bad_ReadValue;

				/* Now, set up the default invalid page pointers... */
				mmu->mmu_Indirect=((ULONG)(mmu->mmu_Magic)) | 0x041; /* valid, non-cached, serialized */
				mmu->mmu_InvalidC=((ULONG)&(mmu->mmu_Indirect)) | 0x02; /* Indirect to the invalid one... */
				mmu->mmu_InvalidB=((ULONG)(mmu->mmu_LevelC)) | 0x03; /* Resident table */
				mmu->mmu_InvalidA=((ULONG)(mmu->mmu_LevelB)) | 0x03; /* Resident table */

				for (i=0;i<LEVELC_SIZE_040;i++) mmu->mmu_LevelC[i]=mmu->mmu_InvalidC;
				for (i=0;i<LEVELB_SIZE_040;i++) mmu->mmu_LevelB[i]=mmu->mmu_InvalidB;
				for (i=0;i<LEVELA_SIZE_040;i++) mmu->mmu_LevelA[i]=mmu->mmu_InvalidA;

				mmu->mmu_TC=0x00008000;	/* Enable MMU with 4K pages */
				mmu->mmu_CRP[0]=(ULONG)mmu->mmu_LevelA;
				mmu->mmu_CRP[1]=(ULONG)mmu->mmu_LevelA;

				/* For simple games, make default PAGE0 point at the indirect page */
				mmu->mmu_Page0=&(mmu->mmu_Indirect);

				/* We now have a full 68040 MMU tree, everything is marked invalid... */
				rc=TRUE;
			}
		}
	}

	if (!rc)
	{
		Free_MMU_Frame(mmu);
		mmu=NULL;
	}

	return (mmu);
}
