RP6502-OS

RP6502 - Operating System

Introduction

The RP6502-RIA runs a 32-bit protected operating system that you can call from the 6502. The OS does not use any 6502 system RAM and will not interfere with developing a native 6502 OS.

The OS loosely follows POSIX with an Application Binary Interface (ABI) similar to cc65’s fastcall. It provides stdio.h and unistd.h services to both cc65 and llvm-mos compilers. There are also calls to access RP6502 features and manage FAT32 filesystems.

Note

ExFAT is ready to go and will be enabled when the patents expire.

Memory Map

There is no ROM. Nothing in zero page is used or reserved. The Picocomputer starts as a clean slate for every project. VGA, audio, storage, keyboards, mice, gamepads, RTC, and networking are all accessed using only the 32 registers of the RIA.

Address

Description

$0000-$FEFF

RAM, 63.75K

$FF00-$FFCF

Unassigned

$FFD0-$FFDF

VIA, see the WDC datasheet

$FFE0-$FFFF

RIA, see the RP6502-RIA datasheet

$10000-$1FFFF

XRAM, 64K for RP6502-RIA and RP6502-VGA

The unassigned space is available for hardware experimenters. Design your own chip select hardware to use this address space. Add additional VIAs downward and other hardware upward. For example: VIA0 at $FFD0, VIA1 at $FFC0, SID0 at $FF00, and SID1 at $FF20.

Application Binary Interface

See also

RP6502-RIA — the hardware register map referenced throughout this section.

The ABI for calling the operating system is based on fastcall from the cc65 internals. The OS does not use or require anything from cc65 and is easy for assembly programmers to use. At its core, the OS ABI is four simple rules.

  • Stack arguments are pushed left to right.

  • Last argument passed by register A, AX, or AXSREG.

  • Return value in register AX or AXSREG.

  • May return data on the stack.

A and X are the 6502 registers. The pseudo register AX combines them for 16 bits. AXSREG allows 32 bits with the 16 additional SREG bits. Let’s look at how to make an OS call through the RIA registers. All OS 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 RIA_XSTACK pops bytes; writing pushes bytes. It’s a top-down stack, so push each argument left to right and maintain little-endian byte order.

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. Alternatively, JSR to RIA_SPIN to block.

JSR RIA_SPIN can unblock within 3 clock cycles and immediately loads A and X. Sequential operations 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 C integer promotion. RIA_SREG is only updated for 32-bit returns. RIA_ERRNO is only updated if there is an error.

Some operations return strings or structures 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.

Short Stacking

In the pursuit of saving every cycle, you can save a few on the stack push when you don’t need the full range. This only applies to the first stack argument pushed. For example, in LSEEK:

long f_lseek(long offset, char whence, int fildes)

Here we need to push a 32 bit value. Not coincidentally, it’s in the right position for short stacking. If, for example, the offset always fits in 16 bits, push only two bytes instead of four.

Shorter AX

Many operations can save a few cycles by ignoring REG_X. All returned integers are always available as at least 16 bits to assist with C integer promotion. However, many operations will ignore REG_X in the register parameter and limit their return to fit in REG_A. These will be documented below as “A regs”.

Bulk Data

Functions that move bulk data come in two flavors, depending on where the data lives. A RAM pointer is meaningless to the RIA because it cannot change 6502 RAM. Instead, use the XSTACK or XRAM to move data.

Bulk XSTACK Operations

These only work if the size is 512 bytes or less. Bulk data is passed on the XSTACK, which is 512 bytes. A pointer appears in the C prototype to indicate the type and direction (to or from the OS) of this data. Let’s look at some examples.

int open(const char *path, int oflag);

Send oflag in RIA_A. RIA_X doesn’t need to be set according to the OPEN docs. 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 RIA_A. RIA_X doesn’t need to be set according to the READ_XSTACK docs. 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 RIA_A. RIA_X doesn’t need to be set according to the WRITE_XSTACK docs. Push the buf data to XSTACK. Do not send count, the OS knows this from its internal stack pointer. If you call this from the C SDK then it will copy count bytes of buf[] to XSTACK for you.

Note that read() and write() are part of the C SDK, not an OS operation. C requires these to support a count larger than the XSTACK can return so the implementation makes multiple OS calls as necessary.

Bulk XRAM Operations

