Context

A Context models the processor’s unprivileged state with Brittle-specific extensions:

  • The sixteen general-purpose registers plus the program status word, for storing the program’s execution state.
  • A separate copy of the BASEPRI register, so that separate Contexts can mask and unmask interrupts independently.
  • Sixteen Key Registers for storing keys.
  • A configurable number [1] of MPU Region Registers for defining the program’s address space.

One Context is created by the kernel during boot.

Programs can create additional Contexts using the Become (4) method on Memory.

Branding

Brands separate Context keys into two classes, reply keys and service keys.

Reply Keys

A reply key grants the authority to deliver a single reply message to the program inhabiting the Context. Reply keys have the top bit of their brand set; the other 63 bits encode a call count. The Context internally tracks the call count, and only honors reply keys with the right count, to ensure that reply keys cannot be reused. (The call count is incremented at each successful reply, and whenever Make Runnable (7) is used to interrupt a Context awaiting reply.)

Reply keys are transparent — they implement no native methods, and instead just forward any received messages through to the program inhabiting the Context.

The only way to obtain a reply key [2] is by invoking a key with a call-style invocation. The kernel will internally fabricate a reply key and send it with the message. When the send phase completes, the Context atomically switches to expecting a reply through a reply key.

Service Keys

A service key grants the authority to perform maintenance on the Context, e.g. inspecting and altering register values. Service keys have the top bit of their brand clear, and implement the methods described below.

Warning

Currently, any Context service key can be used to call any method listed below. This is temporary. The service key brand will be further defined to allow weakened service keys.

Invalidation

At invalidation of a Context, the kernel does the following additional work:

  • Removes it from any wait queues.
  • Resets its state to stopped.
  • Advances the expected brand of its kor-context-reply-key, invalidating them as well.
  • If the invalidated Context is current (i.e. a program is invalidating its own Context), causes a context switch to choose a new current Context.

Methods

Read Register (1)

Reads a register, by index, from this Context. Only the registers specified as “callee-save” by the ARM Procedure Call Standard, plus BASEPRI and the stack pointer, are stored in the Context; others are stored on the stack. Indices for use with this method are as follows:

Index Register
0 r4
1 r5
2 r6
3 r7
4 r8
5 r9
6 r10
7 r11
8 BASEPRI
9 r13 (stack pointer)

Warning

Because this method reads a single register per IPC, it can become a bottleneck for programs (like debuggers) that need to read the entire register set. For better performance in such programs, see Read (Low/High) Registers (10/11).

Call

  • d0: register index.

Reply

  • d0: register contents.

Exceptions

  • k.bad_argument if the register index is invalid.

Write Register (2)

Writes a register, by index, in this context. Only the registers specified as “callee-save” by the ARM Procedure Call Standard, plus BASEPRI and the stack pointer, are stored in the context; others are stored on the stack. Indices for use with this method are identical to those used with Read Register (1).

Warning

Because this method writes a single register per IPC, it can become a bottleneck for programs (like debuggers) that need to write the entire register set. For better performance in such programs, see Write (Low/High) Registers (12/13).

Call

  • d0: register index
  • d1: value

Reply

Empty.

Exceptions

  • k.bad_argument if the register index is invalid.

Read Key Register (3)

Reads a key from one of this Context’s key registers, by index. Note that there are currently 16 key registers.

Key register 0 always reads as null.

Call

  • d0: key index.

Reply

No data.

  • k1: key from Context.

Exceptions

  • k.bad_argument if the key index is not valid.

Write Key Register (4)

Writes a key into one of this Context’s key registers, by index. Note that there are currently 16 key registers. Because key register 0 is permanently null, index 0 is treated as invalid for this operation and will return an exception.

Call

  • d0: key index
  • k1: key

Reply

Empty.

Exceptions

  • k.bad_argument if the key index is not valid (including 0).

Read MPU Region Register (5)

Reads out the contents of one of this Context’s MPU region registers. The number of MPU region registers per Context is configurable at build time [1].

Call

  • d0: region index

Reply

No data.

  • k1: region key

Exceptions

  • k.bad_argument if the region index is not valid for this Context.

Write MPU Region Register (6)

Alters one of this Context’s MPU region registers. The number of MPU region registers per Context is configurable at build time [1].

The change takes effect when this Context next becomes current, unless it is already current (i.e. it is modifying itself), in which case it takes effect immediately, before the reply is sent. This implies that a Context using this operation to remove its own access to stack will never receive the reply, taking a fault instead.

Real Memory keys can be loaded directly into the region registers. Any other type of key will be treated as a null key and confer no authority.

Note

This is probably going to change; bogus keys should be rejected.

Call

  • d0: region index
  • k1: region key

Reply

Empty.

Exceptions

  • k.bad_argument if the region register index is not valid for this Context.

Make Runnable (7)

Switches this Context into “runnable” state. The practical effect of this depends on this Context’s current state:

  • If blocked waiting to send, without receive phase, the send is cancelled and the Context receives no notification.
  • If blocked in receive, without send phase, the Context immediately receives a k.would_block exception.
  • If blocked in send-receive or call, the send phase is skipped and the Context immediately receives a k.would_block exception. In the case of a call, any issued reply keys become invalid.
  • If stopped, the Context is simply resumed.
  • If already runnable, nothing happens.

Note

Careful reading of this list above will show that a Context trying to make itself runnable will always succeed but receive an exception anyway.

Call

Empty.

Reply

Empty.

Get Priority (8)

Gets the current priority of this Context.

Call

Empty.

Reply

  • d0: priority

Warning

This API may change; priorities may need to be capabilities.

Set Priority (9)

Alters the current priority of this Context. If this Context is runnable, this might trigger a Context switch.

Call

  • d0: priority

Reply

Empty.

Warning

This API may change; priorities may need to be capabilities.

Read (Low/High) Registers (10/11)

Each of these two methods reads a block of five kernel-maintained registers from this Context. There are ten total such registers, so five are the “low” registers, and five are the “high”. They are ordered in the same way as for Read Register (1).

This operation is intended to make “swapping” — multiplexing multiple logical tasks across a single Context — faster, and is also useful in debuggers.

Call

Empty.

Reply

dn Low High
d0 r4 r9
d1 r5 r10
d2 r6 r11
d3 r7 r12
d4 r8 r13

Write (Low/High) Registers (12/13)

Each of these two methods writes a block of five kernel-maintained registers into this Context. There are ten total such registers, so five are the “low” registers, and five are the “high”. They are ordered in the same way as for Read Register (1).

This operation is intended to make “swapping” — multiplexing multiple logical tasks across a single Context — faster, and is also useful in debuggers.

Call

dn Low High
d0 r4 r9
d1 r5 r10
d2 r6 r11
d3 r7 r12
d4 r8 r13

Reply

Empty.

Footnotes

[1](1, 2, 3) The number of MPU region registers can be configured at build time. The current default is six. The number of MPU region registers must be no larger than the actual number of MPU regions implemented by the hardware.
[2]Except by guessing, of course. With an Object Table key a program can fabricate fake reply keys. If it can guess the call count, it can fake a reply. This is one reason why call counts are so large (63 bits). Currently they’re zeroed at Context creation, but we may randomize them in the future to make guessing really hard.