Joined: Thu May 28, 2009 9:46 pm Posts: 8505 Location: Midwestern USA
|
I have been contemplating the "bank $00 dilemma" for quite a while, even before POC V2 was more than some passing thoughts. To briefly recapitulate, the "bank $00 dilemma" exists due to a characteristic of the 65C816 that causes it to "hard wire" all zero page (ZP) and stack accesses, as well as indirect absolute addressing, to the first 64 KB of RAM, resulting in a $00xxxx effective address. Also, response to an interrupt of any kind causes the program bank to be forced to $00, thus requiring that the MPU's hardware vectors and interrupt service routines be located in bank $00. This convergence on bank $00 is inconvenient in any system that is expected to support preemptive multitasking, as it complicates the implementation of hardware memory protection.
Ideally, the natural memory segmentation produce by the '816's bank-oriented architecture should allow the system designer to confine each running process to an arbitrarily assigned bank, and prevent inter-bank access without the permission and cooperation of a supervisory program (e.g., an operating system kernel). This is the architecture I envision with POC V2. As ZP and stack access is necessary in most programs, a dilemma is created about what to do with the fact that ZP and stack accesses are directed to bank $00, thus violating the rule that no process be allowed to access any bank other than its own. If an exception to this rule is created so ZP and stack accesses will work, what would prevent any process from overwriting parts of bank $00 that it shouldn't touch?
In current RISC and x86 hardware, the MPU has a feature called "privilege rings," which can be utilized to prevent user-mode programs from executing instructions that should only be executed by the kernel. In such hardware, the kernel operates with maximum privileges and user applications operate with much lower privileges. Hence an attempt by a user-space process to execute potentially destructive instructions can be blocked. Unfortunately, the '816 doesn't have this feature, which if present, would solve the bank $00 dilemma. Therefore, the system glue logic has to somehow take over and create a "privilege rings" analog. I've started to develop some ideas on how to go about doing this. So the rest of this post will be a sort of sounding board for a possible solution to the bank $00 dilemma. I won't go into any hardware or software specifics, since the solution can't be designed until the problem has been defined.
Architectures that support memory protection typically use virtual addressing rather than physical, both for segregation of running processes and for economizing on RAM consumption. In virtual addressing architectures, the smallest amount of contiguous RAM that can be protected is often referred to as a page (not to be confused with the 65xx notion of a page) and, in many cases, is 4 KB in size. Multiple pages can be ganged to produce a larger protected execution space when needed.
In POC V2, I'm not particularly concerned about RAM consumption at this time. Therefore, I've decided to use fixed sized protected space equal in size to a bank, or 64 KB. As the '816's architecture is bank-oriented, I'll use that term instead of page when talking about protected space. V2 will have 1024 KB of RAM, which is equal to 16 banks, enough to prove that my virtual addressing concept will work—or not. The CPLD will be tasked with generating the A16-A23 bank bits, which means it will always determine into which bank an access will occur. If my logic is correctly implemented, accesses that would normally be forced into bank $00 by the MPU could instead be directed to a different bank without the running process being aware of the change. The result would be a solution to the bank $00 dilemma.
In order to remap bank $00 accesses, the CPLD logic has to know where to remap the accesses and when such a translation is appropriate. The former would be determine by consulting some virtual registers in the CPLD that the operating system kernel would maintain. The latter would be determined by whether the hardware is running in "real" or "protected" mode. Also, when in protected mode, a distinction will be made between user or kernel mode. Additional rules will apply when an interrupt is serviced:
- User mode. Full protection is enabled. The effective address is always in the bank set in the CPLD's user bank virtual register. Bank $00 accesses are always remapped to the bank in which the user mode process is executing. Attempted accesses to another bank will cause an memory fault and trigger a hardware abort.
- Kernel mode. Reduced memory protection is enabled. The effective address is normally in the bank set in the CPLD's kernel bank virtual register but can be changed to access another process' bank. Bank $00 accesses are normally remapped to the kernel bank but can be remapped to any other bank, such as for accessing another process' stack.
- Interrupt mode. This mode occurs when the MPU starts to service an interrupt. The following generalized steps would occur:
- The current mode and bank $00 remapping is maintained until the MPU has pushed the program bank (PB), program counter (PC) and status register (SR). As bank $00 remapping has not been changed during this step, PB, PC and SR will be pushed onto the interrupted process' stack.
- As soon as the MPU has pushed SR (cycle 6 of the interrupt sequence), it will assert (negate) its VPB (vector pull) output, which will cause the CPLD to enable interrupt mode. Protection will be changed to kernel mode and bank $00 remapping will be disabled, causing the MPU hardware vectors to appear in the range $00FE00-$00FFFF.
- During cycles 7 and 8 of the interrupt sequence, the MPU will load PC from the relevant interrupt service routine (ISR) jump vector and execute the ISR.
- With bank $00 remapping disabled, any stack activity performed by the ISR will be directed to physical bank $00. The ISR is permitted to access any bank and may also enable bank $00 remapping as long as when doing so subsequent ISR code is not in the real bank $00. Special handling of the stack pointer would be required if an ISR itself was interrupted. Proper coding, however, can prevent this from occurring.
As kernel APIs are called via software interrupts, the system always goes into interrupt mode before the API code is executed. Once the kernel has saved context it can (probably should) clear interrupt mode and re-enable IRQs before continuing.
- Real mode. All protection is disabled. The effective address is always that generated by the MPU—the CPLD will use the DB (data bank) or PB register contents to assert the appropriate address bus and chip select lines. Real mode is the default following reset.
Also essential to effective protection is preventing execution of certain MPU instructions that could take down the system. Some instructions will automatically cause a memory fault because the associated operand uses a 24 bit address. However, there are instructions that should never be executed and some that should not be executable by user-mode processes. In the first group are COP, STP, WDM and XCE. In the second group are JML, JSL, RTI, RTL and SEI. SEP is potentially dangerous in one case: SEP #%00000100, which is effectively an SEI instruction. The sequence:
Code: LDA #%00000100 PHA PLP which would likewise disable IRQs, causing a total system halt (which a watchdog timer could fix by triggering an NMI whose handler would re-enable IRQs).
It is possible for CPLD code to look at D0-D7 while the MPU is fetching an opcode and compare it to the above list for further action. Checking SEP would mean also looking at the operand. Unfortunately, I can't think of any way to prevent a program from pulling a stack value into SR that would shut down IRQs.
Anyhow, now that I've got some of this down on paper the next step is to create some logic tables that describe what I'm trying to achieve.
_________________ x86? We ain't got no x86. We don't NEED no stinking x86!
|
|