![]() | by Michael Sinz (MKSoft Development) Copyright 1992-2001 - All Rights Reserved Enforcer.c |
by Michael Sinz MKSoft Development Copyright 1992-2001 All Rights Reserved Documentation Enforcer Archive ![]() Source code for the main Enforcer tool: Source code for the MMU tool: Source code for SegTracker: Source code for FindHit: Source code for RebootOff: Source code for Move4K: Source code for LawBreaker:
|
/*
* Enforcer by
* Michael Sinz
*
* Copyright 1992-2001
* All Rights Reserved
*
*****************************************************************************
* *
* Permission is hereby granted to distribute the Enforcer archive *
* containing the executables and documentation for non-commercial purposes *
* so long as the archive and its contents are not modified in any way. *
* *
* Enforcer and related tools are not in the public domain. *
* *
* Enforcer and related tools may not be distributed for a profit. *
* *
*****************************************************************************
*
*/
/*
******* 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 not to 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 a bit 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. 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 run, a set of MMU tables that map addresses that are not
* in the system's address map as invalid are installed. Enforcer
* will then trap invalid access attempts 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. Invalid
* 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
* date and time if needed)
*
* WARNING
* Enforcer is for software testing. In this role it is vital.
* Software that causes Enforcer hits may 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
* 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. To 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
* 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.
*
* 68060 NOTES
* Enforcer, on the 68060, *requires* that the 68060.library be
* installed. Due to the fact that various possible 68060.library
* versions may exist, Enforcer tries to not second guess it.
* Thus, Enforcer assumes that the 68060.library has all of the
* same functionality as V37.30 or better of the 68040.library.
*
* It turns out that some of the 68060 libraries do not have the
* same functionality of the 68040.library. One common library
* has elected not to handle Pre/Post DMA MMU table operations
* when Enforcer installs its MMU table. This results in some
* DMA/Cache interactions. Enforcer can not work around this
* problem safely. If you happen to have a 68060.library with
* version 2.1 (19.07.96) you may be able to patch it to not
* have this problem. At offset $09BE there should be the
* 4-byte sequence $20 6D 00 04 Changing this to $4E 7A 88 06
* will let it handle Enforcer's MMU tables too. (The same
* patch may work in other versions of the library)
*
* For implementers of 68060.library, see my notes as to what
* had to be done in 68040.library for correct operation.
* Note that this does not mean that Enforcer needs this. The Amiga
* system needs this to operate correctly. Enforcer just may
* cause these problems to become more evident. The notes are only
* in the AmigaGuide version of the Enforcer documentation.
*
* The 68060 exception model is full-restart, which means that
* all instructions are re-run. Both reads *and* writes.
* This means that Enforcer can not tell you what the data
* that would be written is, unlike the 68040 and earlier CPUs.
* So, the output for a write will not include the data that
* was to be written. This does mean that faults happen
* before the instruction is executed (usually) and thus the
* reported PC will be more exact. This restart model also
* means that if an real bus-fault happens, Enforcer will be
* unable to do much other than let it happen. (The same is
* true for reads on the 68040) Enforcer maps all addresses
* as either valid based on system configuration or invalid.
* This is so that no address should cause a bus fault unless
* the system configuration is incorrect and an address that
* was marked valid actually causes a fault.
*
* Be sure to read the 68040 notes as the 68060 is a superset
* of much of these notes.
*
* Due to the complexity of emulating access to lower memory and
* the fact that the 68060 was introduced well after V39 kickstart,
* it is highly recommended that you use V39 or better with 68060 CPUs.
* This mainly has to deal with lower 4K of memory. As of V38 of
* exec.library, 68040/68060 processors would map out the lower 4K
* of RAM rather than just 1K. This was required since the newer
* CPUs did not have page sizes less than 4K.
*
* It turns out that some 68060 CPU cards also have other hardware
* on them. This is not a problem, unless this hardware does not
* autoconfigure. Enforcer needs to know about hardware in the
* system so it can map the MMU to that hardware. If the hardware
* is not true Amiga AutoConfig (as in no expansion.library entry)
* then Enforcer has no way of knowing it exists.
*
* A common location for such hardware control registers to be
* placed is in the reserved $00F00000 address range (known as
* F-Space). This 512K space was reserved for future Kickstart
* growth. It also has some magic in it so that you can wedge
* special startup routines there for things like 68060 cards.
* At least one vendor is doing all of this correctly and
* has even made sure that expansion.library knows about the
* hardware that is located in the $00F00000 address range.
*
* If, when running Enforcer, your machine does lock up *and*
* when you run Enforcer with the VERBOSE option it does not
* say that the $00F00000 address range is a "board address"
* then you may wish to try the FSPACE option to see if this
* is the reason. If this does not fix the problem, you more
* than likely have either a real bug or some other non-AutoConfig
* hardware in your system.
*
* 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 common part...
* ;
* 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
* ;
* ; 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:
* Common: 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 68020/68030 version...
* ;
* NewVector030: cmp.l #4,$10(sp) ; 68020 and 68030
* beq.s TraceSkip ; If AbsExecBase, OK
* bra.s Common ; Do the common stuff
* ;
* ; This is the 68040 version...
* ;
* NewVector040: cmp.l #4,$14(sp) ; 68040
* beq.s TraceSkip ; If AbsExecBase, OK
* bra.s Common ; Do the common stuff
* ;
* ; This is the 68060 version...
* ;
* NewVector060: cmp.l #4,$08(sp) ; 68060
* beq.s TraceSkip ; If AbsExecBase, OK
* bra.s Common ; Do the common stuff
*
*
* 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... (Note - must match what is in handler.asm)
*/
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/68060, 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/68060 slimmy trick... */
ULONG *mmu_Magic; /* Special memory area for magic code (such as the 68040/68060 bus error remapping magic) */
ULONG *mmu_Page0; /* For 68040/68060 page 0 work */
ULONG mmu_InvalidA; /* The invalid value at LevelA (68040/68060 only) */
ULONG mmu_InvalidB; /* The invalid value at LevelB (68040/68060 only) */
ULONG mmu_InvalidC; /* The invalid value at LevelC (68040/68060 only) */
ULONG mmu_TT[4]; /* Storage for TT regs (68040/68060 only) */
};
/* Values for MMU_Frame.mmu_Flag */
#define MMU_030 1 /* MMU setup 68030 or 68020+68851 */
#define MMU_040 2 /* MMU setup 68040 */
#define MMU_060 3 /* MMU setup 68060 */
/*
* General flags for setting up the MMU
*/
#define VALID (0x01)
#define NONCACHEABLE (0x40)
#define CACHEABLE (0x00)
#define INVALID (0x00)
#define WRITEPROTECT (0x04)
/*
* This is for the 68060 marking of writethrough because
* of the various 68060.library implementations. (Enforcer
* itself does not need it)
* *DO NOT USE THIS IN NON-68060 MMUs*
*/
#define MMU_060_TYPE (0x81)
/*
* Number of copies of memory headers
* (Most systems have only 1 or 2 or maybe 3 so 400
* should cover more than any system for a long time!
*/
#define NUM_COPIES (400)
/******************************************************************************/
/* 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 struct Library *Lib68060;
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 68060... If it fails, try 68040... */
Lib68060=OpenLibrary("68060.library",0);
Lib68040=(Lib68060 ? NULL : 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]))
{
struct SignalSemaphore *sem;
ULONG MMU_Flag;
ULONG Verbose=FALSE;
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
* 3 - For 68060
*/
MMU_Flag=Test_MMU();
if (mmu=Init_MMU_Frame(MMU_Flag))
{
struct MemHeader *mem;
struct MemHeader *copies;
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);
/*
* Map the ROM in...
*/
mmu=Map_ROM(mmu,ROM_Addr);
if (Verbose) VPrintf("ROM Physical: $%08lx Size: $00080000 (512K)\tType=$FF CACHEABLE\n",(LONG *)&ROM_Addr);
/*
* Protect $00F00000 card memory unless told not to...
* We do this up here in case someone made a "card" that
* does it later as something else...
*/
if (opts[OPT_FSPACE])
{
mmu=Mark_Address(mmu,0x00F00000,0x00080000,VALID | NONCACHEABLE);
}
else
{
mmu=Mark_Address(mmu,0x00F00000,0x00080000,VALID | CACHEABLE | WRITEPROTECT);
}
/*
* Map in the memory areas...
*/
if (copies=AllocVec(sizeof(struct MemHeader)*NUM_COPIES,MEMF_PUBLIC|MEMF_CLEAR))
{
LONG numHeaders=0;
LONG i;
Forbid();
mem=(struct MemHeader *)SysBase->MemList.lh_Head;
while ((mem->mh_Node.ln_Succ) && (numHeaders<NUM_COPIES))
{
copies[numHeaders++]=*mem;
mem=(struct MemHeader *)(mem->mh_Node.ln_Succ);
}
Permit();
for (i=0;i<numHeaders;i++)
{
mem=&copies[i];
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)));
if (Verbose)
{
ULONG tmp[5];
tmp[0]=(ULONG)(mem->mh_Lower);
tmp[1]=(ULONG)(mem->mh_Upper)-(ULONG)(mem->mh_Lower);
tmp[2]=tmp[1] / 1024;
tmp[3]=(ULONG)(MEMF_CHIP & TypeOfMem(mem->mh_Lower));
tmp[4]=tmp[3] ? ((ULONG)"CACHE DISABLED") : ((ULONG)"CACHEABLE");
VPrintf("Memory Address: $%08lx Size: $%08lx (%ldK)\tType=$%02lx %s\n",(LONG *)tmp);
}
}
FreeVec(copies);
}
else
{
Free_MMU_Frame(mmu);
mmu=NULL;
}
/*
* 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",(LONG *)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);
/*
* Special case the first page of CHIP RAM
*/
mmu=Mark_Address(mmu,0,0x1000,VALID | NONCACHEABLE);
/*
* 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);
}
/*
* 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... 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(Lib68060);
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_GRANA_040 (0x01FF)
#define ALLOC_GRANB_040 (0x01FF)
#define ALLOC_GRANC_040 (0x00FF)
#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/68060 MMU Frame Cleanup */
case MMU_040:
case MMU_060: 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_GRANB_040);
for (j=0;j<LEVELB_SIZE_040;j++) if ((temp1[j]&TABLE_MASK) != mmu->mmu_InvalidB)
{
FreeMem((ULONG *)(temp1[j] & ~ALLOC_GRANC_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
{
temp1=addr >> 24;
temp2=(addr+size-1) >> 24;
while (temp1<=temp2)
{
mmu->mmu_LevelA[temp1] |= orBits;
temp1++;
}
}
rc=TRUE;
break;
/*
* For the 68040/68060 we need to maybe allocate another table
* if the address falls within that new table...
*/
case MMU_040:
case MMU_060: /* 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 */
/* If you do 68060 MMU table type, we set to writethrough... */
if (orBits == MMU_060_TYPE) temp1 = 0x0801;
orBits=temp1; /* New orBits */
temp1=addr >> 12; /* Remove page offset... */
temp2=(addr+size-1) >> 12;
if (temp1>temp2) rc=TRUE; /* Small size is OK */
while (mmu && (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_GRANB_040))
{
mmu->mmu_LevelA[indexa]=(((ULONG)levelb) | 0x03); /* Link it in */
for (i=0;i<LEVELB_SIZE_040;i++) levelb[i]=mmu->mmu_InvalidB;
/* Now, if 68060, mark it non-cached/serialized */
if (mmu->mmu_Flag == MMU_060)
{
mmu=Mark_Address(mmu,(ULONG)levelb,LEVELB_ALLOC_040,MMU_060_TYPE);
}
}
}
if (mmu && (mmu->mmu_LevelA[indexa] != mmu->mmu_InvalidA))
{
levelb=(ULONG *)(mmu->mmu_LevelA[indexa] & ~ALLOC_GRANB_040); /* Get table pointer */
if (levelb[indexb] == mmu->mmu_InvalidB)
{
/* We need a new LevelC here... */
if (levelc=AllocAligned(LEVELC_ALLOC_040,ALLOC_GRANC_040))
{
levelb[indexb]=((ULONG)levelc) | 0x03; /* Link it in */
for (i=0;i<LEVELC_SIZE_040;i++) levelc[i]=mmu->mmu_InvalidC;
/* Now, if 68060, mark it non-cached/serialized */
if (mmu->mmu_Flag == MMU_060)
{
mmu=Mark_Address(mmu,(ULONG)levelc,LEVELC_ALLOC_040,MMU_060_TYPE);
}
}
}
if (mmu && (levelb[indexb] != mmu->mmu_InvalidB))
{
levelc=(ULONG *)(levelb[indexb] & ~ALLOC_GRANC_040); /* Get table pointer */
if (levelc[indexc] == mmu->mmu_InvalidC) levelc[indexc]=(temp1 << 12);
levelc[indexc] &= ~0x04; /* Mask out the read-only flag */
levelc[indexc] |= orBits;
/* Cute trick - we make any table entry that could cause */
/* an enforcer hit as local so that we only have to flush */
/* the local ATC and not the global ATC... */
if ((levelc[indexc] & 0x05)==0x01) levelc[indexc] |= 0x0400;
else if (levelc[indexc] & 0x0400) levelc[indexc] -= 0x0400;
/* 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;
/* Special case: Check if we set the 68060 MMU type user bit */
/* If so, we set it to cached - writethrough */
if (levelc[indexc] & 0x0800) levelc[indexc] &= ~0x60;
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/68060 case is a bit harder...
*/
case MMU_040:
case MMU_060: 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_GRANB_040); /* LevelB */
level=(ULONG *)(level[(temp1 >> 6) & 0x7F] & ~ALLOC_GRANC_040); /* LevelC */
level[temp1 & 0x3F] = addr | 0x025; /* Local, 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/68060 case is a bit harder...
*/
case MMU_040:
case MMU_060: /*
* 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_GRANB_040); /* Get LevelB */
level=(ULONG *)(level[0] & ~ALLOC_GRANC_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))
{
switch (mmu->mmu_Flag=MMU_Flag)
{
/* Initialize the 68030 frame... */
case 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;
}
break;
/* Initialize the 68040/68060 frame... */
case MMU_040:
case MMU_060: mmu->mmu_LevelA=AllocAligned(LEVELA_ALLOC_040,ALLOC_GRANA_040);
mmu->mmu_LevelB=AllocAligned(LEVELB_ALLOC_040,ALLOC_GRANB_040);
mmu->mmu_LevelC=AllocAligned(LEVELC_ALLOC_040,ALLOC_GRANC_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;
/* Now, if 68060, mark it non-cached/serialized */
if (mmu->mmu_Flag == MMU_060)
{
if (mmu) mmu=Mark_Address(mmu,(ULONG)mmu->mmu_LevelA,LEVELA_ALLOC_040,MMU_060_TYPE);
if (mmu) mmu=Mark_Address(mmu,(ULONG)mmu->mmu_LevelB,LEVELB_ALLOC_040,MMU_060_TYPE);
if (mmu) mmu=Mark_Address(mmu,(ULONG)mmu->mmu_LevelC,LEVELC_ALLOC_040,MMU_060_TYPE);
}
}
break;
}
}
if (!rc)
{
Free_MMU_Frame(mmu);
mmu=NULL;
}
return (mmu);
}
| ||
![]() |

visitors to this page.