Classic Computer Magazine Archive COMPUTE! ISSUE 9 / FEBRUARY 1981 / PAGE 88

A Small Operating System: OS65D The Kernel

Part 2 of 3

Tom R. Berger
School of Math
University of Minnesota
Minneapolis, MN

Subroutine Descriptions

Table 3 is a short memory map of the kernel. In this section we examine some of the subroutines in the map in more detail because they are either useful or interesting. The operating system input/output section will be discussed in some detail in another article, however, three subroutine addresses are vital for understanding the kernel subroutines. These are listed below.

$2339 Input a character without echo to output.
$2340 Input a character with echo to output.
$2343 Output a character.

Input and output for these subroutines is set by the I/O command.

Most programs are greatly enhanced if they can: (1) give instructions or state questions for users; (2) receive replies or input from users; and (3) convert ASCII hex input to binary and vice versa. The kernel contains subroutines to perform these functions. Below are some of the useful routines in the kernel.

Carriage return, line feed ($2D6A)

This routine sends a carriage return followed by a line feed to the output ($2343). It preserves the X-and Y- registers and uses no Z-page locations.

Output a string of embedded text ($2D73)

Assume we have the code listed below.

XX00 20732D JSR $2D73
XX03 484921 HI!
XX06 00
XX07 A200 LDX #$00

Suppose this segment of code is embedded in our machine language program and the computer is executing instructions just prior to address $XX00. When $XX00 is encountered, the computer jumps to the kernel subroutine at $2D73. This subroutine treats every byte from $XX03 onward as ASCII text to be sent as output ($2343) until the next $00 is encountered. The code above sends the message 'HI!'. Once output is stopped with a $00 (in this case at address $XX06), control is returned to the main program at the next address (in this case $XX07) where execution continues.

Both the Y-register and the Accumulator are destroyed by this routine, but the X-register remains intact. Z-page locations $E3 and $E4 point to the address (low byte-high byte) before the beginning of the embedded text ($XX02 in the example above). Thus, up to 254 characters may be sent out by this routine. More characters may be sent by repeatedly calling the subroutine.

Line buffer input ($2C98)

The buffer is in $2E1E to $2E2F. The subroutine begins with a carriage return ($0D) and line feed ($0A). Further, a carriage return terminates input and is stored in the buffer. Therefore, the user may input up to 17 additional characters in the buffer. Backarrow ($5F) is the standard erase character used by OSI so that from the polled keyboard (shift-locked) Shift-O erases a character. If you disassemble this subroutine you will see a clever use of the routine $2D73. It is used to output backspaces and spaces in order to erase characters on output. Input is obtained via the subroutine $2340 and subroutine $2D6A is called to send out a carriage return followed by a line feed.

This program destroys all registers. It uses only Z-page locations via $2D73. At $2C9B it resets the line buffer output terminator at $2CED.

Line buffer output ($2CE4)

Each time this routine is called, it returns the next character in the line buffer in the Accumulator. The line buffer pointer ($2CE5) is the operand of an LDY #NN instruction at $2CE4. Locations $E1 and $E2 in Z-page point to the beginning of the line buffer and the Y-register is used to index the buffer. After the seventeenth character the buffer will return a carriage return in the Accumulator. The subroutine leaves only the X-register intact.

ASCII hex to binary nibble ($2D3D,$2D40)

If entered at $2D3D, this routine will read the next buffer character ($2CE4), or you may enter the subroutine at $2D40 with an ASCII hex digit in the Accumulator. It will return with a binary number (0-15) in the first four bits of the Accumulator and 0's in the upper four bits. If entered at $2D40, it uses no Z-page locations and leaves the X- and Y-registers intact, provided there is no error. If something other than an ASCII hex digit is read, subroutine $2CA4 is called to output an error Number 7 (Syntax Error). Further, return will occur to the controlling software system via the link set in the jump at $2A4E.

A much more useful routine which does the same thing occurs in the ROM machine monitor at $FE93. This latter routine is entered with the hex digit in the Accumulator. It returns with the same data as before except in the case of an error, where $80 is returned in the Accumulator. The ROM subroutine leaves the X- and Y-registers unchanged and uses no Z-page locations.

