6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Thu Nov 21, 2024 9:15 pm

All times are UTC




Post new topic Reply to topic  [ 53 posts ]  Go to page Previous  1, 2, 3, 4  Next
Author Message
PostPosted: Mon Jun 19, 2023 9:09 am 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 746
Location: Germany
Nice, a topic about something I've been fantasizing about for a while now.
But damn, 2007. Didn't realize how old this topic is!

I don't have any logic designed yet, only a written concept so I'll just throw it in here and see if I do something with it in the future one day.

Registers: (W = write only, RW = read/write)
XSRT - Execute segment start (16-bit, W)
XEND - Execute segment end (16-bit, W)
DSRT - Data segment start (16-bit, W)
DEND - Data segment end (16-bit, W)
ZBNK - Zero Bank (8-bit, W)
SCTR - Status/Control register (8-bit, RW)

MMU part:
Whenever Bank 0 is accessed (and VPB is not asserted) the contents of ZBNK are used for the upper 8 bits of the address bus. This only counts for data accesses (VDA & !VPA), instruction/operand fetches are not redirected, this is done to allow for interrupts to work properly.

MPU part:
Depending on the state of VPA and VDA the upper 16-bits of the address are checked against either the X (execution) or D (data) registers.
(For bank 0 redirected data accesses, the address is checked after the upper 8-bits are replaced by ZBNK).

When an address is detected outside the bounds of either the X or D registers (and VPB is not asserted), the current instruction will be aborted.

Once the CPU asserts VPB (or an access violation is detected) the MPU part is disabled and the CPU can freely access all of memory.
Because execution and vectors are not redirected from Bank 0, the CPU will handle the interrupt normally (though the stack and DP an still be somewhere else)
you could in theory fill all of Bank 0 with ROM to make decoding simpler, but you would need to set ZBNK to 0 to access data from it.

The status/control register can be read to see if the MPU if it aborted because of an execution or data access violation.

To re-enable the MPU part, the CPU has to write to the status/control register. After doing that the MPU will monitor the address bus and wait for the CPU to do an instruction fetch within the bounds of the X registers, only then will the MPU part be enabled again.

This is to allow the CPU to take it's time to return to the user program without having to worry about returning within a set amount of cycles.

.