These load and save XRAM directly via READ_XRAM and WRITE_XRAM. 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 OS expects buf and count on the XSTACK as integers with fildes in RIA_A. The OS has direct access to XRAM so internally it will use something like &XRAM[buf]. You will need to use RIA_RW0 or RIA_RW1 to access this memory from the 6502.

These operations stand out for their high performance and ability to run in the background while the 6502 does other work. Expect close to 64 KB/sec, meaning a game level’s worth of assets loads in under a second.

Bulk XRAM operations are why the Picocomputer 6502 was designed without paged memory.

Application Programmer Interface

See also

FatFs documentation — many of the filesystem functions below are thin wrappers around FatFs.

Much of this API is based on POSIX and FatFs. In particular, filesystem and console access should feel extremely familiar. However, some operations will have a different argument order or data structures 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 f_lseek() described below has reordered arguments that are optimized for short stacking the long argument. But you don’t have to call f_lseek() from C, you can call the usual lseek() which has the traditional argument order.

The OS is built around FAT filesystems, the de facto standard for unsecured USB storage devices. POSIX filesystems are not fully compatible with FAT but there is a solid intersection of basic IO that is 100% compatible. You will see some familiar POSIX functions like open() and others like f_stat() which are similar to the POSIX function but tailored to FAT. Should it ever become necessary to have a POSIX stat(), it can be implemented in the C standard library or in an application by translating f_stat() data.

ZXSTACK

void zxstack(void);

Abandon the xstack by resetting the xstack pointer. This is the only operation that doesn’t require waiting for completion. You do not need to call this for failed operations. It can be useful if you want to quickly ignore part of a returned structure.

Op code:

RIA_OP_ZXSTACK 0x00

C proto:

rp6502.h

XREG

int xreg(char device, char channel, unsigned char address, ...);
int xregn(char device, char channel, unsigned char address, unsigned count, ...);

Using xreg() from C is preferred to avoid making a counting error. Count doesn’t need to be sent in the ABI so both prototypes are correct.

The variadic argument is a list of ints to be stored in extended registers starting at address on the specified device and channel. See the RP6502-RIA and RP6502-VGA documentation for what each register does. Setting extended registers can fail, which you can use for feature detection. EINVAL means the device responded with a negative acknowledgement. EIO means there was a timeout waiting for ack/nak.

This is how you add virtual hardware to extended RAM. Both the RP6502-RIA and RP6502-VGA have a selection of virtual devices you can install. You can also make your own hardware for the PIX bus and configure it with this call.

Op code:

RIA_OP_XREG 0x01

C proto:

rp6502.h

Parameters:
  • device – PIX device ID. 0:RIA, 1:VGA, 2-6:unassigned

  • channel – PIX channel. 0-15

  • address – PIX address. 0-255

  • ... – 16 bit integers to set starting at address.

A regs:

return

Errno:

EINVAL, EIO

ARGV

int _argv(char *argv, int size)

The virtual _argv is called by C initialization to provide argc and argv for main(). It returns an array of zero terminated string indexes followed by the strings. e.g. [“ABC”, “DEF”] is 06 00 0A 00 00 00 41 42 43 00 44 45 46 00 The returned data is guaranteed to be valid.

Because this can use up to 512 bytes of RAM you must opt-in by providing storage for the argv data. You may use static memory, or dynamically allocated memory which can be freed after use. You may also reject an oversized argv by returning NULL.

void *argv_mem(size_t size) { return malloc(size); }
Op code:

RIA_OP_ARGV 0x08

C proto:

(none)

Returns:

Size of argv data

Errno:

will not fail

EXEC

int ria_execl(const char *path, ...)
int ria_execv(const char *path, char *const argv[])
int _exec(const char *argv, int size)

The virtual _exec is called by ria_execl() and ria_execv(). Be aware of the one difference from the execl() and execv() you may be used to. Because RAM is precious, the path is only supplied once, not again in argv[0]. The launched ROM will see argv[0] as the filename.

The data sent by _exec() will be checked for pointer safety and sanity, but will assume the path points to a loadable ROM file. If EINVAL is returned, the argv buffer is cleared so further attempts to _argv() will return an empty set. If the ROM is invalid, the user will be left on the console with an error message.

Op code:

RIA_OP_EXEC 0x09

C proto:

rp6502.h

Returns:

Does not return on success — the new ROM begins executing. -1 on error.