Full byte binary buffer read ($2D2E)

This routine reads two hex digits from the line buffer and returns with a binary byte in the Accumulator. It calls $2D3D and therefore, has the error procedure of that routine. It uses $E0 as a temporary storage location and affects other registers via subroutine $2CE4.

Full binary address read ($2D23)

By calling $2D2E twice, this subroutine reads four hex digits from the line buffer and stores them as a two byte binary address in Z-page locations $FE and $FF (low byte-high byte).

Nibble to hex digit ($2D9B)

This subroutine converts the first four bits in the Accumulator into an ASCII hex digit and outputs this digit via $2343. It returns with the hex digit in the Accumulator, uses no Z-page addresses, and leaves the X- and Y-registers the same.

One byte binary to two hex digits ($2D92)

By calling $2D9B twice, this routine outputs via $2343 the contents of one full byte binary (in the Accumulator) as an ASCII hex two digit number. It preserves the X- and Y-registers and uses no Z-page locations. The Accumulator is destroyed.

Error output ($2AC4)

If called, this subroutine will reset the 10 flags to the default value, it will disengage the disk head, and it will output "Error # N" where N is a hex digit equal to the first four bits in the Accumulator. Presumably, since an error has occurred, it does not matter which registers have changed.

Stack and Z-page swapper ($2CF7)

This subroutine swaps locations $0000-$01FF (Z-page and the stack) for locations $2F79-$3278 respectively. It returns with the Accumulator and Y-register changed and the X-register equal to 0. When BASIC is resident, OS65D keeps a Z-page and stack separate from BASIC. When the Extended Monitor and Assembler are resident, OS65D and the Extended Monitor keep a Z-page and stack separate from the Assembler.

Shall we swap? ($2D50)

If the contents of $00 are zero the swapper is called, otherwise this subroutine returns with the contents of $00 in the Accumulator and no other changes. BASIC and the Assembler keep nonzero values in $00 while OS65D and the Extended Monitor keep 0 in $00. Thus software can recognize whether or not to swap Z-page and the stack.

Symbol checker ($2D58, $2D5B, $2D5E)

This subroutine reads the buffer to see if the next character is '=' ($2D58), ',' ($2D5B), or '/' ($2D5E). If an error occurs the routine behaves as ($2D3D) does, returning to system software control after error Number 7 (Syntax Error). It calls subroutine $2CE4 and uses Z-page location $E0 for temporary storage. This routine uses a standard programming trick of masking 2-byte OPcodes by using a 3-byte BIT instruction.

This concludes a description of the more useful subroutines in the kernel. Most routines are not difficult to decipher. A few have mildly complex flow. The three most involved are: $2A84, The command processor; $2C98, The line buffer input; and $2DA6, The DIRECTORY search. These subroutines are described via flowcharts in Figures 2 to 4. These flowcharts should make it possible to understand disassemblies of the corresponding subroutines.