So yea that's the main concept. There are probably some edge cases that would break this. Or ways you could optimize it to use fewer registers or have a faster propagation delay, but I can't think of anything on the spot. So any ideas and criticism would be welcome.
(On a side note there is no IO protection because I would assume IO to be located somewhere outside the user program segments so that it doesn't need any extra logic to be protected.


Top
 Profile  
Reply with quote  
PostPosted: Mon Jun 19, 2023 7:33 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8504
Location: Midwestern USA
Proxy wrote:
Nice, a topic about something I've been fantasizing about for a while now. But damn, 2007. Didn't realize how old this topic is!

Actually, the first post was from 2004.

Quote:
I don't have any logic designed yet, only a written concept so I'll just throw it in here and see if I do something with it in the future one day.

I've given some thought to it as well, but nothing firm has gelled and time is a problem for me, especially now.

Once thing to think about is the concept of user and supervisor modes a la the MC68000. The 816 doesn’t have those, of course, but it could be done in a sufficiently capable logic device. In user mode, some instructions, e.g., RTI or STP, would be disallowed, and direct page and stack accesses would be confined to a set range. In supervisor mode, DP and stack accesses could be anywhere, and all instructions would be allowed.

Supervisor mode would be enabled by the logic detecting the assertion of VPB. The return to user mode would be triggered by the MPU writing to a specific logic register to clear supervisor mode. That would be the final step before returning control via an RTI instruction. The actual switch would occur a couple of clock cycles later following the write to the control register, which prevents the logic from thinking the RTI is a disallowed instruction.

The above implies that operating system kernel calls are implemented via a software interrupt (I use COP for that purpose), which would implicitly trigger the switch from user to supervisor mode.

There would be more to it, of course...just some thoughts at this point.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Tue Jun 20, 2023 3:42 am 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 746
Location: Germany
BigDumbDinosaur wrote:
In user mode, some instructions, e.g., RTI or STP, would be disallowed, and direct page and stack accesses would be confined to a set range. In supervisor mode, DP and stack accesses could be anywhere, and all instructions would be allowed.

hmm, disallowing instructions requires the MMU/MPU to actively snoop the data bus along side the address bus (during opcode fetches, aka "VPA & !VDA"). adds a bit of complexity but there aren't a lot of instructions that you would need to check for.
like you said STP is one that would be useful to disallow as it puts the CPU into a state that cannot be recovered from without resetting it. but i don't see why you would want to disallow RTI. it's almost the same thing as an RTL instruction except the address is a bit different and it also pulls the status register. there is not really any reason why

BigDumbDinosaur wrote:
Supervisor mode would be enabled by the logic detecting the assertion of VPB. The return to user mode would be triggered by the MPU writing to a specific logic register to clear supervisor mode. That would be the final step before returning control via an RTI instruction. The actual switch would occur a couple of clock cycles later following the write to the control register, which prevents the logic from thinking the RTI is a disallowed instruction.

i'm personally not a fan of the "activating x cycles after writing to the MPU-enable register/bit" because you need to start counting cycles when writing code to make sure you return fast enough to be in-bounds when it's active again but also not too fast to allow the user program to execute a few instructions still in supervisor mode/without memory protection.
which is why i would instead do this: writing to the re-activation register/bit puts the MPU into a state where it waits for the first opcode fetch within the bounds of the user program's memory area and only then re-active the memory protection/switch to user mode.
this avoids the need for having to count cycles or similar.


Top
 Profile  
Reply with quote  
PostPosted: Tue Jun 20, 2023 7:39 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8504
Location: Midwestern USA
Proxy wrote:
hmm, disallowing instructions requires the MMU/MPU to actively snoop the data bus along side the address bus (during opcode fetches, aka "VPA & !VDA"). adds a bit of complexity but there aren't a lot of instructions that you would need to check for.

In user mode, you want to “police” the data bus during VDA & VPA to watch for any opcode that could potentially result in system instability or fatality. Foremost would be STP, since, as you note, its effects cannot be undone except with a hard reset. XCE would be another one. SEI could be fatal—loss of task switching, I/O processing, etc., as could any instruction that inadvertently sets i in SR. In that vein, SEP would need watching to see if the mask that is its operand would set i in SP. Ditto for PLP and RTI. I would completely disallow RTI in user mode, due to potentially dangerous effects on SR (that pesky i bit again) and that it pulls a potentially-unknown value into PB.

Quote:
i'm personally not a fan of the "activating x cycles after writing to the MPU-enable register/bit" because you need to start counting cycles when writing code to make sure you return fast enough to be in-bounds when it's active again but also not too fast to allow the user program to execute a few instructions still in supervisor mode/without memory protection.

If the write that switches from supervisor mode to user mode is the last instruction executed before the RTI that returns control to the user, it’s easy to predict when the next user instruction opcode fetch will occur. I don’t see a problem with that—a state machine in the logic is started with the “switch modes” write and times out after the MPU has executed RTI, which takes exactly seven cycles.

Quote:
which is why i would instead do this: writing to the re-activation register/bit puts the MPU into a state where it waits for the first opcode fetch within the bounds of the user program's memory area and only then re-active the memory protection/switch to user mode.
this avoids the need for having to count cycles or similar.

And what happens when the top of the stack is accidentally corrupted and the return is not to the user code that was interrupted? :)

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Tue Jun 20, 2023 8:33 am 
Offline
User avatar

Joined: Tue Oct 25, 2016 8:56 pm
Posts: 362
When I have thought about this in the past - and I actually did half-design a memory protection capable MMU for the 65C02, but I had trouble getting WinCUPL to cooperate with my design - I didn't consider protecting the P register and/or the I bit therein terribly important, because I'd put the Task Switching routine on NMI. Obviously its not ideal if all tasks disable their interrupt bit, but the interrupts will still get serviced during the OS's jiffy interrupt on NMI.

Stopping STP is very important though, and as I recall I also had it prevent a handful of other instructions though I do not recall what they are off the top of my head. I'd have to go and look at the design docs in my archive...

_________________
Want to design a PCB for your project? I strongly recommend KiCad. Its free, its multiplatform, and its easy to learn!
Also, I maintain KiCad libraries of Retro Computing and Arduino components you might find useful.


Top
 Profile  
Reply with quote  
PostPosted: Tue Jun 20, 2023 3:53 pm 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 746
Location: Germany
I Agree that NMI should be used for the periodic interrupt and task switching instead of IRQ.
exactly because it avoids having to keep track of the Interrupt flag and instructions that modify it, like RTI, REP/SEP, CLI/SEI, etc.
IRQ could be used for device interrupts or similar, where it doesn't matter if they get handled immediately or not.