Errno:

EINVAL

ATTR_GET

long ria_attr_get(unsigned char id)

Returns the current value of a RIA attribute. See RIA Attributes for attribute IDs and descriptions.

Op code:

RIA_OP_ATTR_GET 0x0A

C proto:

rp6502.h

Parameters:
  • id – Attribute ID. One of the RIA_ATTR_* constants.

A regs:

id

Returns:

The attribute value as a 31-bit integer. -1 on error.

Errno:

EINVAL

ATTR_SET

int ria_attr_set(long val, unsigned char id)

Sets the value of a RIA attribute. See RIA Attributes for attribute IDs and descriptions.

Op code:

RIA_OP_ATTR_SET 0x0B

C proto:

rp6502.h

Parameters:
  • id – Attribute ID. One of the RIA_ATTR_* constants.

  • val – New value.

A regs:

id

Returns:

0 on success

Errno:

EINVAL

CLOCK

unsigned long clock(void)

Obtain the value of a monotonic clock that updates 100 times per second. Wraps approximately every 497 days.

Op code:

RIA_OP_CLOCK 0x0F

C proto:

time.h

Returns:

1/100 second monotonic clock

Errno:

will not fail

CLOCK_GETRES

int clock_getres(clockid_t clock_id, struct timespec *res)
struct timespec {
   uint32_t tv_sec; /* seconds */
   int32_t tv_nsec; /* nanoseconds */
};

Obtains the clock resolution.

Op code:

RIA_OP_CLOCK_GETRES 0x10

C proto:

time.h

Parameters:
  • clock_id – 0 for CLOCK_REALTIME.

Returns:

0 on success. -1 on error.

A regs:

return, clock_id

Errno:

EINVAL

TZSET

void tzset(void);
int _tzset(struct _tzset *tz)
struct _tzset
{
   int8_t daylight;  /* non 0 if daylight savings time active */
   int32_t timezone; /* Number of seconds behind UTC */
   char tzname[5];   /* Name of timezone, e.g. CET */
   char dstname[5];  /* Name when daylight true, e.g. CEST */
};

The virtual _tzset() is called internally by tzset(). Use help set tz on the console monitor to learn about configuring your time zone.

Op code:

RIA_OP_TZSET 0x0D

C proto:

time.h

Returns:

0 on success. -1 on error.

Errno:

EINVAL

TZQUERY

struct tm *localtime(const time_t *timep);
int _tzquery(uint32_t time, struct _tzquery *dst)
struct _tzquery
{
   int8_t daylight;  /* non 0 if daylight savings time active */
};

The virtual _tzquery() is called internally by localtime().

Op code:

RIA_OP_TZQUERY 0x0E

C proto:

time.h

Returns:

Seconds to add to UTC for localtime.

Errno:

will not fail

CLOCK_GETTIME

int clock_gettime(clockid_t clock_id, struct timespec *tp)

Obtains the current time.

Op code:

RIA_OP_CLOCK_GETTIME 0x11

C proto:

time.h

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.

Op code:

RIA_OP_CLOCK_SETTIME 0x12

C proto:

time.h

Parameters:
  • clock_id – 0 for CLOCK_REALTIME.

Returns:

0 on success. -1 on error.

A regs:

return, clock_id

Errno:

EINVAL, EUNKNOWN

OPEN

int open(const char *path, int oflag)

Create a connection between a file and a file descriptor. Up to 8 files may be open at once.

Op code:

RIA_OP_OPEN 0x14

C proto:

fcntl.h

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 0x01
Open for reading only.
O_WRONLY 0x02
Open for writing only.
O_RDWR 0x03
Open for reading and writing.
O_CREAT 0x10
Create the file if it does not exist.
O_TRUNC 0x20
Truncate the file length to 0 after opening.
O_APPEND 0x40
Read/write pointer is set end of the file.
O_EXCL 0x80
If O_CREAT and O_EXCL are set, fail if the file exists.

CLOSE

int close(int fildes)

Finish pending writes and release the file descriptor. File descriptor will rejoin the pool available for use by open().

Op code:

RIA_OP_CLOSE 0x15

C proto:

fcntl.h

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. This is implemented in the compiler library as a series of calls to READ_XSTACK.

Op code:

None

C proto:

unistd.h

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.

Op code:

