The Dauug House Wright State University logo
Dauug|36 minicomputer documentation

Identity-modifying instructions (privileged)

Opcode P/U Category Description
NPCALL priv identity modifying call nonprivileged subroutine
NPRIV priv identity modifying nonprivileged user
PCALL priv identity modifying call privileged subroutine
PEEK priv identity modifying peek
POKE priv identity modifying poke
PRIV priv identity modifying privileged user (superuser)
SETUP priv identity modifying setup
USER priv identity modifying user

As mentioned earlier, the Dauug|36 concept of privilege is simply the presence of a privileged instruction to execute. A related concept is identity, or which running program an instruction affects. A Dauug|36 identity, loosely called a user because it numerically identifies an actual user (executing program), is an eight-bit differentiator applied to the address bits of:

In the netlist, the user is specified in 8-bit register named ff u, and 1-bit flip-flops reguserflop and ptsuserflop can mask its content using AND gates (make the user appear to be user 0, which we call the superuser) on the way to the three destinations. Three electrical situations can result:

ptsuserflop reguserflop Outcome
0 0 superuser’s registers, page table, & stack
0 1 (electrically impossible to reach this state)
1 0 superuser’s registers, but user’s page table & stack
1 1 user’s registers, page table, & stack

Of the eight identity-modifying instructions, half do nothing but change the values of these ten bits. These instructions are NPRIV, PRIV, SETUP, and USER. The other four instructions handle situations where the user and superuser swap roles during register transfers or subroutine calls. These instructions are NPCALL, PCALL, PEEK, and POKE.

The following table describes the state of the two single-bit flip-flops after each instruction described on this page:

Instruction ptsuserflop reguserflop Comment
NPCALL 1 1 NPRIV mode
NPRIV 1 1 NPRIV mode
PCALL 0 0 PRIV mode
PEEK 0 0 PRIV mode
POKE 1 1 NPRIV mode
PRIV 0 0 PRIV mode
SETUP 1 0 SETUP mode
USER unchanged unchanged mode unchanged

NPCALL Call nonprivileged subroutine

Syntax
npcall subr
No registers used
No flags changed

NPCALL is a variant of CALL where after the return address and flags are pushed on the stack, ptsuserflop and reguserflop are both set to 1, causing further code to run in user mode as opposed to superuser mode.

NPCALL is used to move the CPU’s attention from the operating system to a user program. Here is how that works:

1. Because NPCALL is being executed by the operating system, the user program is already frozen, and its flags and next instruction address are ready at the top of its stack.

2. Even though the purpose of NPCALL is to switch to a user program, the code at subr is actually part of the operating system instead. It’s just one instruction: REVERT.

3. NPCALL does three things, the first of which is save the return address (of the instruction immediately after NPCALL) on the superuser’s stack.

4. Second, NPCALL changes the instruction pointer to point to subr, which should be the location of a REVERT instruction within the operating system’s code.

5. Third, NPCALL switches the system to user mode. The page table, stack, and registers now all belong to the user program.

4. Now the REVERT in the operating system’s code is executed, except the user’s stack is now in effect, so the flags and instruction pointer recovered are that of the suspended user program.

5. The next instruction executed is a continuation of the user’s program.

6. At this point, the frozen operating system has its flags and return address from NPCALL on top of its stack. These will be restored later by instruction decoder hijacking when either the multitasking timer expires or the user program executes a YIELD instruction.

NPRIV Nonprivileged user

Syntax
npriv
No registers used
No flags changed

NPRIV sets the one-bit flip-flops ptsuserflop and reguserflop to both output 1, forcing executing code to use the (nonprivileged) user’s registers, return address stack, and page table. This instruction is the opposite of PRIV.

The multitasking timer is enabled by reguserflop and doesn’t have a separate control, so going NPRIV starts the multitasking countdown from whatever its configured initial setpoint is. Partial countdowns are not saved, so any NPRIV restarts the clock at the beginning. See also TIMER under Program initialization.

Simulator note: 11 July 2023

NPRIV has been observed crashing the electrical simulation with the message:

glitch on D1 pin(s) a11

