RP6502-API¶
Rumbledethumps Picocomputer 6502 Application Programming Interface.
Table of Contents
1. Introduction¶
The RP6502-RIA runs a protected 32-bit kernel that you can call from the 6502. The kernel runs on a processor that is significantly faster than a 6502. This is the only practical way to run modern file systems, networking, and USB host stacks.
The RP6502 presents an opportunity to create a new type of operating system. A 6502 OS based on the C programming language with familiar POSIX-like operations.
2. Calling with fastcall¶
The binary interface is based on fastcall from the CC65 Internals. The RP6502 fastcall does not use or require anything from CC65 and is easy for assembly programmers to use. At its core, the API is based on a C ABI with three simple rules.
Stack arguments are pushed left to right.
Last argument optionally passed by register A, AX, or AXSREG.
Return value in register AX or AXSREG.
A and X are the 6502 registers. The register AX combines them for 16 bits. AXSREG is 32 bits with the SREG bits in zero page. Let’s look at how to make an OS call through the RIA registers. All kernel calls are specified as a C declaration like so:
-
int doit(int arg0, int arg1);
The RIA has registers called RIA_A, RIA_X, and RIA_SREG. An int is 16 bits, so we set the RIA_A and RIA_X registers with arg1. I’ll use “A” for the 6502 register and “RIA_A” for the RIA register in this explanation.
We use the XSTACK for arg0. Reading or writing data to the RIA_XSTACK register removes or adds bytes to the XSTACK. It’s a top-down stack, so push each argument from left to right and maintain little endian-ness in memory.
To execute the call, store the operation ID in RIA_OP. The operation begins immediately. You can keep doing 6502 things, like running a loading animation, by polling RIA_BUSY. Or, JSR RIA_SPIN to block.
The JSR RIA_SPIN method can unblock in less than 3 clock cycles and does an immediate load of A and X. Sequential operations will run fastest with this technique. Under the hood, you’re jumping into a self-modifying program that runs on the RIA registers.
BRA #$?? ; RIA_BUSY {-2 or 0}
LDA #$?? ; RIA_A
LDX #$?? ; RIA_X
RTS
Polling is simply snooping on the above program. The RIA_BUSY register is the -2 or 0 in the BRA above. The RIA datasheet specifies bit 7 indicates busy, which the 6502 can check quickly by using the BIT operator to set flag N. Once clear, we read RIA_A and RIA_X with absolute instructions.
wait:
BIT RIA_BUSY
BMI wait
LDA RIA_A
LDX RIA_X
All operations returning RIA_A will also return RIA_X to assist with CC65’s integer promotion requirements. RIA_SREG is only updated for 32-bit returns. RIA_ERRNO is only updated if there is an error.
Some operations return data on the stack. You must pull the entire stack before the next call. However, tail call optimizations are possible. For example, you can chain read_xstack() and write_xstack() to copy a file without using any RAM or XRAM.
2.1. Short Stacking¶
In the never ending pursuit of saving all the clocks, it is possible to save a few on the stack push if you don’t need all the range. This only works on the stack argument that gets pushed first. For example:
long lseek_impl(long offset, char whence, int fildes)
Here we are asked for a 64 bit value. Not coincidentally, it’s in the right position for short stacking. If, for example, you only need 24 bits, push only three bytes. The significant bytes will be implicit.
2.2. Shorter Integers¶
Many operations can save a few clocks by ignoring REG_X. All integers are always available as 16 bits to assist with CC65 and integer promotion. However, many operations will ignore REG_X on the register parameter and limit their return to fit in REG_A. This will be documented below as “A regs”.
2.3. Bulk Data¶
Functions that move bulk data may come in two flavors. These are any function with a pointer parameter. This pointer is meaningless to the kernel because it can not change 6502 RAM. Instead, we use the XSTACK or XRAM for data buffers.
2.3.1. Bulk XSTACK Operations¶
These only work if the count is 256 or less. Bulk data is passed on the XSTACK, which is 256 bytes. A pointer appears in the C prototype to indicate the type and direction of this data. Let’s look at some examples.
int open(const char *path, int oflag);
Send oflag in AX. Send the path on XSTACK by pushing the string starting with the last character. You may omit pushing the terminating zero, but strings are limited to a length of 255. Calling this from the C SDK will “just work” because there’s an implementation that pushes the string for you.
int read_xstack(void *buf, unsigned count, int fildes)
Send count as a short stack and fildes in AX. The returned value in AX indicates how many values must be pulled from the stack. If you call this from the C SDK then it will copy XSTACK to buf[] for you.
int write_xstack(const void *buf, unsigned count, int fildes)
Send fildes in AX. Push the data to XSTACK. Do not send count, the kernel knows this from its internal stack pointer. If you call this from the C SDK then it will copy buf[] to XSTACK for you.
Note that read() and write() are part of the C SDK, not a kernel operation. CC65 requires them to support more than 256 bytes, so they have wrapper logic to make multiple kernel calls when necessary.
2.3.2. Bulk XRAM Operations¶
These load and save XRAM directly. You can load game assets without going through 6502 RAM or capture a screenshot with ease.
int read_xram(xram_addr buf, unsigned count, int fildes)
int write_xram(xram_addr buf, unsigned count, int fildes)
The kernel expects buf and count on the XSTACK as integers with filedes in AX. The buffer is effectively &XRAM[buf] here. There’s nothing special about these calls in regards to how the binary interface rules are applied. They are interesting because of their high performance for loading assets.
3. Function Reference¶
Much of this API is based on CC65 and POSIX. In particular, filesystem access should feel extremely modern. However, some operations will have different argument orders or bitfield values than what you’re used to. The reason for this becomes apparent when you start to work in assembly and fine tune short stacking and integer demotions. You might not notice the differences if you only work in C because the standard library has wrapper functions and familiar prototypes. For example, the lseek_impl() described below has reorderd arguments that are optimized for short stacking the long argument. But you never call lseek_impl() from C, you call the usual lseek() which has the traditional argument order.
zxstack¶
-
void zxstack(void);
Abandon the xstack by resetting the pointer. Not needed for normal operation. This is the only operation that doesn’t require waiting for completion.
xreg¶
-
int xreg(char device, char channel, unsigned char address, ...);
-
int xregn(char device, char channel, unsigned char address, unsigned count, ...);
Use xreg() with cc65 and xregn() with LLVM-MOS. The only difference is that xregn() requires a count of the variadic arguments.
Set extended registers on a PIX device. See the RP6502-RIA and RP6502-VGA documentation for what each register does. Setting extended registers can fail, which you should use for feature detection. EINVAL means the device responded with a negative acknowledgementg. EIO means there was a timeout waiting for ack/nak.
- Parameters
device – PIX device ID. 0-6
channel – PIX channel. 0-15
address – PIX address. 0-255
... – 16 bit integers to set starting at address.
- Errno
EINVAL, EIO
phi2¶
-
int
phi2
(void)¶ Retrieves the PHI2 setting from the RIA. Applications can use this for adjusting to or rejecting different clock speeds.
- Returns
The 6502 clock speed in kHz. 500 <= x <= 8000
- Errno
will not fail
codepage¶
-
int
codepage
(void)¶ Retrieves the CP setting from the RIA. This is the encoding the filesystem is using and, if VGA is installed, the console and default font.
- Returns
The code page. One of: 437, 720, 737, 771, 775, 850, 852, 855, 857, 860, 861, 862, 863, 864, 865, 866, 869, 932, 936, 949, 950.
- Errno
will not fail
lrand¶
-
long
lrand
(void)¶ Generates a random number starting with entropy on the RIA. This is suitable for seeding a RNG or general use. The 16-bit rand() in the CC65 library can be seeded with this by calling its non-standard _randomize() function.
- Returns
Chaos. 0x0 <= x <= 0x7FFFFFFF
- Errno
will not fail
stdin_opt¶
-
int
stdin_opt
(unsigned long ctrl_bits, unsigned char str_length)¶ Additional options for the STDIN line editor. Set the str_length to your buffer size - 1 to make gets() safe. This can also guarantee no split lines when using fgets() on STDIN.
- Parameters
ctrl_bits – Bitmap of ASCII 0-31 defines which CTRL characters can abort an input. When CTRL key is pressed, any typed input remains on the screen but the applicaion receives a line containing only the CTRL character. e.g. CTRL-C + newline.
str_length – 0-255 default 254. The input line editor won’t allow user input greater than this length.
- A regs
return, str_length
- Returns
0 on success
- Errno
will not fail
clock¶
-
unsigned long
clock
(void)¶ Obtain the value of a monotonic clock that updates 100 times per second. Wraps approximately every 497 days.
- Returns
1/100 second monotonic clock
- Errno
will not fail
clock_getres¶
-
int
clock_getres
(clockid_t clock_id, struct timespec *res)¶ Copies the clock resolution to res.
- Parameters
clock_id – 0 for CLOCK_REALTIME.
- Returns
0 on success. -1 on error.
- A regs
return, clock_id
- Errno
EINVAL
clock_gettime¶
-
int
clock_gettime
(clockid_t clock_id, struct timespec *tp)¶ Copies the current time to tp.
- Parameters
clock_id – 0 for CLOCK_REALTIME.
- Returns
0 on success. -1 on error.
- A regs
return, clock_id
- Errno
EINVAL, EUNKNOWN
clock_settime¶
-
int
clock_settime
(clockid_t clock_id, const struct timespec *tp)¶ Sets the current time to tp.
- Parameters
clock_id – 0 for CLOCK_REALTIME.
- Returns
0 on success. -1 on error.
- A regs
return, clock_id
- Errno
EINVAL, EUNKNOWN
clock_gettimezone¶
-
int
clock_gettimezone
(clockid_t clock_id, struct _timezone *tz)¶ Not implemented. Always fails.
- Parameters
clock_id – 0 for CLOCK_REALTIME.
- Returns
-1 always.
- A regs
return, clock_id
- Errno
ENOSYS
open¶
-
int
open
(const char *path, int oflag)¶ Create a connection between a file and a file descriptor.
- Parameters
path – Pathname to a file.
oflag – Bitfield of options.
- Returns
File descriptor. -1 on error.
- A regs
return, oflag
- Errno
EINVAL, EMFILE, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_NO_FILE, FR_NO_PATH, FR_INVALID_NAME, FR_DENIED, FR_EXIST, FR_INVALID_OBJECT, FR_WRITE_PROTECTED, FR_INVALID_DRIVE, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_TIMEOUT, FR_LOCKED, FR_NOT_ENOUGH_CORE, FR_TOO_MANY_OPEN_FILES
- Options
- O_RDONLY 0x01Open for reading only.O_WRONLY 0x02Open for writing only.O_RDWR 0x03Open for reading and writing.O_CREAT 0x10Create the file if it does not exist.O_TRUNC 0x20Truncate the file length to 0 after opening.O_APPEND 0x40Read/write pointer is set end of the file.O_EXCL 0x80If O_CREAT and O_EXCL are set, fail if the file exists.
close¶
-
int
close
(int fildes)¶ Release the file descriptor. File descriptor will rejoin the pool available for use by open().
- Parameters
fildes – File descriptor from open().
- Returns
0 on success. -1 on error.
- A regs
return, fildes
- Errno
EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_INVALID_OBJECT, FR_TIMEOUT
read¶
-
int
read
(int fildes, void *buf, unsigned count)¶ Read count bytes from a file to a buffer.
- Parameters
buf – Destination for the returned data.
count – Quantity of bytes to read. 0x7FFF max.
fildes – File descriptor from open().
- Returns
On success, number of bytes read is returned. On error, -1 is returned.
- A regs
fildes
- Errno
EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_DENIED, FR_INVALID_OBJECT, FR_TIMEOUT
read_xstack¶
-
int
read_xstack
(void *buf, unsigned count, int fildes)¶ Read count bytes from a file to xstack.
- Parameters
buf – Destination for the returned data.
count – Quantity of bytes to read. 0x100 max.
fildes – File descriptor from open().
- Returns
On success, number of bytes read is returned. On error, -1 is returned.
- A regs
fildes
- Errno
EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_DENIED, FR_INVALID_OBJECT, FR_TIMEOUT
read_xram¶
-
int
read_xram
(unsigned buf, unsigned count, int fildes)¶ Read count bytes from a file to xram.
- Parameters
buf – Destination for the returned data.
count – Quantity of bytes to read. 0x7FFF max.
fildes – File descriptor from open().
- Returns
On success, number of bytes read is returned. On error, -1 is returned.
- A regs
fildes
- Errno
EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_DENIED, FR_INVALID_OBJECT, FR_TIMEOUT
write¶
-
int
write
(int fildes, const void *buf, unsigned count)¶ Write count bytes from buffer to a file.
- Parameters
buf – Location of the data.
count – Quantity of bytes to write. 0x7FFF max.
fildes – File descriptor from open().
- Returns
On success, number of bytes written is returned. On error, -1 is returned.
- A regs
fildes
- Errno
EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_DENIED, FR_INVALID_OBJECT, FR_TIMEOUT
write_xstack¶
-
int
write_xstack
(const void *buf, unsigned count, int fildes)¶ Write count bytes from xstack to a file.
- Parameters
buf – Location of the data.
count – Quantity of bytes to write. 0x100 max.
fildes – File descriptor from open().
- Returns
On success, number of bytes written is returned. On error, -1 is returned.
- A regs
fildes
- Errno
EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_DENIED, FR_INVALID_OBJECT, FR_TIMEOUT
write_xram¶
-
int
write_xram
(unsigned buf, unsigned count, int fildes)¶ Write count bytes from xram to a file.
- Parameters
buf – Location of the data.
count – Quantity of bytes to write. 0x7FFF max.
fildes – File descriptor from open().
- Returns
On success, number of bytes written is returned. On error, -1 is returned.
- A regs
fildes
- Errno
EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_DENIED, FR_INVALID_OBJECT, FR_TIMEOUT
lseek¶
-
off_t
lseek
(int fildes, off_t offset, int whence)¶
-
static long
lseek_impl
(long offset, char whence, int fildes)¶ Move the read/write pointer. This is implemented internally with an argument order to take advantage of short stacking the offset.
- Parameters
offset – How far you wish to seek.
whence – From whence you wish to seek.
fildes – File descriptor from open().
- Returns
Read/write position. -1 on error. If this value would be too large for a long, the returned value will be 0x7FFFFFFF.
- A regs
fildes
- Errno
EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_INVALID_OBJECT, FR_TIMEOUT
- Whence
- SEEK_SET = 2The start of the file plus offset bytes.SEEK_CUR = 0The current location plus offset bytes.SEEK_END = 1The size of the file plus offset bytes.
unlink¶
-
int
unlink
(const char* name)¶ Removes a file or directory from the volume.
- Parameters
name – File or directory name to unlink (remove).
- Returns
0 on success. -1 on error.
- Errno
FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_NO_FILE, FR_NO_PATH, FR_INVALID_NAME, FR_DENIED, FR_WRITE_PROTECTED, FR_INVALID_DRIVE, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_TIMEOUT, FR_LOCKED, FR_NOT_ENOUGH_CORE
rename¶
-
int
rename
(const char* oldname, const char* newname)¶ Renames and/or moves a file or directory.
- Parameters
oldname – Existing file or directory name to rename.
newname – New object name.
- Returns
0 on success. -1 on error.
- Errno
EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_NO_FILE, FR_NO_PATH, FR_INVALID_NAME, FR_EXIST, FR_WRITE_PROTECTED, FR_INVALID_DRIVE, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_TIMEOUT, FR_LOCKED, FR_NOT_ENOUGH_CORE
exit¶
-
void
exit
(int status)¶ Halt the 6502 and return to the kernel command interface. This is the only operation that does not return. RESB will be pulled down before the next instruction can execute. Status is currently ignored but will be used in the future.
- Parameters
status – 0 is good, 1-255 for error.
- A regs
status