BigDumbDinosaur wrote:
a state machine in the logic is started with the “switch modes” write and times out after the MPU has executed RTI, which takes exactly seven cycles.

not always, if you have wait states for ROM accesses but not RAM it would take a different amount of cycles to execute RTI. especially if the instruction can be found in both ROM and RAM. so you would need to make the counter run long enough to account for that (like maybe 10-ish cycles or something).
or in case of clock stretching you could use the CPU's clock for the counter instead of the global one. which would keep it in sync with the instruction cycles.

BigDumbDinosaur wrote:
And what happens when the top of the stack is accidentally corrupted and the return is not to the user code that was interrupted? :)

though it can obviously happen, i wouldn't expect a system function to corrupt it's own stack.
if you go that direction then you could also argue that a counter (or anything) doesn't protect against a system function being bugged and never writing to the MPU register in the first place.

anyways i think both ways of returning to user mode have their pros and cons. a counter is simplier to implement but probably also uses more logic. i think it would be fair to just try out both. (maybe even at the same time, have a counter as a fall back, if the CPU wasn't detected in the user program within x cycles, abort directly).


Top
 Profile  
Reply with quote  
PostPosted: Tue Jun 20, 2023 4:26 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10985
Location: England
In our 6809+MMU thinking, we had the idea of a read-only MMU register which returns the value of an RTS (or in this case perhaps an RTL) - the idea is that you don't need a counter, you just jump or branch to the register location, and as a side-effect the MMU does the necessary thing such that the RTS lands back in user space. (You need to cater for the dummy read at the next location up, of course.) It feels to me that this approach is fairly minimal, but it's enough.


Top
 Profile  
Reply with quote  
PostPosted: Tue Jun 20, 2023 5:03 pm 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 746
Location: Germany
hmm, that is an interesting option. though wouldn't an RTI instruction make more sense in the context of returning to a user task?
you would likely still need to account for potential wait states, so maybe it should have a 1 or 2 cycle delay after the CPU fetched the instruction from the MMU before re-enabling memory protection.

on another note, can you even abort a STP instruction? the datasheet lists it as having "3 cycles" which i interpret as the CPU needing 2 cycles after fetching the instruction to actually go into low-power mode.
if that's the case then it is possible to fire an abort signal fast enough, but will the CPU actually process it?
of course you could have the MMU between the CPU and the rest of the system to capture the STP opcode and send a NOP to the CPU instead. but that is way more complicated, needs more logic, and likely drags the maximum clock speed down a lot... compared to just reading the data bus during opcode fetches and sending an abort signal if it matches STP.


Top
 Profile  
Reply with quote  
PostPosted: Wed Jun 21, 2023 5:06 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8504
Location: Midwestern USA
Proxy wrote:
I Agree that NMI should be used for the periodic interrupt and task switching instead of IRQ.

...something I would never do. In my POC units, an NMI is the means by which I regain control when a programming error causes the MPU to get stuck in a loop.

In my more than 50 years of working with preemptive, multitasking, multiuser systems, I have yet to see any of them use NMIs for jiffy interrupt generation and task switching. Once an NMI occurs, no more NMIs will be recognized until all causes of the original NMI have been cleared. A trivial logic error in the NMI code could fail to detect a still-pending NMI, ultimately resulting in deadlock.

Where NMIs were often used was in minis that were powered from a “dumb” UPS. The UPS had a relay-contact output that could trigger an NMI to tell the MPU that power had been lost and that an immediate shutdown was needed. Nothing else was wired to the NMI input, so there was never an ambiguity. Most modern systems don’t use NMI at all.

Quote:
exactly because it avoids having to keep track of the Interrupt flag and instructions that modify it, like RTI, REP/SEP, CLI/SEI, etc.

If you’re going to go to the trouble to design an MMU (technically, an HMU or hardware management unit), you might as well make the effort to police for potentially-dangerous instruction usage in user mode. The 65C816’s design makes this easier than with the eight-bit MPUs due to the availability of the ABORT interrupt feature, as well as E, MX, VDA, VPA and VPB (the latter also on the W65C02) to give the logic a better picture of what the MPU is doing at any given instant.

Quote:
IRQ could be used for device interrupts or similar, where it doesn't matter if they get handled immediately or not.

Au contraire, my friend. I/O is a shared resource. In a preemptive, multitasking OS, I/O-bound processes generally must take priority over compute-bound processes to avoid conditions that can lead to deadlock or unforeseen race conditions. In virtually all modern multitasking systems (Linux is a good example), interrupting devices are assigned priorities so the most time-critical interrupts are serviced as quickly as the MPU can get to them. Determining that priority and telling the MPU about it could also be a function of your hypothetical HMU.

Quote:
not always, if you have wait states for ROM accesses but not RAM it would take a different amount of cycles to execute RTI...

Why would you be using ROM to run your preemptive kernel? A real OS would be loaded from mass storage during boot and run entirely out of RAM. Available SRAM is as fast as 10ns, which eliminates any need to wait-state anything except I/O hardware.

Quote:
BigDumbDinosaur wrote:
And what happens when the top of the stack is accidentally corrupted and the return is not to the user code that was interrupted? :)
though it can obviously happen, i wouldn't expect a system function to corrupt it's own stack.