RIA_OP_READ_XSTACK 0x16

C proto:

rp6502.h

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.

Op code:

RIA_OP_READ_XRAM 0x17

C proto:

rp6502.h

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. This is implemented in the compiler library as a series of calls to WRITE_XSTACK.

Op code:

None

C proto:

unistd.h

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.

Op code:

RIA_OP_WRITE_XSTACK 0x18

C proto:

rp6502.h

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.

Op code:

RIA_OP_WRITE_XRAM 0x19

C proto:

rp6502.h

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

static long f_lseek(long offset, char whence, int fildes)
off_t lseek(int fildes, off_t offset, int whence)

Move the read/write pointer. The OS uses the ABI format of f_seek(). An lseek() compatible wrapper is provided with the compiler library.

This can also be used to obtain the current read/write position with f_lseek(0, SEEK_CUR, fd).

Op code:

See table below.

C proto:

f_lseek: rp6502.h, lseek: unistd.h

Parameters:
  • offset – How far you wish to seek.

  • whence – From whence you wish to seek. See table below.

  • 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

RIA_OP_LSEEK_LLVM

RIA_OP_LSEEK_CC65

RIA_OP_LSEEK

0x1D

0x1A

SEEK_SET

0

2

SEEK_CUR

1

0

SEEK_END

2

1

RENAME

int rename(const char *oldname, const char *newname)

Renames and/or moves a file or directory.

Op code:

RIA_OP_RENAME 0x1C

C proto:

stdio.h

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

SYNCFS

int syncfs(int fildes)

Finish pending writes for the file descriptor.

Op code:

RIA_OP_SYNCFS 0x1E

C proto:

unistd.h

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

STAT

int f_stat(const char *path, f_stat_t *dirent)
typedef struct {
   unsigned long fsize;
   unsigned fdate;
   unsigned ftime;
   unsigned crdate;
   unsigned crtime;
   unsigned char fattrib;
   char altname[12 + 1];
   char fname[255 + 1];
} f_stat_t;

Returns file or directory info for requested path. See the FatFs documentation for details about the data structure.

Op code:

RIA_OP_STAT 0x1F

C proto:

rp6502.h

Parameters:
  • path – Pathname to a directory entry.

  • dirent – Returned f_stat_t data.

Returns:

0 on success. -1 on error.

A regs:

return, dirent

Errno:

EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_NO_FILE, FR_NO_PATH, FR_INVALID_NAME, FR_INVALID_DRIVE, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_TIMEOUT, FR_NOT_ENOUGH_CORE

OPENDIR

int f_opendir(const char *name)

Create a connection between a directory and a directory descriptor. Up to 8 directories may be open at once.

Op code:

RIA_OP_OPENDIR 0x20

C proto:

rp6502.h

Parameters:
  • name – Pathname to a directory.

Returns:

Directory descriptor. -1 on error.

A regs:

return

Errno:

EINVAL, EMFILE, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_NO_PATH, FR_INVALID_NAME, FR_INVALID_OBJECT, FR_INVALID_DRIVE, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_TIMEOUT, FR_NOT_ENOUGH_CORE, FR_TOO_MANY_OPEN_FILES

READDIR

int f_readdir(f_stat_t *dirent, int dirdes)

Returns directory entry info for the current read position of a directory descriptor, then advances the read position.

Op code:

RIA_OP_READDIR 0x21

C proto:

rp6502.h

Parameters:
  • dirdes – Directory descriptor from f_opendir().

  • dirent – Returned f_stat_t data.

Returns:

0 on success. -1 on error.

A regs:

return, dirent

Errno:

EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_INVALID_OBJECT, FR_TIMEOUT, FR_NOT_ENOUGH_CORE

CLOSEDIR

int f_closedir(int dirdes)

Release the directory descriptor. Directory descriptor will rejoin the pool available for use by f_opendir().

Op code:

RIA_OP_CLOSEDIR 0x22

C proto:

rp6502.h

Parameters:
  • dirdes – Directory descriptor from f_opendir().

Returns:

0 on success. -1 on error.

A regs:

return, dirdes

Errno:

EINVAL, FR_INT_ERR, FR_INVALID_OBJECT, FR_TIMEOUT

TELLDIR

long f_telldir(int dirdes)

Returns the read position of the directory descriptor.

Op code:

RIA_OP_TELLDIR 0x23