2A4B Output an OS65D error # then return to linked software (link is via a jump at 2A4E).
2A51 OS65D Start-up address.
2A7D Set up the return to software address at 2A4E. Set to 20D7 at 20D1 in BA. Set to 1532 at 152C in ASM. Set to 1756 at 1F31 in in EM. Set to 2A51 at 2A54 in OS65D.
2A84 OS65D Command Processor: called by 2A51. Commands in a table at 2E30 -2E77.
2AC4 Error message. Enter with error # in accumulator. Resets I/O flags. Disengages disk head.
2ADE Command AS. Load Tracks 5, 6, and 7, then run the Assembler. Jumps to start at 1300.
2AE6 Command BA. Load Tracks 2, 3, and 4, then run BASIC. Jumps to start at 20E4.
2AEE Load from the disk the track numbers requested by a command routine starting at 0200 and continuing for 3 tracks.
2B11 Command CA. Call a track and sector from the disk to memory.
2B1A Engage head, read a sector to memory, then disengage the head.
2B23 Command D9. Disable error #9 in the disk routines. This routine is not called in my version of OS65D. It may be called by changing the address in the COMMAND DIRECTORY.
2B29 Command DI. Give a sector map of a track.
2B2F Command EM. Load Tracks 5, 6, and 7, then run Extended Monitor. Jumps to start at 1700.
2B37 Command EX. Load an entire disk track to memory for examination.
2B46 Command GO. Start a machine program at specified address.
2B55 Command IN. Initialize a track or the whole disk.
2B68 Text: 'ARE YOU SURE?'
2B83 Command IO. Change the I/O flags.
2BA7 Command LO. Load a named disk file to memory.
2BC6 Command ME. Sets the vectors for memory input and output.
2BDD Command PU. Puts named file on disk.
2BFD Command RE. Returns from OS65D to linked software. If software is not in memory, return set to 2AC0 for error #7 out. Settings as follows: ASM to 1303; EM to 1700; BA to 20C4; and M to FEFC (which jumps to FE00).
2C22 Command XQ. Load (starting at 3179) and execute (starting at 317E) a named program
2C28 Command SA. Save memory on a specified sector and track of the disk.
2C43 Command SE. Select a disk drive (A,B,C,D,).
2C60 Get the disk ready for a read or write on a given sector and track.
2C70 Buffer loader. Set the disk start vector to 3179. Engage the disk head.
2C83 Advance head one track. Check for the last track in a file. Report error #D if a read goes beyond the last track of the file.
2C98 Carriage return, line feed, then:
2C9B Enter and edit a line in the OS65D line buffer at 2E1E - 2E2F.
2CD3 Three empty bytes.
2CD6 Routes input to the Indirect File.
2CE4 Read a line from the OS65D line buffer software, one character at a time.
2CF7 Swapper routine. Switches 0-page and stack for 2F79 - 3178.
2D23 Read 4 ASCII hex digits from the buffer and convert to 2 bytes of binary. Store in FE, FF.
2D2E Read 2 ASCII hex digits from the buffer and convert to 1 byte binary in accumulator.
2D3D Read 1 ASCII hex digit from buffer and convert to 1/2 low byte binary in accumulator. Enter at 2D40 with digit in accumulator to skip buffer read.
2D50 Swapper flag check. Initialize for a return to BASIC after an error message. (See BA addresses 20D7 and 20C7).
2D58/2D5B/2D53 Check character to see if it is ' =', ',', or '/'. Three entry points. Two hidden by BIT instructions.
2D6A Carriage return and line feed.
2D73 Display embedded text. Display text from the JSR 2D73 instruction until the next null (00).
2D92 1 byte binary in accumulator is converted to 2 ASCII hex digits and displayed in order.
2D9B Low half byte binary in accumulator is converted to 1 ASCII hex digit and displayed.
2DA6 Directory search. The code from 2DA6-2E1D searches the DISK DIRECTORY to match a file name in the OS65D Buffer with one in the DIRECTORY. When a match is found, the track numbers of the file are saved: last track in 00E5; first track in the accumulator. If a track number (rather than a file name) is given then the track number is read from the line buffer. This routine is used by PU and LO to process the DISK DIRECTORY.
2E1E-2E2F OS65D Line buffer.
2E30-2E77 OS65D Command directory. 4 bytes per command. First two bytes = First two ASCII letters of Command. Second two bytes = Address of routine - 1.
2E79-2F78 DISK DIRECTORY buffer.
2F79-3078 Buffer for Swapper. Swapped 0-page and stack put here.
3179-317A Source file start address. (317F if no disk buffers, 3D7F for one buffer, and 497F for two buffers. Address as low byte - high byte.
317B-317C Source file end address. Address as low byte - high byte.
317B-317C Source file end address. Address as low byte - high byte.
317D Number of disk tracks needed to store source file.
317E Null (00).
2E30 AS DD 2A
2E34 BA E5 2A
2E38 CA 10 2B
2E3C D9 BF 2A
2E40 DI 28 2B
2E44 EM 2E 2B
2E48 EX 36 2B
2E4C GO 45 2B
2E50 HO 62 26
2E54 IN 54 2B
2E58 IO 82 2B
2E5C LO A6 2B
2E60 ME C5 2B
2E64 PU DC 2B
2E68 RE FC 2B
2E6C XQ 21 2C
2E70 SA 27 2C
2E74 SE 42 2C