Although most routines have been rewritten, many of these calls still
have the same parameters and behave the same way.
Also the PCBUF
is still used (unfortunately). This is the general
communications buffer needed for filesystem operation and some kernel
calls. It is a global buffer, and as such it is protected by the
SEM_SENDBUF
system semaphore. Each task that wants to use
has to allocate this semaphore with PSEM
before using the
buffer.
Also there still is no block oriented communication, although the stream based communication has been improved by the out-of-band error, brk and push/pull flags.
I try to keep this documentation as correct and up-to-date as possible,
but if in doubt, read the source... :-)
There are now some comments like "this need not be available in all OS implementations". These comments mostly refer to the embedded versions of the OS where the kernel can be shortened to (almost) 2k in size by leaving out some stuff not necessary for the particular application.
$f000 RESET System reset
$f003 ENMEM Enable 4k memory block, no in xr, for memory management - MMU version only $f006 SETBLK set memory block ac at MMU entry yr - returns old entry in ac. MMU version only
The Stream functions have not changed much. The only addition compared to kernel 1.x is the error code byte, that allows passing of error conditions and push/pull signals as "out-of-band" data.
In the embedded versions the stream functions are not necessarily implemented.
$f009 GETSTR Get a free stream. increase read and write task counter, returns stream number in x $f00c FRESTR decreases read and write task counter, thus freeing the stream, if both result in zero. parameter: stream number in x $f00f PUTC puts a byte on the stream. parameter: stream number in x, data byte in a. returns errorcode in a $f012 GETC Get a byte from the stream. parameter: stream number in x, returns data byte in a (with carry=0) or error code (carry=1) $f015 UNGETC Gives byte from stream back. parameter: stream number in x, data byte in a, returns error code in a $f018 STRCMD executes a stream command. parameter: stream number in x, stream command in a, returns error code in a. possible stream commands are: SC_GET 0 same as GETSTR SC_REG_RD 1 increase read task counter by one SC_REG_WR 2 increase write task counter by one SC_CLR 3 empty the stream SC_EOF 4 closing the stream from the sender side, i.e. decrease write task counter by one SC_NUL 5 closing the stream from the receiver side, i.e. decrease read task counter. SC_FRE 6 same as FRESTR SC_STAT 7 read stream status into ac SC_GANZ 8 returns number of bytes in stream in a SC_RWANZ 9 returns the stream write pointer in a and the stream read pointer in y SC_ESTAT 10 Get the error code byte in yr, and the normal (i.e. what you get with SC_STAT) stream state in ac. SC_SSTAT 11 Set bits in the error code byte each bit set in yr is set in error byte SC_CSTAT 12 Clear bits in the error code byte each bit set in yr is cleared in error byte Possible stream status are: E_NUL Noone reading from stream (i.e. read task counter is zero) E_EOF stream is empty and noone is writing anymore (i.e. write task counter is zero) E_SEMPTY stream buffer is empty E_SFULL stream buffer is full E_SLWM number of bytes below Low Water Mark (1/4 buffer size) E_SHWM number of bytes above Hogh Water Mark (3/4 buffer size) The error code byte has the following bits: SCE_PULL %10000000 reader pulls data from sender SCE_PUSH %01000000 writer pushes data to receiver SCE_BRK %00100000 writer sends 'break' signal SCE_RERRM %00001100 reader error condition mask SCE_WERRM %00000011 writer error condition maskThe error condition byte can be set and read from both ends. This byte is cleared when the stream is allocated. If the reader "pulls" data, this is a sign for the writer to flush all its internal buffers into the stream. When this is done, the writer clears the pull flag.
If the writer wants to push his data (like after an CR on a terminal, for example) the reader should get the number of bytes in the stream to have an inidcation of how many bytes to read as "pushed" (or urgent in Internet speak). Then it removes the push flag and gets the bytes from the stream
If an error occurs, e.g. a read error when reading a file, the writer sets all the bits in the error code byte masked by SCE_WERRM to 1 (which is general error code - other codes still have to be defined). The reader of the stream then reads this and can handle the error.
If an error on the reading side occurs (like disk full when writing a file), The reader sets all the error bits masked by SCE_RERRM to 1 (which is general error code - other codes still have to be defined). The writer can then close the file on his side.
If the writer wants to send a Break
(i.e. a ^C
equivalent)
code to the reader, it sets
the SCE_BRK
bit. The reader must clear it itself.
$f01b DEVCMD executes device commands. parameter: device number in x, device command in a, optional parameter in y. returns error code in a. possible stream commands are: DC_IRQ 0 execute IRQ routine the interrupt routine has to return E_OK if an interrupt source is cleared or E_NOIRQ if not. DC_RES 1 initialize device DC_GS 2 sets the stream (in y) the device reads from DC_PS 3 sets the stream (in y) the device writes to DC_RX_ON 4 switch on receive DC_TX_ON 5 switch off receive DC_RX_OFF 6 switch on send DC_TX_OFF 7 switch off send DC_SPD 8 set speed (in y), device dependend DC_HS 9 set handshake (in y), device dependend DC_ST 10 get device status, device dependend DC_EXIT 11 disable device, including IRQ sources DC_GNAM 16 get the name of a device, parameter: device number in x, returns name PCBUF ($0200) DC_GNUM 17 get number of a device from name, parameter: name in PCBUF, length of name in x, returns device number in x DC_REGDEV 18 register new device(s)With the registration of the device the system has to know what to do with it and where to put it in memory. Therefore you have to put the address of the following struct into x/y registers.
x/y -> 2 byte pointer to label1 JMP Start_of_1st_device Name_of_device,0 ... label1 2 byte pointer to label2 JMP Start_of_2nd_device Name_of_2nd_device,0 ... labeln $ffffWith this method it is possible to register several devices, as long as they are in one memory block, with one call. The devices are started with DC_RESET. In y they get a system feature byte. Currently only bit 7 is defined, where $00 is 1 MHz system clock and $80 is 2 MHz system clock.
$f01e FORK start a new task. parameter: yr = length of the following struct in PCBUF FORK_SIZE 0 The size of the needed memory in 256 byte blocks, from botton up FORK_SHARED 1 number of 256 byte blocks that share the mapping with the current task, from top down. FORK_PRIORITY 2 priority of new task (0=inherit from parent) FORK_STDIN 3 stdin stream FORK_STDOUT 4 stdout stream FORK_STDERR 5 stderr stream FORK_ADDR 6 start address of task FORK_NAME 8 name of the program, ended by a nullbyte. After that follows the command line that started the program. FORK returns the PID of the new task in xr.The commandline is put into the PCBUF of the starting task. The new task gets its own task id in x The stream read/write task counter are not changed. I.e. a thread that wants to use the end given to fork too has to increase the read/write counters itself.
The priority is a hack to allow to run tasks with different execution priorities. The priority counts the number of interruptions before a thread switch occurs. Default value is 3. A value of 0 lets the new task inherit the priority of the current task.
It is now somewhat difficult to really start a new task, as
the READ/WRITE routines to access another tasks memory
have been removed.
One standard procedure could be to write a boot routine
into the current PCBUF, after the command line, and
jump to this boot routine when the FORK parameters are
copied to the target PCBUF. The length parameter
to FORK must of course include this routine.
You could also use a routine in the ROM, which then
should be shared between the calling and the called
task. The boot routine can then load the executable.
For a stdlib with null-separated command line parameters,
they may also be included after the name, as long as the
length byte includes them too.
In fact, the FORK_NAME entry is somewhat arbitrary at this time, as it is not used within the kernel. In fact, one could write anything there. But to keep compatible, the name and command line must have the same format as for the ROM startup, i.e. a nullbyte separated list of program name and parameter, with a zero-length parameter at the end.
The newly created task must release the SEM_SENDBUF semaphore for the PCBUF.
$f021 TERM <- a = return code ends the current thread. If still threads in the tasks are running, the return code is ignored. Otherwise the return code is sent to the parent with SIGCHLD. $f024 KILL <- a = return code, x = task id ends a complete task, with all threads in them. ac is return code, xr is task ID to kill. The return code is sent to the parent with SIGCHLD. $f027 YIELD just give control back to the scheduler, as there is nothing to do at the moment - but keep running (not needed for preemtive multitasking, an interrupt interrupts any task!) $f02a FORKT forks a new thread in the current task. Execution start address must be in a/y. returns c=0 for ok and new thread no in xr; or c=1 for error (errno in AC). $f02d SBRK (carry=1): sets the freely available RAM area in the active environment to ac blocks with 256 byte each, by adding memory to or removing memory from the end of the already available memory area. Returns in ac the length of the available memory in 256 byte blocks, if c=0 on return. Otherwise returns error. (carry=0): returns in ac the length of the available memory in 256 byte blocks. $f030 GETINFO getinfo returns process information about all (up to 16) running processes in the PCBUF (which must be allocated by the process before) May not be available in all OS implementations. There are ANZ_ENV valid entries in the table, a 0 in the TN_NTHREADS field indicates an empty slot. The fields are as follows: TN_PID 0 PID of process in this entry TN_NTHREADS 1 Number of threads for process TN_ENV 2 Environment number for process TN_PARENT 3 Parent process TN_MEM 4 amount of allocated RAM TN_SIGNAL 5 signal address TN_STDIN 7 STDIN stream TN_STDOUT 8 STDOUT stream TN_STDERR 9 STDERR stream TN_SIGMASK 10 signal mask value TN_NAME 11 start of prog name (if available) each field is TN_SLEN bytes long and the length of the name field is TNAMLEN. If the name is longer than TNAMLEN-1, the the last characters are cut off, and in the table the ending nullbyte is missing. $f033 DUP (carry=1): set new STD* stream. parameter: STD* number in x, new stream in a, returns old stream in ac; read/write task counters are not touched! (carry=0): get redirected STD* stream number. parameter: STD* number in x, returns stream in a
The semaphore calls need not be available in all OS implementations.
$f036 GETSEM gets a free semaphore. returns semaphore number in x $f039 FRESEM frees a semaphore. parameter: semaphore number in x negative (system) semaphores cannot be freed (nor are they returned by GETSEM) $f03c PSEM 'PSEM' operation on a given semaphore. task waits till semaphore is freed. parameter: semaphore number in x carry=0: block till semaphore is free carry=1: do a test&set operation and return with E_OK if semaphore gotten, or E_SEMSET if semaphore is in use. $f03f VSEM 'VSEM' operation on semaphore, allows other tasks to grab the semaphore. parameter: semaphore number in x
$f042 SEND send a message to another task parameter: optional message type in a, target task in x, length of data in PCBUF in y; y=0 means 256 byte in PCBUF The data is in PCBUF ($0200-$02ff). returns a and y as given and the 'redirected' target (e.g. from filesystem manager) in x $f045 RECEIVE receives a message. (carry=1): waits for any message (carry=0): returns immediately, with an error if no message received. Otherwise if a message is received, return sender task id in x, length of data in PCBUF in y (0 means 256) and the optional message type as given with SEND in a.
$f048 SETSIG sets the signal address and the signal mask (carry=1): sets signal address in a/y (carry=0): sets signal mask returns the old mask resp. the old signal address in the same registers. $f04b SENDSIG c=1: ac contains the signal mask to be sent to the task ID given in xr c=0: set thread to sleep until it receives a signalSignal handling works as follows: With SETSIG the signal address and the signal mask can be set. There is only one signal address, that is the address of a subroutine that is called on a signal. When the routine is called, it has the current pending signal mask in AC. The routine must return with a code sniplet
pla rtiwhich jumps directly to the code executed before the signal occured. Note that the x and y register must not be changed or must be preserved during the signal routine. The current thread execution is changed to run the signal handler and to immediately return with the code like above.
SENDSIG can be called from other tasks or from devices, not from a thread in the same task. When SENDSIG is called, first the signal mask is checked to see if the signal is allowed. If it is not, SENDSIG just returns. If the signal is allowed, threads in the receiving task can be in different states: Ready, Interrupted, or Waiting. If ready or interrupted, the thread is directly set up to execute the signal routine. If the thread is waiting, it depends on the INT bit in the signal mask what happens.
If the INT bit is set, then any system call that lets the thread block (i.e. wait for Semaphore, wait for Receive, wait for Send) will be interrupted. They then return E_INT in ac - after the signal routine has been called. The other registers are bogus after an interrupted system call.
If the INT bit is not set, then the signal is pending, and the signal routine is called when a thread of the task is set to running again.
Signal mask values are
SIG_INT %10000000 /* allow interruptable kernel calls */ SIG_CHLD %01000000 /* Child process has terminated */ SIG_TERM %00100000 /* Child process has terminated */ SIG_USR4 %00001000 /* user signal 4 - used for STDLIB */ SIG_USR3 %00000100 /* user signal 3 */ SIG_USR2 %00000010 /* user signal 2 */ SIG_USR1 %00000001 /* user signal 1 */When a SIG_CHLD is executed, the receiving task is notified that a child has died. This means it should call CHECKCHLD to see which child has died with what error code.
The SIG_TERM code should be used by "standard" libraries. Currently there is no way to inform a task about its death and let it clean up all resources - that are not bookkept by the kernel! - before it dies. So other libraries should, instead of calling "KILL", send a SIG_TERM signal when they want to kill a process. The signal routine of the receiving process should catch this signal and clean up and then call TERM itself.
SENDSIG with carry clear (wait for signal) must be called by threads only,
not by devices. Also the SIG_INT bit must be set before this call is done.
If there is only one thread in the task, then the thread is sleeping until
a signal is received. When a signal is received, the signal handler is
called, then the thread is awakened and returns with E_INT
.
The other registers are not preserved.
$f04e TDUP register a task for a (negative) system task number. parameter: (negative) task number to replace in x, new task number to be used instead in a $f051 XRECEIVE receives a message from a specified task only. parameter as with RECEIVE, plus sender task id in x $f054 SETNMI in systems without MMU, set the system NMI routine address. (carry=1): set the new NMI routine address (in a/y) returns the old NMI address in a/y. The old address is saved in the place pointed to by (newadress-4) (see below) by this routine. (carry=0): clear NMI routine. The NMI routine has to save all registers by itself! At NMI address -2 there must be the address of a ctrl routine that can be called via CTRLNMI. Expect that the ctrlnmi routine is called during this SETNMI with either NMI_ON of NMI_OFF in AC, to set the initial state. The ctrlnmi routines is called with JSR, i.e. return with RTS. At NMI address -4 is a pointer to a memory location where the address of the next NMI routine can be found. If the address found there is $ffff, then the list is at its end. A possible scenario then is: Task calls SETNMI with NEWNMI in a/y. We then have the structure: (NEWNMI-4) address of oldvec --> address of OLDNMI (NEWNMI-2) address of nmictrl NEWNMI --> NMI routine code This way the structure can be held in ROM, and still allow flexible NMI chaining. If CTRLNMI is called, each of the nmictrl routines is called with the given value in AC. $f057 CTRLNMI Send command in AC to all currently chained NMI ctrl routines as described with SETNMI. Possible values are: NMI_ON = allow NMIs NMI_OFF = disallow NMIs This routine is called when initiating and ending an IEC serial bus transfer, for example.
$f05a GETPID returns the current task ID in x and the current thread ID in y $f05d SLOCK c=1: locks scheduler to actual task. c=0: unlocks scheduler returns c=0 when ok, c=1 with E_NOTIMP if error. May not be available in all OS implementations. $f060 RENICE changes the priority of the current task. ac = value to add to the priority. returns ac = old priority. May not be available in all OS implementations. $f063 CHECKCHLD returns: c=0: returns a child process ID in x and child return code in a of a child process that has died. Only works if the SIGCHLD signal mask has been set when the child died. c=1: no child that has died found.
/* Hardware-Errors */ #define HE_ZP <-1 /* zeropage mem test */ #define HE_RAM <-2 /* RAM test (to few RAM) */ #define HE_ROM <-3 /* Couldn't start a program */ #define HE_DEV <-4 /* device returns error upon init */ #define HE_TASK <-5 /* all programs have terminated - reboot */possible error codes are (from oadef/oa1str.def, where most things, except filesystem stuff are defined) (These codes must be reordered):
/* Software-Errors */ #define E_OK 0 /* no error */ #define E_NOTIMP <-1 /* feature/function not implemented */ #define E_CONFIG <-2 /* in this configuration not available */ #define E_ILLPAR <-3 /* illegal parameter */ #define E_NOMEM <-4 /* no more memory */ /* stream handling errors */ #define E_NOSTR <-5 /* no more streams available */ #define E_SFULL <-6 /* stream is full */ #define E_SEMPTY <-7 /* stream is empty */ #define E_SLWM <-8 /* stream below low-water-mark (1/4) */ #define E_SHWM <-9 /* stream above high-water-mark (3/4) */ #define E_EOF <-10 /* last byte read, other side has closed */ #define E_NUL <-11 /* noone listening on stream */ /* device handling errors */ #define E_NODEV <-12 /* illegal device number */ #define E_DON <-13 /* device already in use */ #define E_DOFF <-14 /* device not in use */ #define E_NOTX <-15 /* device doesn't send */ /* misc errors */ #define E_NOENV <-16 /* no more free environment/task ID */ #define E_NOSEM <-17 /* no more free semaphore */ #define E_SEMSET <-18 /* semaphore is already set (with PSEM) */ #define E_NOIRQ <-19 /* irq routine has not removed irq source */ #define E_VERSION <-20 /* wrong (executable file) version */ #define E_NOTASK <-21 /* no more free task */ #define E_INT <-22 /* interrupted (by signal) system call */ #define E_ILLSIG <-23 /* illegal signal number */ #define E_TRYAGAIN <-24 /* try again */ /* file handling errors */ #define E_FNODRV <-32 /* illegal drive number */ #define E_FNOPATH <-33 /* wrong path */ #define E_FILLNAM <-34 /* illegal name (joker "*","?","\"") */ #define E_FNAMLEN <-35 /* name too long */ #define E_FNOFIL <-36 /* file not found */ #define E_FWPROT <-37 /* file write protected */ #define E_FILEXIST <-38 /* file exists */ #define E_FDISKFULL <-39 /* disk full */ #define E_FDNEMPTY <-40 /* subdirectory not empty when rmdir */ #define E_FLOCKED <-41 /* file locked */ #define E_FMEDIA <-42 /* media error */ #define E_FLOGICAL <-43 /* bad logical structure on media */ #define E_FINTERNAL <-44 /* internal error - should not happen! */ /* lib6502 errors */ #define E_ILLADDR <-64 /* illegal address for lib6502 mfree */ #define E_NOFILE <-65 /* illegal file number for lib6502 */ #define E_NOSEEK <-66 /* file not seekable */ #define E_NOREAD <-67 /* read on file would block */ #define E_NOWRITE <-68 /* write on file would block */ #define E_FVERSION <-69 /* file version number not supported */ #define E_LASTERR <-96 /* for customized error numbers */
During bootup, the kernel initializes the memory and sets up the standard system memory configuration. This includes mapping the system ROM from the top of memory down, and the system RAM is mapped from the lower end up.
When the system initialization is done, the ROM is searched for executable
files. The first two bytes of the ROM are taken as an address.
If this address is not $ffff
, then directly after the first two
bytes follows the program ROM structure described below.
After starting the first file, the address at the beginning is taken
as a pointer to the next file and so forth, until the kernel finds
a $ffff
.
The program ROM structure looks like:
P_KIND 0 /* byte, type of file */ P_ADDR 1 /* word, start address of execution */ P_RAM 3 /* byte, amount of RAM needed from lower end of memory in 256-byte blocks */ P_SHARED 4 /* byte, size of memory shared with parent counted from top of memory, in 256-byte blocks */ P_PRIORITY 5 /* byte, priority of the task to be started */ P_DEV 6 /* 2 byte, the first byte gives the device number that is opened for the stdin stream, the second gives the device number for stdout/stderr */ P_NAME 8 /* name of the program, ended by nullbyte. After the nullbyte is the commandline the program is started with. The parameter are separated by nullbytes. After the last parameter follow two nullbytes to indicate the end of the command line. */There are several program types available:
PK_PRG 0 /* simple program */ PK_DEV 1 /* device block */ PK_FS 2 /* filesystem */ PK_INIT 3 /* ROM init process */These file types have to be filled into the
P_KIND
field above.
In addition they can be or'd by the following flags.
PK_AUTOEXEC $80 /* autoexecution */ PK_RESTART $40 /* restart when died */When the
PK_AUTOSTART
bit is not set, the file is ignored by
the kernel.
The ROM only starts device blocks (PK_DEV
) and init processes
(PK_INIT
), because they are set up before the scheduler starts
and therefore the PCBUF
is (on non-MMU systems) not available.
I.e. init processes do not check their command line, and do not release
the PCBUF
.
Also PK_INIT
tasks get no stdin, stdout and stderr streams
and have to set them up themselves.
When starting device blocks, the address given in the
P_ADDR
field is given to the DEVCMD
command to register
the devices.
When using the special init process (init v1.0 from the sysapps directory),
the other entries are started by the init process.
PK_PRG
tasks are started with their stdin/out/err opened
to the devices given by the PK_DEV
field.
The name and command line are copied from the P_NAME
field
to the FORK_NAME
field for the fork.
PK_FS
are started as programs, but with the stdin/out/err
streams set to STDNUL
, which is ignored.
If the init process is configured for it, and the PK_RESTART
bit is set, it registers the task id of the started process and
restarts it when the task dies.
ROM tasks are started from the init
task and have no way to
determine which RAM locations are used by other tasks.
Therefore the variable allocation must be done when the ROM is
assembled. When using an MMU-based system, where each task has its
own memory environment there need not be much caution. But when
all tasks use the same environment (like in the C64) care has to be
taken not to use a memory location twice. Especially shared libraries
must use an own memory range, and they must be thread-save.
This allows for different threads of one task as well as threads
of different tasks to use the same library in one memory environment.
For each environment, task and thread the kernel saves two bytes that are guaranteed to be unique within the environment, task or thread respectively. The bytes at addresses 2 and 3 are set to zero when a memory environment is created and are unique to this environment. The bytes at addresses 4 and 5 are unique to the task and can be used for task-specific data, like a pointer to a task structure. The bytes at addresses 6 and 7 are unique to the thread and can be used for thread data.
The kernel is configurable in a wide range. Many options are available
for all versions, but also many options are architecture dependent.
The options are generally set in a global file that builds a
system ROM. Therefore, to not set the options in the kernel file
itself, the macro ROM
must be defined.
General options:
CMOSCPU /* have (rockwell) R65C02 */ NMIDEV /* allows handling of NMI in devices (only for non-MMU systems, untested) */ NMIRESET /* calls RESET when an NMI arrives */ STACKCOPY /* on non-MMU systems the stack can either been split up or, with this options, copied to a save area */Memory options:
RAMSIZE /* size of checked RAM during bootup, in 256-byte blocks */ MIN_MEM /* minimum memory size to boot */ RAMTEST /* if set, RAM is tested, if not RAMSIZE is taken to be the real RAM size */ BATMEM /* memory is not cleared but kept during memory test */ NOMIRRORMEM /* can be set if we _know_ that we do not have mirrored memory */ MEMINIVAL /* value with which to set the memory at boot. If not set, 0 */Some Architecture specific options:
NOSYSPORT /* The CS/A65 computer has a certain */ port where it can read the IRQ line for example. Not available if set */ ROMTEST /* on CS/A65 systems allow booting from a running OS/A65 (replacing it) */Kernel configuration options for various system calls are the following. The second half is mostly used to customize for embedded applications.
NEED_CHECKCHLD /* checkchld available if set */ NEED_GETINFO /* getinfo available if set */ NEED_RENICE /* renice available if set */ NEED_SLOCK /* slock available if set */ NO_STREAMS /* streams not available if set */ NO_FSM /* filesystem manager not avail. if set */ NO_SEM /* semaphores not available if set */ NO_SEND /* no send/receive/xreceive if set */ EOK_SEPARATE /* kernel is not at end of ROM - */ /* kernel/end.a65 is included extra. */ EOK_NOFILL /* don't fill between kernel and end of */ /* it. kinit _must_ copy the stuff from */ /* eok_addr to $fff* */
Porting has been made much easier with this kernel version.
In general, there is a proto
architecture subdirectory that holds
some prototype files for a non-MMU system. that can easily be changed to
fit own needs.
The kernel needs two files in the arch/*/kernel subdirectory, namely
kenv.a65
and kinit.a65
. The kinit.a65
file is included in the kernel startup code, i.e. after setting
the interrupt flag and clearing the decimal flag this file is
executed, and it is expected that the
CPU gets out of this file at the end of it.
This file must initialize the stack, the memory management and mapping of the
system ROM and RAM.
Then the system preemption timer must be set up.
When including kernel/zerotest.a65
and kernel/ramtest.a65
here
all the memory options from above are available.
Also the ramtest file returns the size of memory found in 256-byte blocks
in a. This value must be passed to the end of the init routine in this file.
In addition to this some Macros have to be defined, that are used in the kernel later.
GETFREQ() returns a=0 for 1Mhz, a=128 for 2MHz LEDPORT address of LED port to signal hardware failures LED_LED bit of LED bit in LEDPORT address H_ERROR() if LEDPORT is not defined, then H_ERROR replaces the LED toggle routine if needed. CLRTIMER() clear the system preemption timer interrupt KERNELIRQ() returns if an interrupt occured in the kernel or outside. Is checked by "BNE IRQ_in_Kernel" SAVEMEM() save system mem config for interrupt RESTOREMEM() restore system mem config after interrupt STARTMEM() This is used to alloc/enable memory pages found during kernel init. Takes mem size in AC as given by the kernel init. It is called _after_ inimem!For the most systems without MMU, most of these macros can be taken from the C64
kinit.a65
file.
If you have a more complicated mapping, you can have a look at the
CS/A65 kinit.a65
file.
The kenv.a65
file is included in another part of the kernel.
It is the part that maps the memory according to the environment
number for each task.
Several routines must be defined here:
inienv init environment handling setthread set active thread initsp init task for thread (no in xr) push push a byte to active threads stack pull pull a byte from active threads stack memtask jump to active thread memsys enter kernel getenv get e free environment freenv free an environmen sbrk reduce/increase process memory in an environment enmem enable memory blocks for environments (this kernel call is used for MMU systems and heavily system specific) setblk sets a specific memory block in the memory map of the active task. Also for MMU systems onlyAlso some macros must be defined:
MEMTASK2() This is equivalent to memtask above, but it is used for returns from interrupt MAPSYSBUF maps the PCBUF of the active task to the address given by SYSBUF in system memory SYSBUF mapped tasks PCBUF MAPENV() maps the address given in a/y in env x to somewhere in the kernel map, returns mapped address in a/y MAPAENV() as MAPENV, but does it for actual env. GETTASKMEM returns (in AC) how much memory a task has (task id in y) CPPCBUFRX copies PCBUF from other task (yr) to active one CPPCBUFTX copies PCBUF from active task to another one (yr) CPFORKBUF() copies the PCBUF from the FORK call to the task. GETACTENV() returns active environment in acThe full descriptions can be found in the C64 kenv file for example. This may look complicated, but it isn't. If you have a simple 6502 system without memory management, just copy (or set a link) to the C64 file. If you have a more complicated system, have a look at the CS/A65 file.
/* Terminal Commands */ #define TC_BEL 7 bell #define TC_BS 8 backspace #define TC_HT 9 horizontal tabulator #define TC_LF 10 line feed #define TC_VT 11 vertical tabulator #define TC_FF 12 form feed #define TC_CR 13 carriage return #define TC_ESC 27 Escape code #define TC_CLFT $80 cursor left #define TC_CRGT $81 cursor right #define TC_CUP $82 cursor up #define TC_CDWN $83 cursor down #define TC_HOME $84 cursor to the upper left edge #define TC_CLR $85 clear screen #define TC_DEL $86 delete char #define TC_INS $87 insert #define TC_WLO $88 set upper left window corner by cursor pos #define TC_WRU $89 set lower right window corner by cursor pos #define TC_WCLS $8a clear window #define TC_EOL $8b put cursor to the end of line #define TC_CLL $8c clear rest of line from cursorFORK, SETIRQ and TRESET can NO MORE be called via a SEND system call, when the receiver address is SEND_SYS and the message type is SP_* !!
/* SysProcCalls */ #define PCBUF $200STD* stream number are replaced by the numbers saved in the environment struct.
/* StdStream */ #define STDNUL $fc /* will be ignored (for FS STDIO)*/ #define STDIN $fd #define STDOUT $fe #define STDERR $ffReserved system environment numbers:
#define SEND_FM $fe /* filesystem manager */ #define SEND_ERROR $fd /* critical error handler */ #define SEND_TIME $fc /* set/get actual time */ #define SEND_NET $fb /* open network connections */