TRS-80 DOS - LDOS 5.3.1 (Model I) - KI/DVR Keyboard Driver Disassembly

Page Customization

Program Overview

KI/DVR is the DOS version 5 keyboard driver for the TRS-80 Model I, supplied with LDOS 5.3.1 (MISOSYS, 1991). It is not a SYS overlay but a device driver installed with the library command SET *KI TO KI/DVR (parms). Its four options are TYPE (type-ahead), JKL (screen print), DELAY=d and RATE=r (key-repeat timing). Once installed it establishes the CLEAR key as a control key for KSM, MiniDOS, LCOMM and the SPOOLer, and SHIFT-0 as a CAPS-lock toggle through KFLAG$ bit 5.

The file is a transient installer that runs at 5200H and relocates up to three resident modules into high memory: $KI, the keyboard scan, debounce, translation and key-repeat engine (always installed); $TA, the 128-character type-ahead ring buffer with its own interrupt task (only with TYPE); and $JKL, the screen-print filter (only with JKL). Each module is hooked in front of the previous keyboard driver through KIDCB$ at 4016H, or through KIJCL$ at 43BEH when a Job Control Language job holds the real driver address - the same cell SYS11/SYS saves and restores for code 9DH.

The image loads as one contiguous block, 5200H through 573CH (1341 bytes), transfer address 5200H, with no gaps. Unlike every other LDOS file examined, it carries no type-1FH copyright record ahead of the load records; the copyright string is inside the loaded image. The disassembly below is byte-verified: the memory image was rebuilt from the file's LDOS load records and every opcode and data byte matches (0 mismatches, full coverage). The message and table regions, which the .txt disassembler renders as bogus instructions, are decoded from the binary and shown as DEFB/DEFM/DEFW data.

File Format and Load Map

The file contains only load records - no copyright, module-name or directory records. Six type-01H load blocks fill 5200H-573CH (five full 256-byte pages and a 61-byte tail), followed by a type-02H transfer record to 5200H. The transient installer occupies 5200H-528BH, 5403H-5495H, 5547H-559EH and 5613H-562CH; the resident modules and their data occupy 528CH-5402H ($KI, relocation table and translation tables), 5496H-5546H ($TA) and 559FH-55F4H ($JKL); the relocator is 55F5H-5612H; the messages are 562DH-56FBH and the @PARAM table is 56FCH-573CH.

Variables, Buffers and Self-Modifying Cells

Address RangePurpose
4015H
1 byte
KIDCB$ - the *KI device control block type byte; set to 01H for $KI, 05H for $TA.
4016H-4017H
2 bytes
KIDCB$+1/+2 - the current keyboard driver vector; each installed module is written here so it becomes the *KI driver.
4036H-403DH
8 bytes
$KI keyboard matrix row-state save table, one byte per scanned row (3801H-3840H), used for change detection and debounce.
4040H
1 byte
TIMER$ - system tick counter read for the key-repeat delay and rate deadlines.
4049H
2 bytes
HIGH$ - top of free memory; lowered by 016BH ($KI), 00B1H ($TA) and 0056H ($JKL) as each module is reserved.
4300H
vector
@KITSK - keyboard-interrupt task chain, called at the head of $KI and $TA.
430FH
1 byte
SFLAG$ - bit 5 marks a Job Control Language job active, selecting KIJCL$ over KIDCB$ for the hook.
441FH
1 byte
DFLAG$ - bit 1 type-ahead active, bit 2 screen-print active, bit 4 KI/DVR active, bit 7 graphics screen-print.
4423H
1 byte
KFLAG$ - bit 5 CAPS lock, bit 6 special-key translation-table selector, bit 7 type-ahead buffer-has-data.
43BEH
2 bytes
KIJCL$ - holds the real keyboard driver address while a JCL job runs; hooked instead of KIDCB$.
4DF2H / 4DF4H / 4DF8H
2 bytes each
Reuse pointers in SYS0 resident RAM recording the reserved high-memory base of $TA, $JKL and $KI; a second SET reuses the same block.
521DH-521EH
2 bytes
LD BC operand seeded with the DELAY value (20); @PARAM overwrites it when DELAY=d is given (floor 10).
522CH-522DH
2 bytes
LD BC operand seeded with the RATE value (2); @PARAM overwrites it when RATE=r is given (floor 1).
52EAH, 53C4H
1 byte each
$KI repeat-delay operands, patched from the resolved DELAY at 5225H/5228H.
5353H, 5512H, 5519H
1 byte each
$KI/$TA repeat-rate operands, patched from the resolved RATE at 5234H/5237H/523AH.
5498H / 55A1H / 529AH
2 bytes each
$TA, $JKL and $KI module link words, patched with each module's reserved base.
54D8H, 54F9H
2 bytes each
$TA chained-driver call operands, patched with the previous *KI driver address plus 45.
54A4H, 54E7H
2 bytes each
$TA buffer-control operands, patched with the type-ahead buffer base.
55A9H, 55C1H
2 bytes each
$JKL chained-driver and abort-scan operands, patched at install time.
55E5H
1 byte
$JKL non-ASCII replacement byte, set to 2EH (period) or 0FEH (graphics) from DFLAG$ bit 7.

Major Routines

AddressRoutine and Purpose
5200HInstaller / Sign-On
Displays the banner, confirms the SET context (4758H bit 3), parses parameters and seeds the repeat-timing operands.
523DH$KI Allocation, Relocation and Hook
Reserves (or reuses at 4DF8H) 363 bytes below HIGH$, relocates the $KI body, sets DFLAG$ bit 4, copies it up and points KIDCB$ at it.
52C5H$KI Resident Keyboard Driver
Runs the keyboard task chain, scans and debounces the eight matrix rows, applies SHIFT/CTRL/CAPS and the translation tables, and manages the repeat delay and rate.
5403HTYPE Installer + $TA Module
Reserves and copies the 177-byte type-ahead module, patches its chained-driver and buffer cells, hooks it in front and adds its interrupt task via @ADTSK.
5484HHook Subroutine
Writes the new driver entry into KIDCB$+1/+2, or into KIJCL$ under JCL.
5547HJKL Installer + $JKL Module
Reserves and copies the 86-byte screen-print module, patches its cells, hooks it in front and returns to DOS via @EXIT.
55F5HModule Relocator
Adds the $KI relocation displacement to every address word named in the table at 528CH, including the $TA-to-$KI cross-reference at 54F3H.
5613HError Exits
Four paths using a DD-prefix overlap trick to select one of four messages; parameter errors raise code 6CH through @ERROR.

Cross-Reference Notes

KI/DVR issues no RST 28H request of its own. It calls the resident SYS0/SYS services @KITSK (4300H), @PARAM (4476H), @DSPLY (4467H), @LOGOT (447BH), @ERROR (4409H), @ADTSK (4410H), @EXIT (402DH) and @ABORT (4030H), the ROM @PRT (003BH) and @PAUSE (0060H) vectors, and the resident routine at 4BEFH. It reads and writes the SYS0 resident cells KIDCB$ (4015H-4017H), KIJCL$ (43BEH), DFLAG$ (441FH), KFLAG$ (4423H), SFLAG$ (430FH), HIGH$ (4049H), TIMER$ (4040H) and the reuse pointers 4DF2H/4DF4H/4DF8H, all confirmed against SYS0.SYS.

The driver word KI/DVR installs at 4016H is the same one SYS11/SYS saves into KIJCL$ (43BEH) when it starts a compiled job (RST 28H code 9DH) and restores when the job ends; the hook subroutine at 5484H honours that arrangement by writing to KIJCL$ instead of KIDCB$ whenever SFLAG$ bit 5 is set. The SPOOLer's despool interrupt task is chained through @KITSK (4300H), which is why $KI and $TA call it before scanning.