This apparently results from a multitasking timer bit not being initialized to high or low, but instead being left as uncertain. I was able to avoid this crash by initializing the timer prior to going NPRIV. For instance:

    unsigned timer.setting
    timer.setting = 10_0000_0000_0000_0000`b
disable.timer:
    timer timer.setting
    jump >= disable.timer

Although I consider this a bug in that the simulation crashes, it’s not a high-priority one to fix, because code that makes this bug surface already places the CPU at the mercy of an unpredictable timer. So correcting the simulator would still leave buggy software in simulation.

PCALL Call privileged subroutine

Syntax
pcall subr
No registers used
No flags changed

PCALL is a candidate for removal from the architecture.

PCALL is a variant of CALL subr where after the return address and flags are pushed on the stack, ptsuserflop and reguserflop are both set to 0, causing further code to run in superuser mode as opposed to user mode.

I wrote PCALL at the same time I was writing NPCALL, although PCALL does not have an identified use as of 19 June 2023. Because the opposite process of what NPCALL does is not done by PCALL, but is done by hijacking the instruction decoder.

The HIJACK Pseudo instruction is almost exactly a PCALL.

PEEK Peek

Syntax
su_reg = peek u_reg
Register Signedness
All ignored
1 opcode only
Flag Set if and only if
N bit 35 of the result is set
Z all result bits are zero
T flag does not change
R flag does not change

PEEK fetches the contents of register u_reg into the arithmetic logic unit (ALU), forces the CPU into PRIV mode regardless of its preceding mode, then writes the result to register su_reg. The superuser’s flags are modified as if the destination is an unsigned register. No other user’s flags are modified.

PEEK provides a mechanism for the superuser to read the contents of a user register. Ordinarily, registers belonging to different users cannot mingle, due to the 8-bit user differentiator’s contribution to the register file’s address bits. PEEK overrides this limitation by clearing the single-bit flip-flop reguserflop while the ALU is actively copying a register. For consistency, ptsuserflop is also cleared.

Ideally PEEK would keep the system in superuser (PRIV) mode, but there is no electrical means to do this. The second choice would be to have PEEK temporarily enter NPRIV mode and return to PRIV mode upon completion, but there isn’t enough time in the instruction cycle to do this either. So what actually happens is:

  1. The CPU must already be in NPRIV mode when PEEK is executed.
  2. PEEK will leave the CPU in PRIV mode, which is as we would want it.

Be warned that the multitasking timer will be active while the system is NPRIV. This won’t affect most operating systems, but an uncharacteristically short timer setting in combination with an uncharacteristically long set of instructions while in NPRIV mode could produce a surprising result. See POKE for an example of how to use NPRIV before PEEK.

When using PEEK and POKE, keep in mind that the user and superuser need to coordinate numbering of any user registers that exchange data. The KEEP assembler keyword can be used to force consistent register numbering.

POKE Poke

Syntax
poke u_reg = su_reg
Register Signedness
All ignored
1 opcode only
No flags changed

POKE fetches the contents of register su_reg into the arithmetic logic unit (ALU), forces the CPU into NPRIV mode regardless of its preceding mode, then writes the result to register u_reg. No flags are modified.

POKE provides a mechanism for the superuser to set the contents of a user register. Ordinarily, registers belonging to different users cannot mingle, due to the 8-bit user differentiator’s contribution to the register file’s address bits. POKE overrides this limitation by setting the single-bit flip-flop reguserflop while the ALU is actively copying a register. For consistency, ptsuserflop is also set.

Ideally POKE would keep the system in superuser (PRIV) mode, but there is no electrical means to do this. The second choice would be to have POKE temporarily enter NPRIV mode and return to PRIV mode upon completion, but there isn’t enough time in the instruction cycle to do this either. So what actually happens is:

  1. The CPU must be in PRIV or SETUP mode when POKE is executed.
  2. POKE will leave the CPU in NPRIV mode as an unwanted side-effect.

Things will go very wrong if you forget to restore PRIV mode after using POKE, because (i) the multitasking timer will have started, (ii) your program will have the wrong page table, (iii) the wrong stack, and (iv) the wrong registers. The silver lining is that because PEEK leaves the system in PRIV mode, you can “collapse” mode changes if data is being exchanged in two directions. In the code:

; Here is an example of how to increment `u_reg`,
; which the superuser cannot directly do.
npriv
su_reg = peek u_reg
priv                    ; can't have NPRIV mode
su_reg = su_reg +1
poke u_reg = su_reg
priv

the first PRIV is extraneous, because the PEEK already took us there. The remaining NPRIV and PRIV are necessary, however. The code should be one instruction shorter like this:

; Here is an example of how to increment `u_reg`,
; which the superuser cannot directly do.
npriv                   ; PEEK requires CPU in NPRIV mode
su_reg = peek u_reg     ; PEEK leaves CPU in PRIV mode
su_reg = su_reg +1
poke u_reg = su_reg     ; POKE leaves CPU in NPRIV mode
priv

On the other hand, for one-directional transfers of data into multiple registers, you need to restore the correct mode at the start of each transfer like this:

; This example zeros three user registers from within a superuser program.
poke u_reg1 = 0
priv                ; POKE gave up the CPU's PRIV mode
poke u_reg2 = 0
priv                ; POKE gave up the CPU's PRIV mode
poke u_reg3 = 0
priv                ; POKE gave up the CPU's PRIV mode

When using PEEK and POKE, keep in mind that the user and superuser need to coordinate numbering of any user registers that exchange data. The KEEP assembler keyword can be used to force consistent register numbering.

PRIV Privileged user

Syntax
priv
No registers used
No flags changed

PRIV sets the one-bit flip-flops ptsuserflop and reguserflop to both output 0, forcing code to execute with the superuser’s registers, return address stack, and page table. This instruction is the opposite of NPRIV.

The multitasking timer is enabled by reguserflop and doesn’t have a separate control, so the superuser is not subject to preemption. You could hack around that by:

user 0
npriv

which gives you a superuser that can be interrupted by the multitasking timer. Control would be transferred from the superuser to itself. Maybe even successfully, but I haven’t tried it.

SETUP Setup

Syntax
setup
No registers used
No flags changed

SETUP is a halfway state between NPRIV and PRIV, where the one-bit flip-flop ptsuserflop outputs 1, enabling access to the (nonprivileged) user’s call stack and page table, but reguserflop outputs 0, preserving the running superuser program’s access to its own registers.

Were it not for having a SETUP instruction, a superuser would go NPRIV to set up a user’s page table. But without its own registers, the program would have forgotten what it wanted to do with the page table. So it would go PRIV to try to remember, but then the user’s page table wouldn’t be accessible. It would be a hot mess.

SETUP is also used to initially set the instruction pointer for a user program that hasn’t run at all yet. This is achieved via some shenanigans that temporarily inserts a function call at the instruction immediately prior to the user program’s start address, and then switches call stacks while the function is called. You only need to understand the whole story if you’re writing an operating system, but there are examples under Preemptive multitasking and Cooperative multitasking to help you.

The multitasking timer is enabled by reguserflop and doesn’t have a separate control, so SETUP mode is not subject to preemption.

USER User

Syntax
user uid
Register Signedness
All ignored
1 opcode only
No flags changed

USER sets the 8-bit user differentiator within ff u, thereby associating a running program with one of the 256 independent return address stacks, 256 independent page tables, and 256 independent sets of 512 registers. Bits 8–35 of uid are ignored.

Although USER is the principal component of selecting which program is running, it does not modify that program’s instruction pointer or flags. These are modified using the REVERT instruction to switch from operating system to user code, and by control decoder hijacking initiated by a YIELD instruction or the multitasking timer to switch from user code to the operating system.

USER may or may not take immediate effect, depending on the condition of the one-bit flip-flops ptsuserflop and reguserflop. The condition of these flip-flops depend on every other instruction on this page—but not on USER.


Marc W. Abel
Computer Science and Engineering
College of Engineering and Computer Science
marc.abel@wright.edu
Without secure hardware, there is no secure software.
937-775-3016