C proto:

rp6502.h

Parameters:
  • dirdes – Directory descriptor from f_opendir().

Returns:

Read position. -1 on error.

A regs:

dirdes

Errno:

EINVAL, EBADF

SEEKDIR

int f_seekdir(long offs, int dirdes)

Set the read position for the directory descriptor. Internally, the FatFs directory read position can only move forward by one, so use this for convenience, not performance.

Op code:

RIA_OP_SEEKDIR 0x24

C proto:

rp6502.h

Parameters:
  • dirdes – Directory descriptor from f_opendir().

Returns:

Read position. -1 on error.

A regs:

return, dirdes

Errno:

EINVAL, EBADF, FR_DISK_ERR, FR_INT_ERR, FR_INVALID_OBJECT, FR_TIMEOUT, FR_NOT_ENOUGH_CORE

REWINDDIR

int f_rewinddir(int dirdes)

Rewind the read position of the directory descriptor.

Op code:

RIA_OP_REWINDDIR 0x25

C proto:

rp6502.h

Parameters:
  • dirdes – Directory descriptor from f_opendir().

Returns:

0 on success. -1 on error.

A regs:

dirdes

Errno:

EINVAL, EBADF, FR_DISK_ERR, FR_INT_ERR, FR_INVALID_OBJECT, FR_TIMEOUT, FR_NOT_ENOUGH_CORE

CHMOD

int f_chmod(const char *path, unsigned char attr, unsigned char mask)

Change the attributes of a file or directory.

Op code:

RIA_OP_CHMOD 0x26

C proto:

rp6502.h

Parameters:
  • path – Pathname to a file or directory.

  • attr – New bitfield of attributes. See table.

  • mask – Only attributes with bits set here will be changed.

Returns:

0 on success. -1 on error.

A regs:

return, mask

Errno:

EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_NO_FILE, FR_NO_PATH, FR_INVALID_NAME, FR_WRITE_PROTECTED, FR_INVALID_DRIVE, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_TIMEOUT, FR_NOT_ENOUGH_CORE

Attribute

Bit

Read Only

0x01

Hidden

0x02

System

0x04

Directory

0x10

Archive

0x20

UTIME

int f_utime(const char *path, unsigned fdate, unsigned ftime, unsigned crdate, unsigned crtime)

Update the date and time stamps of a file or directory. A date of 0 (invalid) leaves the date and time unchanged.

Op code:

RIA_OP_UTIME 0x27

C proto:

rp6502.h

Parameters:
  • path – Pathname to a file or directory.

  • fdate – Modification date.

  • ftime – Modification time.

  • crdate – Creation date.

  • crtime – Creation time.

Returns:

0 on success. -1 on error.

A regs:

return, mask

Errno:

EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_NO_FILE, FR_NO_PATH, FR_INVALID_NAME, FR_WRITE_PROTECTED, FR_INVALID_DRIVE, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_TIMEOUT, FR_NOT_ENOUGH_CORE

Date

bit15:9

Years since 1980 (0..127)

bit8:5

Month (1..12)

bit4:0

Day (1..31)

Time

bit15:11

Hour (0..23)

bit10:5

Minute (0..59)

bit4:0

Second / 2 (0..29)

MKDIR

int f_mkdir(const char *name)

Make a new directory entry.

Op code:

RIA_OP_MKDIR 0x28

C proto:

rp6502.h

Parameters:
  • name – Pathname of the directory to create.

Returns:

0 on success. -1 on error.

A regs:

return

Errno:

FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_NO_PATH, FR_INVALID_NAME, FR_DENIED, FR_EXIST, FR_WRITE_PROTECTED, FR_INVALID_DRIVE, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_TIMEOUT, FR_NOT_ENOUGH_CORE

CHDIR

int chdir(const char *name)

Change to a directory entry.

Op code:

RIA_OP_CHDIR 0x29

C proto:

unistd.h

Parameters:
  • name – Pathname of the directory to make current.

Returns:

0 on success. -1 on error.

A regs:

return

Errno:

FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_NO_PATH, FR_INVALID_NAME, FR_INVALID_DRIVE, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_TIMEOUT, FR_NOT_ENOUGH_CORE

CHDRIVE

int f_chdrive(const char *name)

Change the current drive. Valid names are USB0:USB9: with shortcuts 0:9:.

Op code:

RIA_OP_CHDRIVE 0x2A

C proto:

rp6502.h

Parameters:
  • name – Drive name to change to.

Returns:

0 on success. -1 on error.

A regs:

return

Errno:

FR_INVALID_DRIVE

GETCWD

int f_getcwd(char *name, int size)

Get the current working directory. Size is ignored by the OS but the C wrapper will use it.

Op code:

RIA_OP_GETCWD 0x2B

C proto:

rp6502.h

Parameters:
  • name – The returned directory.

Returns:

Size of returned name. -1 on error.

Errno:

ENOMEM, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_TIMEOUT, FR_NOT_ENOUGH_CORE

SETLABEL

int f_setlabel(const char *name)

Change the volume label. Max 11 characters.

Op code:

RIA_OP_SETLABEL 0x2C

C proto:

rp6502.h

Parameters:
  • name – Label with optional volume name.

Returns:

0 on success. -1 on error.

A regs:

return

Errno:

EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_INVALID_NAME, FR_WRITE_PROTECTED, FR_INVALID_DRIVE, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_TIMEOUT

GETLABEL

int f_getlabel(const char *path, char *label)

Get the volume label. Label must have room for (22+1) bytes.

Op code:

RIA_OP_GETLABEL 0x2D

C proto:

rp6502.h

Parameters:
  • name – Volume name.

  • label – Storage for returned label.

Returns:

Size of returned label. -1 on error.

A regs:

return

Errno:

EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_INVALID_DRIVE, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_TIMEOUT

GETFREE

int f_getfree(const char *name, unsigned long *free, unsigned long *total)
struct {
   unsigned long free;
   unsigned long total;
};

Get the volume free and total space in number of 512 bytes blocks.

Op code:

RIA_OP_GETFREE 0x2E

C proto:

rp6502.h

Parameters:
  • name – Volume name.

  • free – Storage for returned value.

  • total – Storage for returned value.

Returns:

0 on success. -1 on error.

A regs:

return

Errno:

EINVAL, FR_DISK_ERR, FR_INT_ERR, FR_NOT_READY, FR_INVALID_DRIVE, FR_NOT_ENABLED, FR_NO_FILESYSTEM, FR_TIMEOUT

EXIT

void exit(int status)

Halt the 6502 and return the console to RP6502 monitor control. This is the only operation that does not return. The OS pulls RESB low before the next instruction can execute. The status argument is currently unused but reserved for future use.

In general, dropping the user back to the monitor is discouraged. But calling exit() or falling off main() is preferred to locking up.

Op code:

RIA_OP_EXIT 0xFF

C proto:

stdlib.h

A regs:

status

Parameters:
  • status – 0 is success, 1-255 for error.

Launcher

The launcher is a mechanism in the RP6502 process manager that lets one ROM serve as a persistent host for all others. A ROM registers itself as the launcher by setting RIA_ATTR_LAUNCHER to 1 via ria_attr_set(). Once registered, the process manager automatically re-executes the launcher ROM whenever any subsequently launched ROM stops. When the launcher ROM itself stops, the chain ends, the registration is cleared, and control returns to the console monitor. A console break (Ctrl-Alt-Del) unconditionally clears the registration at any time.

The launcher ROM decides what to run next by calling EXEC and may pass arguments to the new ROM via argv. The launched ROM retrieves those arguments with ARGV.

ROM Cartridge Menu

The most straightforward use of the launcher is a menu-driven ROM selector, analogous to inserting a physical cartridge into a retro console. The launcher ROM scans the storage device for .rp6502 files, presents a list to the user, and calls EXEC with the chosen filename. When that ROM stops — whether normally or due to an error — the process manager automatically re-executes the launcher, returning the user to the selection menu.

No manual reset is needed between runs. Each ROM is a self-contained binary that knows nothing about the menu system. The launcher may supply context through argv — for example, a save-file path or difficulty setting — and the ROM simply calls EXIT when it is done.

Native OS Boot Sequence

A more powerful use is as the foundation for a native 6502 operating system. A small launcher ROM is installed to the RIA as the boot ROM using the set boot command in the monitor. When the Picocomputer powers on or reboots, the process manager loads this launcher ROM automatically.

