Privileged Instructions and Execution Modes
CREATOR supports privilege levels to simulate operating system and user space separation, similar to real processors.
Execution Modes
Source: src/core/executor/interrupts.mts - ExecutionMode enum
CREATOR defines two execution modes:
enum ExecutionMode {
User = 0, // Non-privileged mode
Kernel = 1 // Privileged mode
}
Mode Tracking
The current mode is tracked by currentExecutionMode:
let currentExecutionMode = ExecutionMode.User; // Initial mode
function setExecutionMode(mode: ExecutionMode): void {
currentExecutionMode = mode;
}
function getExecutionMode(): ExecutionMode {
return currentExecutionMode;
}
function isKernelMode(): boolean {
return currentExecutionMode === ExecutionMode.Kernel;
}
function isUserMode(): boolean {
return currentExecutionMode === ExecutionMode.User;
}
Privileged Instructions
Definition
An instruction is privileged if it has the privileged property set to true in its definition:
# Architecture YAML
instructions:
- name: "mret"
format: "R"
opcode: 0x73
funct3: 0x0
funct7: 0x18
privileged: true # This instruction requires kernel mode
behavior: |
pc = mepc; // Return to saved PC
currentExecutionMode = ExecutionMode.User; // Back to user mode
description: "Return from machine mode trap"
Privileged Instruction Check
Before executing any instruction, CREATOR checks privilege requirements:
function execute_one_instruction() {
// Fetch and decode
const instruction = fetch_and_decode();
// Check privilege level
if (instruction.privileged && isUserMode()) {
// Illegal instruction exception
throw new PrivilegeViolationError(
`Cannot execute privileged instruction '${instruction.name}' in user mode`
);
}
// Execute if allowed
instruction.handler(state, instruction.operands);
}
Mode Transitions
User to Kernel (Trap)
Transitions to kernel mode occur on:
- Exceptions: Illegal instructions, page faults, etc.
- Interrupts: Timer, external devices
- System Calls:
ecallinstruction
function handleTrap(cause: number): void {
// Save current PC
set_mepc(get_pc());
// Save trap cause
set_mcause(cause);
// Switch to kernel mode
setExecutionMode(ExecutionMode.Kernel);
// Disable interrupts
disableInterrupts();
// Jump to trap handler
const handler_addr = get_mtvec();
set_pc(handler_addr);
}
Kernel to User (Return)
Return to user mode with privileged instructions:
// RISC-V: mret (Machine Return)
function execute_mret() {
// Restore PC
const return_addr = get_mepc();
set_pc(return_addr);
// Return to user mode
setExecutionMode(ExecutionMode.User);
// Re-enable interrupts (if they were enabled)
const prev_mie = (get_mstatus() >> 7) & 0x1;
if (prev_mie) {
enableInterrupts();
}
}
// MIPS: eret (Exception Return)
function execute_eret() {
// Similar to mret
set_pc(get_epc());
setExecutionMode(ExecutionMode.User);
// Clear exception flag
const status = get_status();
set_status(status & ~0x2); // Clear EXL bit
}
Common Privileged Instructions
RISC-V Privileged Instructions
Machine Trap Return (mret)
- name: "mret"
privileged: true
behavior: |
// Restore PC from mepc
pc = mepc;
// Restore interrupt enable (MPIE -> MIE)
const mpie = (mstatus >> 7) & 0x1;
mstatus = (mstatus & ~0x8) | (mpie << 3);
// Set MPIE to 1
mstatus = mstatus | 0x80;
// Return to user mode
currentExecutionMode = ExecutionMode.User;
Supervisor Trap Return (sret)
- name: "sret"
privileged: true
behavior: |
pc = sepc;
const spie = (sstatus >> 5) & 0x1;
sstatus = (sstatus & ~0x2) | (spie << 1);
sstatus = sstatus | 0x20;
currentExecutionMode = ExecutionMode.User;
CSR Instructions
Control and Status Register (CSR) access is often privileged:
- name: "csrrw"
privileged: true # Some CSRs are privileged
behavior: |
const csr_addr = imm & 0xFFF;
// Check CSR access permissions
if (isPrivilegedCSR(csr_addr) && isUserMode()) {
makeInterrupt(InterruptType.IllegalInstruction);
return;
}
// Read CSR
const old_value = readCSR(csr_addr);
// Write CSR
writeCSR(csr_addr, rs1);
// Write old value to rd
rd = old_value;
MIPS Privileged Instructions
Exception Return (eret)
- name: "eret"
privileged: true
behavior: |
pc = epc;
const status = readCP0(12); // Status register
writeCP0(12, status & ~0x2); // Clear EXL bit
currentExecutionMode = ExecutionMode.User;
Move to CP0 (mtc0)
- name: "mtc0"
privileged: true
behavior: |
const cp0_reg = rd; // CP0 register number
const value = rs1;
writeCP0(cp0_reg, value);
Move from CP0 (mfc0)
- name: "mfc0"
privileged: true
behavior: |
const cp0_reg = rd;
const value = readCP0(cp0_reg);
rs1 = value;
Control and Status Registers (CSRs)
CSR Categories
CSRs are classified by privilege level:
const CSR_CATEGORIES = {
// Machine-mode CSRs (0x300-0x3FF)
MACHINE: {
min: 0x300,
max: 0x3FF,
required_mode: ExecutionMode.Kernel
},
// Supervisor-mode CSRs (0x100-0x1FF)
SUPERVISOR: {
min: 0x100,
max: 0x1FF,
required_mode: ExecutionMode.Kernel
},
// User-mode CSRs (0x000-0x0FF)
USER: {
min: 0x000,
max: 0x0FF,
required_mode: ExecutionMode.User
}
};
function isPrivilegedCSR(csr_addr: number): boolean {
// Check privilege level encoded in CSR address
const privilege = (csr_addr >> 8) & 0x3;
// 0=user, 1=supervisor, 2=hypervisor, 3=machine
return privilege > 0;
}
function checkCSRAccess(csr_addr: number): void {
const privilege = (csr_addr >> 8) & 0x3;
if (privilege === 3 && !isKernelMode()) {
throw new PrivilegeViolationError(
`Cannot access machine CSR 0x${csr_addr.toString(16)} in user mode`
);
}
}
Common CSRs
Machine-Mode CSRs:
0x300 mstatus - Machine status
0x301 misa - ISA and extensions
0x304 mie - Machine interrupt enable
0x305 mtvec - Machine trap-handler base address
0x340 mscratch - Machine scratch register
0x341 mepc - Machine exception program counter
0x342 mcause - Machine trap cause
0x343 mtval - Machine trap value
0x344 mip - Machine interrupt pending
User-Mode CSRs:
0xC00 cycle - Cycle counter (read-only)
0xC01 time - Timer (read-only)
0xC02 instret - Instructions retired (read-only)
Implementing Privilege Checks
Architecture Definition
In your architecture YAML:
instructions:
# Privileged instruction
- name: "csrw"
privileged: true
behavior: |
// Privilege check is automatic
// This code only runs if check passes
const csr_addr = imm & 0xFFF;
// Additional CSR-specific check
if (isPrivilegedCSR(csr_addr) && isUserMode()) {
makeInterrupt(InterruptType.IllegalInstruction);
return;
}
writeCSR(csr_addr, rs1);
# Non-privileged instruction
- name: "add"
privileged: false # or omit (default is false)
behavior: |
rd = rs1 + rs2;
Runtime Checks
// Custom privilege validation
function validatePrivilege(instruction) {
if (!instruction.privileged) {
return true; // Always allowed
}
if (isKernelMode()) {
return true; // Kernel can execute anything
}
// User mode cannot execute privileged instruction
throw new PrivilegeViolationError(
`Privilege violation: ${instruction.name} requires kernel mode`
);
}
Exception Handling
Privilege Violations
class PrivilegeViolationError extends Error {
constructor(message: string) {
super(message);
this.name = "PrivilegeViolationError";
}
}
function handlePrivilegeViolation(error: PrivilegeViolationError): void {
console.error(`Privilege violation at PC=0x${get_pc().toString(16)}`);
console.error(`Current mode: ${isKernelMode() ? 'Kernel' : 'User'}`);
console.error(error.message);
// Trigger illegal instruction trap
makeInterrupt(InterruptType.IllegalInstruction);
}
System Call Mechanism
System calls provide controlled transition from user to kernel mode:
// ECALL instruction (RISC-V)
function execute_ecall() {
// Save context
set_mepc(get_pc() + 4n); // Save return address
// Set trap cause
const cause = isKernelMode() ? 11 : 8; // M-mode or U-mode ecall
set_mcause(cause);
// Switch to kernel mode
setExecutionMode(ExecutionMode.Kernel);
// Disable interrupts
disableInterrupts();
// Jump to trap handler
const handler = get_mtvec();
set_pc(handler);
}
// Trap handler (in kernel mode)
function trapHandler() {
const cause = get_mcause();
if (cause === 8) {
// User-mode ecall - handle syscall
const syscall_num = readRegister('a7');
handleSyscall(syscall_num);
}
// Return to user mode
execute_mret();
}
Debugging Privileged Code
Mode Tracking
# CLI commands
creator> reg execution_mode
execution_mode = User
creator> reg mstatus
mstatus = 0x00000088 # MIE=1, MPIE=1
Privilege Violations
creator> step
Error: Privilege violation
Instruction: mret
Current mode: User
PC: 0x00400010
Best Practices
- Minimal Privileged Instructions: Only mark truly privileged operations
- Clear Mode Transitions: Document when and why mode changes
- Validate CSR Access: Check both instruction and CSR privilege levels
- Proper Context Saving: Always save PC and status before mode switch
- Interrupt Handling: Disable interrupts during mode transitions
- Testing: Test both user and kernel mode paths
Example: Complete Privilege System
# Architecture with privilege support
name: "Privileged RISC-V"
# ... registers, memory, etc. ...
instructions:
# User-mode instruction
- name: "add"
format: "R"
privileged: false
behavior: "rd = rs1 + rs2;"
# Trap to kernel
- name: "ecall"
format: "I"
privileged: false # Users can call ecall
behavior: |
mepc = pc + 4;
mcause = isKernelMode() ? 11 : 8;
currentExecutionMode = ExecutionMode.Kernel;
disableInterrupts();
pc = mtvec;
# Return to user
- name: "mret"
format: "R"
privileged: true # Only kernel can return
behavior: |
pc = mepc;
const mpie = (mstatus >> 7) & 0x1;
mstatus = (mstatus & ~0x8) | (mpie << 3);
currentExecutionMode = ExecutionMode.User;
Next Steps
- Review Interrupts for trap handling details
- See Devices for system call implementation
- Read Core Architecture for system integration
- Check Custom Architectures for privilege configuration