Input and output is performed using access to coprocessor #3, denoted in source code by p3
. There are three valid access types, and each accesses a different set of registers.
All usage of CP3 requires privilege, i.e. CP3 instructions are Undefined in User mode.
Memory Transfer
This covers the LDC
and STC
instructions that respectively load and store registers directly from and to memory. The normal set of indexing combinations for a load/store are available.
LDC p3, cr1, [r2] // Read memory starting at r2 into register cr1
STC p3, cr1, [r2] // Write the register cr1 to memory starting at r2
Registers
cr0
(on load/store): Signal Buffer
int cp3_store_signal_buffer(void* icb);
size_t cp3_safe_store_signal_buffer(void* icb, size_t icb_size);
void cp3_load_signal_buffer(void* icb);
Writes: Interchange buffer (or nothing)
The buffer contains the next queued signal, which is popped from the queue. If there was no signal, no write is performed and the V bit in the APSR is set, otherwise the V bit is cleared.
The signal consists of a signal name (always ICTAG_STRING
) and zero or more objects giving further details about the signal, exactly as if they were in an ICTAG_ARRAY
(but without the ICTAG_ARRAY
).
Reads: Interchange buffer
The buffer contains a signal to queue, exactly as stored by a store of the Signal Buffer. The signal is added exactly like a real one.
If the signal is added successfully, V is cleared. If it isn't (for instance, because the signal queue is full), V is set.
cr1
(on load): Invoke Buffer
void cp3_load_invoke_buffer(void* icb);
Reads: Interchange buffer
The buffer will be used for a future Component Invoke or Value Invoke.
cr1
(on store): Reply Buffer
int cp3_store_reply_buffer(void* icb);
size_t cp3_safe_store_reply_buffer(void* icb, size_t icb_size);
Writes: Result code, interchange buffer (or nothing)
Writes the result code of the last (non-discarded) invoke, followed by an interchange buffer containing its results. If the last invocation succeeded, N will be cleared, otherwise it will be set. (This can also be checked by reading the result code.)
If no invocation has yet been performed, writes an empty success.
cr2
(on load): Asynchronous Invoke Buffer
void cp3_load_async_invoke_buffer(void* icb);
Reads: Interchange buffer
The buffer will be used for a future Asynchronous Component Invoke or Asynchronous Value Invoke.
cr2
(on store): Asynchronous Reply Buffer
int cp3_store_async_reply_buffer(void* icb);
size_t cp3_safe_store_async_signal_buffer(void* icb, size_t icb_size);
Writes: Result code, interchange buffer (or nothing)
If it has completed, returns the result of the last Asynchronous Component Invoke or Asynchronous Value Invoke (see Reply Buffer) and clears the V bit of the APSR. If it hasn't, sets the V bit (and the N bit is unpredictable). If the last invocation suceeded, N will be cleared, otherwise it will be set.
If no Asynchronous Invoke has ever been initiated, writes an empty success.
cr3
(on store): Component List Buffer
int cp3_store_component_buffer(void* icb);
Writes: Interchange buffer
Stores a series of ICTAG_UUID
/ICTAG_STRING
pairs, ending in an ICTAG_END
, giving the address of each component and the type of that component.
Undefined if the Component List Buffer has not ever been latched.
cr4
(on store): Compact Component
struct compact_component { uuid address; char type[16]; }
int cp3_get_compact_component(struct compact_component* buf, int number);
Writes: struct compact_component
This access method is designed to facilitate iteration through the Component List Buffer with small, fixed memory usage and complexity. It is recommended only for boot ROMs and early OS initialization.
Writes the component selected by the Compact Component Index Register. If there is no such component (because off the end of the list), Z is set and V is cleared. Otherwise, Z is cleared; if the component's name is too large or it otherwise does not meet the format requirements, V is set (and the next index should be tried), otherwise V is clear and the buffer is written.
The type WILL include a NUL terminator, and any remaining space in the buffer will be zeroed. The type WILL also contain only ASCII characters. The write DOES require strict alignment, regardless of the INTERCHANGE_PACKED
and SCTLR.A
bits.
Undefined if the Component List Buffer has not ever been latched.
cr5
(on store): Reply Buffer IO
/* no current C API */
As with Reply Buffer, clears N if the last invocation succeeded and sets it if it failed. But in addition, sets C if the invocation succeeded and the only element of the reply was an ICTAG_BYTE_ARRAY
, and then writes the byte contents of the byte array directly to the target memory with no interchange information. Clears C and writes nothing if the invocation failed or the reply had more or different elements.
If INTERCHANGE_PACKED
is clear, the write is performed one word at a time (and the buffer must be a multiple of four bytes). Otherwise, it's performed one byte at a time.
cr6
(on store): Asynchronous Reply Buffer IO
/* no current C API */
The Asynchronous counterpart to Reply Buffer IO. Sets APSR bits as an Asynchronous Reply Buffer store, and treats the reply (if one currently exists) the same as Reply Buffer IO does. (Either sets V, or clears it and sets/clears N/C bits as appropriate.)
cr7
(on store): Node Address
void cp3_get_node_address(struct uuid*);
Writes the address of the current machine directly to the target memory with no interchange information. Always sets Z.
cr8
(on store): Temporary Address
int cp3_get_node_address(struct uuid*);
Writes the address of the tmp
filesystem directly to the target memory and sets Z if there is one, otherwise writes all zeroes and clears Z.
(All zeroes is not technically a valid UUID, so it is safe to check for it.)
cr9
(on store): Battery Status
int cp3_get_battery_status(struct battery_status*);
Writes the current energy level and maximum energy level, in that order, as double-precision floats, to the target memory. Clears Z if infinite power is in effect, sets it otherwise.
cr15
(on load): Crash Buffer
void cp3_crash(const char*);
Reads: Null-terminated UTF-8 string (up to 1024 non-null bytes)
The computer sets this as the error message (which can be read with an Analyzer), then hard-crashes. The computer must be externally reset to recover from such a crash.
Core Register Transfer
This covers the MRC
and MCR
instructions that (respectively) read and write coprocessor registers to and from core registers.
MRC p3, X, r1, cr2, cr3 // r1 := value of register cr2, cr3
MCR p3, X, r1, cr2, cr3 // register cr2, cr3 := value of r1
// (X is ignored in both cases and should be 0)
Registers
cr0, cr0
(when written): Memory Module Index Register
void cp3_write_mmir(uint32_t);
The index of the memory module to read information on via the MMSR/MMLR registers. 0 is the ROM/NVRAM module, RAM modules start at 1. The number of valid RAM modules can be determined by reading MMSR with increasing MMIR values (starting from one) until the first time zero is read.
cr0, cr0
(when read): Memory Module Size Register
uint32_t cp3_read_mmsr();
The size, in bytes, of the memory module with the index last written to MMIR. Zero if there is no memory module with the given index. (If MMIR is 0, this means there is no ROM installed.)
cr0, cr1
(when read): Memory Module Latency Register
uint32_t cp3_read_mmlr();
Bits 1 through 31 give the access latency (in cycles) of the memory module index last written to MMIR. Bit 0 is zero when the module is 32-bit, one when it is 16-bit.
cr0, cr2
(when read): Central Processor Speed Register
uint32_t cp3_read_cpsr();
The speed, in cycles per Minecraft tick, of the installed CPU. Multiply by 20 to get Hz, or divide by 50 to get KHz.
cr1, cr0
(read/write): Interchange Control Register
uint32_t cp3_read_icr();
void cp3_write_icr(uint32_t);
- bit 0:
INTERCHANGE_PACKED
if 0, interchange will take place with an alignment-friendly "unpacked" format. if 1, it will take place with a tightly-packed format. Initial value is 0.
- all other bits: reserved, always reads as zero
cr1, cr1
(read/write): Invoke Target Register
uint32_or_ptr_t cp3_read_itr();
void cp3_write_itr(uint32_or_ptr_t);
When invoking on a component, contains the virtual address of the invoke target. This is a 128-bit UUID of a component.
When invoking on a Value, contains the actual identifier for the target Value. (Not a pointer.)
cr1, cr2
(read/write): Compact Component Index Register
uint32_t cp3_read_ccir();
void cp3_write_ccir(uint32_t);
See Compact Component.
cr1, cr3
(read/write): Interchange Store Truncation Register
uint32_t cp3_read_istr();
void cp3_write_istr(uint32_t);
If this register is non-zero, all interchange writes (Signal Buffer, Reply Buffer, and Asynchronous Reply Buffer, as well as reads of the respective Size registers) will ignore any elements past the given number. (e.g. if ISTR is 1, a Signal Buffer store will store only the signal name and an ICTAG_END
, and not any other data. In this example, note that the signal is still popped, and therefore all other data about the signal is lost!)
This only applies to stores, and only stores of the Signal Buffer, Reply Buffer, and Asynchronous Reply Buffer (or reads of their size registers). In addition, it only applies to top-level elements---elements inside arrays and compounds are not counted.
As with Compact Component, this is intended to facilitate early boot-up, when more complex memory management is problematic.
cr2, cr0
(on read): Signal Size Register
uint32_t cp3_read_ssr();
If there is a queued signal, returns the necessary size in bytes to store it in an interchange buffer. Otherwise, returns 0. Also sets the APSR bits the same way STC p3, c0
does. Does not pop the signal.
cr2, cr1
(on read): Reply Size Register
uint32_t cp3_read_rsr();
Returns the necessary size to store the result of the last Invoke in an interchange buffer. Also sets the APSR bits the same way STC p3, c1
does.
cr2, cr2
(on read): Asynchronous Reply Size Register
uint32_t cp3_read_arsr();
If the call has completed, returns the necessary size to store the result of the last Asynchronous Invoke in an interchange buffer. Otherwise returns zero. Also sets the APSR bits the same way STC p3, c2
does.
cr2, cr3
(on read): Component List Size Register
uint32_t cp3_read_clsr();
Returns the necessary size to store the contents of the Component List Buffer.
Undefined if the Component List Buffer has not ever been latched.
cr2, cr5
(on read): Reply IO Size Register
uint32_t cp3_read_risr();
If a Reply Buffer IO store would succeed, returns the byte length of the IO, not including any required padding. Returns zero otherwise (or for a zero-length IO). Sets the APSR bits the same way STC p3, cr5
does.
cr2, cr6
(on read): Asynchronous Reply IO Size Register
uint32_t cp3_read_arisr();
If an Asynchronous Reply Buffer IO store would succeed, returns the byte length of the IO, not including any required padding. Returns zero otherwise (or for a zero-length IO). Sets the APSR bits the same way STC p3, cr6
does.
cr4, cr0
(on write): Sleep Amount
void cp3_sleep(int32_t ticks);
This isn't a proper register. Writing a value to this register will sleep until at least this many ticks have passed or a signal arrives. (Will finish immediately if a signal is already here.) Note that sleeping is not entirely accurate; to avoid accumulating temporal drift, you may find it more convenient to write to the World Clock Register instead. Also note that negative and zero sleeps return immediately.
Multiple Core Register Transfer
This covers the MRRC
and MCRR
instructions that (respectively) read and write coprocessor registers to and from two core registers.
MRRC p3, X, r1, r2, cr3 // r1 (low), r2 (high) := value of register cr3
MCRR p3, X, r1, r2, cr3 // register cr3 := value of r1 (low), r2 (high)
// (X is ignored in both cases and should be 0)
cr0
(on read/write): World Clock Register
uint64_t cp3_read_wcr();
void cp3_targeted_sleep(uint64_t);
The current time in the world at large. Unlike many other CP3 operations, reading this clock does not require privilege. This may change in the future.
Writing to this register (which does require privilege) will sleep until either the written time has arrived (or passed) or a signal arrives. (Will finish immediately if a signal is already here.)
cr1
(on read): Real Time Register
long long cp3_get_real_time();
Gets the number of milliseconds since midnight, January 1, 1970 UTC, as returned by Java's System.currentTimeMillis()
function.
cr2
(on read/write): Up Time Register
long long cp3_get_up_time();
Gets the number of ticks since the computer was started up. Unlike many other CP3 operations, reading this clock does not require privilege. This may change in the future.
Writing to this register (which does require privilege) will sleep until either the written time has arrived (or passed) or a signal arrives. (Will finish immediately if a signal is already here.)
This should be used instead of the World Clock for short-term timing, as it is not vulnerable to changes due to sleeping players / time travel / doDaylightCycle
.
cr3
(on read): CPU Time Register
double cp3_get_cpu_time();
Gets the approximate number of seconds of real CPU time that have been consumed thus far by this processor. It is not recommended to use this for any timing purposes, though it may be useful for estimating the server load incurred by the emulator.
Operations
Covers the CDP
instruction, and therefore all operations that are neither logical loads/stores of coprocessor registers nor transfers between them and core registers.
CDP p3, 0, crX, crY, crZ, #W
: Shutdown
void cp3_shutdown();
Shuts down the computer. (X/Y/Z/W are ignored)
CDP p3, 1, crX, crY, crZ
: Reboot
void cp3_reboot();
Reboots the computer. (X/Y/Z are ignored)
CDP p3, 2, crX, crY, crZ, #flag
: Invoke
void cp3_invoke(const int flag);
Invoke. Depending on flag
, reads the UUID pointed to by the Invoke Target Register or the Value handle stored directly therein, and attempts to invoke the contents of the Invoke Buffer on it.
It is Undefined to call this before the Invoke Buffer is first written.
flag
bits:
- bit 0:
INVOKE_DISCARD_REPLY
if 0, the results of the invocation will be stored in the Reply Buffer. if 1, the result will be discarded.
- bit 1:
INVOKE_NON_BLOCKING
if 0, the invocation will block if necessary (call limit exceeded, or mandatory indirect call). if 1, the invocation will never block
- bit 2:
INVOKE_VALUE_TARGET
if 0, this is a Component Invoke (and ITR contains a pointer to a UUID). if 1, this is a Value Invoke (and ITR contains a Value handle).
CDP p3, 3, crX, crY, crZ, #flag
: Asynchronous Invoke
void cp3_async_invoke(const int flag);
Asynchronous Invoke. Depending on flag
, reads the UUID pointed to by the Invoke Target Register or the Value handle stored directly therein, and queues an asynchronous invocation of the current contents of the Asynchronous Invoke Buffer. Only one Asynchronous Invocation may be queued at a time; if one was already queued and has not yet completed, it is lost.
It is Undefined to call this before the Asynchronous Invoke Buffer is first written.
flag
bits:
- bit 0:
INVOKE_ASYNC_FROM_SYNC
if 1, the Invoke Buffer is copied to the Asynchronous Invoke Buffer before the invocation is queued.
- bit 1: reserved, must be 0
- bit 2:
INVOKE_VALUE_TARGET
CDP p3, 4, crX, crY, crZ
: NVRAM Flush
void cp3_nvram_flush();
Flushes SRAM-resident changes to NVRAM. This is an expensive operation in terms of power usage and time.
CDP p3, 5, crX, crY, crZ
: Component List Buffer Latch
void cp3_components_latch();
Stores the currently-connected components in the Component List Buffer. Must be called at least once before the Component List Buffer can be read, and again if components are added/removed and it is desired that a Component List Buffer dependent read be up-to-date.
CDP p3, 6, crX, crY, crZ
: Signal Pop
void cp3_signal_pop();
Pops the current signal without storing it. Sets V if there was no signal to pop, clears it otherwise.
Interchange Buffers
Interchange buffers are used to exchange data between OpenComputers devices and the ARM processor. There are two formats depending on the value of the Interchange Control Register: the default unpacked format is alignment friendly, and significantly faster when using 32-bit memory; the packed format requires relaxed alignment and is significantly slower using 32-bit memory, but not too much slower using 16-bit memory, and somewhat smaller.
Interchange Value
An Interchange Value is a type tag, followed by its data. In unpacked format, the tag is a 32-bit word; in packed format, it is a 16-bit halfword.
- 0x0000...0x3FFF / 0...16383:
ICTAG_STRING
A UTF-8 sequence of the given byte length (maximum length 16383). No NUL terminator is required; in fact, the string may contain null bytes freely. Any invalid UTF-8 sequences are simply ignored.
In the unpacked format, the string is padded to a multiple of four bytes with NUL bytes. This padding is not given in the length.
- 0x4000...0x7FFF / 16384...32767:
ICTAG_BYTE_ARRAY
A byte sequence of the given length (maximum length 16383).
In the unpacked format, the array is padded to a multiple of four bytes with NUL bytes. This padding is not given in the length.
- -8:
ICTAG_UUID
A 128-bit UUID, usually indicating a component address.
- -7:
ICTAG_COMPOUND
Contains a series of key-value pairs. Each key and value is an Interchange Value. Anything but ICTAG_BYTE_ARRAY
, ICTAG_COMPOUND
, ICTAG_ARRAY
, or ICTAG_NULL
may be used as keys, and anything may be used as values. Ends when the first ICTAG_END
key is encountered. (ICTAG_END
as a value is Undefined.)
- -6:
ICTAG_ARRAY
Contains a series of Interchange Values. Any type is valid. Ends when the first ICTAG_END
value is encountered.
- -5:
ICTAG_INT
A 32-bit signed word.
- -4:
ICTAG_DOUBLE
A 64-bit float.
- -3:
ICTAG_BOOLEAN
In packed mode, a single byte; in unpacked mode, a 32-bit word. Zero is false, any other value is true. When storing interchange buffers, CP3 always stores true as all-bits-set.
- -2:
ICTAG_NULL
No data is stored; the value is a strongly-typed NULL (equivalent to Lua nil
).
- -1:
ICTAG_END
Designates the end of a ICTAG_COMPOUND
, ICTAG_ARRAY
, or Interchange Buffer.
Interchange Buffer
An interchange buffer consists of a series of values, like an implicit ICTAG_ARRAY
. In most cases (Invoke and signals), an interchange buffer should contain a ICTAG_STRING
as its first element. Reply Buffers have a 32-bit (or 16-bit if packed) result code before its first element, containing one of the following codes:
INVOKE_SUCCESS
/0: Call succeeded. Results follow.
INVOKE_UNKNOWN_ERROR
/1: Unknown error. Human-readable message follows.
INVOKE_LIMIT_REACHED
/2: Call limit reached on a non-blocking call.
INVOKE_UNKNOWN_RECEIVER
/3: No such component or Value.
INVOKE_INDIRECT_REQUIRED
/4: Non-blocking indirect call attempted.
INVOKE_UNKNOWN_METHOD
/5: No such method.
Serial Debug Coprocessor
If the allowSerialDebugCP
option is enabled in mcjarm.cfg, the following unprivileged operations are also possible:
MRC p7, #0, rX, cr0, cr0
void cp7_putchar(unsigned int);
Writes the Unicode code point in rX to the buffer, flushing the buffer if it overflows. Undefined if it is not a valid Unicode code point (U+0000 through U+10FFFF)
CDP p7, #0, crX, crY, crZ
void cp7_flush();
Write the buffer to the log. (X/Y/Z are ignored.)
CDP p7, #1, crX, crY, crZ
void cp7_dump();
Write CPU state to the log. (X/Y/Z are ignored.)