198 lines
5.3 KiB
Text
198 lines
5.3 KiB
Text
|
# I/O Devices
|
||
|
|
||
|
I/O devices on Nga are exposed via three instructions:
|
||
|
|
||
|
ie enumerate i/o devices
|
||
|
iq query i/o device
|
||
|
ii invoke i/o interaction
|
||
|
|
||
|
All devices are registered with the VM. How this occurs is
|
||
|
implementation dependent.
|
||
|
|
||
|
## Counting Devices
|
||
|
|
||
|
Use the `ie` instruction to return the number of attached devices.
|
||
|
|
||
|
i ie......
|
||
|
|
||
|
Upon running, the stack will contain the number of devices. You
|
||
|
can then query these by passing the device number to `iq`.
|
||
|
|
||
|
## Query Devices
|
||
|
|
||
|
Use `iq` to query an attached device. This will return two values,
|
||
|
a device identifer and a revision number.
|
||
|
|
||
|
The device identifier will be the top value on the stack.
|
||
|
|
||
|
## Invoking a Device
|
||
|
|
||
|
You can trigger an I/O operation by passing the device number to
|
||
|
the `ii` instruction.
|
||
|
|
||
|
E.g., to display a character (ASCII code 98 in this case):
|
||
|
|
||
|
i liliii..
|
||
|
d 98
|
||
|
d 0
|
||
|
|
||
|
Be sure to pass the device number, not the device identifier.
|
||
|
|
||
|
## Device Identifiers
|
||
|
|
||
|
Ultimately device identifiers are implementation-specific, but the
|
||
|
most common system (Nga on Unix) provides or reserves the following:
|
||
|
|
||
|
ID | Device Type | Notes |
|
||
|
-----+------------------+----------------------------+
|
||
|
0000 | Generic Output | Always present as device 0 |
|
||
|
0001 | Keyboard | |
|
||
|
0002 | Floating Point | |
|
||
|
0003 | Block Storage | Raw, 1024 cell blocks |
|
||
|
0004 | Filesystem | Unix-style Files |
|
||
|
0005 | Clock | |
|
||
|
0006 | | |
|
||
|
0007 | Network: Sockets | |
|
||
|
0008 | Syscalls: Unix | |
|
||
|
0009 | Scripting Hooks | |
|
||
|
0010 | Random Number | |
|
||
|
1000 | Image Saving | |
|
||
|
|
||
|
It must be noted here that nothing forces devices to use these
|
||
|
identifiers, and one must take care to use an Nga implementation
|
||
|
that provides the devices they need.
|
||
|
|
||
|
## Device Revisions
|
||
|
|
||
|
Over time, the functionality a device provides may change. To allow
|
||
|
detection of this, the query functionality provides a revision number.
|
||
|
Your code can use this to ensure that the device provided supports
|
||
|
the level of functionality you need.
|
||
|
|
||
|
## Nga/Retro-Unix Device Details
|
||
|
|
||
|
### 0000: Generic Output
|
||
|
|
||
|
Supported by all Nga implementations. This is required to be the
|
||
|
first device, and is the only one guaranteed to be provided. It
|
||
|
consumes a value from the stack, writing to to the host-specific
|
||
|
output. (This does not need to be a screen).
|
||
|
|
||
|
### 0001: Keyboard
|
||
|
|
||
|
Read and return a keypress.
|
||
|
|
||
|
Consumes no data, returns a single value representing the
|
||
|
character that was read.
|
||
|
|
||
|
No subcommands are defined.
|
||
|
|
||
|
### 0002: Floating Point
|
||
|
|
||
|
The current revision is 1.
|
||
|
|
||
|
It currently provides:
|
||
|
|
||
|
n:to-float (n-_f:-n)
|
||
|
s:to-float (s-_f:-n)
|
||
|
f:to-number (f:a-__-n)
|
||
|
f:to-string (f:n-__-s)
|
||
|
f:+ (f:ab-c)
|
||
|
f:- (f:ab-c)
|
||
|
f:* (f:ab-c)
|
||
|
f:/ (f:ab-c)
|
||
|
f:floor (f:ab-c)
|
||
|
f:ceiling (f:f-f)
|
||
|
f:sqrt (f:f-f)
|
||
|
f:eq? (f:ab-c)
|
||
|
f:-eq? (f:ab-c)
|
||
|
f:lt? (f:ab-c)
|
||
|
f:gt? (f:ab-c)
|
||
|
f:depth (-n)
|
||
|
f:dup (f:a-aa)
|
||
|
f:drop (f:a-)
|
||
|
f:swap (f:ab-ba)
|
||
|
f:log (f:ab-c)
|
||
|
f:power (f:ab-c)
|
||
|
f:sin (f:f-f)
|
||
|
f:cos (f:f-f)
|
||
|
f:tan (f:f-f)
|
||
|
f:asin (f:f-f)
|
||
|
f:acos (f:f-f)
|
||
|
f:atan (f:f-f)
|
||
|
f:push (f:f-)
|
||
|
f:pop (f:-f)
|
||
|
f:adepth (-n)
|
||
|
|
||
|
### 0003: Block Storage
|
||
|
|
||
|
Reserved for future use.
|
||
|
|
||
|
### 0004: Filesystem
|
||
|
|
||
|
Currently at revision 0.
|
||
|
|
||
|
This implements a device providing traditional Unix-like files.
|
||
|
|
||
|
Takes a value indicating an operation, and each operation takes
|
||
|
additional values.
|
||
|
|
||
|
| Operation | Stack | Action |
|
||
|
| --------- | ----- | -------------------------------- |
|
||
|
| 0 | sm-h | Open a file |
|
||
|
| 1 | h- | Close a file |
|
||
|
| 2 | h-c | Read a byte from a file |
|
||
|
| 3 | ch- | Write a byte to a file |
|
||
|
| 4 | h-n | Return current pointer into file |
|
||
|
| 5 | nh- | Move pointer in a file |
|
||
|
| 6 | h-n | Return the size of a file |
|
||
|
| 7 | s- | Delete a file |
|
||
|
| 8 | h- | Flush pending writes |
|
||
|
|
||
|
### 0010: Random Number Generator
|
||
|
|
||
|
This is currently at revision 0.
|
||
|
|
||
|
On invocation, this returns a random number.
|
||
|
|
||
|
## Implementation Details (C)
|
||
|
|
||
|
On the C implementation, each I/O device has the needed support
|
||
|
functions defined, then a query function and invocation function
|
||
|
defined.
|
||
|
|
||
|
As an example, to add a device that has two functions, I might do:
|
||
|
|
||
|
void one() {
|
||
|
stack_push(100);
|
||
|
}
|
||
|
|
||
|
void two() {
|
||
|
stack_push(200);
|
||
|
}
|
||
|
|
||
|
Handler device_actions[] = {
|
||
|
one, two
|
||
|
}
|
||
|
|
||
|
void io_device() {
|
||
|
device_actions[stack_pop()]();
|
||
|
}
|
||
|
|
||
|
void query_device() {
|
||
|
stack_push(0); /* Revision */
|
||
|
stack_push(1234); /* Device ID */
|
||
|
}
|
||
|
|
||
|
Then add pointers to `io_device` to `IO_deviceHandlers` and
|
||
|
`query_device` to `IO_queryHandlers` and increase the `NUM_DEVICES`
|
||
|
by one.
|
||
|
|
||
|
You will then need to write a set of Retro words to use the new
|
||
|
device.
|
||
|
|
||
|
:device:one #1 #1234 io:scan-for io:invoke ;
|
||
|
:device:two #2 #1234 io:scan-for io:invoke ;
|
||
|
|
||
|
Rebuild the VM, adding these to image.
|