Stuff happens, especially in systems that are not running error-correcting RAM. Also, in any large body of software, e.g., a kernel, a specific set of unanticipated conditions could arise that could cause a wild write to RAM—and result in potential stack corruption. If you are intent on developing a truly “bullet-proof” environment, you should prepare for all possible scenarios, up to and including hardware aberrations.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Wed Jun 21, 2023 5:28 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8504
Location: Midwestern USA
Proxy wrote:
on another note, can you even abort a STP instruction?

No. Toggling ABORT does not terminate any instruction—an “aborted” instruction is executed to completion.

What ABORTing does is prevent the MPU from modifying a register, or “touching” memory or I/O hardware.¹ When the MPU finishes the instruction that is being “aborted,” an interrupt will occur that sequences the same as an IRQ or NMI, except the value of PC that is written to the stack is that of the “aborted” instruction.

For more information on the 65C816’s interrupt behavior, see here.

————————————————————
¹If the instruction being “aborted” is read-modify-write, e.g., ASL <addr>, the abort must occur before MLB is asserted, otherwise memory will be modified.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Wed Jun 21, 2023 6:23 am 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10985
Location: England
BigDumbDinosaur wrote:
If you’re going to go to the trouble to design an MMU (...), you might as well make the effort to police for potentially-dangerous instruction usage in user mode.


This is the kind of thinking which leads to ever-increasing project scope, projects which never finish, and sometimes even to projects which never get started. "You might as well" is a danger sign - start things simple.

In the case of an MMU, I think it's well worth thinking through what the aims are, and what they could be reduced to. Relocation is very useful indeed. Protection between tasks, or between a user task and physical devices, could be handy. Absolute safety in the presence of malicious applications, somewhere near the end of the list. Note that the Macintosh, the Amiga, the Communicator, and probably the QL, are all multi-tasking without being perfectly safe, and they were all shipped and useful. Two of them were successful! We don't know how many products never shipped because of scope creep or perfectionism.


Top
 Profile  
Reply with quote  
PostPosted: Wed Jun 21, 2023 10:27 am 
Offline
User avatar

Joined: Wed Feb 14, 2018 2:33 pm
Posts: 1488
Location: Scotland
BigEd wrote:
In the case of an MMU, I think it's well worth thinking through what the aims are, and what they could be reduced to. Relocation is very useful indeed. Protection between tasks, or between a user task and physical devices, could be handy. Absolute safety in the presence of malicious applications, somewhere near the end of the list. Note that the Macintosh, the Amiga, the Communicator, and probably the QL, are all multi-tasking without being perfectly safe, and they were all shipped and useful. Two of them were successful! We don't know how many products never shipped because of scope creep or perfectionism.


Indeed - and there is also a version of Linux designed to run on microcontrollers without MMU too.

https://en.wikipedia.org/wiki/%CE%9CClinux

My own 65xx project - (now) an OS written in BCPL running on the '816 is multi-tasking (but not multi-user) and it performs very well. Of-course there is nothing to stop a program writing all over RAM, but my own use with it coding, editing, etc. on it have shown it to be quite robust as long as you adopt defensive programming techniques - e.g. my system equivalent of malloc & free include "guard" words either side of the allocated memory to free() can determine if a program has overrun - similarly there is an option to check the stack usage (although that does slow things down a little). So it's all very possible.