The launcher ROM reads its own argv — supplied by any arguments appended to the set boot command — then searches the mounted drive for the OS ROM and calls EXEC to launch it, forwarding any relevant arguments. By design, the OS cannot alter the launcher ROM; OS launchers are meant to stay simple and trustworthy. This indirection provides important capabilities:

  • Fault recovery — If the OS kernel encounters a fatal error it cannot handle internally, it calls EXIT rather than locking up. The process manager re-executes the launcher, which can choose to relaunch the kernel or take some other action.

  • Self-update — An OS can prepare its own ROM update and call EXIT. The launcher detects the pending update, applies it, and boots the new OS ROM — achieving an in-place update without requiring a manual reset.

RIA Attributes

RIA attributes are 31-bit values identified by an 8-bit ID. They are accessed with ria_attr_get() and ria_attr_set(). Both functions succeed for any valid attribute ID. Attempting to get or set an unknown ID returns -1 with errno set to EINVAL. Attempting to set a get-only attribute also returns -1 with EINVAL.

ID / Name

Description

0x00
RIA_ATTR_ERRNO_OPT

Errno mapping option. Selects which set of errno constants the OS uses. Both cc65 and llvm-mos set this automatically at C runtime startup; assembly programs must set it before making OS calls that can fail. See ERRNO_OPT Compiler Constants for option values.

0x01
RIA_ATTR_PHI2_KHZ

CPU clock speed in kHz. Range 100–8000. Changes take effect immediately and revert to the system setting when the ROM stops.

0x02
RIA_ATTR_CODE_PAGE

Active OEM code page used by the filesystem, console, and default VGA font. Reverts to the system setting when the ROM stops. If the requested page is unavailable, the system setting is selected; follow a set with a get to confirm the result. One of: 437, 720, 737, 771, 775, 850, 852, 855, 857, 860, 861, 862, 863, 864, 865, 866, 869, 932, 936, 949, 950.

0x03
RIA_ATTR_RLN_LENGTH

Maximum input line length for the stdin line editor. 1–255, default 254.

0x04
RIA_ATTR_LRAND

31-bit hardware random number seeded with entropy from the RIA. Returns a value in the range 0x0 to 0x7FFFFFFF. Suitable for seeding a PRNG or direct use. The 16-bit rand() in the cc65 library can be seeded with this by calling _randomize().

0x05
RIA_ATTR_BEL

BEL (\a) output enable on the console UART. 0 silences the alert; 1 (default) enables it.

0x06
RIA_ATTR_LAUNCHER

Launcher flag. Set to 1 to register the current ROM as the launcher; set to 0 to deregister. See the Launcher section for full details and usage patterns.

ERRNO_OPT Compiler Constants

OS calls will set RIA_ERRNO when an error occurs. The errno option selects which numeric values to use because cc65 and llvm-mos each define their own errno constants. Both compilers set this automatically in their C runtime. Assembly programs must set RIA_ATTR_ERRNO_OPT before any OS call that can fail. errno in C maps directly to RIA_ERRNO.

The OS maps FatFs errors onto errno. RP6502 emulation and simulation software is expected to map their native errors as well. The table below shows the FatFs mappings. Because FatFs is so integral to the OS, calls are documented here with their native FatFs errors to assist when cross referencing the FatFs documentation.

cc65

llvm_mos

FatFs

option

1

2

ENOENT

1

2

FR_NO_FILE, FR_NO_PATH

ENOMEM

2

12

FR_NOT_ENOUGH_CORE

EACCES

3

23

FR_DENIED, FR_WRITE_PROTECTED

ENODEV

4

19

FR_NOT_READY, FR_INVALID_DRIVE, FR_NOT_ENABLED, FR_NO_FILESYSTEM

EMFILE

5

24

FR_TOO_MANY_OPEN_FILES

EBUSY

6

16

FR_LOCKED

EINVAL

7

22

FR_INVALID_NAME, FR_INVALID_PARAMETER

ENOSPC

8

28

EEXIST

9

17

FR_EXIST

EAGAIN

10

11

FR_TIMEOUT

EIO

11

5

FR_DISK_ERR, FR_INT_ERR, FR_MKFS_ABORTED

EINTR

12

4

ENOSYS

13

38

ESPIPE

14

29

ERANGE

15

34

EBADF

16

9

FR_INVALID_OBJECT

ENOEXEC

17

8

EDOM

18

33

EILSEQ

18

84

EUNKNOWN

18

85