A refinement to the SYS0 resident reference: the KI/DVR-active flag is DFLAG$ bit 4 (tested and set at 5272H/5277H), not bit 3; DFLAG$ bit 2 is the screen-print/JKL flag. KFLAG$ bit 5 is CAPS lock (matching the DOS manual's POKE), bit 6 selects the special-key translation table, and bit 7 signals type-ahead data. These were read directly off the driver code.

Disassembly:

5200H - Sign-On and SET-Context Check

The transient installer entry. The SET library command loads KI/DVR at 5200H and enters here with Register Pair DE addressing the *KI device control block KIDCB$ and Register Pair HL addressing the parameter text. The banner is displayed and the SET context confirmed before any memory is committed.

5200
PUSH DE D5
Save Register Pair DE (the SET command's parameter-text pointer) across the sign-on display.
5201
PUSH HL E5
Save Register Pair HL (the caller's working pointer) across the sign-on display.
5202
LD HL,562DH 21 2D 56
Point Register Pair HL at the sign-on banner string at 562DH.
5205
@DSPLY - send the banner at (HL) to the video display.
5208
LD A,(4758H) 3A 58 47
Load Register A from the SYS1 status/flag byte 4758H.
520B
BIT 3,A CB 5F
Test bit 3 of Register A - the flag SET turns on to mark that this program was invoked as a device driver by the SET library command.
520D
If the SET-context bit is clear the program was run directly, so jump to the Must be installed via: SET error exit.

5210H - Parameter Parse and RATE/DELAY Setup

Parses the command tail against the parameter table at 56FCH and seeds the repeat-timing operands with their defaults and floors.

5210
POP HL E1
Restore Register Pair HL saved at 5201H.
5211
POP IX DD E1
Pop the value pushed from DE at 5200H into Register IX; IX now addresses the *KI device control block KIDCB$ (4015H) passed by SET.
5213
LD DE,56FCH 11 FC 56
Point Register Pair DE at the parameter table at 56FCH.
5216
@PARAM - parse the command tail against the table at (DE); each matched keyword deposits its value at the address the table names.
5219
If @PARAM reported a bad parameter (NZ) jump to the parameter-error exit at 5628H.
521C
LD BC,0014H 01 14 00
Load Register Pair BC with 0014H (20), the pre-set DELAY value (@PARAM overwrites the operand at 521DH when DELAY=d is given).
521F
LD A,0AH 3E 0A
Load Register A with 0AH (10), the minimum DELAY.
5221
CP C B9
Compare the DELAY floor 10 against Register C (the requested DELAY).
5222
If 10 is not above the requested value, keep the floor; otherwise fall through to adopt the requested value.
5224
LD A,C 79
Adopt the requested DELAY from Register C.
5225
LD (52EAH),A 32 EA 52
Store the resolved DELAY into the repeat-timing operand at 52EAH in the $KI body.
5228
LD (53C4H),A 32 C4 53
Store the same DELAY into the second repeat-timing operand at 53C4H.
522B
LD BC,0002H 01 02 00
Load Register Pair BC with 0002H (2), the pre-set RATE value (@PARAM overwrites the operand at 522CH when RATE=r is given).
522E
LD A,01H 3E 01
Load Register A with 01H (1), the minimum RATE.
5230
CP C B9
Compare the RATE floor 1 against Register C (the requested RATE).
5231
If 1 is not above the requested value, keep the floor; otherwise fall through to adopt the requested value.
5233
LD A,C 79
Adopt the requested RATE from Register C.
5234
LD (5353H),A 32 53 53
Store the resolved RATE into the repeat-rate operand at 5353H in the $KI body.
5237
LD (5512H),A 32 12 55
Store the same RATE into the type-ahead repeat operand at 5512H.
523A
LD (5519H),A 32 19 55
Store the same RATE into the type-ahead repeat operand at 5519H.

523DH - $KI High-Memory Allocation and Relocation

Reserves space for the main $KI driver below HIGH$ (or reuses a previously reserved block recorded at 4DF8H) and relocates the module image to the address it will occupy.

523D
LD BC,016BH 01 6B 01
Load Register Pair BC with 016BH (363), the assembled length of the $KI module.
5240
LD HL,(4DF8H) 2A F8 4D
Load Register Pair HL from the $KI reuse pointer 4DF8H - the high-memory base kept from a previous SET, or zero the first time.
5243
PUSH HL E5
Save that pointer.
5244
INC HL 23
Advance Register Pair HL toward the stored base-address field.
5245
INC HL 23
Advance Register Pair HL to the low byte of the stored base.
5246
LD E,(HL) 5E
Load the low byte of the previously reserved base into Register E.
5247
INC HL 23
Advance to the high byte.
5248
LD D,(HL) 56
Load the high byte of the previously reserved base into Register D; Register Pair DE now holds the old base (zero if none).
5249
POP HL E1
Restore the reuse-pointer value into Register Pair HL.
524A
LD A,H 7C
Copy the high byte of the reuse pointer into Register A.
524B
OR L B5
OR with the low byte to test the pointer for zero.
524C
If the reuse pointer is non-zero, a block is already reserved, so skip the fresh allocation and reuse it at 525DH.
524E
LD HL,(4049H) 2A 49 40
Load Register Pair HL from HIGH$ (4049H), the current top of free memory.
5251
LD E,L 5D
Copy the low byte of HIGH$ into Register E.
5252
LD D,H 54
Copy the high byte of HIGH$ into Register D; Register Pair DE now marks the top of the block being reserved.
5253
XOR A AF
Clear Register A and the carry for the subtraction.
5254
SBC HL,BC ED 42
Subtract the $KI length (BC) from HIGH$ (HL), giving the new top-of-memory below the reserved block.
5256
LD (4049H),HL 22 49 40
Store the lowered value back into HIGH$ (4049H).
5259
INC HL 23
Step Register Pair HL up one byte to the base address of the reserved block.
525A
LD (4DF8H),HL 22 F8 4D
Record that base in the $KI reuse pointer 4DF8H for the next SET.
525D
LD (529AH),DE ED 53 9A 52
Store the module base (Register Pair DE) into the relocation-displacement cell 529AH used by the copy.
5261
PUSH BC C5
Save Register Pair BC (the module length).
5262
PUSH DE D5
Save Register Pair DE (the module base).
5263
LD IY,528CH FD 21 8C 52
Point Register IY at the $KI relocation table at 528CH.
5267
LD HL,5402H 21 02 54
Load Register Pair HL with 5402H, the in-image reference base handed to the relocator.
526A
Relocate the $KI address words listed at (IY) to their high-memory values.
526D
POP DE D1
Restore the module base into Register Pair DE.
526E
POP BC C1
Restore the module length into Register Pair BC.

526FH - Activate Flag, Copy $KI and Hook the Keyboard DCB

Marks KI/DVR active in DFLAG$, copies the relocated $KI body into high memory, and points KIDCB$ (or KIJCL$ under JCL) at it.

526F
LD HL,441FH 21 1F 44
Point Register Pair HL at DFLAG$ (441FH).
5272
BIT 4,(HL) CB 66
Test bit 4 of DFLAG$ - the KI/DVR-active flag.
5274
If KI/DVR is already active jump to the KI/dvr is already active! error exit.
5277
SET 4,(HL) CB E6
Set bit 4 of DFLAG$ to mark KI/DVR active.
5279
LD HL,5402H 21 02 54
Point Register Pair HL at 5402H, the top of the source $KI image, for a downward copy.
527C
LDDR ED B8
Block-copy the $KI body downward from (HL) into the reserved high-memory block at (DE), Register Pair BC bytes.
527E
DI F3
Disable interrupts while the keyboard driver vector is switched.
527F
LD (IX+00H),01H DD 36 00 01
Set the device-control-block type byte KIDCB$+0 (via Register IX) to 01H - a driver is present.
5283
INC DE 13
Advance Register Pair DE to the entry of the copied module.
5284
EX DE,HL EB
Move that entry address into Register Pair HL for the hook subroutine.
5285
Hook the copied $KI entry into KIDCB$ (or KIJCL$ under JCL).
5288
EI FB
Re-enable interrupts.
5289
Continue to the TYPE installer at 5403H.

528CH - $KI Relocation Table

A null-terminated list of the in-image address words that the relocator at 55F5H must adjust when the $KI body is moved into high memory.

528C
DEFW 52DAH DA 52
Relocation word list for $KI: 52DAH, 5342H, 53DFH and 54F3H are the in-image address operands the relocator rewrites; the 0000H word ends the list.
528E
DEFW 5342H 42 53
Data word.
5290
DEFW 53DFH DF 53
Data word.
5292
DEFW 54F3H F3 54
Data word.
5294
DEFW 0000H 00 00
Data word.

5296H - $KI Module Header and Translation Tables

The resident $KI module as it sits before being copied up: a JR entry, the module link word and $KI name that let @LINK/@ROUTE find it, and the two 16-byte special-key translation tables plus the five-byte CLR-key bracket table.

5296
DEFW 0000H 00 00
Padding word between the relocation table and the module image; not copied to high memory.
5298
Resident $KI module entry - jump past the header and translation tables to the scan driver at 52C5H.
529A
DEFW 0000H 00 00
$KI module link word - set into the HIGH$ module chain so @LINK/@ROUTE and the BCH module-lookup service can find the driver by name.
529C
DEFB 03H 03
Module name length (3 characters).
529D
DEFM '$KI' 24 4B 49
Module name $KI.
52A0
DEFB 0DH, 1DH, 1FH, 1FH, 01H, 00H, 5BH, 1BH, 0AH, 1AH, 08H, 18H, 09H, 19H, 20H, 20H 0D 1D 1F 1F 01 00 5B 1B 0A 1A 08 18 09 19 20 20
Special-key translation table A (CAPS off), indexed by the resolved matrix code: 0DH, 1DH, 1FH, 1FH, 01H, 00H, 5BH, 1BH, 0AH, 1AH, 08H, 18H, 09H, 19H, 20H, 20H.
52B0
DEFB 0DH, 1DH, 1FH, 1FH, 01H, 00H, 82H, 92H, 88H, 1AH, 81H, 91H, 84H, 94H, 20H, 20H 0D 1D 1F 1F 01 00 82 92 88 1A 81 91 84 94 20 20
Special-key translation table B (CAPS on / KFLAG$ bit 6 set): 0DH, 1DH, 1FH, 1FH, 01H, 00H, 82H, 92H, 88H, 1AH, 81H, 91H, 84H, 94H, 20H, 20H.
52C0
DEFB 2CH, 2FH, 2EH, 3BH, 0DH 2C 2F 2E 3B 0D
CLR-key table used to build the bracket characters: 2CH, 2FH, 2EH, 3BH, 0DH (the punctuation keys that CLR turns into [ \ ] ^ and CLR-ENTER into _).

52C5H - $KI Resident Keyboard Scan Driver

The resident keyboard driver proper, reached through the header JR at 5298H. It services the keyboard-interrupt task chain, scans and debounces the eight keyboard matrix rows, applies SHIFT/CTRL/CAPS, resolves the key through the translation tables, and manages the key-repeat delay and rate.

52C5
@KITSK - run the keyboard-interrupt task chain (clock, spooler despool and any linked keyboard tasks) before scanning.
52C8
LD HL,4036H 21 36 40
Point Register Pair HL at 4036H, the base of the eight-byte matrix row-state save table (4036H-403DH).
52CB
LD BC,3801H 01 01 38
Load Register Pair BC with 3801H - Register B = 38H (keyboard page), Register C = 01H selects matrix row 0.
52CE
LD D,00H 16 00
Clear Register D, the running key-code / row index.
52D0
LD A,(BC) 0A
Read the current bits of this keyboard matrix row from the memory-mapped keyboard at (BC).
52D1
LD E,A 5F
Hold the current row bits in Register E.
52D2
XOR (HL) AE
Exclusive-OR against the saved previous state at (HL); the result is the bits that changed.
52D3
If any key in this row changed, branch to the key-change handler at 52EFH.
52D5
INC D 14
No change: advance the row index in Register D by eight keys (added below).
52D6
INC L 2C
Advance Register L to the next row's save byte.
52D7
RLC C CB 01
Rotate the row-select bit in Register C to address the next matrix row (01H, 02H, 04H ... ).
52D9
While the row-select bit has not rotated into bit 7, loop back for the next row (rows 0-6, addresses 3801H-3840H).
52DC
LD A,(IX+03H) DD 7E 03
No new keypress: load Register A from KIDCB$+3 (via Register IX), the key code currently held for auto-repeat.
52DF
OR A B7
Test the held key code.
52E0
RET Z C8
If nothing is held, return with no character.
52E1
LD A,(4040H) 3A 40 40
Load Register A from TIMER$ (4040H), the system tick counter.
52E4
SUB (IX+04H) DD 96 04
Subtract the repeat deadline stored in KIDCB$+4 (via Register IX).
52E7
If the deadline tick has been reached exactly, emit the repeated key at 534FH.
52E9
SUB 14H D6 14
Otherwise subtract a further 14H (20 ticks) to test the repeat window.
52EB
If the tick count falls in the repeat window, emit the repeated key at 534FH.
52ED
XOR A AF
No character due this pass: clear Register A.
52EE
RET C9
Return with no character.
52EF
LD (HL),E 73
A key changed: store the new row bits (Register E) back into the save byte at (HL).
52F0
AND E A3
AND the change with the current bits to isolate keys that were newly pressed (rather than released).
52F1
If only releases occurred, branch to the key-up handling at 5367H.
52F3
LD E,A 5F
Hold the newly pressed column bit(s) in Register E.
52F4
LD A,D 7A
Copy the row index from Register D into Register A.
52F5
RLCA 07
Multiply the row index by two.
52F6
RLCA 07
Multiply by two again.
52F7
RLCA 07
Multiply by two again - Register A now holds row number times eight.
52F8
LD D,A 57
Save row-times-eight in Register D as the base key code.
52F9
LD C,01H 0E 01
Set Register C to the column-0 bit mask 01H.
52FB
LD A,C 79
Copy the column mask into Register A.
52FC
AND E A3
Test the mask against the newly pressed bits in Register E.
52FD
When the pressed column is found, branch to key resolution at 5318H.
52FF
INC D 14
Not this column: increment the key code in Register D.
5300
RLC C CB 01
Rotate the column mask in Register C to the next column.
5302
Continue the column search.
5304
SUB 70H D6 70
Punctuation/number group: subtract 70H to index the shifted-punctuation logic.
5306
If the result did not borrow, branch to the special-key table lookup at 5339H.
5308
ADD A,40H C6 40
Add 40H to restore the working value.
530A
CP 3CH FE 3C
Compare with 3CH to split the two punctuation sub-ranges.
530C
Branch by range to 5310H.
530E
XOR 10H EE 10
Toggle bit 4 of the code to apply the shifted form.
5310
RRC B CB 08
Rotate the SHIFT bits in Register B to test the shift state.
5312
If SHIFT is not active, take the unshifted value to the finish at 5357H.
5314
XOR 10H EE 10
Apply the shifted form by toggling bit 4.
5316
Go to the common finish at 5357H.
5318
LD A,(3880H) 3A 80 38
Read the modifier (SHIFT) matrix row at 3880H.
531B
LD B,A 47
Hold the SHIFT bits in Register B.
531C
LD A,D 7A
Copy the key code from Register D into Register A.
531D
ADD A,40H C6 40
Add 40H to bias the code into the letter/character range.
531F
CP 60H FE 60
Compare with 60H.
5321
If the code is at or above 60H it is a punctuation/number key - branch to 5304H.
5323
RRC B CB 08
Rotate the SHIFT bits in Register B to test the shift state.
5325
If SHIFT is not active, take the unshifted letter to the finish at 5357H.
5327
ADD A,20H C6 20
Add 20H to fold the letter to its lower-case value.
5329
LD D,A 57
Hold the folded value in Register D.
532A
LD A,(3840H) 3A 40 38
Read matrix row at 3840H to test the extra modifier keys.
532D
AND 10H E6 10
Mask bit 4 (the key tested here).
532F
If that key is not down, take the value at 5356H.
5331
LD A,D 7A
Recover the working value from Register D.
5332
SUB 60H D6 60
Subtract 60H.
5334
If the result is non-zero take the finish at 5357H.
5336
CP 0FFH FE FF
Compare with 0FFH.
5338
RET C9
Return.
5339
RRC B CB 08
Special-key group: rotate the SHIFT bits in Register B.
533B
ADC A,A 8F
Add-with-carry to fold the shift state into the code.
533C
LD HL,4423H 21 23 44
Point Register Pair HL at KFLAG$ (4423H).
533F
BIT 6,(HL) CB 76
Test bit 6 of KFLAG$, the special-key translation-table selector.
5341
LD HL,52A0H 21 A0 52
Point Register Pair HL at translation table A (52A0H).
5344
If bit 6 is clear, use table A.
5346
ADD A,10H C6 10
If bit 6 is set, add 10H to select translation table B at 52B0H.
5348
LD C,A 4F
Copy the table index into Register C.
5349
LD B,00H 06 00
Clear Register B for a sixteen-bit index.
534B
ADD HL,BC 09
Add the index to the table base in Register Pair HL.
534C
LD A,(HL) 7E
Fetch the translated special-key value from the table.
534D
Go to the common finish at 5357H.
534F
LD A,(4040H) 3A 40 40
Repeat emit: load Register A from TIMER$ (4040H).
5352
ADD A,02H C6 02
Add 2 to set the next repeat deadline.
5354
Go store the deadline and return the held key at 53C5H.
5356
LD A,D 7A
Take the resolved value from Register D into Register A.
5357
CP 01H FE 01
Compare the resolved code with 01H.
5359
If the resolved code is 01H, call the SYS0 resident routine at 4BEFH.
535C
LD BC,1A9FH 01 9F 1A
Load Register Pair BC with 1A9FH - Register B = 1AH is the key code compared next, Register C = 9FH a comparison constant.
535F
LD HL,4039H 21 39 40
Point Register Pair HL at the row-3 save byte 4039H.
5362
CP B B8
Compare Register A with 1AH (Register B).
5363
If it does not match, continue at 5369H.
5365
BIT 2,(HL) CB 56
Test bit 2 of the row-3 save byte.
5367
Key-up / no-key: if that bit is clear, take the clear-character path at 53B6H.
5369
LD L,3CH 2E 3C
Point Register L at the row-6 save byte 403CH.
536B
BIT 1,(HL) CB 4E
Test bit 1 of that save byte.
536D
LD HL,3880H 21 80 38
Point Register Pair HL at the SHIFT matrix row 3880H.
5370
If the tested bit was clear, skip the OR at 5373H.
5372
OR L B5
OR Register L into Register A to fold in the modifier.
5373
LD D,A 57
Hold the working value in Register D.
5374
RES 5,A CB AF
Reset bit 5 of Register A to test the letter case-folded form.
5376
SUB 41H D6 41
Subtract 41H (the code for 'A').
5378
CP B B8
Compare with Register B to test whether the code is a letter.
5379
LD A,D 7A
Recover the working value from Register D.
537A
If it is not a letter, branch to 5389H.
537C
XOR 20H EE 20
Toggle bit 5 to swap the letter case.
537E
EX DE,HL EB
Exchange Register Pair DE and HL to reach KFLAG$.
537F
LD HL,4423H 21 23 44
Point Register Pair HL at KFLAG$ (4423H).
5382
BIT 5,(HL) CB 6E
Test bit 5 of KFLAG$, the CAPS-lock flag (set by SHIFT-0).
5384
EX DE,HL EB
Exchange Register Pair DE and HL back.
5385
If CAPS lock is off, leave the case as typed at 5389H.
5387
RES 5,A CB AF
Force upper case by resetting bit 5.
5389
BIT 0,(HL) CB 46
Test bit 0 of KFLAG$ (the BREAK/enable bit).
538B
If set, continue at 5391H.
538D
BIT 1,(HL) CB 4E
Test bit 1 of KFLAG$.
538F
If clear, take the clear-character path at 53B3H.
5391
CP C B9
Compare the code with the constant in Register C.
5392
If equal, take the toggle path at 53B2H.
5394
LD HL,403AH 21 3A 40
Point Register Pair HL at the row-4 save byte 403AH.
5397
CP 0A0H FE A0
Compare Register A with 0A0H.
5399
If not equal, branch to 53A3H.
539B
BIT 0,(HL) CB 46
Test bit 0 of the row-4 save byte.
539D
If set, take the clear-character path at 53B7H.
539F
LD A,40H 3E 40
Load Register A with 40H.
53A1
Go to the toggle-and-store step at 53ABH.
53A3
CP 20H FE 20
Compare Register A with 20H.
53A5
If not equal, take the clear-character path at 53B7H.
53A7
BIT 0,(HL) CB 46
Test bit 0 of the row-4 save byte.
53A9
If clear, take the clear-character path at 53B7H.
53AB
LD HL,4423H 21 23 44
Point Register Pair HL at KFLAG$ (4423H).
53AE
XOR (HL) AE
Exclusive-OR Register A into KFLAG$ to toggle the selected flag bit.
53AF
LD (HL),A 77
Store the toggled flags back into KFLAG$.
53B0
Go to the clear-character step at 53B6H.
53B2
XOR L AD
Exclusive-OR Register L to form the toggle mask.
53B3
CP C B9
Compare with Register C.
53B4
If not equal, take the clear-character path at 53B7H.
53B6
XOR A AF
Clear Register A - no character is produced this pass.
53B7
LD (IX+03H),A DD 77 03
Store the resolved code (or zero) into KIDCB$+3, the held/repeat key code.
53BA
LD BC,0333H 01 33 03
Load Register Pair BC with 0333H, the debounce delay count.
53BD
@PAUSE (ROM 0060H) - a short debounce delay of Register Pair BC iterations.
53C0
LD A,(4040H) 3A 40 40
Load Register A from TIMER$ (4040H).
53C3
ADD A,14H C6 14
Add 14H (the DELAY constant, patched at 5228H) to form the first-repeat deadline.
53C5
LD (IX+04H),A DD 77 04
Store the repeat deadline into KIDCB$+4.
53C8
LD A,(IX+03H) DD 7E 03
Reload the held key code from KIDCB$+3.
53CB
OR A B7
Test it.
53CC
RET Z C8
If it is zero, return with no character.
53CD
LD HL,3840H 21 40 38
Point Register Pair HL at matrix row 3840H.
53D0
BIT 4,(HL) CB 66
Test bit 4 of that row (the CLR key group).
53D2
If clear, branch to 53DBH.
53D4
LD L,80H 2E 80
Point Register L at the SHIFT row 3880H.
53D6
BIT 0,(HL) CB 46
Test bit 0 (a SHIFT key).
53D8
SCF 37
Set the carry flag to mark the SHIFT-with-CLR case.
53D9
If SHIFT is down, branch to 53DDH with carry set.
53DB
OR A B7
Clear the carry (no CLR-combination active).
53DC
RET P F0
Return if the code has bit 7 clear (an ordinary character).
53DD
PUSH AF F5
Save the code and the carry flag.
53DE
LD HL,52C0H 21 C0 52
Point Register Pair HL at the CLR-key bracket table 52C0H.
53E1
RES 7,A CB BF
Reset bit 7 of Register A to get the base code.
53E3
LD B,05H 06 05
Set Register B to five, the table length.
53E5
If SHIFT is not set, skip the count adjustment.
53E7
DEC B 05
Decrement the loop count for the SHIFT case.
53E8
LD C,5BH 0E 5B
Set Register C to 5BH, the code for '['.
53EA
CP (HL) BE
Compare Register A against the current table entry.
53EB
If it matches, take the produce-character path at 53FCH.
53ED
XOR 10H EE 10
Toggle bit 4 to test the shifted form.
53EF
CP (HL) BE
Compare again against the table entry.
53F0
If it matches, take the set-bit-5 path at 53FAH.
53F2
XOR 10H EE 10
Toggle bit 4 back.
53F4
INC HL 23
Advance to the next table entry.
53F5
INC C 0C
Increment the candidate character code in Register C.
53F6
Loop over the five table entries.
53F8
POP AF F1
No match: recover the original code.
53F9
RET C9
Return it unchanged.
53FA
SET 5,C CB E9
Set bit 5 of Register C to select the alternate bracket form.
53FC
POP AF F1
Recover the saved carry (SHIFT state).
53FD
LD A,C 79
Move the produced bracket code from Register C into Register A.
53FE
If SHIFT was not set, clear it out at 53DBH.
5400
AND 1FH E6 1F
Mask to the low five bits to form the control code.
5402
RET C9
Return with the resolved character in Register A.

5403H - TYPE: Type-Ahead Installer

Runs only when the TYPE switch was given. Reserves (or reuses at 4DF2H) space for the $TA module, patches its chained-driver and link cells, copies it up, hooks it in front of $KI and adds its interrupt task through @ADTSK.

5403
LD BC,0000H 01 00 00
Load Register Pair BC with the TYPE flag (00H, overwritten with 0FFFFH by @PARAM when TYPE is given).
5406
LD A,B 78
Test the TYPE flag in Register Pair BC.
5407
OR C B1
OR the low byte to test the TYPE flag for zero.
5408
If TYPE was not requested, skip to the JKL installer at 5547H.
540B
LD HL,441FH 21 1F 44
Point Register Pair HL at DFLAG$ (441FH).
540E
BIT 1,(HL) CB 4E
Test bit 1 of DFLAG$, the type-ahead-active flag.
5410
If type-ahead is already active jump to the Type-ahead is already active! error exit at 561FH.
5413
SET 1,(HL) CB CE
Set bit 1 of DFLAG$ to mark type-ahead active.
5415
LD HL,(4DF2H) 2A F2 4D
Load Register Pair HL from the $TA reuse pointer 4DF2H.
5418
LD A,H 7C
Copy the high byte into Register A.
5419
OR L B5
OR with the low byte to test the reuse pointer for zero.
541A
PUSH AF F5
Save the zero test.
541B
If the pointer is non-zero, reuse the previously reserved block at 5423H.
541D
LD HL,(4049H) 2A 49 40
Load Register Pair HL from HIGH$ (4049H).
5420
LD (4DF2H),HL 22 F2 4D
Record the base in the $TA reuse pointer 4DF2H.
5423
LD (5498H),HL 22 98 54
Patch the $TA module link word at 5498H with the reserved base.
5426
DEC H 25
Step back into the reserved-block header area.
5427
LD (HL),00H 36 00
Zero a header byte.
5429
DEC HL 2B
Step back and zero another header byte.
542A
LD (HL),00H 36 00
Zero that header byte.
542C
DEC HL 2B
Step back to the next header byte.
542D
LD (HL),00H 36 00
Zero it.
542F
LD (54A4H),HL 22 A4 54
Patch the $TA buffer-control operand at 54A4H with this address.
5432
LD (54E7H),HL 22 E7 54
Patch the $TA interrupt-task buffer operand at 54E7H with the same address.
5435
EX DE,HL EB
Exchange Register Pair DE and HL, holding the base in Register Pair DE.
5436
LD HL,(4016H) 2A 16 40
Load Register Pair HL from KIDCB$+1 (4016H), the current keyboard driver vector to chain to.
5439
LD A,(430FH) 3A 0F 43
Load Register A from SFLAG$ (430FH).
543C
BIT 5,A CB 6F
Test bit 5, set while a Job Control Language job holds the real driver.
543E
If no job is active, keep the KIDCB$ vector at 5443H.
5440
LD HL,(43BEH) 2A BE 43
Under JCL, chain instead to the real driver saved in KIJCL$ (43BEH).
5443
LD BC,002DH 01 2D 00
Load Register Pair BC with 002DH (45).
5446
ADD HL,BC 09
Add 45 to the chained-driver address.
5447
LD (54F9H),HL 22 F9 54
Patch the $TA chained-call operand at 54F9H.
544A
LD (54D8H),HL 22 D8 54
Patch the $TA chained-call operand at 54D8H with the same address.
544D
EX DE,HL EB
Exchange Register Pair DE and HL, holding the base in Register Pair HL.
544E
LD BC,00B1H 01 B1 00
Load Register Pair BC with 00B1H (177), the assembled length of the $TA module.
5451
DEC HL 2B
Step back one byte to the block base.
5452
XOR A AF
Clear Register A and the carry for the subtraction.
5453
SBC HL,BC ED 42
Subtract the $TA length to compute the new top of memory.
5455
POP AF F1
Recover the zero test; if the block was freshly allocated, lower HIGH$.
5456
If it was reused, keep the existing HIGH$.
5458
LD (4049H),HL 22 49 40
Store the lowered top of memory into HIGH$ (4049H).
545B
INC HL 23
Step up to the $TA destination base.
545C
PUSH HL E5
Save the destination base.
545D
EX DE,HL EB
Exchange into Register Pair DE for the copy destination.
545E
LD HL,5496H 21 96 54
Point Register Pair HL at the $TA source image at 5496H.
5461
LDIR ED B0
Block-copy the 177-byte $TA module into high memory.
5463
DI F3
Disable interrupts while the driver chain is switched.
5464
LD (IX+00H),05H DD 36 00 05
Set the KIDCB$+0 type byte to 05H - a driver with an interrupt task is present.
5468
POP HL E1
Recover the $TA base and keep a copy on the stack.
5469
PUSH HL E5
Keep a copy of the $TA base on the stack.
546A
Hook the $TA entry into KIDCB$ (or KIJCL$ under JCL).
546D
EI FB
Re-enable interrupts.
546E
POP HL E1
Recover the $TA base.
546F
LD DE,0048H 11 48 00
Load Register Pair DE with 0048H (72).
5472
ADD HL,DE 19
Advance to the interrupt-task control block at $TA base + 72.
5473
LD D,H 54
Copy the control-block address into Register D.
5474
LD E,L 5D
Copy it into Register E as well.
5475
INC DE 13
Advance Register Pair DE by one.
5476
INC DE 13
Advance again so Register Pair DE points two bytes on.
5477
LD (HL),E 73
Store the low byte of that self-pointer into the control block.
5478
INC HL 23
Advance Register Pair HL.
5479
LD (HL),D 72
Store the high byte, completing the task control block's self-pointer.
547A
DEC HL 2B
Step Register Pair HL back to the control block.
547B
EX DE,HL EB
Move the control-block address into Register Pair HL for @ADTSK.
547C
LD A,0AH 3E 0A
Load Register A with 0AH, the interrupt-task slot number.
547E
@ADTSK - install the type-ahead poll as an interrupt task.
5481
Continue to the JKL installer at 5547H.

5484H - Keyboard DCB / KIJCL Hook Subroutine

Shared by all three installers. Writes the new driver entry in Register Pair HL into KIDCB$+1/+2 (4016H/4017H), or into KIJCL$ (43BEH) when a JCL job holds the real driver address, so that the module just installed becomes the current *KI driver.

5484
LD A,(430FH) 3A 0F 43
Load Register A from SFLAG$ (430FH).
5487
BIT 5,A CB 6F
Test bit 5 - is a Job Control Language job holding the real keyboard driver?
5489
If a job is active, store into KIJCL$ instead at 5492H.
548B
LD (IX+01H),L DD 75 01
Store the low byte of the new driver entry (Register L) into KIDCB$+1 (4016H).
548E
LD (IX+02H),H DD 74 02
Store the high byte (Register H) into KIDCB$+2 (4017H).
5491
RET C9
Return.
5492
LD (43BEH),HL 22 BE 43
Under JCL, store the new driver entry into KIJCL$ (43BEH) so the job's saved chain is updated.
5495
RET C9
Return.

5496H - $TA Resident Type-Ahead Module

The resident type-ahead body copied into high memory. Header (JR entry, link word, $TA name) followed by the character-fetch and 128-byte ring-buffer code. Its chained-driver address (54D8H/54F9H) and link word (5498H) are patched by the installer rather than through the relocator.

5496
Resident $TA module entry - jump past the header to the type-ahead code at 549EH.
5498
DEFW 0000H 00 00
$TA module link word (patched by the installer at 5423H with the reserved high-memory base).
549A
DEFB 03H 03
Module name length (3 characters).
549B
DEFM '$TA' 24 54 41
Module name $TA.
549E
PUSH AF F5
Save Register A across the task-chain call.
549F
@KITSK - run the keyboard-interrupt task chain.
54A2
POP AF F1
Restore Register A.
54A3
LD HL,0000H 21 00 00
Point Register Pair HL at the type-ahead buffer control block (operand patched at 542FH).
54A6
LD (HL),0FFH 36 FF
Set the control byte to 0FFH.
54A8
If entered with carry set, branch to the buffer read at 54B0H.
54AA
If entered with the zero flag, branch to the buffer read at 54B0H.
54AC
LD A,C 79
Copy the request selector from Register C into Register A.
54AD
OR A B7
Test it.
54AE
If zero, branch to the control-reset path at 551EH.
54B0
PUSH HL E5
Save the buffer control pointer.
54B1
INC HL 23
Advance to the buffer tail index.
54B2
LD A,(HL) 7E
Load the tail index into Register A.
54B3
INC HL 23
Advance to the head index.
54B4
CP (HL) BE
Compare tail with head.
54B5
If they are equal the buffer is empty - branch to the chained-driver fetch at 54D3H.
54B7
PUSH HL E5
Save the pointer to the head/tail indices.
54B8
LD E,(HL) 5E
Load the tail offset into Register E.
54B9
INC HL 23
Advance Register Pair HL.
54BA
LD D,00H 16 00
Clear Register D for a sixteen-bit offset.
54BC
ADD HL,DE 19
Add the tail offset to reach the next buffered character.
54BD
LD B,(HL) 46
Load that character into Register B.
54BE
INC HL 23
Advance Register Pair HL.
54BF
LD C,(HL) 4E
Load the following byte into Register C.
54C0
POP HL E1
Recover the index pointer.
54C1
INC (HL) 34
Advance the tail index past the character just taken.
54C2
INC (HL) 34
Advance it a second byte.
54C3
LD A,(HL) 7E
Load the new tail index into Register A.
54C4
DEC HL 2B
Step back to the head index.
54C5
CP (HL) BE
Compare tail with head.
54C6
If they differ, characters remain - skip the empty flag at 54CDH.
54C8
LD HL,4423H 21 23 44
Point Register Pair HL at KFLAG$ (4423H).
54CB
RES 7,(HL) CB BE
Reset bit 7 of KFLAG$, marking the type-ahead buffer empty.
54CD
POP HL E1
Recover the buffer control pointer.
54CE
LD (HL),00H 36 00
Clear the control byte.
54D0
PUSH BC C5
Push the fetched character (Register Pair BC).
54D1
POP AF F1
Pop it into Register Pair AF, returning the character in Register A.
54D2
RET C9
Return with the buffered character.
54D3
LD IX,4015H DD 21 15 40
Buffer empty: point Register IX at KIDCB$ (4015H).
54D7
CALL 0000H CD 00 00
Call the chained keyboard driver (operand patched at 544AH) to fetch a real key.
54DA
POP HL E1
Recover the buffer control pointer.
54DB
LD (HL),00H 36 00
Clear the control byte.
54DD
RET C9
Return with the character from the chained driver.
54DE
DEFW 54E0H E0 54
Interrupt-task control block self/next pointer (DEFW 54E0H; relocated to the $TA high-memory copy at install time).
54E0
LD A,(441FH) 3A 1F 44
Task entry: load Register A from DFLAG$ (441FH).
54E3
AND 02H E6 02
Mask bit 1, the type-ahead-active flag.
54E5
RET Z C8
If type-ahead has been switched off, return.
54E6
LD HL,0000H 21 00 00
Point Register Pair HL at the buffer control block (operand patched at 5432H).
54E9
LD A,(HL) 7E
Load the control byte into Register A.
54EA
OR A B7
Test it.
54EB
RET NZ C0
If a read is in progress, return without disturbing the buffer.
54EC
INC HL 23
Advance past the control byte.
54ED
PUSH HL E5
Save the buffer pointer.
54EE
LD IX,4015H DD 21 15 40
Point Register IX at KIDCB$ (4015H).
54F2
LD HL,53BDH 21 BD 53
Point Register Pair HL at the debounce CALL at 53BDH inside the relocated $KI scan routine.
54F5
LD (HL),01H 36 01
Overwrite its opcode with 01H to suppress the debounce delay during this interrupt poll.
54F7
PUSH HL E5
Save the patch pointer.
54F8
CALL 0000H CD 00 00
Call the chained keyboard driver (operand patched at 5447H) to poll for a real key.
54FB
POP HL E1
Recover the patch pointer.
54FC
LD (HL),0CDH 36 CD
Restore the CALL opcode 0CDH at 53BDH.
54FE
POP HL E1
Recover the buffer pointer.
54FF
INC A 3C
Increment Register A to test for the no-key value 0FFH.
5500
DEC A 3D
Decrement it back.
5501
RET Z C8
If no key was available, return.
5502
PUSH AF F5
Save the key in Register Pair AF.
5503
POP BC C1
Copy it into Register Pair BC.
5504
CP 0C0H FE C0
Compare with 0C0H, the buffer-clear control code (CLR-@).
5506
If it is the clear code, empty the buffer at 551FH.
5508
LD E,(HL) 5E
Load the head index into Register E.
5509
LD A,E 7B
Copy it into Register A.
550A
INC HL 23
Advance to the tail index.
550B
CP (HL) BE
Compare head with tail.
550C
If they match the buffer was empty - store the character at 5535H.
550E
LD A,(4040H) 3A 40 40
Load Register A from TIMER$ (4040H).
5511
ADD A,02H C6 02
Add the repeat interval (RATE operand patched at 5237H).
5513
CP (IX+04H) DD BE 04
Compare with the repeat deadline in KIDCB$+4.
5516
If the deadline has not been reached, branch to 5530H.
5518
ADD A,02H C6 02
Advance the deadline by the repeat interval (RATE operand patched at 523AH).
551A
LD (IX+04H),A DD 77 04
Store the new deadline into KIDCB$+4.
551D
RET C9
Return.
551E
INC HL 23
Step Register Pair HL to the buffer control fields.
551F
LD (HL),02H 36 02
Store 02H as the reset tail index.
5521
INC HL 23
Advance and store 00H.
5522
LD (HL),00H 36 00
Store the head index low byte.
5524
INC HL 23
Advance and store 00H.
5525
LD (HL),00H 36 00
Store the head index high byte.
5527
INC HL 23
Advance to the buffer-size field.
5528
LD (HL),40H 36 40
Store 40H (64 words = the 128-character buffer).
552A
LD HL,4423H 21 23 44
Point Register Pair HL at KFLAG$ (4423H).
552D
RES 7,(HL) CB BE
Reset bit 7, marking the buffer empty.
552F
RET C9
Return.
5530
LD A,E 7B
Recover the head index into Register A.
5531
INC A 3C
Advance it one character.
5532
INC A 3C
Advance it a second byte.
5533
CP (HL) BE
Compare with the tail index.
5534
RET Z C8
If the buffer is full, return and drop the key.
5535
PUSH HL E5
Save the buffer pointer.
5536
INC HL 23
Advance past the head index and clear Register D for a sixteen-bit offset.
5537
LD D,00H 16 00
Clear Register D for a sixteen-bit offset.
5539
ADD HL,DE 19
Add the head offset to reach the free slot.
553A
LD (HL),B 70
Store the key code (Register B) there.
553B
INC HL 23
Advance and store the following byte (Register C).
553C
LD (HL),C 71
Store the following byte (Register C) into the buffer.
553D
LD HL,4423H 21 23 44
Point Register Pair HL at KFLAG$ (4423H).
5540
SET 7,(HL) CB FE
Set bit 7, marking that the type-ahead buffer holds data.
5542
POP HL E1
Recover the buffer pointer.
5543
DEC HL 2B
Step back to the head index and advance it two bytes past the character just stored.
5544
INC (HL) 34
Advance the head index a second byte.
5545
INC (HL) 34
Advance the head index a second byte, completing the two-byte step.
5546
RET C9
Return.

5547H - JKL: Screen-Print Installer

Runs only when the JKL switch was given. Reserves (or reuses at 4DF4H) space for the $JKL module, patches its chained-driver (55A9H) and link (55A1H) cells, copies it up, hooks it in front, and returns to DOS through @EXIT.

5547
LD BC,0000H 01 00 00
Load Register Pair BC with the JKL flag (00H, overwritten with 0FFFFH by @PARAM when JKL is given).
554A
LD A,B 78
Test the JKL flag in Register Pair BC.
554B
OR C B1
OR the low byte to test the JKL flag for zero.
554C
If JKL was not requested, return to DOS through @EXIT at 559CH.
554F
LD HL,441FH 21 1F 44
Point Register Pair HL at DFLAG$ (441FH).
5552
BIT 2,(HL) CB 56
Test bit 2 of DFLAG$, the screen-print-active flag.
5554
If screen print is already active jump to the JKL is already active! error exit at 5617H.
5557
SET 2,(HL) CB D6
Set bit 2 of DFLAG$ to mark screen print active.
5559
LD DE,(4016H) ED 5B 16 40
Load Register Pair DE from KIDCB$+1 (4016H), the driver to chain to.
555D
LD A,(430FH) 3A 0F 43
Load Register A from SFLAG$ (430FH).
5560
BIT 5,A CB 6F
Test bit 5, the JCL-active flag.
5562
If no job is active, keep the KIDCB$ vector at 5568H.
5564
LD DE,(43BEH) ED 5B BE 43
Under JCL, chain to the real driver in KIJCL$ (43BEH).
5568
LD (55A9H),DE ED 53 A9 55
Patch the $JKL chained-call operand at 55A9H with the driver to chain to.
556C
LD BC,0056H 01 56 00
Load Register Pair BC with 0056H (86), the assembled length of the $JKL module.
556F
LD HL,(4DF4H) 2A F4 4D
Load Register Pair HL from the $JKL reuse pointer 4DF4H.
5572
LD A,H 7C
Copy the high byte into Register A.
5573
OR L B5
OR with the low byte to test the pointer for zero.
5574
PUSH AF F5
Save the zero test.
5575
If the pointer is non-zero, reuse the previously reserved block at 557DH.
5577
LD HL,(4049H) 2A 49 40
Load Register Pair HL from HIGH$ (4049H).
557A
LD (4DF4H),HL 22 F4 4D
Record the base in the $JKL reuse pointer 4DF4H.
557D
LD (55A1H),HL 22 A1 55
Patch the $JKL module link word at 55A1H with the reserved base.
5580
XOR A AF
Clear carry and subtract the length to compute the new top of memory below $JKL.
5581
SBC HL,BC ED 42
Subtract the $JKL length to compute the new top of memory.
5583
POP AF F1
Recover the zero test; if freshly allocated, lower HIGH$.
5584
If reused, keep the existing HIGH$.
5586
LD (4049H),HL 22 49 40
Store the lowered top of memory into HIGH$ (4049H).
5589
INC HL 23
Step up to the $JKL destination base.
558A
PUSH HL E5
Save the destination base.
558B
EX DE,HL EB
Exchange it into Register Pair DE for the copy destination.
558C
LD HL,0046H 21 46 00
Load Register Pair HL with 0046H (70).
558F
ADD HL,DE 19
Advance to the abort-scan operand address at $JKL base + 70.
5590
LD (55C1H),HL 22 C1 55
Patch the $JKL operand at 55C1H.
5593
LD HL,559FH 21 9F 55
Point Register Pair HL at the $JKL source image at 559FH.
5596
LDIR ED B0
Block-copy the 86-byte $JKL module into high memory.
5598
POP HL E1
Recover the $JKL base.
5599
Hook the $JKL entry into KIDCB$ (or KIJCL$ under JCL).
559C
@EXIT - return to DOS; installation is complete.

559FH - $JKL Resident Screen-Print Module

The resident screen-print body. Header (JR entry, link word, $JKL name) followed by code that, when the screen-print key is seen, walks video RAM 3C00H-3FFFH and sends each line to *PR through the ROM @PRT vector, translating non-ASCII to a period unless the graphics option is set.

559F
Resident $JKL module entry - jump past the header to the screen-print code at 55A8H.
55A1
DEFW 0000H 00 00
$JKL module link word (patched by the installer at 557DH).
55A3
DEFB 04H 04
Module name length (4 characters).
55A4
DEFM '$JKL' 24 4A 4B 4C
Module name $JKL.
55A8
CALL 0000H CD 00 00
Call the chained keyboard driver (operand patched at 5568H) to fetch the next key.
55AB
RET NC D0
If no key was available, return.
55AC
PUSH AF F5
Save the key.
55AD
CP 2AH FE 2A
Compare it with 2AH, the screen-print trigger code.
55AF
If it is the screen-print key, perform the print at 55B3H.
55B1
POP AF F1
Otherwise recover the key.
55B2
RET C9
Return the key to the caller unchanged.
55B3
POP AF F1
Screen print: discard the trigger key.
55B4
PUSH BC C5
Save Register Pair BC.
55B5
PUSH DE D5
Save Register Pair DE.
55B6
LD A,(441FH) 3A 1F 44
Load Register A from DFLAG$ (441FH).
55B9
RLCA 07
Rotate bit 7 (the graphics-print flag) into carry.
55BA
LD A,3EH 3E 3E
Set the non-ASCII replacement to 3EH.
55BC
If graphics printing is off, keep the replacement at 55C0H.
55BE
LD A,0FEH 3E FE
If graphics printing is on, use 0FEH so graphics bytes pass through.
55C0
LD (55E5H),A 32 E5 55
Patch the non-ASCII replacement operand at 55E5H.
55C3
LD HL,3C00H 21 00 3C
Point Register Pair HL at the start of video RAM, 3C00H.
55C6
LD A,(3840H) 3A 40 38
Load Register A from matrix row 3840H to watch for an abort key.
55C9
AND 04H E6 04
Mask the abort-key bit.
55CB
If a key is held, end the print at 55ECH.
55CD
LD A,L 7D
Copy the low byte of the video address into Register A.
55CE
AND 3FH E6 3F
Mask to the 64-column position within the line.
55D0
LD A,0DH 3E 0D
Load Register A with 0DH (carriage return).
55D2
At the end of every 64-column line, send a carriage return to *PR through ROM @PRT (003BH).
55D5
BIT 6,H CB 74
Test bit 6 of the video address high byte.
55D7
When the address reaches 4000H (past the screen), finish at 55F1H.
55D9
LD A,(HL) 7E
Read the next video byte.
55DA
INC HL 23
Advance the video pointer.
55DB
CP 20H FE 20
Compare the byte with 20H (space).
55DD
If it is a printable code, keep it at 55E1H.
55DF
ADD A,40H C6 40
Add 40H to bring a control-range video code into printable range.
55E1
CP 80H FE 80
Compare with 80H.
55E3
If it is an ASCII code, print it at 55E7H.
55E5
LD A,2EH 3E 2E
Replace a non-ASCII byte with the character set at 55C0H (a period, or the byte itself when graphics printing is on).
55E7
Send the character to *PR through ROM @PRT (003BH).
55EA
Continue with the next video byte at 55C6H.
55EC
LD A,0DH 3E 0D
Abort/end: load Register A with a carriage return.
55EE
Send a final carriage return through ROM @PRT (003BH).
55F1
POP DE D1
Restore Register Pair DE.
55F2
POP BC C1
Restore Register Pair BC.
55F3
XOR A AF
Clear Register A - no character is returned to the caller.
55F4
RET C9
Return.

55F5H - Module Relocator

Adds a fixed displacement to every address word named in the relocation table at (IY), converting the $KI body's in-image references to their high-memory equivalents. Entered with Register Pair HL = old base, Register Pair DE = new base.

55F5
EX DE,HL EB
Exchange so Register Pair HL holds the new high-memory base and Register Pair DE the in-image base 5402H.
55F6
OR A B7
Clear the carry for the subtraction.
55F7
SBC HL,DE ED 52
Subtract to form the relocation displacement in Register Pair HL.
55F9
LD B,H 44
Copy the displacement high byte into Register B.
55FA
LD C,L 4D
Copy the displacement low byte into Register C; Register Pair BC now holds the displacement.
55FB
LD L,(IY+00H) FD 6E 00
Load the next fixup address from the relocation table at (IY) into Register Pair HL.
55FE
LD H,(IY+01H) FD 66 01
Load its high byte.
5601
LD A,L 7D
Test the fixup address for zero.
5602
OR H B4
OR the high byte to test the fixup address for zero.
5603
RET Z C8
A zero entry ends the table - return.
5604
LD E,(HL) 5E
Load the address word stored at the fixup location into Register Pair DE.
5605
INC HL 23
Advance Register Pair HL to the high byte of the stored word.
5606
LD D,(HL) 56
Load the high byte into Register D, completing the address word in Register Pair DE.
5607
EX DE,HL EB
Move it into Register Pair HL.
5608
ADD HL,BC 09
Add the relocation displacement.
5609
EX DE,HL EB
Move the adjusted word back into Register Pair DE.
560A
LD (HL),D 72
Store the adjusted word (Register D then Register E) back at the fixup location.
560B
DEC HL 2B
Step back to the low byte.
560C
LD (HL),E 73
Store the low byte.
560D
INC IY FD 23
Advance Register IY to the next table entry.
560F
INC IY FD 23
Advance the second byte of the table entry.
5611
Loop back for the next relocation entry.

5613H - Error Message Exits

The four error paths. Each loads the message address, logs it through @LOGOT and aborts (or, for a parameter error, raises error 6CH through @ERROR).

5613
LD HL,5691H 21 91 56
Point Register Pair HL at the Must be installed via: SET message at 5691H.
5616
LD IX,56E5H DD 21 E5 56
On the fall-through path this is a dead LD IX; reached at 5617H (past the DD prefix) it is LD HL,56E5H, selecting the JKL is already active! message.
561A
LD IX,56ADH DD 21 AD 56
On the fall-through path this is a dead LD IX; reached at 561BH it is LD HL,56ADH, selecting the KI/dvr is already active! message.
561E
LD IX,56C7H DD 21 C7 56
On the fall-through path this is a dead LD IX; reached at 561FH it is LD HL,56C7H, selecting the Type-ahead is already active! message.
5617
LD HL,56E5H 21 E5 56
DD-prefix skip: entering here (past the DD at 5616H) executes 21 E5 56 as LD HL,56E5H, the JKL is already active! message.

DD-prefix skip: entering here (past the DD at 5616H) executes 21 E5 56 as LD HL,56E5H, the JKL is already active! message.

561B
LD HL,56ADH 21 AD 56
DD-prefix skip: entering here (past the DD at 561AH) executes 21 AD 56 as LD HL,56ADH, the KI/dvr is already active! message.

DD-prefix skip: entering here (past the DD at 561AH) executes 21 AD 56 as LD HL,56ADH, the KI/dvr is already active! message.

561F
LD HL,56C7H 21 C7 56
DD-prefix skip: entering here (past the DD at 561EH) executes 21 C7 56 as LD HL,56C7H, the Type-ahead is already active! message.

DD-prefix skip: entering here (past the DD at 561EH) executes 21 C7 56 as LD HL,56C7H, the Type-ahead is already active! message.

5622
@LOGOT - send the selected carriage-return-terminated message in Register Pair HL to the video.
5625
@ABORT - abort the installation and return to DOS.
5628
LD A,6CH 3E 6C
Parameter error: load Register A with error code 6CH (108).
562A
@ERROR - display the parameter-error message and abort.

562DH - Message Block

The sign-on banner and the four installer error strings, each terminated by 0DH or 0AH.

562D
DEFM 'KI - LDOS Keyboard Input Driver - Version 5.3.1 ' 4B 49 20 2D 20 4C 44 4F 53 20 4B 65 79 62 6F 61 72 64 20 49 6E 70 75 74 20 44 72 69 76 65 72 20 2D 20 56 65 72 73 69 6F 6E 20 35 2E 33 2E 31 20 0A
Sign-on banner: KI - LDOS Keyboard Input Driver - Version 5.3.1 then the copyright line, framed by 0AH/0DH.
565E
DEFM 'Copyright 1991 MISOSYS, Inc., All rights reserved' 43 6F 70 79 72 69 67 68 74 20 31 39 39 31 20 4D 49 53 4F 53 59 53 2C 20 49 6E 63 2E 2C 20 41 6C 6C 20 72 69 67 68 74 73 20 72 65 73 65 72 76 65 64 0A
Message string.
5690
DEFM '' 0D
Message string.
5691
DEFM 'Must be installed via: SET!' 4D 75 73 74 20 62 65 20 69 6E 73 74 61 6C 6C 65 64 20 76 69 61 3A 20 53 45 54 21 0D
Message string.
56AD
DEFM 'KI/dvr is already active!' 4B 49 2F 64 76 72 20 69 73 20 61 6C 72 65 61 64 79 20 61 63 74 69 76 65 21 0D
Message string.
56C7
DEFM 'Type-ahead is already active!' 54 79 70 65 2D 61 68 65 61 64 20 69 73 20 61 6C 72 65 61 64 79 20 61 63 74 69 76 65 21 0D
Message string.
56E5
DEFM 'JKL is already active!' 4A 4B 4C 20 69 73 20 61 6C 72 65 61 64 79 20 61 63 74 69 76 65 21 0D
Message string.

56FCH - Parameter Table (@PARAM)

The table @PARAM scans at 5216H. Eight 8-byte entries, each a six-character blank-padded keyword followed by the two-byte address @PARAM acts on, terminated by 00H. Every parameter appears twice, spelled out and abbreviated to its first letter.

56FC
DEFM 'TYPE ', DEFW 5404H 54 59 50 45 20 20 04 54
TYPE switch (full spelling). @PARAM writes 0FFFFH to the LD BC operand at 5403H when TYPE is present, enabling the type-ahead install.
5704
DEFM 'T ', DEFW 5404H 54 20 20 20 20 20 04 54
TYPE switch (single-letter abbreviation).
570C
DEFM 'JKL ', DEFW 5548H 4A 4B 4C 20 20 20 48 55
JKL switch (full spelling). @PARAM writes 0FFFFH to the LD BC operand at 5547H when JKL is present, enabling the screen-print install.
5714
DEFM 'J ', DEFW 5548H 4A 20 20 20 20 20 48 55
JKL switch (single-letter abbreviation).
571C
DEFM 'RATE ', DEFW 522CH 52 41 54 45 20 20 2C 52
RATE=r value. @PARAM stores r into the LD BC operand at 522BH; the repeat rate, floored at 1.
5724
DEFM 'R ', DEFW 522CH 52 20 20 20 20 20 2C 52
RATE (single-letter abbreviation).
572C
DEFM 'DELAY ', DEFW 521DH 44 45 4C 41 59 20 1D 52
DELAY=d value. @PARAM stores d into the LD BC operand at 521CH; the first-repeat delay, floored at 10.
5734
DEFM 'D ', DEFW 521DH 44 20 20 20 20 20 1D 52
DELAY (single-letter abbreviation).
573C
DEFB 00H 00
End of the parameter table.