I could go much further because the system is running a bytecode VM and I could, if I wanted to, include MMU code inside the bytecode interpreter (it's commonly done in emulators for e.g. RISC-V) but that would me it slower than it already is.

So effort vs. reward? Would it be nice to have a little bolt on MMU for the 6502/816? Yes - is the effort worth it? Only you can decide that one - afterall, these are all hobby projects and if we enjoy it then we should carry on...

-Gordon

_________________
--
Gordon Henderson.
See my Ruby 6502 and 65816 SBC projects here: https://projects.drogon.net/ruby/


Top
 Profile  
Reply with quote  
PostPosted: Wed Jun 21, 2023 9:01 pm 
Offline

Joined: Tue Jul 05, 2005 7:08 pm
Posts: 1043
Location: near Heidelberg, Germany
I would have loved to have the ABORT feature back in the day when I designed my computer.

The CPU board uses an MMU that provides mapping of 16 4k pages from a 1M physical address space. I used it in GeckOS for remapping memory for each parallel task.

The MMU can detect access to unmapped pages, write to read-only pages, or trying to execute code in no-execute pages.

However, without ABORT I was only able to stop the CPU, and actually have a second CPU take over the bus, to remap pages, or NMI the main CPU after that opcode. You could also do single stepping and tracing the main CPU using this feature.

With an ABORT I could have just aborted the main CPU instead. Now, with the 816 the need for an MMU becomes less important though.

Here's my CPU board,: http://www.6502.org/users/andre/csa/cpu/index.html

André

_________________
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/


Top
 Profile  
Reply with quote  
PostPosted: Sat Jun 24, 2023 8:23 pm 
Offline
User avatar

Joined: Fri Aug 03, 2018 8:52 am
Posts: 746
Location: Germany
BigDumbDinosaur wrote:
Proxy wrote:
I Agree that NMI should be used for the periodic interrupt and task switching instead of IRQ.

...something I would never do. In my POC units, an NMI is the means by which I regain control when a programming error causes the MPU to get stuck in a loop.

well that's not really necessary as in that case the NMI would fire periodically instead of at a press of a button, making it functionally almost the same, but automated. so if a task gets stuck in a loop the whole system isn't brought to a halt.
(that also makes me wonder, how would an OS detect if a task is stuck in a loop?)

BigDumbDinosaur wrote:
In my more than 50 years of working with preemptive, multitasking, multiuser systems, I have yet to see any of them use NMIs for jiffy interrupt generation and task switching.

seems perfect to me if you have nothing else on the board using NMI. plus it could be routed through the MMU to only allow NMIs to pass when the system is in user-mode. meaning the system won't receive another NMI while it's already handling one. effectly turning it into an IRQ but not in control of the CPU itself, making it easier to protect against user tasks compared to the internal interrupt flag.

BigDumbDinosaur wrote:
Once an NMI occurs, no more NMIs will be recognized until all causes of the original NMI have been cleared. A trivial logic error in the NMI code could fail to detect a still-pending NMI, ultimately resulting in deadlock.

this assumes the interrupt source would keep the NMI line low instead of just pulsing it, in which case yes, additional NMIs coming in wouldn't be recognized.
but if i were to design a board around a 65816 with an MMU i would have exactly 1 device on the NMI line, the jiffy timer, to avoid ambiguity and this issue above.
but that's just how i would design it. if you build the timer around the IRQ instead then you either need a more sophisticated MMU (or HMU) to avoid interrupt flag changing instructions or just trust user programs to not mess with the i flag and simply deal with accidents that mess with it anyways.


and i do agree with BigEd. feature creep is a slippery slope!
so i'll just lock down the MMU/MPU idea i had to just be simple 2 segment memory protection, with bank 0 relocation, interrupt detection to switch into a supervisor mode, and a counter-delayed swich back to user mode when writing to a specific IO register.
it won't ptotect against STP or messing with the interrupt flag, but that's good enough.


Top
 Profile  
Reply with quote  
PostPosted: Sat Jun 24, 2023 9:04 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8504
Location: Midwestern USA
Proxy wrote:
and i do agree with BigEd. feature creep is a slippery slope!

It is...Microsoft Windows is a classic example. :D Unfortunately, Linux seems to be heading down that path as well. Every time someone says “systemd” I grit my teeth and snarl. :twisted:

That said, I still disagree with the notion of using NMI the way you envision. I suspect once you get into it, you’ll rethink that aspect of your design.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 53 posts ]  Go to page Previous  1, 2, 3, 4  Next

All times are UTC


Who is online

Users browsing this forum: No registered users and 33 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: