NucleoOS -- An OS for the 84+/84+SE
For months I have been catching up on my knowledge of z80 assembly, 84+ hardware, OS design, and many other things, so that I could begin to work on a project I've been considering tackling for a long time: a simple, but fully fledged operating system for the 84+ and 84+SE. My quest finally began earlier this week, when I set up a nice little dev environment for my operating system, and started organizing my concept code for the layout and how different modules would work together. I currently am still working on setting up hardware and such, not much more than a period of time spent fleshing out initializing components, and RTFMing at WikiTI (thanks for the site Brandon, and everyone else who submitted information there -- it's extraordinarily helpful).
I have a few inspirations in mind for my endeavors; namely, MenuetOS (x86) for it's simplicity and very small size, KOS (z80) for its decently handled process scheduling, and GlassOS (z80) to some extent for cranking the full possible use out of the 84+'s hardware. The name of the project is NucleoOS, since Nucleo in Italian means "core"; the main goal of NucleoOS is to be as small as possible, only holding the core necessities and a few limited "sweets" in its hull. It is planned to sport the following:
multiprocessing capabilities
Likely the largest planned element, and placed closest to my personal expectations goals for this whole project. Planned to support to 16 processes running at the same time, accomplished in a mix of Round-robin and Priority ranking scheduling techniques. Each process will be given a flash page for execution in bank $4000-$7FFF, which allows for consistency in program execution space, and in a sense a slight feeling of pseudo-virtualization of process memory. Processes taking more than 2^14 bytes of memory, and therefore not fitting into a single execution flash page, will have to act similarly to how multi-paged flash apps do on TI-OS -- manually call for swapping pages through the OS to access later chunks of code or data; single page processes will not have to worry about this at all. Processes can have a priority level of 0-3. The values corresponding to priorities are as follows, with some examples of the types of processes that would be in these priority ranks:
0 - services, very idle (link-port/battery monitor)
1 - non-interactive, background tasks (background real time clock)
2 - interactive, CPU/IO semi-intensive (text editor)
3 - real-time, CPU/IO intensive (games, something using the LCD for grayscale)
Rank 3 processes will get ~70% shared CPU time, 2 gets ~20%, 1 gets ~8%, and services get ~2%. Processes are time sliced in order in a round robin formation, but get a higher throughput ratio and longer timeslice based on their rank. Processes will have the ability to halt for a set period of time; if the time limit is reached during another process's timeslice, and the currently running process is of a lower or equal rank, execution of the current process will be forked over to the other process of which the limit was reached; if the current process is more critically ranked, execution will be forked over to the limit-reaching process after the current process has finished its slice.
Threading within a process so that both may share the same execution space and CPU time is semi-planned out at this point; I'm thinking of having a system call to initialize new threads, with given label/addresses within the current process:
thread_init: executed on creation of thread, ran to entirety, ignoring thread/process scheduling
thread_run: executed on a thread's timeslice within the process timeslice
thread_kill: executed upon the killing of the thread, and this whole set of code will be run to it's entirety, ignoring thread/process scheduling
Scheduling will be handled through hardware timer-based interrupts set at a higher speed, in IM 1. I plan on having a Short-term/Dispatcher pair working together to shift all the processes and their execution flash page in and out of the bank; the short term scheduler keeps track of how many scheduler time slice calls from the interrupt a process has run through, to handle priority ranking effectively. A long term scheduler is planned to keep the amount of running processes relatively clean, so that very idle processes in ranks 1-3 will be set aside until their wanted wakeup signal is reached (essentially, a signal that tells that the amount of high-ranked processes has dropped quite a bit, and there is more CPU time for them to take advantage of); services take up very little time as it is, so they won't be affected by the long term scheduler. The long term scheduler is planned to be very minimal, only being activated every 16 or so short-term runs, to minimize slowdowns where possible. Since the programs aren't all stored in the same chunk of memory at the same time, and therefore don't need to be swapped out per-se, there is no mid term scheduler planned.
Primitive MMU (Memory Management)
Since the 84+(SE) has code execution limit hardware, I am putting it to use so that executable code running only in user space in bank $4000-$7FFF is limited to running almost strictly in that address space. If a jump is made outside that realm, a reset will occur, which NucleoOS will catch and determine if the reset was caused by a process, and if so will call for the process to be terminated and set the last recorded signal to be SIGMEMACC (signal message illegal memory access). The definition between user space and priv space is solely determined by address range, and when execution veers out of user space back into priv space, I.e. With the short term scheduler interrupt, the limits are relieved and the OS has execution space of $0000-$7FFF.
The hardware enabling this is fortunately there, but unfortunately, it is extremely limited, almost solely to page and address execution limits; therefore, it would be possible for user space programs to take control of priv space, but since the whole point of memory management and priv leveling here is to make user space programs generally safer, there's really no incentive of the user hacking into the internals, since I'll likely provide some syscalls to do some of that with services and rank 1 processes anyways. I could always scan a program to see if it edits the limits of these ports before dispatching it, but what's the fun in making an OS like the restrictive one made by TI corporate pigs? The whole point is safety and organization, but if the user wants to go amuck with the system, they're open to do it, it's their calculator, not mine
Simple device drivers/hardware interaction modules
I plan on making hardware interfacing routines in driver modules for some devices up front (some of which are already coded):
LCD - provide an easy way to send/get LCD data, change some status modes, very simple sprite routines, and allow for use of the LCD's internal RAM for extra scratch space (thinking of options on how to do this, and if I'll only allow it if no other process is using it)
Link port - not thought out much; most driver functions will likely be for sending some simple data, setting the line states, etc.
USB - far out goal, near the end of my actual priorities.
Timers - support for using the second and third crystal timers easily.
Coding Timeline
This project is now finally on my list of project in the works, and will stay that way; it'll likely be lower on scale than TaN, replacing most of my time being spent elsewhere on Raptor. Don't expect super fast progress. I hope to have basic CPU control, the LCD driver, and a few simple OS routines done in a week or so, and the goal after that will be to get full multiprocessing done by sometime in summer. I feel ready with the knowledge base to take on such a project, and I am 100% aware of the amount of time it will require to fulfill even the basic operations. I don't even have no plan of a GUI yet, or for that matter, even some sort of terminal emulator, so I'm hopefully not off on the wrong track of expectations that others have run into
I will be asking help in this thread as often as I will be updating; until a few months ago when I started my OS inner workings crash course, I knew nothing about how an OS should work, and I'm still far from knowing a lot. Most questions will likely be technical ones about why some code is causing nasty stuff to happen, but I'll definitely be asking for help with advice for general OS structure.
I'm not very far into actual coding now, so if you see something in my plans above that sounds awful or plain wrong, please tell me if you can! My only reason for taking on this project is as a personal learning experience, so I'm fine with blunt answers or advice; I'll learn more that way -- hit me with your best design rebuttals!
(PS -- sorry for being so technical with my descriptions above, I want to throw as much out here as I can for the best advice, and to show I actual put thought/research into all this; this is also all the current digital documentation available right now, most other documentation is drawn out diagrams of how things would work together)
For months I have been catching up on my knowledge of z80 assembly, 84+ hardware, OS design, and many other things, so that I could begin to work on a project I've been considering tackling for a long time: a simple, but fully fledged operating system for the 84+ and 84+SE. My quest finally began earlier this week, when I set up a nice little dev environment for my operating system, and started organizing my concept code for the layout and how different modules would work together. I currently am still working on setting up hardware and such, not much more than a period of time spent fleshing out initializing components, and RTFMing at WikiTI (thanks for the site Brandon, and everyone else who submitted information there -- it's extraordinarily helpful).
I have a few inspirations in mind for my endeavors; namely, MenuetOS (x86) for it's simplicity and very small size, KOS (z80) for its decently handled process scheduling, and GlassOS (z80) to some extent for cranking the full possible use out of the 84+'s hardware. The name of the project is NucleoOS, since Nucleo in Italian means "core"; the main goal of NucleoOS is to be as small as possible, only holding the core necessities and a few limited "sweets" in its hull. It is planned to sport the following:
multiprocessing capabilities
Likely the largest planned element, and placed closest to my personal expectations goals for this whole project. Planned to support to 16 processes running at the same time, accomplished in a mix of Round-robin and Priority ranking scheduling techniques. Each process will be given a flash page for execution in bank $4000-$7FFF, which allows for consistency in program execution space, and in a sense a slight feeling of pseudo-virtualization of process memory. Processes taking more than 2^14 bytes of memory, and therefore not fitting into a single execution flash page, will have to act similarly to how multi-paged flash apps do on TI-OS -- manually call for swapping pages through the OS to access later chunks of code or data; single page processes will not have to worry about this at all. Processes can have a priority level of 0-3. The values corresponding to priorities are as follows, with some examples of the types of processes that would be in these priority ranks:
0 - services, very idle (link-port/battery monitor)
1 - non-interactive, background tasks (background real time clock)
2 - interactive, CPU/IO semi-intensive (text editor)
3 - real-time, CPU/IO intensive (games, something using the LCD for grayscale)
Rank 3 processes will get ~70% shared CPU time, 2 gets ~20%, 1 gets ~8%, and services get ~2%. Processes are time sliced in order in a round robin formation, but get a higher throughput ratio and longer timeslice based on their rank. Processes will have the ability to halt for a set period of time; if the time limit is reached during another process's timeslice, and the currently running process is of a lower or equal rank, execution of the current process will be forked over to the other process of which the limit was reached; if the current process is more critically ranked, execution will be forked over to the limit-reaching process after the current process has finished its slice.
Threading within a process so that both may share the same execution space and CPU time is semi-planned out at this point; I'm thinking of having a system call to initialize new threads, with given label/addresses within the current process:
thread_init: executed on creation of thread, ran to entirety, ignoring thread/process scheduling
thread_run: executed on a thread's timeslice within the process timeslice
thread_kill: executed upon the killing of the thread, and this whole set of code will be run to it's entirety, ignoring thread/process scheduling
Scheduling will be handled through hardware timer-based interrupts set at a higher speed, in IM 1. I plan on having a Short-term/Dispatcher pair working together to shift all the processes and their execution flash page in and out of the bank; the short term scheduler keeps track of how many scheduler time slice calls from the interrupt a process has run through, to handle priority ranking effectively. A long term scheduler is planned to keep the amount of running processes relatively clean, so that very idle processes in ranks 1-3 will be set aside until their wanted wakeup signal is reached (essentially, a signal that tells that the amount of high-ranked processes has dropped quite a bit, and there is more CPU time for them to take advantage of); services take up very little time as it is, so they won't be affected by the long term scheduler. The long term scheduler is planned to be very minimal, only being activated every 16 or so short-term runs, to minimize slowdowns where possible. Since the programs aren't all stored in the same chunk of memory at the same time, and therefore don't need to be swapped out per-se, there is no mid term scheduler planned.
Primitive MMU (Memory Management)
Since the 84+(SE) has code execution limit hardware, I am putting it to use so that executable code running only in user space in bank $4000-$7FFF is limited to running almost strictly in that address space. If a jump is made outside that realm, a reset will occur, which NucleoOS will catch and determine if the reset was caused by a process, and if so will call for the process to be terminated and set the last recorded signal to be SIGMEMACC (signal message illegal memory access). The definition between user space and priv space is solely determined by address range, and when execution veers out of user space back into priv space, I.e. With the short term scheduler interrupt, the limits are relieved and the OS has execution space of $0000-$7FFF.
The hardware enabling this is fortunately there, but unfortunately, it is extremely limited, almost solely to page and address execution limits; therefore, it would be possible for user space programs to take control of priv space, but since the whole point of memory management and priv leveling here is to make user space programs generally safer, there's really no incentive of the user hacking into the internals, since I'll likely provide some syscalls to do some of that with services and rank 1 processes anyways. I could always scan a program to see if it edits the limits of these ports before dispatching it, but what's the fun in making an OS like the restrictive one made by TI corporate pigs? The whole point is safety and organization, but if the user wants to go amuck with the system, they're open to do it, it's their calculator, not mine
Simple device drivers/hardware interaction modules
I plan on making hardware interfacing routines in driver modules for some devices up front (some of which are already coded):
LCD - provide an easy way to send/get LCD data, change some status modes, very simple sprite routines, and allow for use of the LCD's internal RAM for extra scratch space (thinking of options on how to do this, and if I'll only allow it if no other process is using it)
Link port - not thought out much; most driver functions will likely be for sending some simple data, setting the line states, etc.
USB - far out goal, near the end of my actual priorities.
Timers - support for using the second and third crystal timers easily.
Coding Timeline
This project is now finally on my list of project in the works, and will stay that way; it'll likely be lower on scale than TaN, replacing most of my time being spent elsewhere on Raptor. Don't expect super fast progress. I hope to have basic CPU control, the LCD driver, and a few simple OS routines done in a week or so, and the goal after that will be to get full multiprocessing done by sometime in summer. I feel ready with the knowledge base to take on such a project, and I am 100% aware of the amount of time it will require to fulfill even the basic operations. I don't even have no plan of a GUI yet, or for that matter, even some sort of terminal emulator, so I'm hopefully not off on the wrong track of expectations that others have run into
I will be asking help in this thread as often as I will be updating; until a few months ago when I started my OS inner workings crash course, I knew nothing about how an OS should work, and I'm still far from knowing a lot. Most questions will likely be technical ones about why some code is causing nasty stuff to happen, but I'll definitely be asking for help with advice for general OS structure.
I'm not very far into actual coding now, so if you see something in my plans above that sounds awful or plain wrong, please tell me if you can! My only reason for taking on this project is as a personal learning experience, so I'm fine with blunt answers or advice; I'll learn more that way -- hit me with your best design rebuttals!
(PS -- sorry for being so technical with my descriptions above, I want to throw as much out here as I can for the best advice, and to show I actual put thought/research into all this; this is also all the current digital documentation available right now, most other documentation is drawn out diagrams of how things would work together)