├── CPM Source License.txt.txt ├── LICENSE ├── README.md ├── altbdos.asm ├── altccp.asm ├── asm.bas ├── asm.cfg ├── bindisk.bas ├── bios.asm ├── bootstrap.asm ├── ccp.asm ├── linstall.asm ├── loki.cfg ├── loki2.bas ├── lokidisk.bas ├── lokinvm.bas ├── make.bat ├── make.linux ├── movebdos.asm ├── moveccp.asm ├── my-bdos.asm ├── net.asm ├── network.asm ├── startup.asm └── video512.asm /CPM Source License.txt.txt: -------------------------------------------------------------------------------- 1 | CP/M original binaries were digitally converted to z80 format and tested to be binary compatible and represent 2 | an installation of the original CP/M source as was intended for CP/M machines. 3 | Although LokiOS completely replaces these with scratch-written versions, there are times ( for example, when testing 4 | and possible compatability issues arise ) when a user might want to load up the original CP/M binaries rather than 5 | the supplied LokiOS binaries. 6 | 7 | The code for the original alternative CP/M binaries is converted to z80 by David Kitson from original Digital Research 8 | source code. 9 | 10 | Many thanks for the current CP/M owner for licensing this software to the community. 11 | 12 | License details for this are noted below, which is a copy of the license located at 13 | http://www.gaby.de/cpm/license.html; 14 | 15 | 16 | License agreement for the CP/M material presented on this site 17 | 18 | Please note: The Caldera license agreement found in this place before is no longer valid. 19 | 20 | == Updated 9 July 2022 == 21 | Please find the current e-mail thread below, with the new license wording highlighted. 22 | 23 | 24 | From: Scott Chapman <*****@mischko.com> Jul 6, 2022, 1:42 PM 25 | To: Bryan Sparks <*****@drycanyon.com> 26 | 27 | Bryan, 28 | 29 | I'm one of the community of people who are playing around with good old CP/M. 30 | I have seen your release letter on the Unofficial CP/M Web Site, which says the following: 31 | 32 | "Let this email represent a right to use, distribute, modify, enhance and otherwise 33 | make available in a nonexclusive manner the CP/M technology as part of the 34 | 'Unofficial CP/M Web Site' with its maintainers, developers and community." 35 | 36 | I am trying to find out what your intent was. Here's the situation: 37 | 38 | There is a fellow, David Given, who wrote CPMish (https://github.com/davidgiven/cpmish) 39 | This port of CPM does not use any code from Digital Research, which you released. 40 | I asked David, "Why?". He replied: 41 | "The DR code can only be distributed via the Unofficial CP/M Site (the 'as part of' 42 | clause), 43 | which makes it officially Not Free and so cannot be distributed alongside open source 44 | software." 45 | 46 | The CP/M community needs clarification on what your intent was with this clause: 47 | "as part of the 'Unofficial CP/M Web Site' with its maintainers, developers and 48 | community". 49 | 50 | From reading that, people in the CP/M community believe that you meant for CP/M to be 51 | modified but only distributed via that one web site. Can you please clarify? 52 | 53 | I will send your reply to the community so we can work with a better understanding. 54 | 55 | Sincerely, 56 | Scott Chapman 57 | ===================================================================================== 58 | From: Bryan Sparks <*****@drycanyon.com> Thu, Jul 7, 8:04 AM 59 | To: Scott Chapman <******@mischko.com> 60 | 61 | Hmmm. Well, what you describe wasn't my intent but I get that this was unclear. 62 | It was also some time ago. 63 | 64 | Not sure how to "officially" clear this up except to modify the original email content 65 | removing the constraint to the website/group that was mentioned. So, perhaps, this 66 | will suffice: 67 | 68 | "Let this paragraph represent a right to use, distribute, modify, enhance, and otherwise 69 | make available in a nonexclusive manner CP/M and its derivatives. This right comes from 70 | the company, DRDOS, Inc.'s purchase of Digital Research, the company and all assets, 71 | dating back to the mid-1990's. DRDOS, Inc. and I, Bryan Sparks, President of DRDOS, 72 | Inc. as its representative, is the owner of CP/M and the successor in interest of 73 | Digital Research assets." 74 | 75 | It's a bit clumsy but this may get the intent cleared and authority upon which it is 76 | granted. 77 | 78 | Thanks for the email. 79 | 80 | Bryan 81 | 82 | Previous license e-mail as of 2001 83 | 84 | 85 | Subject: Re: Unofficial CP/M Website/licensing of CP/M material 86 | To: gaby@gaby.de 87 | Date sent: Fri, 19 Oct 2001 10:36:31 -0600 88 | 89 | 90 | Let this email represent a right to use, distribute, modify, enhance and 91 | otherwise make available in a nonexclusive manner the CP/M technology as 92 | part of the "Unofficial CP/M Web Site" with its maintainers, developers and 93 | community. 94 | 95 | I further state that as Chairman and CEO of Lineo, Inc. that I have the 96 | right to do offer such a license. 97 | 98 | Lineo and its affiliates, partners and employees make no warranties of any 99 | kind with regards to this technology and its usefulness or lack thereof. 100 | 101 | --- 102 | Bryan Sparks 103 | CEO Lineo, Inc. 104 | http://www.lineo.com 105 | 106 | BACK to main page 107 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 cj7hawk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | CP/M Source code in the following files is licensed as per the attached CP/M 24 | license documentation; altbdos.asm, altccp.asm -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LokiOS 2 | 3 | HOW TO BUILD: Download FREEBASIC - https://www.freebasic.net/ 4 | Put Freebasic in a directory. Copy the contents of this repository to the same directory. 5 | Run MAKE.BAT and it will build all the files. 6 | You can run the LOKI2.EXE to then run the emulator, and it should build the environment and work. 7 | 8 | LokiOS is a Modern version of CP/M written from the CP/M 2.2 API upwards in z80 Assembly - Created in the same way MS-DOS 1.0 was. 9 | 10 | LokiOS is a simple CP/M-compatible OS that is small, hardware agnostic, and builds upon CP/M concepts in a new way to maintain the original objectives of CP/M 2.2 11 | It provides a base for anyone needing a z80 based OS that works with CP/M software and calls while being updated and intended to be built upon for modern architectures. 12 | 13 | The BIOS and BDOS together sit within a 4K memory space making it ideal to port to low-memory systems and it brings none of the previous disk baggage 14 | from other versions of CP/M that require physical floppy hardware which is often omitted from modern CP/M designs. 15 | 16 | LokiOS has also been optimized for modern storage and memory requirements, without limits and without requiring paging through the ability to support large memory spaces 17 | in hardware. The code itself however is somewhat skeletal, allowing room for significant customisation. It will connect straight up to Flash and other drives 18 | though if you meet the hardware requirements. 19 | 20 | It is written entirely in z80 assembler, and uses the extended capabilities of the processor, so will not work on 8080/8085 systems. 21 | 22 | It is intended to run any software that would run on CP/M 2.2 and can be ported to most z80 based systems. All source is documented, and the assembler, while BASIC 23 | and Command Line, can be compiled on modern systems, so no more Windows 11 compatability issues with older assemblers ! 24 | 25 | The primary differences between LokiOS and CP/M are; 26 | * LokiOS is designed to run on a minimum z80 system and does not support 8080/8085 based systems. 27 | * It is Case Insensitive ( CP/M is just CAPS only ) 28 | * The addition of new commands into the CCP - 29 | # Copy - Copies files in a similar way to DOS copy. 30 | # Monitor - Built in monitor 31 | * Makes all Restarts available for the user, and can separate RSTs from Mode 0 Interrupts ( or can merge them ) 32 | * Uses reserved memory from 0040H to 005BH inclusive for Video and Network hooks 33 | * Inbuilt support for TCP and UDP 34 | * Supports remote network disk sharing and mounting under TCP and UDP port 280 (z80) or Decimal 640 for Loki Network Protocol. ( Under Construction ) 35 | * Extends network controls to CP/M ( Under Construction ) 36 | * Designed for 1Mb of memory space (Version 1) including mapping boot BIOSes and disk auto boot executable capability. 37 | * Simple bootstrap if the full OS is built. 38 | 39 | But 40 | * Can be used with JUST the BDOS and BIOS and CCP or can swap between Loki and DRI BDOS or CCP. 41 | 42 | Future iterations intentions to support; 43 | * Hardware architectures to support large memory systems without requiring paging by accessing all memory space under existing BDOS calls. 44 | * Hardware architectures to support large-scale Symmetrical Multiprocessor environments. 45 | * Memory architectures beginning at 256Mb and extending out to any size. 46 | 47 | Loki-OS in it's basic format is a suitable replacement for CP/M for modern z80 computer systems with backwards compatability to CP/M 2.2, and although it is designed for the Loki and Loki MAD architectures, it is less hardware fixed than you might imagine, and would be easy to rebuild for other architectures. 48 | 49 | This repository is designed for PCs or x86 machines, and uses FREEBASIC as the initial language. Download FREEBASIC, copy this respository to the Freebasic directory ( where the FBC.EXE or FBC64.EXE files exist ) and run the MAKE batch file for a clean set of binaries you should be able to trust. You may need to rename FBC_64.EXE or whatever the latest is to FBC.EXE - but it first builds an assembler then it builds the emulation environment, then the support files, then it assembles all of the z80 binaries with the new assembler. I'll include instructions on that elsewhere. 50 | -------------------------------------------------------------------------------- /altccp.asm: -------------------------------------------------------------------------------- 1 | ;************************************************************** 2 | ;* 3 | ;* C P / M version 2 . 2 CCP VERSION 4 | ;* 5 | ;* Reconstructed from memory image on February 27, 1981 6 | ;* 7 | ;* by Clark A. Calkins 8 | ;* 9 | ;************************************************************** 10 | ; 11 | ; Set memory limit here. This is the amount of contigeous 12 | ; ram starting from 0000. CP/M will reside at the end of this space. 13 | ; 14 | EQU MEM , 62 ;for a 62k system (TS802 TEST - WORKS OK). 15 | ; 16 | EQU IOBYTE , 3 ;i/o definition byte. 17 | EQU TDRIVE , 4 ;current drive name and user number. 18 | EQU ENTRY , 5 ;entry point for the cp/m bdos. 19 | EQU TFCB , $5C ;default file control block. 20 | EQU TBUFF , $80 ;i/o buffer and command line storage. 21 | EQU TBASE , $100 ;transiant program storage area. 22 | ; 23 | ; Set control character equates. 24 | ; 25 | EQU CNTRLC , 3 ;control-c 26 | EQU CNTRLE , $05 ;control-e 27 | EQU BS , $08 ;backspace 28 | EQU TAB , $09 ;tab 29 | EQU LF , $0A ;line feed 30 | EQU FF , $0C ;form feed 31 | EQU CR , $0D ;carriage return 32 | EQU CNTRLP , $10 ;control-p 33 | EQU CNTRLR , $12 ;control-r 34 | EQU CNTRLS , $13 ;control-s 35 | EQU CNTRLU , $15 ;control-u 36 | EQU CNTRLX , $18 ;control-x 37 | EQU CNTRLZ , $1A ;control-z (end-of-file mark) 38 | EQU DEL , $7F ;rubout 39 | ; 40 | ; Set origin for CP/M 41 | ; 42 | ; ORG MEM-7*1024 43 | org $E500 44 | ; 45 | CBASE: JP COMMAND ;execute command processor (ccp). 46 | JP CLEARBUF ;entry to empty input buffer before starting ccp. 47 | 48 | ; 49 | ; Standard cp/m ccp input buffer. Format is (max length), 50 | ; (actual length), (char #1), (char #2), (char #3), etc. 51 | ; 52 | INBUFF: DEFB 127 ;length of input buffer. 53 | DEFB 0 ;current length of contents. 54 | INPOINTER: DEFB 'Copyright' 55 | DEFB ' 1979 (c) by Digital Research ' 56 | DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 57 | DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 58 | DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 59 | DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 60 | INPOINT:DEFW INPOINTER ;input line pointer 61 | NAMEPNT:DEFW 0 ;input line pointer used for error message. Points to 62 | ; ;start of name in error. 63 | ; 64 | ; Routine to print (A) on the console. All registers used. 65 | ; 66 | PRINT: LD E,A ;setup bdos call. 67 | LD C,2 68 | JP ENTRY 69 | ; 70 | ; Routine to print (A) on the console and to save (BC). 71 | ; 72 | PRINTB: PUSH BC 73 | CALL PRINT 74 | POP BC 75 | RET 76 | ; 77 | ; Routine to send a carriage return, line feed combination 78 | ; to the console. 79 | ; 80 | CRLF: LD A,CR 81 | CALL PRINTB 82 | LD A,LF 83 | JP PRINTB 84 | ; 85 | ; Routine to send one space to the console and save (BC). 86 | ; 87 | SPACE: LD A,' ' 88 | JP PRINTB 89 | ; 90 | ; Routine to print character string pointed to be (BC) on the 91 | ; console. It must terminate with a null byte. 92 | ; 93 | PLINE: PUSH BC 94 | CALL CRLF 95 | POP HL 96 | PLINE2: LD A,(HL) 97 | OR A 98 | RET Z 99 | INC HL 100 | PUSH HL 101 | CALL PRINT 102 | POP HL 103 | JP PLINE2 104 | ; 105 | ; Routine to reset the disk system. 106 | ; 107 | RESDSK: LD C,13 108 | JP ENTRY 109 | ; 110 | ; Routine to select disk (A). 111 | ; 112 | DSKSEL: LD E,A 113 | LD C,14 114 | JP ENTRY 115 | ; 116 | ; Routine to call bdos and save the return code. The zero 117 | ; flag is set on a return of $FF. 118 | ; 119 | ENTRY1: CALL ENTRY 120 | LD (RTNCODE),A ;save return code. 121 | INC A ;set zero if $FF returned. 122 | RET 123 | ; 124 | ; Routine to open a file. (DE) must point to the FCB. 125 | ; 126 | OPEN: LD C,15 127 | JP ENTRY1 128 | ; 129 | ; Routine to open file at (FCB). 130 | ; 131 | OPENFCB:XOR A ;clear the record number byte at fcb+32 132 | LD (FCB+32),A 133 | LD DE,FCB 134 | JP OPEN 135 | ; 136 | ; Routine to close a file. (DE) points to FCB. 137 | ; 138 | CLOSE: LD C,16 139 | JP ENTRY1 140 | ; 141 | ; Routine to search for the first file with ambigueous name 142 | ; (DE). 143 | ; 144 | SRCHFST:LD C,17 145 | JP ENTRY1 146 | ; 147 | ; Search for the next ambigeous file name. 148 | ; 149 | SRCHNXT:LD C,18 150 | JP ENTRY1 151 | ; 152 | ; Search for file at (FCB). 153 | ; 154 | SRCHFCB:LD DE,FCB 155 | JP SRCHFST 156 | ; 157 | ; Routine to delete a file pointed to by (DE). 158 | ; 159 | DELETE: LD C,19 160 | JP ENTRY 161 | ; 162 | ; Routine to call the bdos and set the zero flag if a zero 163 | ; status is returned. 164 | ; 165 | ENTRY2: CALL ENTRY 166 | OR A ;set zero flag if appropriate. 167 | RET 168 | ; 169 | ; Routine to read the next record from a sequential file. 170 | ; (DE) points to the FCB. 171 | ; 172 | RDREC: LD C,20 173 | JP ENTRY2 174 | ; 175 | ; Routine to read file at (FCB). 176 | ; 177 | READFCB:LD DE,FCB 178 | JP RDREC 179 | ; 180 | ; Routine to write the next record of a sequential file. 181 | ; (DE) points to the FCB. 182 | ; 183 | WRTREC: LD C,21 184 | JP ENTRY2 185 | ; 186 | ; Routine to create the file pointed to by (DE). 187 | ; 188 | CREATE: LD C,22 189 | JP ENTRY1 190 | ; 191 | ; Routine to rename the file pointed to by (DE). Note that 192 | ; the new name starts at (DE+16). 193 | ; 194 | RENAM: LD C,23 195 | JP ENTRY 196 | ; 197 | ; Get the current user code. 198 | ; 199 | GETUSR: LD E,$FF 200 | ; 201 | ; Routne to get or set the current user code. 202 | ; If (E) is FF then this is a GET, else it is a SET. 203 | ; 204 | GETSETUC: LD C,32 205 | JP ENTRY 206 | ; 207 | ; Routine to set the current drive byte at (TDRIVE). 208 | ; 209 | SETCDRV:CALL GETUSR ;get user number 210 | ADD A,A ;and shift into the upper 4 bits. 211 | ADD A,A 212 | ADD A,A 213 | ADD A,A 214 | LD HL,CDRIVE ;now add in the current drive number. 215 | OR (HL) 216 | LD (TDRIVE),A ;and save. 217 | RET 218 | ; 219 | ; Move currently active drive down to (TDRIVE). 220 | ; 221 | MOVECD: LD A,(CDRIVE) 222 | LD (TDRIVE),A 223 | RET 224 | ; 225 | ; Routine to convert (A) into upper case ascii. Only letters 226 | ; are affected. 227 | ; 228 | UPPER: CP 'a' ;check for letters in the range of 'a' to 'z'. 229 | RET C 230 | CP '{' 231 | RET NC 232 | AND $5F ;convert it if found. 233 | RET 234 | ; 235 | ; Routine to get a line of input. We must check to see if the 236 | ; user is in (BATCH) mode. If so, then read the input from file 237 | ; ($$$.SUB). At the end, reset to console input. 238 | ; 239 | GETINP: LD A,(BATCH) ;if =0, then use console input. 240 | OR A 241 | JP Z,GETINP1 242 | ; 243 | ; Use the submit file ($$$.sub) which is prepared by a 244 | ; SUBMIT run. It must be on drive (A) and it will be deleted 245 | ; if and error occures (like eof). 246 | ; 247 | LD A,(CDRIVE) ;select drive 0 if need be. 248 | OR A 249 | LD A,0 ;always use drive A for submit. 250 | CALL NZ,DSKSEL ;select it if required. 251 | LD DE,BATCHFCB 252 | CALL OPEN ;look for it. 253 | JP Z,GETINP1 ;if not there, use normal input. 254 | LD A,(BATCHFCB+15) ;get last record number+1. 255 | DEC A 256 | LD (BATCHFCB+32),A 257 | LD DE,BATCHFCB 258 | CALL RDREC ;read last record. 259 | JP NZ,GETINP1 ;quit on end of file. 260 | ; 261 | ; Move this record into input buffer. 262 | ; 263 | LD DE,INBUFF+1 264 | LD HL,TBUFF ;data was read into buffer here. 265 | LD B,128 ;all 128 characters may be used. 266 | CALL HL2DE ;(HL) to (DE), (B) bytes. 267 | LD HL,BATCHFCB+14 268 | LD (HL),0 ;zero out the 's2' byte. 269 | INC HL ;and decrement the record count. 270 | DEC (HL) 271 | LD DE,BATCHFCB ;close the batch file now. 272 | CALL CLOSE 273 | JP Z,GETINP1 ;quit on an error. 274 | LD A,(CDRIVE) ;re-select previous drive if need be. 275 | OR A 276 | CALL NZ,DSKSEL ;don't do needless selects. 277 | ; 278 | ; Print line just read on console. 279 | ; 280 | LD HL,INBUFF+2 281 | CALL PLINE2 282 | CALL CHKCON ;check console, quit on a key. 283 | JP Z,GETINP2 ;jump if no key is pressed. 284 | ; 285 | ; Terminate the submit job on any keyboard input. Delete this 286 | ; file such that it is not re-started and jump to normal keyboard 287 | ; input section. 288 | ; 289 | CALL DELBATCH ;delete the batch file. 290 | JP CMMND1 ;and restart command input. 291 | ; 292 | ; Get here for normal keyboard input. Delete the submit file 293 | ; incase there was one. 294 | ; 295 | GETINP1:CALL DELBATCH ;delete file ($$$.sub). 296 | CALL SETCDRV ;reset active disk. 297 | LD C,10 ;get line from console device. 298 | LD DE,INBUFF 299 | CALL ENTRY 300 | CALL MOVECD ;reset current drive (again). 301 | ; 302 | ; Convert input line to upper case. 303 | ; 304 | GETINP2:LD HL,INBUFF+1 305 | LD B,(HL) ;(B)=character counter. 306 | GETINP3:INC HL 307 | LD A,B ;end of the line? 308 | OR A 309 | JP Z,GETINP4 310 | LD A,(HL) ;convert to upper case. 311 | CALL UPPER 312 | LD (HL),A 313 | DEC B ;adjust character count. 314 | JP GETINP3 315 | GETINP4:LD (HL),A ;add trailing null. 316 | LD HL,INBUFF+2 317 | LD (INPOINT),HL ;reset input line pointer. 318 | RET 319 | ; 320 | ; Routine to check the console for a key pressed. The zero 321 | ; flag is set is none, else the character is returned in (A). 322 | ; 323 | CHKCON: LD C,11 ;check console. 324 | CALL ENTRY 325 | OR A 326 | RET Z ;return if nothing. 327 | LD C,1 ;else get character. 328 | CALL ENTRY 329 | OR A ;clear zero flag and return. 330 | RET 331 | ; 332 | ; Routine to get the currently active drive number. 333 | ; 334 | GETDSK: LD C,25 335 | JP ENTRY 336 | ; 337 | ; Set the stabdard dma address. 338 | ; 339 | STDDMA: LD DE,TBUFF 340 | ; 341 | ; Routine to set the dma address to (DE). 342 | ; 343 | DMASET: LD C,26 344 | JP ENTRY 345 | ; 346 | ; Delete the batch file created by SUBMIT. 347 | ; 348 | DELBATCH: LD HL,BATCH ;is batch active? 349 | LD A,(HL) 350 | OR A 351 | RET Z 352 | LD (HL),0 ;yes, de-activate it. 353 | XOR A 354 | CALL DSKSEL ;select drive 0 for sure. 355 | LD DE,BATCHFCB ;and delete this file. 356 | CALL DELETE 357 | LD A,(CDRIVE) ;reset current drive. 358 | JP DSKSEL 359 | ; 360 | ; Check to two strings at (PATTRN1) and (PATTRN2). They must be 361 | ; the same or we halt.... 362 | ; 363 | VERIFY: LD DE,PATTRN1 ;these are the serial number bytes. 364 | LD HL,PATTRN2 ;ditto, but how could they be different? 365 | LD B,6 ;6 bytes each. 366 | VERIFY1:LD A,(DE) 367 | CP (HL) 368 | JP NZ,HALT ;jump to halt routine. 369 | INC DE 370 | INC HL 371 | DEC B 372 | JP NZ,VERIFY1 373 | RET 374 | ; 375 | ; Print back file name with a '?' to indicate a syntax error. 376 | ; 377 | SYNERR: CALL CRLF ;end current line. 378 | LD HL,(NAMEPNT) ;this points to name in error. 379 | SYNERR1:LD A,(HL) ;print it until a space or null is found. 380 | CP ' ' 381 | JP Z,SYNERR2 382 | OR A 383 | JP Z,SYNERR2 384 | PUSH HL 385 | CALL PRINT 386 | POP HL 387 | INC HL 388 | JP SYNERR1 389 | SYNERR2:LD A,'?' ;add trailing '?'. 390 | CALL PRINT 391 | CALL CRLF 392 | CALL DELBATCH ;delete any batch file. 393 | JP CMMND1 ;and restart from console input. 394 | ; 395 | ; Check character at (DE) for legal command input. Note that the 396 | ; zero flag is set if the character is a delimiter. 397 | ; 398 | CHECK: LD A,(DE) 399 | OR A 400 | RET Z 401 | CP ' ' ;control characters are not legal here. 402 | JP C,SYNERR 403 | RET Z ;check for valid delimiter. 404 | CP '=' 405 | RET Z 406 | CP '_' 407 | RET Z 408 | CP '.' 409 | RET Z 410 | CP ':' 411 | RET Z 412 | CP ';' 413 | RET Z 414 | CP '<' 415 | RET Z 416 | CP '>' 417 | RET Z 418 | RET 419 | ; 420 | ; Get the next non-blank character from (DE). 421 | ; 422 | NONBLANK: LD A,(DE) 423 | OR A ;string ends with a null. 424 | RET Z 425 | CP ' ' 426 | RET NZ 427 | INC DE 428 | JP NONBLANK 429 | ; 430 | ; Add (HL)=(HL)+(A) 431 | ; 432 | ADDHL: ADD A,L 433 | LD L,A 434 | RET NC ;take care of any carry. 435 | INC H 436 | RET 437 | ; 438 | ; Convert the first name in (FCB). 439 | ; 440 | CONVFST:LD A,0 441 | ; 442 | ; Format a file name (convert * to '?', etc.). On return, 443 | ; (A)=0 is an unambigeous name was specified. Enter with (A) equal to 444 | ; the position within the fcb for the name (either 0 or 16). 445 | ; 446 | CONVERT:LD HL,FCB 447 | CALL ADDHL 448 | PUSH HL 449 | PUSH HL 450 | XOR A 451 | LD (CHGDRV),A ;initialize drive change flag. 452 | LD HL,(INPOINT) ;set (HL) as pointer into input line. 453 | EX DE,HL 454 | CALL NONBLANK ;get next non-blank character. 455 | EX DE,HL 456 | LD (NAMEPNT),HL ;save pointer here for any error message. 457 | EX DE,HL 458 | POP HL 459 | LD A,(DE) ;get first character. 460 | OR A 461 | JP Z,CONVRT1 462 | SBC A,'A'-1 ;might be a drive name, convert to binary. 463 | LD B,A ;and save. 464 | INC DE ;check next character for a ':'. 465 | LD A,(DE) 466 | CP ':' 467 | JP Z,CONVRT2 468 | DEC DE ;nope, move pointer back to the start of the line. 469 | CONVRT1:LD A,(CDRIVE) 470 | LD (HL),A 471 | JP CONVRT3 472 | CONVRT2:LD A,B 473 | LD (CHGDRV),A ;set change in drives flag. 474 | LD (HL),B 475 | INC DE 476 | ; 477 | ; Convert the basic file name. 478 | ; 479 | CONVRT3:LD B,$08 480 | CONVRT4:CALL CHECK 481 | JP Z,CONVRT8 482 | INC HL 483 | CP '*' ;note that an '*' will fill the remaining 484 | JP NZ,CONVRT5 ;field with '?'. 485 | LD (HL),'?' 486 | JP CONVRT6 487 | CONVRT5:LD (HL),A 488 | INC DE 489 | CONVRT6:DEC B 490 | JP NZ,CONVRT4 491 | CONVRT7:CALL CHECK ;get next delimiter. 492 | JP Z,GETEXT 493 | INC DE 494 | JP CONVRT7 495 | CONVRT8:INC HL ;blank fill the file name. 496 | LD (HL),' ' 497 | DEC B 498 | JP NZ,CONVRT8 499 | ; 500 | ; Get the extension and convert it. 501 | ; 502 | GETEXT: LD B,$03 503 | CP '.' 504 | JP NZ,GETEXT5 505 | INC DE 506 | GETEXT1:CALL CHECK 507 | JP Z,GETEXT5 508 | INC HL 509 | CP '*' 510 | JP NZ,GETEXT2 511 | LD (HL),'?' 512 | JP GETEXT3 513 | GETEXT2:LD (HL),A 514 | INC DE 515 | GETEXT3:DEC B 516 | JP NZ,GETEXT1 517 | GETEXT4:CALL CHECK 518 | JP Z,GETEXT6 519 | INC DE 520 | JP GETEXT4 521 | GETEXT5:INC HL 522 | LD (HL),' ' 523 | DEC B 524 | JP NZ,GETEXT5 525 | GETEXT6:LD B,3 526 | GETEXT7:INC HL 527 | LD (HL),0 528 | DEC B 529 | JP NZ,GETEXT7 530 | EX DE,HL 531 | LD (INPOINT),HL ;save input line pointer. 532 | POP HL 533 | ; 534 | ; Check to see if this is an ambigeous file name specification. 535 | ; Set the (A) register to non zero if it is. 536 | ; 537 | LD BC,11 ;set name length. 538 | GETEXT8:INC HL 539 | LD A,(HL) 540 | CP '?' ;any question marks? 541 | JP NZ,GETEXT9 542 | INC B ;count them. 543 | GETEXT9:DEC C 544 | JP NZ,GETEXT8 545 | LD A,B 546 | OR A 547 | RET 548 | ; 549 | ; CP/M command table. Note commands can be either 3 or 4 characters long. 550 | ; 551 | EQU NUMCMDS , 6 ;number of commands 552 | CMDTBL: DEFB 'DIR ' 553 | DEFB 'ERA ' 554 | DEFB 'TYPE' 555 | DEFB 'SAVE' 556 | DEFB 'REN ' 557 | DEFB 'USER' 558 | ; 559 | ; The following six bytes must agree with those at (PATTRN2) 560 | ; or cp/m will HALT. Why? 561 | ; 562 | PATTRN1:DEFB 0,22,0,0,0,0 ;(* serial number bytes *). 563 | ; 564 | ; Search the command table for a match with what has just 565 | ; been entered. If a match is found, then we jump to the 566 | ; proper section. Else jump to (UNKNOWN). 567 | ; On return, the (C) register is set to the command number 568 | ; that matched (or NUMCMDS+1 if no match). 569 | ; 570 | SEARCH: LD HL,CMDTBL 571 | LD C,0 572 | SEARCH1:LD A,C 573 | CP NUMCMDS ;this commands exists. 574 | RET NC 575 | LD DE,FCB+1 ;check this one. 576 | LD B,4 ;max command length. 577 | SEARCH2:LD A,(DE) 578 | CP (HL) 579 | JP NZ,SEARCH3 ;not a match. 580 | INC DE 581 | INC HL 582 | DEC B 583 | JP NZ,SEARCH2 584 | LD A,(DE) ;allow a 3 character command to match. 585 | CP ' ' 586 | JP NZ,SEARCH4 587 | LD A,C ;set return register for this command. 588 | RET 589 | SEARCH3:INC HL 590 | DEC B 591 | JP NZ,SEARCH3 592 | SEARCH4:INC C 593 | JP SEARCH1 594 | ; 595 | ; Set the input buffer to empty and then start the command 596 | ; processor (ccp). 597 | ; 598 | CLEARBUF: XOR A 599 | LD (INBUFF+1),A ;second byte is actual length. 600 | ; 601 | ;************************************************************** 602 | ;* 603 | ;* 604 | ;* C C P - C o n s o l e C o m m a n d P r o c e s s o r 605 | ;* 606 | ;************************************************************** 607 | ;* 608 | COMMAND:LD SP,CCPSTACK ;setup stack area. 609 | PUSH BC ;note that (C) should be equal to: 610 | LD A,C ;(uuuudddd) where 'uuuu' is the user number 611 | RRA ;and 'dddd' is the drive number. 612 | RRA 613 | RRA 614 | RRA 615 | AND $0F ;isolate the user number. 616 | LD E,A 617 | CALL GETSETUC ;and set it. 618 | CALL RESDSK ;reset the disk system. 619 | LD (BATCH),A ;clear batch mode flag. 620 | POP BC 621 | LD A,C 622 | AND $0F ;isolate the drive number. 623 | LD (CDRIVE),A ;and save. 624 | CALL DSKSEL ;...and select. 625 | LD A,(INBUFF+1) 626 | OR A ;anything in input buffer already? 627 | JP NZ,CMMND2 ;yes, we just process it. 628 | ; 629 | ; Entry point to get a command line from the console. 630 | ; 631 | CMMND1: LD SP,CCPSTACK ;set stack straight. 632 | CALL CRLF ;start a new line on the screen. 633 | CALL GETDSK ;get current drive. 634 | ADD A,'A' 635 | CALL PRINT ;print current drive. 636 | LD A,'>' 637 | CALL PRINT ;and add prompt. 638 | CALL GETINP ;get line from user. 639 | ; 640 | ; Process command line here. 641 | ; 642 | CMMND2: LD DE,TBUFF 643 | CALL DMASET ;set standard dma address. 644 | CALL GETDSK 645 | LD (CDRIVE),A ;set current drive. 646 | CALL CONVFST ;convert name typed in. 647 | CALL NZ,SYNERR ;wild cards are not allowed. 648 | LD A,(CHGDRV) ;if a change in drives was indicated, 649 | OR A ;then treat this as an unknown command 650 | JP NZ,UNKNOWN ;which gets executed. 651 | CALL SEARCH ;else search command table for a match. 652 | ; 653 | ; Note that an unknown command returns 654 | ; with (A) pointing to the last address 655 | ; in our table which is (UNKNOWN). 656 | ; 657 | LD HL,CMDADR ;now, look thru our address table for command (A). 658 | LD E,A ;set (DE) to command number. 659 | LD D,0 660 | ADD HL,DE 661 | ADD HL,DE ;(HL)=(CMDADR)+2*(command number). 662 | LD A,(HL) ;now pick out this address. 663 | INC HL 664 | LD H,(HL) 665 | LD L,A 666 | JP (HL) ;now execute it. 667 | ; 668 | ; CP/M command address table. 669 | ; 670 | CMDADR: DEFW DIRECT,ERASE,TYPE,SAVE 671 | DEFW RENAME,USER,UNKNOWN 672 | ; 673 | ; Halt the system. Reason for this is unknown at present. 674 | ; 675 | HALT: LD HL,$76F3 ;'DI HLT' instructions. 676 | LD (CBASE),HL 677 | LD HL,CBASE 678 | JP (HL) 679 | ; 680 | ; Read error while TYPEing a file. 681 | ; 682 | RDERROR:LD BC,RDERR 683 | JP PLINE 684 | RDERR: DEFB 'Read error',0 685 | ; 686 | ; Required file was not located. 687 | ; 688 | NONE: LD BC,NOFILE 689 | JP PLINE 690 | NOFILE: DEFB 'No file',0 691 | ; 692 | ; Decode a command of the form 'A>filename number{ filename}. 693 | ; Note that a drive specifier is not allowed on the first file 694 | ; name. On return, the number is in register (A). Any error 695 | ; causes 'filename?' to be printed and the command is aborted. 696 | ; 697 | DECODE: CALL CONVFST ;convert filename. 698 | LD A,(CHGDRV) ;do not allow a drive to be specified. 699 | OR A 700 | JP NZ,SYNERR 701 | LD HL,FCB+1 ;convert number now. 702 | LD BC,11 ;(B)=sum register, (C)=max digit count. 703 | DECODE1:LD A,(HL) 704 | CP ' ' ;a space terminates the numeral. 705 | JP Z,DECODE3 706 | INC HL 707 | SUB '0' ;make binary from ascii. 708 | CP 10 ;legal digit? 709 | JP NC,SYNERR 710 | LD D,A ;yes, save it in (D). 711 | LD A,B ;compute (B)=(B)*10 and check for overflow. 712 | AND $0E0 713 | JP NZ,SYNERR 714 | LD A,B 715 | RLCA 716 | RLCA 717 | RLCA ;(A)=(B)*8 718 | ADD A,B ;.......*9 719 | JP C,SYNERR 720 | ADD A,B ;.......*10 721 | JP C,SYNERR 722 | ADD A,D ;add in new digit now. 723 | DECODE2:JP C,SYNERR 724 | LD B,A ;and save result. 725 | DEC C ;only look at 11 digits. 726 | JP NZ,DECODE1 727 | RET 728 | DECODE3:LD A,(HL) ;spaces must follow (why?). 729 | CP ' ' 730 | JP NZ,SYNERR 731 | INC HL 732 | DECODE4:DEC C 733 | JP NZ,DECODE3 734 | LD A,B ;set (A)=the numeric value entered. 735 | RET 736 | ; 737 | ; Move 3 bytes from (HL) to (DE). Note that there is only 738 | ; one reference to this at (A2D5h). 739 | ; 740 | MOVE3: LD B,3 741 | ; 742 | ; Move (B) bytes from (HL) to (DE). 743 | ; 744 | HL2DE: LD A,(HL) 745 | LD (DE),A 746 | INC HL 747 | INC DE 748 | DEC B 749 | JP NZ,HL2DE 750 | RET 751 | ; 752 | ; Compute (HL)=(TBUFF)+(A)+(C) and get the byte that's here. 753 | ; 754 | EXTRACT:LD HL,TBUFF 755 | ADD A,C 756 | CALL ADDHL 757 | LD A,(HL) 758 | RET 759 | ; 760 | ; Check drive specified. If it means a change, then the new 761 | ; drive will be selected. In any case, the drive byte of the 762 | ; fcb will be set to null (means use current drive). 763 | ; 764 | DSELECT:XOR A ;null out first byte of fcb. 765 | LD (FCB),A 766 | LD A,(CHGDRV) ;a drive change indicated? 767 | OR A 768 | RET Z 769 | DEC A ;yes, is it the same as the current drive? 770 | LD HL,CDRIVE 771 | CP (HL) 772 | RET Z 773 | JP DSKSEL ;no. Select it then. 774 | ; 775 | ; Check the drive selection and reset it to the previous 776 | ; drive if it was changed for the preceeding command. 777 | ; 778 | RESETDR:LD A,(CHGDRV) ;drive change indicated? 779 | OR A 780 | RET Z 781 | DEC A ;yes, was it a different drive? 782 | LD HL,CDRIVE 783 | CP (HL) 784 | RET Z 785 | LD A,(CDRIVE) ;yes, re-select our old drive. 786 | JP DSKSEL 787 | ; 788 | ;************************************************************** 789 | ;* 790 | ;* D I R E C T O R Y C O M M A N D 791 | ;* 792 | ;************************************************************** 793 | ; 794 | DIRECT: CALL CONVFST ;convert file name. 795 | CALL DSELECT ;select indicated drive. 796 | LD HL,FCB+1 ;was any file indicated? 797 | LD A,(HL) 798 | CP ' ' 799 | JP NZ,DIRECT2 800 | LD B,11 ;no. Fill field with '?' - same as *.*. 801 | DIRECT1:LD (HL),'?' 802 | INC HL 803 | DEC B 804 | JP NZ,DIRECT1 805 | DIRECT2:LD E,0 ;set initial cursor position. 806 | PUSH DE 807 | CALL SRCHFCB ;get first file name. 808 | CALL Z,NONE ;none found at all? 809 | DIRECT3:JP Z,DIRECT9 ;terminate if no more names. 810 | LD A,(RTNCODE) ;get file's position in segment (0-3). 811 | RRCA 812 | RRCA 813 | RRCA 814 | AND $60 ;(A)=position*32 815 | LD C,A 816 | LD A,10 817 | CALL EXTRACT ;extract the tenth entry in fcb. 818 | RLA ;check system file status bit. 819 | JP C,DIRECT8 ;we don't list them. 820 | POP DE 821 | LD A,E ;bump name count. 822 | INC E 823 | PUSH DE 824 | AND $03 ;at end of line? 825 | PUSH AF 826 | JP NZ,DIRECT4 827 | CALL CRLF ;yes, end this line and start another. 828 | PUSH BC 829 | CALL GETDSK ;start line with ('A:'). 830 | POP BC 831 | ADD A,'A' 832 | CALL PRINTB 833 | LD A,':' 834 | CALL PRINTB 835 | JP DIRECT5 836 | DIRECT4:CALL SPACE ;add seperator between file names. 837 | LD A,':' 838 | CALL PRINTB 839 | DIRECT5:CALL SPACE 840 | LD B,1 ;'extract' each file name character at a time. 841 | DIRECT6:LD A,B 842 | CALL EXTRACT 843 | AND $7F ;strip bit 7 (status bit). 844 | CP ' ' ;are we at the end of the name? 845 | JP NZ,DRECT65 846 | POP AF ;yes, don't print spaces at the end of a line. 847 | PUSH AF 848 | CP 3 849 | JP NZ,DRECT63 850 | LD A,9 ;first check for no extension. 851 | CALL EXTRACT 852 | AND $7F 853 | CP ' ' 854 | JP Z,DIRECT7 ;don't print spaces. 855 | DRECT63:LD A,' ' ;else print them. 856 | DRECT65:CALL PRINTB 857 | INC B ;bump to next character psoition. 858 | LD A,B 859 | CP 12 ;end of the name? 860 | JP NC,DIRECT7 861 | CP 9 ;nope, starting extension? 862 | JP NZ,DIRECT6 863 | CALL SPACE ;yes, add seperating space. 864 | JP DIRECT6 865 | DIRECT7:POP AF ;get the next file name. 866 | DIRECT8:CALL CHKCON ;first check console, quit on anything. 867 | JP NZ,DIRECT9 868 | CALL SRCHNXT ;get next name. 869 | JP DIRECT3 ;and continue with our list. 870 | DIRECT9:POP DE ;restore the stack and return to command level. 871 | JP GETBACK 872 | ; 873 | ;************************************************************** 874 | ;* 875 | ;* E R A S E C O M M A N D 876 | ;* 877 | ;************************************************************** 878 | ; 879 | ERASE: CALL CONVFST ;convert file name. 880 | CP 11 ;was '*.*' entered? 881 | JP NZ,ERASE1 882 | LD BC,YESNO ;yes, ask for confirmation. 883 | CALL PLINE 884 | CALL GETINP 885 | LD HL,INBUFF+1 886 | DEC (HL) ;must be exactly 'y'. 887 | JP NZ,CMMND1 888 | INC HL 889 | LD A,(HL) 890 | CP 'Y' 891 | JP NZ,CMMND1 892 | INC HL 893 | LD (INPOINT),HL ;save input line pointer. 894 | ERASE1: CALL DSELECT ;select desired disk. 895 | LD DE,FCB 896 | CALL DELETE ;delete the file. 897 | INC A 898 | CALL Z,NONE ;not there? 899 | JP GETBACK ;return to command level now. 900 | YESNO: DEFB 'All (y/n)?',0 901 | ; 902 | ;************************************************************** 903 | ;* 904 | ;* T Y P E C O M M A N D 905 | ;* 906 | ;************************************************************** 907 | ; 908 | TYPE: CALL CONVFST ;convert file name. 909 | JP NZ,SYNERR ;wild cards not allowed. 910 | CALL DSELECT ;select indicated drive. 911 | CALL OPENFCB ;open the file. 912 | JP Z,TYPE5 ;not there? 913 | CALL CRLF ;ok, start a new line on the screen. 914 | LD HL,NBYTES ;initialize byte counter. 915 | LD (HL),$FF ;set to read first sector. 916 | TYPE1: LD HL,NBYTES 917 | TYPE2: LD A,(HL) ;have we written the entire sector? 918 | CP 128 919 | JP C,TYPE3 920 | PUSH HL ;yes, read in the next one. 921 | CALL READFCB 922 | POP HL 923 | JP NZ,TYPE4 ;end or error? 924 | XOR A ;ok, clear byte counter. 925 | LD (HL),A 926 | TYPE3: INC (HL) ;count this byte. 927 | LD HL,TBUFF ;and get the (A)th one from the buffer (TBUFF). 928 | CALL ADDHL 929 | LD A,(HL) 930 | CP CNTRLZ ;end of file mark? 931 | JP Z,GETBACK 932 | CALL PRINT ;no, print it. 933 | CALL CHKCON ;check console, quit if anything ready. 934 | JP NZ,GETBACK 935 | JP TYPE1 936 | ; 937 | ; Get here on an end of file or read error. 938 | ; 939 | TYPE4: DEC A ;read error? 940 | JP Z,GETBACK 941 | CALL RDERROR ;yes, print message. 942 | TYPE5: CALL RESETDR ;and reset proper drive 943 | JP SYNERR ;now print file name with problem. 944 | ; 945 | ;************************************************************** 946 | ;* 947 | ;* S A V E C O M M A N D 948 | ;* 949 | ;************************************************************** 950 | ; 951 | SAVE: CALL DECODE ;get numeric number that follows SAVE. 952 | PUSH AF ;save number of pages to write. 953 | CALL CONVFST ;convert file name. 954 | JP NZ,SYNERR ;wild cards not allowed. 955 | CALL DSELECT ;select specified drive. 956 | LD DE,FCB ;now delete this file. 957 | PUSH DE 958 | CALL DELETE 959 | POP DE 960 | CALL CREATE ;and create it again. 961 | JP Z,SAVE3 ;can't create? 962 | XOR A ;clear record number byte. 963 | LD (FCB+32),A 964 | POP AF ;convert pages to sectors. 965 | LD L,A 966 | LD H,0 967 | ADD HL,HL ;(HL)=number of sectors to write. 968 | LD DE,TBASE ;and we start from here. 969 | SAVE1: LD A,H ;done yet? 970 | OR L 971 | JP Z,SAVE2 972 | DEC HL ;nope, count this and compute the start 973 | PUSH HL ;of the next 128 byte sector. 974 | LD HL,128 975 | ADD HL,DE 976 | PUSH HL ;save it and set the transfer address. 977 | CALL DMASET 978 | LD DE,FCB ;write out this sector now. 979 | CALL WRTREC 980 | POP DE ;reset (DE) to the start of the last sector. 981 | POP HL ;restore sector count. 982 | JP NZ,SAVE3 ;write error? 983 | JP SAVE1 984 | ; 985 | ; Get here after writing all of the file. 986 | ; 987 | SAVE2: LD DE,FCB ;now close the file. 988 | CALL CLOSE 989 | INC A ;did it close ok? 990 | JP NZ,SAVE4 991 | ; 992 | ; Print out error message (no space). 993 | ; 994 | SAVE3: LD BC,NOSPACE 995 | CALL PLINE 996 | SAVE4: CALL STDDMA ;reset the standard dma address. 997 | JP GETBACK 998 | NOSPACE:DEFB 'No space',0 999 | ; 1000 | ;************************************************************** 1001 | ;* 1002 | ;* R E N A M E C O M M A N D 1003 | ;* 1004 | ;************************************************************** 1005 | ; 1006 | RENAME: CALL CONVFST ;convert first file name. 1007 | JP NZ,SYNERR ;wild cards not allowed. 1008 | LD A,(CHGDRV) ;remember any change in drives specified. 1009 | PUSH AF 1010 | CALL DSELECT ;and select this drive. 1011 | CALL SRCHFCB ;is this file present? 1012 | JP NZ,RENAME6 ;yes, print error message. 1013 | LD HL,FCB ;yes, move this name into second slot. 1014 | LD DE,FCB+16 1015 | LD B,16 1016 | CALL HL2DE 1017 | LD HL,(INPOINT) ;get input pointer. 1018 | EX DE,HL 1019 | CALL NONBLANK ;get next non blank character. 1020 | CP '=' ;only allow an '=' or '_' seperator. 1021 | JP Z,RENAME1 1022 | CP '_' 1023 | JP NZ,RENAME5 1024 | RENAME1:EX DE,HL 1025 | INC HL ;ok, skip seperator. 1026 | LD (INPOINT),HL ;save input line pointer. 1027 | CALL CONVFST ;convert this second file name now. 1028 | JP NZ,RENAME5 ;again, no wild cards. 1029 | POP AF ;if a drive was specified, then it 1030 | LD B,A ;must be the same as before. 1031 | LD HL,CHGDRV 1032 | LD A,(HL) 1033 | OR A 1034 | JP Z,RENAME2 1035 | CP B 1036 | LD (HL),B 1037 | JP NZ,RENAME5 ;they were different, error. 1038 | RENAME2:LD (HL),B ; reset as per the first file specification. 1039 | XOR A 1040 | LD (FCB),A ;clear the drive byte of the fcb. 1041 | RENAME3:CALL SRCHFCB ;and go look for second file. 1042 | JP Z,RENAME4 ;doesn't exist? 1043 | LD DE,FCB 1044 | CALL RENAM ;ok, rename the file. 1045 | JP GETBACK 1046 | ; 1047 | ; Process rename errors here. 1048 | ; 1049 | RENAME4:CALL NONE ;file not there. 1050 | JP GETBACK 1051 | RENAME5:CALL RESETDR ;bad command format. 1052 | JP SYNERR 1053 | RENAME6:LD BC,EXISTS ;destination file already exists. 1054 | CALL PLINE 1055 | JP GETBACK 1056 | EXISTS: DEFB 'File exists',0 1057 | ; 1058 | ;************************************************************** 1059 | ;* 1060 | ;* U S E R C O M M A N D 1061 | ;* 1062 | ;************************************************************** 1063 | ; 1064 | USER: CALL DECODE ;get numeric value following command. 1065 | CP 16 ;legal user number? 1066 | JP NC,SYNERR 1067 | LD E,A ;yes but is there anything else? 1068 | LD A,(FCB+1) 1069 | CP ' ' 1070 | JP Z,SYNERR ;yes, that is not allowed. 1071 | CALL GETSETUC ;ok, set user code. 1072 | JP GETBACK1 1073 | ; 1074 | ;************************************************************** 1075 | ;* 1076 | ;* T R A N S I A N T P R O G R A M C O M M A N D 1077 | ;* 1078 | ;************************************************************** 1079 | ; 1080 | UNKNOWN:CALL VERIFY ;check for valid system (why?). 1081 | LD A,(FCB+1) ;anything to execute? 1082 | CP ' ' 1083 | JP NZ,UNKWN1 1084 | LD A,(CHGDRV) ;nope, only a drive change? 1085 | OR A 1086 | JP Z,GETBACK1 ;neither??? 1087 | DEC A 1088 | LD (CDRIVE),A ;ok, store new drive. 1089 | CALL MOVECD ;set (TDRIVE) also. 1090 | CALL DSKSEL ;and select this drive. 1091 | JP GETBACK1 ;then return. 1092 | ; 1093 | ; Here a file name was typed. Prepare to execute it. 1094 | ; 1095 | UNKWN1: LD DE,FCB+9 ;an extension specified? 1096 | LD A,(DE) 1097 | CP ' ' 1098 | JP NZ,SYNERR ;yes, not allowed. 1099 | UNKWN2: PUSH DE 1100 | CALL DSELECT ;select specified drive. 1101 | POP DE 1102 | LD HL,COMFILE ;set the extension to 'COM'. 1103 | CALL MOVE3 1104 | CALL OPENFCB ;and open this file. 1105 | JP Z,UNKWN9 ;not present? 1106 | ; 1107 | ; Load in the program. 1108 | ; 1109 | LD HL,TBASE ;store the program starting here. 1110 | UNKWN3: PUSH HL 1111 | EX DE,HL 1112 | CALL DMASET ;set transfer address. 1113 | LD DE,FCB ;and read the next record. 1114 | CALL RDREC 1115 | JP NZ,UNKWN4 ;end of file or read error? 1116 | POP HL ;nope, bump pointer for next sector. 1117 | LD DE,128 1118 | ADD HL,DE 1119 | LD DE,CBASE ;enough room for the whole file? 1120 | LD A,L 1121 | SUB E 1122 | LD A,H 1123 | SBC A,D 1124 | JP NC,UNKWN0 ;no, it can't fit. 1125 | JP UNKWN3 1126 | ; 1127 | ; Get here after finished reading. 1128 | ; 1129 | UNKWN4: POP HL 1130 | DEC A ;normal end of file? 1131 | JP NZ,UNKWN0 1132 | CALL RESETDR ;yes, reset previous drive. 1133 | CALL CONVFST ;convert the first file name that follows 1134 | LD HL,CHGDRV ;command name. 1135 | PUSH HL 1136 | LD A,(HL) ;set drive code in default fcb. 1137 | LD (FCB),A 1138 | LD A,16 ;put second name 16 bytes later. 1139 | CALL CONVERT ;convert second file name. 1140 | POP HL 1141 | LD A,(HL) ;and set the drive for this second file. 1142 | LD (FCB+16),A 1143 | XOR A ;clear record byte in fcb. 1144 | LD (FCB+32),A 1145 | LD DE,TFCB ;move it into place at(0$5C). 1146 | LD HL,FCB 1147 | LD B,33 1148 | CALL HL2DE 1149 | LD HL,INBUFF+2 ;now move the remainder of the input 1150 | UNKWN5: LD A,(HL) ;line down to (00$80). Look for a non blank. 1151 | OR A ;or a null. 1152 | JP Z,UNKWN6 1153 | CP ' ' 1154 | JP Z,UNKWN6 1155 | INC HL 1156 | JP UNKWN5 1157 | ; 1158 | ; Do the line move now. It ends in a null byte. 1159 | ; 1160 | UNKWN6: LD B,0 ;keep a character count. 1161 | LD DE,TBUFF+1 ;data gets put here. 1162 | UNKWN7: LD A,(HL) ;move it now. 1163 | LD (DE),A 1164 | OR A 1165 | JP Z,UNKWN8 1166 | INC B 1167 | INC HL 1168 | INC DE 1169 | JP UNKWN7 1170 | UNKWN8: LD A,B ;now store the character count. 1171 | LD (TBUFF),A 1172 | CALL CRLF ;clean up the screen. 1173 | CALL STDDMA ;set standard transfer address. 1174 | CALL SETCDRV ;reset current drive. 1175 | CALL TBASE ;and execute the program. 1176 | ; 1177 | ; Transiant programs return here (or reboot). 1178 | ; 1179 | LD SP,BATCH ;set stack first off. 1180 | CALL MOVECD ;move current drive into place (TDRIVE). 1181 | CALL DSKSEL ;and reselect it. 1182 | JP CMMND1 ;back to comand mode. 1183 | ; 1184 | ; Get here if some error occured. 1185 | ; 1186 | UNKWN9: CALL RESETDR ;inproper format. 1187 | JP SYNERR 1188 | UNKWN0: LD BC,BADLOAD ;read error or won't fit. 1189 | CALL PLINE 1190 | JP GETBACK 1191 | BADLOAD:DEFB 'Bad load',0 1192 | COMFILE:DEFB 'COM' ;command file extension. 1193 | ; 1194 | ; Get here to return to command level. We will reset the 1195 | ; previous active drive and then either return to command 1196 | ; level directly or print error message and then return. 1197 | ; 1198 | GETBACK:CALL RESETDR ;reset previous drive. 1199 | GETBACK1: CALL CONVFST ;convert first name in (FCB). 1200 | LD A,(FCB+1) ;if this was just a drive change request, 1201 | SUB ' ' ;make sure it was valid. 1202 | LD HL,CHGDRV 1203 | OR (HL) 1204 | JP NZ,SYNERR 1205 | JP CMMND1 ;ok, return to command level. 1206 | ; 1207 | ; ccp stack area. 1208 | ; 1209 | DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 1210 | EQU CCPSTACK , ^ ;end of ccp stack area. 1211 | ; 1212 | ; Batch (or SUBMIT) processing information storage. 1213 | ; 1214 | BATCH: DEFB 0 ;batch mode flag (0=not active). 1215 | BATCHFCB: DEFB 0,'$$$ SUB',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 1216 | ; 1217 | ; File control block setup by the CCP. 1218 | ; 1219 | FCB: DEFB 0,' ',0,0,0,0,0,' ',0,0,0,0,0 1220 | RTNCODE:DEFB 0 ;status returned from bdos call. 1221 | CDRIVE: DEFB 0 ;currently active drive. 1222 | CHGDRV: DEFB 0 ;change in drives flag (0=no change). 1223 | NBYTES: DEFW 0 ;byte counter used by TYPE. 1224 | ; 1225 | ; Room for expansion? 1226 | ; 1227 | DEFB 0,0,0,0,0,0,0,0,0,0,0,0,0 1228 | ; 1229 | ; Note that the following six bytes must match those at 1230 | ; (PATTRN1) or cp/m will HALT. Why? - WHY, I think it's because it makes sure that the CCP is exactly 800 bytes as assembled. 1231 | ; Typically this goes straight onto the BDOS, so ensures the BDOS and CCP are of the same version. 1232 | ; 1233 | PATTRN2:DEFB 0,22,0,0,0,0 ;(* serial number bytes *). 1234 | 1235 | ; 1236 | ;************************************************************** 1237 | ;* 1238 | ;* B D O S E N T R Y 1239 | ;* 1240 | ;************************************************************** 1241 | ; BDOS REMOVED FROM FILE. 1242 | ; 1243 | ;************************************************************** 1244 | ;* 1245 | ;* B I O S J U M P T A B L E 1246 | ;* 1247 | ;************************************************************** 1248 | ; I really wish I knew why this bios has a -3 jump... Would be good to figure that out. Might be due to CP/M 3??? 1249 | ; Jump table here - Define the BIOS entry points. 1250 | .EQU BIOS ,$FC03 ; Location of BIOS. Though in reality, we have boot. Need to understand if BOOT=0 or BOOT=-3. 1251 | .EQU BOOT ,BIOS-3 ;-3: Cold Boot - Set up system. 1252 | .EQU WBOOT ,BIOS+0 ; 0: Warm boot - reload command processor 1253 | .EQU WBOOTE ,BIOS+0 1254 | .EQU CONST ,BIOS+3 ; 3: Console status - A=0 No character ready, A=FF character waiting to be read. 1255 | .EQU CONIN ,BIOS+6 ; 6: Console input - Wait until character then A=character. 1256 | .EQU CONOUT ,BIOS+9 ; 9: Console output - Write C register to screen. 1257 | .EQU LIST ,BIOS+12 ;12: Printer output - Wait until ready, then write C register to printer. 1258 | .EQU PUNCH ,BIOS+15 ;15: Paper tape punch output - Wait until ready then write C to Punch Reader ( or AUX ). 1259 | .EQU READER ,BIOS+18 ;18: Paper tape reader input - Wait until ready, then return A=Character. If not implemented, return 26 ( Ctrl Z ) 1260 | .EQU HOME ,BIOS+21 ;21: Move disc head to track 0 1261 | .EQU SELDSK ,BIOS+24 ;24: Select disc drive - C Register = disk 0...F. Enter with E=0 or E=FF. 1262 | ; If bit 0 of E is 0, then the disc is logged in as if new; if the format has to be determined from the boot sector, for example, this will be done. 1263 | ;If bit 0 if E is 1, then the disc has been logged in before. The disc is not accessed; the DPH address (or zero) is returned immediately. 1264 | ;SELDSK returns the address of a Disc Parameter Header in HL. The exact format of a DPH varies between CP/M versions; note that under CP/M 3, 1265 | ; the DPH is in memory bank 0 and probably not visible to programs. If the disc could not be selected it returns HL=0. 1266 | .EQU SETTRK ,BIOS+27 ;27: Set track number - Track in BC - Is a word - Starts at 0. 1267 | .EQU SETSEC ,BIOS+30 ;30: Set sector number - Sector in BC - Is a word. Starts at 1. I think. Will find out. 1268 | .EQU SETDMA ,BIOS+33 ;33: Set DMA address - Set DMA address. 1269 | .EQU READ ,BIOS+36 ;36: Read a sector - To DMA address. A=0 success. A=1 unrecoverable error. A=FF Media changed. 1270 | .EQU WRITE ,BIOS+39 ;39: Write a sector - C contains blocking code. 0=deferred. 1=immediate. 2=Can be deferred. No preread necessary. A=0 success. 1271 | ; A=1 unrecoverable error. A=FF Media changed. 1272 | ;In CP/M 2 and later, the following extra jumps appear: 1273 | .EQU LISTST ,BIOS+42 ;42: Status of list device - A=0 Printer not ready. A=FF -printer ready. 1274 | .EQU SECTRAN ,BIOS+45 ;45: Sector translation for skewing DE=Tableaddress (IGNORE DE). BC=Incoming sector (logical). HL=Translated sector (physical) . Return BC. 1275 | .EQU SECTRN ,BIOS+45 ;45: Sector translation for skewing DE=Tableaddress (IGNORE DE). BC=Incoming sector (logical). HL=Translated sector (physical) . Return BC. 1276 | 1277 | ;.NOTES 1278 | ;Everything after this point can just be text. It's just general notes. The assembly should stop at end or notes. 1279 | ;5.6 System Function Summary 1280 | ;Function 1281 | ;Number Function 1282 | ; Name Input Output 1283 | ;Dec Hex 1284 | ;0 0 System Reset none none 1285 | ;1 1 Console Input none A = ASCII char 1286 | ;2 2 Console Output E = char none 1287 | ;3 3 Reader Input none A = ASCII char 1288 | ;4 4 Punch Output E = char none 1289 | ;5 5 List Output E = char none 1290 | ;6 6 Direct Console I/O E = $FF (input) A = char 1291 | ; E = $FE (status) A = status 1292 | ; E = char none 1293 | ;7 7 Get I/O Byte none A = I/O byte value 1294 | ;8 8 Set I/O Byte E = I/O byte none 1295 | ;9 9 Print String DE = Buffer Address none 1296 | ;10 A Read Console String DE = Buffer Console characters in Buffer 1297 | ;11 B Get Console Status none A = 00/non zero 1298 | ;12 C Return Version # none HL = Version # 1299 | ;13 D Reset Disk System none none 1300 | ;14 E Select Disk E = Disk # none 1301 | ;15 F Open File DE = FCB address A = FF if not found 1302 | ;16 10 Close File DE = FCB address A = FF if not found 1303 | ;17 11 Search For First DE = FCB address A = Directory Code 1304 | ;18 12 Search For Next none A = Directory Code 1305 | ;19 13 Delete File DE = FCB address A = none 1306 | ;20 14 Read Sequential DE = FCB address A = Error Code 1307 | ;21 15 Write Sequential DE = FCB Address A = Error Code 1308 | ;22 16 Make File DE = FCB address A = FF if no DIR Space 1309 | ;23 17 Rename File DE = FCB address A = FF if not found 1310 | ;24 18 Return Login Vector none HL = Login Vector* 1311 | ;25 19 Return Current Disk none A = Current Disk Number 1312 | ;26 1A Set DMA Address DE = DMA address none 1313 | ;27 1B Get ADDR (ALLOC) none HL = ALLOC address* 1314 | ;28 1C Write Protect Disk none none 1315 | ;29 1D Get Read/only Vector none HL = ALLOC address* 1316 | ;30 1E Set File Attributes DE = FCB address A = none 1317 | ;31 1F Get ADDR (Disk Parms) none HL = DPB address 1318 | ;32 20 Set/Get User Code E = $FF for Get A = User Number 1319 | ; E = 00 to $0F for Set none 1320 | ;33 21 Read Random DE = FCB address A = Error 1321 | ;34 22 Write Random DE = FCB address A = Error Code 1322 | ;35 23 Compute File Size DE = FCB address r0, r1, r2 1323 | ;36 24 Set Random Record DE = FCB address r0, r1, r2 1324 | ;37 25 Reset Drive DE = Drive Vector A = 0 1325 | ;38 26 Access Drive not supported not supported 1326 | ;39 27 Free Drive not supported not supported 1327 | ;40 28 Write Random w/Fill DE = FCB A = error code 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | -------------------------------------------------------------------------------- /asm.cfg: -------------------------------------------------------------------------------- 1 | # Config file for z80 emulator. 2 | debug=0 3 | maxinstructions=100000000 4 | showmemory=0 5 | showstart=65024 6 | showend=65535 7 | savememory=0 8 | savestart=0 9 | saveend=65535 10 | savefile=test.bin 11 | showcode=0 12 | showlabels=0 13 | 14 | 15 | #end 16 | # DEBUG1 = General debug. Show what is being read. 17 | # DEBUG2 = routine debug. Need to later make all debug specific 18 | # I have bit routines, so... 19 | # Debug 0 No debug. 20 | # Debug 1 Slightly more information on line being assembled. 21 | # Debug 2 Show the code we're writing into memory. 22 | # Debug 4 Show arithmetic calculations, orgs etc. 23 | # Debug 8 24 | # DEbug 16 25 | # Debug 128 - Something for a single routine. 26 | # Debug 255 - Show everything we have. It's all binary anyway. 27 | # 28 | # etc... 29 | # Write a function to call easily, eg, debug (lvl,"text") - Let the routine figure out the hard work. 30 | # Increase ERROR report if not assembling. 31 | -------------------------------------------------------------------------------- /bindisk.bas: -------------------------------------------------------------------------------- 1 | rem LOKIDISK - Creates a DISK for LOKI L: from a series of files, offset from F1000 - As L: 2 | rem 3 | rem 17-3-23 Fixed bug in record writing that didn't reduce number of records to write for very large files in multiple extents. 4 | 5 | dim shared DISK as string : rem 27512 ROM = 64K. That's the size of this disk. Actual disk includes BOOTSTRAP.BIN from 0000 to 0FFF also, then directory, then files. 6 | dim shared DISK4 as string : rem 64K in total, 4 disks with separate directories. For J: K: O: P: drives. 7 | 8 | dim shared DIRECTORY as string : rem As we build the directory. 9 | 10 | dim shared FILESPACE as string : rem Temporary space to store the files.... 11 | dim shared nullfile as string : rem Construction of a NULL FILE - eg, Blank File followed by FF for Eprom Use. So we can add files later and reburn. 12 | 13 | 14 | dim shared BOOTFILE as string 15 | dim shared BDOSFILE as string 16 | dim shared BIOSFILE as string 17 | 18 | dim shared FILEIN as string : rem DOS. 19 | dim shared fileout as string : rem 8+3 format. 20 | dim shared filecontent as string : rem the actual file itself. 21 | dim shared thisfile as string : rem Contents of the single opened file. 22 | dim shared ALLOCATIONS as integer 23 | dim shared filesize as integer : rem 24 | dim shared records as integer : rem How many records have we written to the file? 25 | dim shared erecords as integer : rem How many records in just this extent? 26 | dim shared blocks as integer : rem How many blocks in the file? 27 | dim shared extents as integer : rem Which extent are we writing? 28 | dim shared nextalloc as integer : rem Current allocation ( base ) 29 | 30 | dim shared eloop as integer : rem Extent LOOP. 31 | dim shared aloop as integer : rem Allocation Loop. 32 | dim shared anum as integer : rem Allocation Number for the loop. 33 | dim shared allopoint as integer : rem allocation pointer. 34 | 35 | dim shared alloc(16) as integer : rem Array of allocations we want to write into the current extent. 36 | 37 | 38 | dim shared missing as integer : rem missing padding on end of file. 39 | 40 | BOOTFILE="bootstrap.bin" 41 | BDOSFILE="my-bdos.bin" 42 | BIOSFILE="bios.bin" 43 | FILEIN="ccp.bin" 44 | FILEOUT="ccp bin" 45 | 46 | rem File Name Structure 47 | rem UU F1 F2 F3 F4 F5 F6 F7 F8 T1 T2 T3 EX S1 S2 RC .FILENAMETYP.... 48 | rem AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL ................ 49 | 50 | rem Initialise variables. 51 | 52 | 53 | Function Printhex (Byref image as string, Byref numhex as integer) as integer 54 | 55 | rem Image is the string you want to dump as HEX. 56 | rem numhex is the number of characters you want to display ( won't be less than 32 displayed... Might adjust that later. ) 57 | 58 | dim a as integer 59 | dim b as integer 60 | dim c as integer 61 | 62 | print " ! ! ! ! ! ! ! ! " 63 | 64 | for a=0 to int(((numhex-1)/32)) 65 | 66 | print hex$(A*32,4);" - "; : rem Print relative location in Hex at start of line. 67 | 68 | for b=1 to 32 69 | print hex$(asc(mid$(image,(a*32)+b,1)),2);" "; : rem Print out the hex of the bytes. 70 | next b 71 | 72 | print " - "; 73 | 74 | for b=1 to 32 : rem Now print out the ASCII characters. 75 | c=asc(mid$(image,(a*32)+b,1)) 76 | if c<32 then c=asc("+") 77 | if c>127 then c=asc("+") 78 | print chr$(c); 79 | next b 80 | 81 | print 82 | 83 | next a 84 | 85 | Return len (image) 86 | 87 | End function 88 | 89 | 90 | Function LoadFile(ByRef filename As String) As String : rem Load in a file from the hard disk. Used to load in DISK DSK files. 91 | 92 | Dim h As Integer 93 | Dim txt As String 94 | 95 | h = FreeFile 96 | 97 | If Open( filename For Binary Access Read As #h ) <> 0 Then Return "" 98 | 99 | If LOF(h) > 0 Then 100 | 101 | txt = String(LOF(h), 0) 102 | If Get( #h, ,txt ) <> 0 Then txt = "" 103 | 104 | endIf 105 | 106 | Close #h 107 | 108 | Return txt 109 | 110 | end function 111 | 112 | 113 | Function PadFile (Byref filename as string, Byref Thislong as integer ) as string 114 | rem Call Loadfile, then pad with spaces until it's "thislong". 115 | dim result as string 116 | 117 | result=LoadFile (filename) 118 | if len(result) > Thislong then print "##### ERROR - Input file is bigger than allocated space. Truncation will occur #####" 119 | print "Padding:";filename;len(result);" bytes - Segment is"; 120 | result=result+string(Thislong,0) 121 | result=left(result,Thislong) 122 | print len(result);" bytes" 123 | return result 124 | end function 125 | 126 | function COPYBOOT (Byref DISK as string) as integer 127 | 128 | dim result as integer 129 | dim block as string 130 | result=0 131 | block=padfile(BOOTFILE,256) 132 | print "Loading:";BOOTFILE;" Length:";LEN(BLOCK) 133 | 134 | close #2 135 | 136 | return result 137 | end function 138 | 139 | function PAD (Byref text as string, Byref Length as integer ) as string 140 | rem Pad out any space to achieve LENGTH size. 141 | dim result as string 142 | 143 | result=text+string(Length,0) 144 | result=left(result,Length) 145 | 146 | return result 147 | end function 148 | 149 | function PADE5 (Byref text as string, Byref Length as integer ) as string 150 | rem Pad out any space to achieve LENGTH size. 151 | dim result as string 152 | 153 | result=text+string(Length,&h0E5) 154 | result=left(result,Length) 155 | 156 | return result 157 | end function 158 | 159 | function SECTPAD (Byref text as string) as string 160 | rem Pad a file to the allocation size ( allocation = 1024bytes) 161 | dim result as string 162 | dim size as integer 163 | dim resize as integer 164 | 165 | rem Pad the sector to allocation size blocks, and records records, allocations and extents. 166 | 167 | size=len(text) 168 | resize=(int((size-1)/1024)+1)*1024 169 | records=(int((size-1)/128)+1) 170 | result=text+string(1023,0) 171 | result=left(result,resize) 172 | ALLOCATIONS=(resize/1024) 173 | extents=(int((allocations-1)/16)+1) 174 | Print "File size:";size;" Resizing to:";resize ;" = ";resize/1024;" allocations contained in"; records;" records to write in";extents;" Extents - SCHECK:";len(RESULT) 175 | 176 | 177 | return result 178 | end function 179 | 180 | 181 | function transfer (Byref DISKFILE as string, Byref Fileout as string) as integer 182 | dim result as integer 183 | rem ALL GLOBAL VARIABLES. 184 | 185 | thisfile=sectpad(loadfile(DISKFILE)) 186 | 187 | print "Adding file:";fileout 188 | filecontent=filecontent+thisfile : rem File is now added as allocations to the filecontent ( written to disk last ). 189 | 190 | for eloop=0 to extents-1 : rem As many allocation filenames as we need to add. 191 | 192 | directory=directory+chr(0)+fileout+chr(eloop)+chr(0)+chr(0) 193 | 194 | if allocations>16 then 195 | allocations=allocations-16 196 | records=records-128 197 | anum=16 198 | directory=directory+chr(&h80) 199 | else 200 | anum=allocations 201 | directory=directory+chr(records) 202 | endif 203 | 204 | for aloop = 1 to 16 : rem number of allocations. 205 | if aloop <= anum then 206 | directory=directory+chr(nextalloc) 207 | nextalloc=nextalloc+1 : rem Increment allocation number 208 | rem print "Writing allocation:";aloop;"with block:";nextalloc 209 | else 210 | directory=directory+chr(0) 211 | rem print "Writing allocation:";aloop;"with block:";0 212 | endif 213 | next aloop 214 | next eloop 215 | 216 | 217 | return result 218 | end function 219 | 220 | SUB VBIOS 221 | 222 | rem disk=disk+padfile(bootfile,256) 223 | rem disk=disk+padfile(bdosfile,2816) 224 | rem disk=disk+padfile(biosfile,1024) 225 | 226 | print "Creating VBIOS Image for J: drive. " 227 | 228 | 229 | 230 | nextalloc=1 : rem Start with allocation 2 - Allocations 0 and 1 are used for the directory. 231 | 232 | 233 | rem FILE TRANSFER LIST - THIS IS FIXED SINCE IT GENERATES AN EPROM OUTPUT. 234 | transfer ("linstall.bin","VIDEO COM") 235 | transfer ("video512.bin","VIDEO512BIN") 236 | 237 | rem print "Directory:";directory 238 | 239 | 240 | print "Base VBIOS Directory Size:";len(directory) 241 | directory=pade5(directory,1024) : rem size of 1 directory allocations. 242 | directory=left(directory,1024) 243 | print "Final Directory Size:";len(directory) 244 | print "File Content Size:";len(filecontent) 245 | filecontent=pade5(filecontent,16384-1024) 246 | filecontent=left(filecontent,16384-1024) 247 | print "Final File Content Size:";len(filecontent) 248 | 249 | printhex (Directory,256) 250 | 251 | DISK=DISK+Directory+filecontent 252 | 253 | PRINT "Disk Size:";len(DISK) 254 | 255 | END SUB 256 | 257 | 258 | 259 | SUB NBIOS 260 | 261 | rem disk=disk+padfile(bootfile,256) 262 | rem disk=disk+padfile(bdosfile,2816) 263 | rem disk=disk+padfile(biosfile,1024) 264 | 265 | print "Creating NBIOS Image for K: drive. " 266 | 267 | 268 | 269 | nextalloc=1 : rem Start with allocation 2 - Allocations 0 and 1 are used for the directory. 270 | 271 | 272 | rem FILE TRANSFER LIST - THIS IS FIXED SINCE IT GENERATES AN EPROM OUTPUT. 273 | 274 | transfer ("nbios.txt","NBIOS TXT") 275 | 276 | 277 | rem print "Directory:";directory 278 | 279 | 280 | print "Base NBIOS Directory Size:";len(directory) 281 | directory=pade5(directory,1024) : rem size of 1 directory allocations. 282 | directory=left(directory,1024) 283 | print "Final Directory Size:";len(directory) 284 | print "File Content Size:";len(filecontent) 285 | filecontent=pade5(filecontent,16384-1024) 286 | filecontent=left(filecontent,16384-1024) 287 | print "Final File Content Size:";len(filecontent) 288 | 289 | printhex (Directory,256) 290 | 291 | DISK=DISK+Directory+filecontent 292 | 293 | PRINT "Disk Size:";len(DISK) 294 | 295 | END SUB 296 | 297 | 298 | SUB UBIOS1 299 | 300 | rem disk=disk+padfile(bootfile,256) 301 | rem disk=disk+padfile(bdosfile,2816) 302 | rem disk=disk+padfile(biosfile,1024) 303 | 304 | print "Creating VBIOS Image for J: drive. " 305 | 306 | 307 | 308 | nextalloc=1 : rem Start with allocation 2 - Allocations 0 and 1 are used for the directory. 309 | 310 | 311 | rem FILE TRANSFER LIST - THIS IS FIXED SINCE IT GENERATES AN EPROM OUTPUT. 312 | transfer ("ubios1.txt","UBIOS1 TXT") 313 | 314 | 315 | rem print "Directory:";directory 316 | 317 | 318 | print "Base VBIOS Directory Size:";len(directory) 319 | directory=pade5(directory,1024) : rem size of 1 directory allocations. 320 | directory=left(directory,1024) 321 | print "Final Directory Size:";len(directory) 322 | print "File Content Size:";len(filecontent) 323 | filecontent=pade5(filecontent,16384-1024) 324 | filecontent=left(filecontent,16384-1024) 325 | print "Final File Content Size:";len(filecontent) 326 | 327 | printhex (Directory,256) 328 | 329 | DISK=DISK+Directory+filecontent 330 | 331 | PRINT "Disk Size:";len(DISK) 332 | 333 | END SUB 334 | 335 | 336 | SUB UBIOS2 337 | 338 | rem disk=disk+padfile(bootfile,256) 339 | rem disk=disk+padfile(bdosfile,2816) 340 | rem disk=disk+padfile(biosfile,1024) 341 | 342 | print "Creating VBIOS Image for J: drive. " 343 | 344 | 345 | 346 | nextalloc=1 : rem Start with allocation 2 - Allocations 0 and 1 are used for the directory. 347 | 348 | 349 | rem FILE TRANSFER LIST - THIS IS FIXED SINCE IT GENERATES AN EPROM OUTPUT. 350 | transfer ("ubios2.txt","UBIOS2 TXT") 351 | 352 | rem print "Directory:";directory 353 | 354 | 355 | print "Base VBIOS Directory Size:";len(directory) 356 | directory=pade5(directory,1024) : rem size of 1 directory allocations. 357 | directory=left(directory,1024) 358 | print "Final Directory Size:";len(directory) 359 | print "File Content Size:";len(filecontent) 360 | filecontent=pade5(filecontent,16384-1024) 361 | filecontent=left(filecontent,16384-1024) 362 | print "Final File Content Size:";len(filecontent) 363 | 364 | printhex (Directory,256) 365 | 366 | DISK=DISK+Directory+filecontent 367 | 368 | PRINT "Disk Size:";len(DISK) 369 | 370 | END SUB 371 | 372 | 373 | MAIN: 374 | 375 | DISK="" : FILECONTENT = "" : DIRECTORY="" 376 | VBIOS : rem Construct Video Bios directory and files. 377 | DISK4=DISK4+DISK : rem Add to file of 4 disks. 378 | 379 | PRINT"-------------------------------------------------------------------------------------------------------------------------" 380 | PRINT 381 | DISK="" : FILECONTENT = "" : DIRECTORY="" 382 | NBIOS 383 | DISK4=DISK4+DISK : rem Add to file of 4 disks. 384 | 385 | PRINT"-------------------------------------------------------------------------------------------------------------------------" 386 | PRINT 387 | DISK="" : FILECONTENT = "" : DIRECTORY="" 388 | UBIOS1 389 | DISK4=DISK4+DISK : rem Add to file of 4 disks. 390 | 391 | 392 | PRINT"-------------------------------------------------------------------------------------------------------------------------" 393 | PRINT 394 | DISK="" : FILECONTENT = "" : DIRECTORY="" 395 | UBIOS2 396 | DISK4=DISK4+DISK : rem Add to file of 4 disks. 397 | 398 | 399 | Print "Final disk image size:";len(DISK4) 400 | 401 | 402 | rem printhex (filecontent,len(filecontent)) 403 | 404 | rem printhex (DISK4,2048) 405 | 406 | open "VBIOS.img" for output as #1 407 | print #1,DISK4; 408 | close #1 409 | 410 | 411 | 412 | end -------------------------------------------------------------------------------- /bootstrap.asm: -------------------------------------------------------------------------------- 1 | ; Bootstrap.asm - 4K Boot Loader at F0000 on the Loki. 2 | ; Later we can use the BDOS to allocate memory - but it's a chicken and egg situation. Let's set up a normal 64K machine at the base of memory. From 01000 to 10FFF. 3 | 4 | .ORG $0000 ; Boots on hard reset. 5 | 6 | EQU MMU,$0F ; The port to read/write MMU bytes to the MMU RAM. 7 | EQU PID,$0E ; The PID port - Which process is the MMU reflecting? 8 | EQU STARTBIT,$0D ; The Start bit and other system bits. 9 | EQU Console,$01 ; The serial console port. 10 | 11 | EQU TRACKPORT ,$06 ; Where we write track data. 12 | EQU SECTORPORT ,$07 ; Where we write sector data. 13 | EQU MADPORT,$0C ; Where we write for MAD reads ( Memory As Disk ) 14 | 15 | 16 | EQU BDOS, $0005 ; BDOS call location 17 | EQU MY-BDOS,$F000 18 | ;EQU BIOS,$FC00 ; Defined elsewhere as FC03 19 | 20 | COLDBOOT: 21 | .equ TRACKPORT ,$06 22 | .equ SECTORPORT ,$07 23 | .equ MADPORT ,$0C 24 | 25 | STEP1: LD A,'*' ; Step 1. Send a star to the console port to let us know it's alive. 26 | OUT (Console),A ; Just pseudo-startup debugging. Can be removed later if necessary. 27 | ; Though it's useful to let something(someone?) know the CPU started and is running 28 | 29 | STEP2: ; Next step - set up default processes for each of the non-prime PIDs so we can at least engage them from MMU PAGE 0. 30 | LD D,$1F ; 32 possible processes. Set them up now. First and last page. 31 | LD E,$20 32 | SETUPALLPROCESSES: ; Now set up default processes for each process. This will reflect Process 0 block 0 and F. 33 | LD A,D ; Get process ID. 34 | OUT (PID),A ; Set process ID into Process Latch - Change which MMU PID we are on. 35 | LD B,$00 ; Page 0. (First 4Kb) 36 | LD C,MMU ; Set up the MMU Address 37 | LD A,$01 ; Block 1 = 01000 to 01FFF 38 | OUT (C),A ; Write it. 39 | LD B,$F0 ; Page F. (Last 4Kb) 40 | LD A,$10 ; Block 10 = 10000 to 10FFF 41 | OUT (C),A ; Write it. 42 | DEC D ; Count backwards through all processes. Last one will be 1. 43 | JR NZ,SETUPALLPROCESSES ; Loop until we have completed down to process 1. 44 | 45 | STEP3: ; Next step. Format the directory for MAD before we begin. 46 | ; It doesn't really matter which process we are in presently. We're in Process 1 by default at this point. 47 | ; Move Block0 (Directory) into Page 1 (located at $1000) so we can format the MAD directory. 48 | LD B,$10 ; Set B for Page 1 (upper nibble only is page, since we're affecting A12,A13,A14,A15) 49 | LD C,MMU ; Set C for the MMU Port 50 | XOR A ; Set A to point to Block 0 (value is the block, so a=0) 51 | OUT (C),A ; And write to the MMU RAM. 52 | 53 | LD HL,$1000 54 | LD BC,$1000 55 | FORMAT: ; Format the MAD directory structure. 56 | LD (HL),$E5 ; Format code. 57 | INC HL 58 | DEC BC 59 | LD A,B 60 | OR C 61 | JR NZ,FORMAT 62 | 63 | LD HL,DEFAULT1 ; Now write the default directory elements so Drive M can be used without corrupting system memory. 64 | LD DE,$1000 65 | LD BC,128 66 | LDIR 67 | 68 | 69 | 70 | 71 | STEP4: ; Next step - set up Process 0 so we can use it. 72 | ; Process 0 is a normal CP/M type memory set up. If we don't use processes then 73 | ; CP/M will run in process 0, and the rest of memory is available as RAMDISK under 74 | ; the MAD (MEMORY AS DISK) system. 75 | ; And there is no need to process switch or use pseudo-segments. 76 | XOR A 77 | OUT (PID),A ; Set the Process ID to Zero. 78 | ; Now set up Process 0. 79 | LD B,A ; Get the port for the MMU. Start at the first page. 80 | LD C,MMU ; And load C with the MMU port. 81 | LD E,$01 ; Allocation 1 - This can change later depending on how many allocations are in the memory directory. 82 | LD D,$10 ; We're going to do 16 cycles. 83 | MMUINIT: 84 | OUT (C),E ; populate the MMU byte. 85 | LD A,$10 ; Increment the values in B upper nibble. 86 | ADD A,B ; 87 | LD B,A 88 | INC E ; Next allocation. 89 | DEC D 90 | JR NZ,MMUINIT ; And loop 16 times. 91 | ; Now the basic memory structure for 64K should be set up - We still need to do other stuff, like write the directory and format memory. 92 | ; But RAM should be available. 93 | 94 | 95 | 96 | STEP5: ; We are still in Process 0 (inactive) presently, so switch the current bootstrap to Page 0 so we can turn the MMU on (active) 97 | LD B,$00 ; Set the MMU to map the current boot block over the Page 0 memory so we can switch to it without losing our place in the code. 98 | LD C,MMU ; MMU address without crashing everything. 99 | LD A,$F0 ; Block F0 - Equates to F0000 - Where the boot rom is. 100 | OUT (C),A ; Set Page0 as the BOOTSTRAP ROM FIrst 4K direct. 101 | ; Process memory (MMU) is done - We need to switch out of startup to process mode. 102 | XOR A ; Now memory has been set up, and we can switch the MMU on, we need to clear the start bit flag. 103 | OUT (PID),A ; Select Process 0 - since it's the one we want to use. 104 | OUT (STARTBIT),A ; Clear startbit (Turn on MMU to active) 105 | 106 | LD A,'#' ; Next symbol indicates we are running now in PID mode - and the PIDs have been set up and seem to be working. 107 | OUT (Console),A ; Until we have the other routines installed, just keep it to simple symbols. 108 | ; Just pseudo-startup debugging. Can be removed later if necessary. 109 | 110 | 111 | STEP6: ; Final Step - Copy the BIOS and BDOS (FDOS) to memory and RESET the computer. 112 | LD HL,$0100 ; Start of stored BDOS in Page 0 ( equates to F0100 in memory ) 113 | LD DE,$F000 ; 64K based memory location of BDOS destination - We want to move the BDOS to F000 in 64K memory. 114 | LD BC,2816 ; Size of the BDOS in bytes ( noting that this is from F000 to FAFF ) 115 | LDIR ; Copy the BDOS. 116 | 117 | LD HL,$0C00 ; And copy the BIOS in the same way, from F0C00 in real memory to FC00 in 64K space. 118 | LD DE,$FC00 119 | LD BC,$0400 120 | LDIR 121 | 122 | STEP7: ; Set any last minute hooks and reset via the now-loaded FDOS ( BDOS+BIOS=FDOS ) which will load the CCP. 123 | 124 | 125 | LD C,$00 ; Set for BIOS reset. 126 | JP $F000 ; BDOS reset will load the CCP from the BDOS - Loads CCP.BIN from L: Since L: is now operational by default. 127 | ; BIOS reset also loads Bank 1 into Page 0 before resetting, so switches 128 | ; out the ROM Bootstrap. 129 | 130 | ; We can now exit Bootstrap. 131 | 132 | 133 | 134 | ; Default root image to populate the MAD directory with the zero process. 135 | ;DEFAULT1: 136 | ;DB $0D,'root-cpmimg',$00,$00,$00,$00 ;Drive M, filename ROOT-CPM.IMG 137 | ;DB 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 138 | ;DB 0,0,0,0 139 | 140 | ;.org $100 141 | ;DMA: BLOCK 128 ; Create the DMA block. 142 | 143 | DEFAULT1: DB $00,'ROOT_CPMIMG',$03,$00,$00,$80,$01,$02,$03,$04,$05,$06,$07,$08,$09,$0A,$0B,$0C,$0D,$0E,$0F,$10 ; Rem default image - mark this out. 144 | DB $00,'VIDEOMEMIMG',$03,$00,$00,$80,$C0,$C1,$C2,$C3,$C4,$C5,$C6,$C7,$C8,$C9,$CA,$CB,$CC,$CD,$CE,$CF 145 | DB $00,'VIDEOMEMIMG',$07,$00,$00,$80,$D0,$D1,$D2,$D3,$D4,$D5,$D6,$D7,$D8,$D9,$DA,$DB,$DC,$DD,$DE,$DF 146 | DB $00,'BOOT_ROMDSK',$03,$00,$00,$80,$F0,$F1,$F2,$F3,$F4,$F5,$F6,$F7,$F8,$F9,$FA,$FB,$FC,$FD,$FE,$FF 147 | 148 | 149 | 150 | 151 | ; ################################################### Other Stuff ################################################## 152 | 153 | 154 | 155 | ;************************************************************** 156 | ; I really wish I knew why this bios has a -3 jump... Would be good to figure that out. Might be due to CP/M 3??? 157 | ; Jump table here - Define the BIOS entry points. 158 | .EQU BIOS ,$FC03 ; Location of BIOS. Though in reality, we have boot. Need to understand if BOOT=0 or BOOT=-3. 159 | .EQU BOOT ,BIOS-3 ;-3: Cold Boot - Set up system. 160 | .EQU WBOOT ,BIOS+0 ; 0: Warm boot - reload command processor 161 | .EQU WBOOTE ,BIOS+0 162 | .EQU CONST ,BIOS+3 ; 3: Console status - A=0 No character ready, A=FF character waiting to be read. 163 | .EQU CONIN ,BIOS+6 ; 6: Console input - Wait until character then A=character. 164 | .EQU CONOUT ,BIOS+9 ; 9: Console output - Write C register to screen. 165 | .EQU LIST ,BIOS+12 ;12: Printer output - Wait until ready, then write C register to printer. 166 | .EQU PUNCH ,BIOS+15 ;15: Paper tape punch output - Wait until ready then write C to Punch Reader ( or AUX ). 167 | .EQU READER ,BIOS+18 ;18: Paper tape reader input - Wait until ready, then return A=Character. If not implemented, return 26 ( Ctrl Z ) 168 | .EQU HOME ,BIOS+21 ;21: Move disc head to track 0 169 | .EQU SELDSK ,BIOS+24 ;24: Select disc drive - C Register = disk 0...F. Enter with E=0 or E=FF. 170 | ; If bit 0 of E is 0, then the disc is logged in as if new; if the format has to be determined from the boot sector, for example, this will be done. 171 | ;If bit 0 if E is 1, then the disc has been logged in before. The disc is not accessed; the DPH address (or zero) is returned immediately. 172 | ;SELDSK returns the address of a Disc Parameter Header in HL. The exact format of a DPH varies between CP/M versions; note that under CP/M 3, 173 | ; the DPH is in memory bank 0 and probably not visible to programs. If the disc could not be selected it returns HL=0. 174 | .EQU SETTRK ,BIOS+27 ;27: Set track number - Track in BC - Is a word - Starts at 0. 175 | .EQU SETSEC ,BIOS+30 ;30: Set sector number - Sector in BC - Is a word. Starts at 1. I think. Will find out. 176 | .EQU SETDMA ,BIOS+33 ;33: Set DMA address - Set DMA address. 177 | .EQU READ ,BIOS+36 ;36: Read a sector - To DMA address. A=0 success. A=1 unrecoverable error. A=FF Media changed. 178 | .EQU WRITE ,BIOS+39 ;39: Write a sector - C contains blocking code. 0=deferred. 1=immediate. 2=Can be deferred. No preread necessary. A=0 success. 179 | ; A=1 unrecoverable error. A=FF Media changed. 180 | ;In CP/M 2 and later, the following extra jumps appear: 181 | .EQU LISTST ,BIOS+42 ;42: Status of list device - A=0 Printer not ready. A=FF -printer ready. 182 | .EQU SECTRAN ,BIOS+45 ;45: Sector translation for skewing DE=Tableaddress (IGNORE DE). BC=Incoming sector (logical). HL=Translated sector (physical) . Return BC. 183 | .EQU SECTRN ,BIOS+45 ;45: Sector translation for skewing DE=Tableaddress (IGNORE DE). BC=Incoming sector (logical). HL=Translated sector (physical) . Return BC. -------------------------------------------------------------------------------- /linstall.asm: -------------------------------------------------------------------------------- 1 | ; Loki Driver Installation Routine - Installs Driver code and hooks entry point to driver. 2 | ; Standard Drivers are at 0040 (Video) and 0048 (Network) 3 | ; Drivers have common pages in the first and last 4K page locations ( 0000-0FFF at Page01 and F000-FFFF at Page 10) with the ROOT-CPM process 0. 4 | ; Install driver uses random write to copy the driver then hooks the BIOS and exits. It can also unload a driver ( replace with ret ). 5 | ; Syntax: 6 | ; A>LINSTALL M: 7 | 8 | 9 | 10 | .EQU BDOS, $0005 11 | 12 | .equ ADRIVE, $00 13 | .equ LDRIVE, $0B 14 | .equ MDRIVE, $0C 15 | 16 | .equ TRACKPORT ,$06 17 | .equ SECTORPORT ,$07 18 | .equ MADPORT ,$0C 19 | 20 | .equ MMU,$0F ; The port to read/write MMU bytes to the MMU RAM. 21 | .equ PID,$0E ; The PID port - Which process is the MMU reflecting? 22 | 23 | .equ DMA,$0080 ; This DMA is suitable. 24 | .equ VIDEO,$0040 ; Video Hook. 25 | 26 | ; Memory Map. 27 | ; Common Lower Page - common to all drivers. 4K in size. 28 | ; $0000 - Common memory, entry points in the Zero Page. Existing applications from $0100 to $0FFF. 29 | ; $1000 - Driver entry, Code and Bitmaps. Resident. 30 | ; $3000 - Video Map - 8 bits - 2040 bytes * 2 ( 8 bits of attributes ). 5x ADM attributes. 31 | ; 32 | 33 | .ORG $100 ; Start as a COM file. 34 | 35 | INITIALISE: ; Let's initialise the screen, Character map, etc. 36 | ; Constantly changing as I get it working under LokiOS and CP/M. 37 | ; Question: Should standard drivers exist already? Eg, Interrupts, Video, Network? 38 | 39 | LD (SAVEHL),HL ; Save HL in case, if, for some reason, we actually want to save it. 40 | LD HL,$0000 41 | ADD HL,SP ; save the SP 42 | LD (SAVESTACK),HL 43 | LD SP,TEMP_STACK ; New stack location for this routine in location 1. 44 | 45 | 46 | CALL MAKE_VIDEO_DRIVER ; Create the driver file on M: 47 | ; Need to put some error checking in here. 48 | ; Once created, we need to set up the MMU to follow the file. 49 | 50 | CALL SET_MMU ; Set the MMU to match the FCB. 51 | 52 | 53 | 54 | CALL TRANSFER ; Transfer the BIOS code to the Process. Later do this in CREATE_FILE (Transfer at same time and write it in) 55 | ; It switches to the process, then just loads the driver at $1000 and executes with C=0. 56 | 57 | CALL INTHOOK ; Install the interrupt hook. No going back now. 58 | ; This sets a hook at $0040 or other interrupt vectors the change the PID and then jump to $1000. 59 | 60 | LD A,$00 ; Back to process 0. 61 | OUT (PID),A 62 | 63 | 64 | CALL RESET_MMU ; Drop back to the default MMU process 0. 65 | 66 | 67 | LD C,$00 ; Reset Video. 68 | CALL VIDEO 69 | 70 | 71 | LD DE,SUCCESS 72 | LD C,PRINT_STRING 73 | CALL BDOS 74 | ; CALL ERROR_MESSAGE 75 | 76 | LD (SAVEHL),HL 77 | LD HL,(SAVESTACK) 78 | LD SP,HL ; Restore the SP. 79 | LD HL,(SAVEHL) 80 | 81 | 82 | RET ; Just exit here. 83 | 84 | SAVESTACK: DW $0000 ; Stack location store. 85 | SAVEHL: DW $0000 ; Temporary location for HL. 86 | 87 | 88 | 89 | 90 | 91 | RESET_MMU: ; Return to Process 0. 92 | LD A,$00 ; Process ID for the driver we are installing. 93 | OUT (PID),A ; SHOULD be safe.. SHOULD have the same process information. Unless someone's changed it. 94 | LD HL,DRVBDOS ; END of driver pages. 95 | LD B,$0E ; Second last page... We'll transfer them backwards. 96 | LD C,MMU ; C is the port address for the MMU. 97 | ret 98 | 99 | SET_MMU: 100 | 101 | LD DE,DRVFCB 102 | LD C,OPEN_FILE 103 | CALL BDOS ; Get current memory unit allocations from FCB after opening file. 104 | 105 | LD A,$08 ; Process ID for the driver we are installing. 106 | OUT (PID),A ; SHOULD be safe.. SHOULD have the same process information. Unless someone's changed it. 107 | 108 | LD HL,FCBCR ; END of driver pages. We'll deduct 1 each time, so will be the last allocation. 109 | LD B,$0F ; last page... We'll transfer them backwards. 110 | LD C,MMU ; C is the port address for the MMU. 111 | 112 | SET_MMU_LOOP: 113 | DEC HL 114 | PUSH BC 115 | LD A,B 116 | RLC A 117 | RLC A 118 | RLC A 119 | RLC A 120 | LD B,A 121 | LD A,(HL) ; Pick up allocation from FCB. 122 | 123 | OUT (C),A ; Set a block of memory... MMU is set for this page. 124 | POP BC 125 | DJNZ SET_MMU_LOOP ; Because we use DJNZ, we won't set page 0, which is reserved for the common zero page. 126 | ret 127 | 128 | 129 | 130 | 131 | 132 | INTHOOK: ; Code to hook the Interrupt, Install the device driver, rebuild the handler and write it all to M: 133 | LD DE,VIDEO 134 | LD HL,INT40 135 | LD BC,$08 136 | LDIR ; Install RST40 hook. 137 | 138 | LD DE,$0050 139 | LD HL,RET50 140 | LD BC,$05 141 | LDIR ; Install RET50 return hook. 142 | 143 | LD DE,$0055 144 | LD HL,RET55 145 | LD BC,$07 146 | LDIR 147 | 148 | ret 149 | 150 | 151 | 152 | INT40: ; Standing to copy relative code to move to $0040 (VIDEO). 8 bytes. 153 | PUSH AF ; Protect the A register. 154 | LD A,$08 ; We're going to run video as Process 8 (reserved) 155 | OUT (PID),A ; Switch to the Video Process. 156 | JP $1000 ; Jump to the next segment at the 4K Boundary where the real handler lies. 157 | ; Map - $0000 to $0FFF - Handler, Install routines, Initialise routines. 158 | ; $1000 to $1FFF - Routines. 159 | ; $2000 to $2FFF - More Routines. 160 | ; $3000 to $3FFF - Video Map (Character Based ) 161 | 162 | ; RET50 is 5 bytes exactly. 163 | RET50: ; Return location for 50 where we install the "Return to previous call". 164 | OUT (PID),A ; Switch to the calling process. ( A contains the process - don't set it here. ) 165 | POP AF ; Recover A ( and maybe other registers ) 166 | ret ; And return to where we were called from. 167 | ; Note - Return programs from 50 to 5B... 168 | NOP ; So I can see a single 0. 169 | ; DO NOT CHANGE THE ABOVE....... below follows on. 170 | 171 | RET55: ; RET55 is exactly 7 bytes. 172 | OUT (PID),A ; Switch to the calling process. 173 | POP AF 174 | RETI ; And the RETI version if it's called by an interrupt. 175 | NOP 176 | NOP 177 | 178 | 179 | 180 | RETEND: 181 | ; Marker for the end. 182 | 183 | LASTDMA: DW $0080 ; Place to store the DMA. 184 | 185 | TRANSFER: 186 | ; Later need to change process ID here to map in Process 08 - This memory segment will be stable at 0000 to 0FFF 187 | ; reuse TYPE routine. 188 | LD DE,$1000 189 | LD (LASTDMA),DE ; Store the DMA. 190 | LD C,Set_DMA_Address ; Function 1A - Set the DMA address to the buffer at 0080. 191 | CALL BDOS ; Set DMA to 0080 - This is because the directory will show up here. Saves us copying the filenames too. 192 | 193 | LD DE,VIDFCB ; The filename we want is located in the first FCB in lower memory from the Command Line 194 | LD C,Open_File ; Open File. 195 | CALL BDOS ; Get the first matching file. 196 | 197 | AND %11111100 ; Mask the result bytes. 198 | JR NZ,FILE_OPEN_ERROR 199 | 200 | TRANSFER_READ: 201 | LD DE,VIDFCB ; The filename we want remains located in the first FCB in lower memory from the Command Line 202 | LD C,Read_Sequential ; read record 203 | CALL BDOS ; Get the first matching file. 204 | OR A ; Check for file status - And exit if we're at EOF ( we already exited earlier for other result other than read OK ) 205 | 206 | JR NZ,TRANSFER_EXIT ; Nothing more to read. 207 | ;;; CALL PRINT_RECORD ; Print the record 208 | 209 | LD HL,(LASTDMA) ; Step the DMA pointer along. 210 | LD DE,$0080 211 | ADD HL,DE 212 | LD (LASTDMA),HL ; DMA is not further along and saved. 213 | EX DE,HL ; Set DMA back in DE. 214 | 215 | LD C,Set_DMA_Address ; Function 1A - Set the DMA address to the buffer at 0080. 216 | CALL BDOS ; Set DMA to 0080 - This is because the directory will show up here. Saves us copying the filenames too. 217 | 218 | JR TRANSFER_READ 219 | 220 | TRANSFER_EXIT: 221 | CALL CRLF ; Start next on a new line. 222 | ret 223 | 224 | 225 | CRLF: LD E,$0D 226 | LD C,Console_Output 227 | CALL BDOS 228 | LD E,$0A 229 | LD C,Console_Output 230 | CALL BDOS 231 | ret 232 | 233 | PRINT_RECORD: 234 | LD B,128 235 | LD HL,$0080 ; DMA address. 236 | PRINT_RECORD_LOOP: 237 | PUSH HL 238 | PUSH BC 239 | LD E,(HL) 240 | LD C,Console_Output 241 | CALL BDOS 242 | POP BC 243 | POP HL 244 | 245 | LD A,(HL) 246 | CP $1A ; CTRL'Z' 247 | RET Z ; Is end of file. Exit here now if that happens. 248 | 249 | INC HL 250 | DJNZ PRINT_RECORD_LOOP 251 | 252 | ret 253 | 254 | 255 | DESTINATION_MISSING_ERROR: 256 | LD DE,NODEST_ERROR 257 | JP ERROR_MESSAGE 258 | 259 | FILE_READ_ERROR: 260 | LD DE,FILEREAD_ERROR 261 | JP ERROR_MESSAGE 262 | 263 | FILE_OPEN_ERROR: 264 | LD DE,FILEOPEN_ERROR 265 | JP ERROR_MESSAGE ; Exit via the error message. 266 | ; ret 267 | 268 | CANT_OPEN_PROCESS: 269 | LD DE,PROCESS_ERROR 270 | JP ERROR_MESSAGE 271 | 272 | ERROR_MESSAGE: 273 | LD C,Print_String 274 | CALL BDOS 275 | RST 0 ; restart - do not continue. 276 | RET 277 | 278 | COMMAND_BAD: db 'Command Unknown or Executable not located',$0D,$0A,'$' 279 | ret 280 | FILENAME_BAD: db 'Filename had unexpected charactors or was malformed',$0D,$0A,'$' 281 | 282 | COMMAND_HINT: db 'Type HELP for a list of commands',$0D,$0A,'$' 283 | 284 | FILEOPEN_ERROR: db 'File could not be opened',$0D,$0A,'$' 285 | 286 | FILEREAD_ERROR: db 'Error reading file',$0D,$0A,'$' 287 | 288 | NODEST_ERROR: db 'Bad Source or Destination',$0D,$0A,'$' 289 | 290 | FILE_COPY_ERROR:db 'Error copying file',$0D,$0A,'$' 291 | 292 | SUCCESS: db 'New video driver installed successfully.',$0D,$0A,'$' 293 | 294 | PROCESS_ERROR: DB 'Could not open process file on M:',$0D,$0A,'$' 295 | 296 | 297 | 298 | 299 | 300 | 301 | MAKE_VIDEO_DRIVER: 302 | LD DE,DRVFCB ; Location of new file FCB. 303 | LD C,MAKE_FILE ; Open the file on M: 304 | CALL BDOS 305 | AND $FC 306 | JP NZ,CANT_OPEN_PROCESS 307 | 308 | LD C,CLOSE_FILE 309 | LD DE,DRVFCB 310 | CALL BDOS ; Now close the file, so it's written. 311 | 312 | LD C,OPEN_FILE 313 | LD DE,DRVFCB 314 | CALL BDOS ; And make sure we can open it. 315 | AND $FC 316 | JP NZ,CANT_OPEN_PROCESS 317 | 318 | LD A,$01 ; Make sure the first segment is mapped to the same as CP/M root, and the last to BDOS/BIOS, 319 | LD (DRVZERO),A ; Set the first page. 320 | LD A,$10 321 | LD (DRVBDOS),A ; Set the BDOS page. 322 | LD A,$0F 323 | LD (DRVDRIBDOS),A ; And set it just in case the DRI BDOS is in place. Software can reallocate this anyway. 324 | ; Next we need to allocate 3 blocks of free memory. 325 | 326 | LD DE,DRVFCB 327 | LD C,CLOSE_FILE 328 | CALL BDOS 329 | 330 | 331 | ; Did we get it working? Let's random write it. 332 | 333 | LD A,$3F ; Next 4K boundary. 334 | LD (FCBR1),A 335 | CALL SET_ALLOCATION 336 | 337 | LD A,$5F ; Next 4K boundary. 338 | LD (FCBR1),A 339 | CALL SET_ALLOCATION 340 | 341 | LD A,$7F ; Next 4K boundary. Note - separate transfer and allocation setting to avoid short files failing to allocate enough. 342 | LD (FCBR1),A ; Anything in the range of $60 to $7F is suitable, however the RC field will reflect the final random write. 343 | CALL SET_ALLOCATION 344 | 345 | 346 | 347 | 348 | LD DE,DRVFCB 349 | LD C,CLOSE_FILE 350 | CALL BDOS 351 | 352 | ret 353 | 354 | SET_ALLOCATION: 355 | LD DE,DRVFCB 356 | LD C,WRITE_RANDOM 357 | CALL BDOS 358 | ret 359 | 360 | ; Incoming Driver File. 361 | VIDFCB: ; Source is VIDEO512BIN on L: 362 | DB $0A ; Driver happens to be on L: but later can load from anywhere. Use this to change video modes too. 363 | DB 'VIDEO512BIN',$00,$00,$00,$00 364 | DB 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 365 | DB $00 366 | DB $00 367 | DB $00 368 | DB $00 369 | 370 | ; Driver File to build on M: 371 | DRVFCB: ; Random FCB to write driver to MAD space at $1000... 372 | DB $0D ; Process will ALWAYS be M: drive. 373 | DB 'VIDEO DRV',$00,$00,$00 374 | DRVRC: DB $0 ; Set RC Record Count to $20 - ie, all initial locations taken. We will subsequently assign three more allocations. 375 | DRVZERO: DB $0 ; Establish the first file location. 376 | DRV0: DB 0 ; Default driver locations. 377 | DRV1: DB 0 ; Default driver locations. 378 | DRV2: DB 0 ; Default driver locations. 379 | DRV_PAGES: DB 0,0,0,0, 0,0,0,0, 0,0 ; Reserved for paging in video for direct access. ( Eg, Scroll etc. ) but we don't need them yet. 380 | DRVDRIBDOS: DB $00 ; Because the DRI BDOS is too fat, if it's installed. 381 | DRVBDOS: DB $00 ; Standard LOKI FDOS. 382 | FCBCR: DB $00 383 | FCBR1: DB $00 384 | FCBR2: DB $00 385 | FCBR3: DB $00 386 | 387 | 388 | 389 | .ORG $0FF0 390 | TEMP_STACK: 391 | ; Put a temporary stack right at the end. 392 | 393 | 394 | ; Wildcard for FCB.... 395 | ;DR F1 F2 F3 F4 F5 F6 F7 F8 T1 T2 T3 EX S1 S2 RC .FILENAMETYP... 396 | ;AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL ............... 397 | ;CR R0 R1 R2 398 | ; 399 | ;The bytes in it have the following meanings: 400 | ; 401 | ; 402 | ;FCB+00h DR - Drive. 0 for default, 1-16 for A-P. In DOSPLUS, 403 | ; bit 7 can be set to indicate that the operation should work with 404 | ; subdirectories rather than files. 405 | ; 406 | ;FCB+01h Fn - Filename, 7-bit ASCII. The top bits of the filename bytes 407 | ; (usually referred to as F1' to F8') have the following 408 | ; meanings: 409 | ; F1'-F4' - User-defined attributes. Any program can use 410 | ; them in any way it likes. The filename in the 411 | ; disc directory has the corresponding bits set. 412 | ; F5'-F8' - Interface attributes. They modify the 413 | ; behaviour of various BDOS functions or 414 | ; indicate error conditions. In the directory 415 | ; these bits are always zero. 416 | ;FCB+09h Tn - Filetype, 7-bit ASCII. T1' to T3' have the following 417 | ; meanings: 418 | ; T1' - Read-Only. 419 | ; T2' - System (hidden). System files in user 0 can be 420 | ; opened from other user areas. 421 | ; T3' - Archive. Set if the file has not been changed 422 | ; since it was last copied. 423 | ;FCB+0Ch EX - Set this to 0 when opening a file and then leave it to 424 | ; CP/M. You can rewind a file by setting EX, RC, S2 and CR to 0. 425 | ;FCB+0Dh S1 - Reserved. But otherwise upper bits of RC ( Remember, Bit 7 is something else ) 426 | ;FCB+0Eh S2 - Reserved. But otherwise upper bits of EX ( Remember, Bit 7 is something else ) 427 | ;FCB+0Fh RC - Set this to 0 when opening a file and then leave it to 428 | ; CP/M. 429 | ;FCB+10h AL - Image of the second half of the directory entry, 430 | ; containing the file's allocation (which disc blocks it 431 | ; owns). 432 | ;FCB+20h CR - Current record within extent. It is usually best to set 433 | ; this to 0 immediately after a file has been opened and 434 | ; then ignore it. 435 | ;FCB+21h Rn - Random access record number (not CP/M 1). A 16-bit 436 | ; value in CP/M 2 (with R2 used for overflow); an 18-bit 437 | ; value in CP/M 3. 438 | ; 439 | ; CR - Current Record ( Sequential Read ) 440 | ; R0, R1, R2 - Random read ( R0, R1 = 16 bit. R2 Overflow. ) 441 | 442 | 443 | 444 | ;************************************************************** 445 | ; I really wish I knew why this bios has a -3 jump... Would be good to figure that out. Might be due to CP/M 3??? 446 | ; Jump table here - Define the BIOS entry points. 447 | .EQU BIOS ,$FC03 ; Location of BIOS. Though in reality, we have boot. Need to understand if BOOT=0 or BOOT=-3. 448 | .EQU BOOT ,BIOS-3 ;-3: Cold Boot - Set up system. 449 | .EQU WBOOT ,BIOS+0 ; 0: Warm boot - reload command processor 450 | .EQU WBOOTE ,BIOS+0 451 | .EQU CONST ,BIOS+3 ; 3: Console status - A=0 No character ready, A=FF character waiting to be read. 452 | .EQU CONIN ,BIOS+6 ; 6: Console input - Wait until character then A=character. 453 | .EQU CONOUT ,BIOS+9 ; 9: Console output - Write C register to screen. 454 | .EQU LIST ,BIOS+12 ;12: Printer output - Wait until ready, then write C register to printer. 455 | .EQU PUNCH ,BIOS+15 ;15: Paper tape punch output - Wait until ready then write C to Punch Reader ( or AUX ). 456 | .EQU READER ,BIOS+18 ;18: Paper tape reader input - Wait until ready, then return A=Character. If not implemented, return 26 ( Ctrl Z ) 457 | .EQU HOME ,BIOS+21 ;21: Move disc head to track 0 458 | .EQU SELDSK ,BIOS+24 ;24: Select disc drive - C Register = disk 0...F. Enter with E=0 or E=FF. 459 | ; If bit 0 of E is 0, then the disc is logged in as if new; if the format has to be determined from the boot sector, for example, this will be done. 460 | ;If bit 0 if E is 1, then the disc has been logged in before. The disc is not accessed; the DPH address (or zero) is returned immediately. 461 | ;SELDSK returns the address of a Disc Parameter Header in HL. The exact format of a DPH varies between CP/M versions; note that under CP/M 3, 462 | ; the DPH is in memory bank 0 and probably not visible to programs. If the disc could not be selected it returns HL=0. 463 | .EQU SETTRK ,BIOS+27 ;27: Set track number - Track in BC - Is a word - Starts at 0. 464 | .EQU SETSEC ,BIOS+30 ;30: Set sector number - Sector in BC - Is a word. Starts at 1. I think. Will find out. 465 | .EQU SETDMA ,BIOS+33 ;33: Set DMA address - Set DMA address. 466 | .EQU READ ,BIOS+36 ;36: Read a sector - To DMA address. A=0 success. A=1 unrecoverable error. A=FF Media changed. 467 | .EQU WRITE ,BIOS+39 ;39: Write a sector - C contains blocking code. 0=deferred. 1=immediate. 2=Can be deferred. No preread necessary. A=0 success. 468 | ; A=1 unrecoverable error. A=FF Media changed. 469 | ;In CP/M 2 and later, the following extra jumps appear: 470 | .EQU LISTST ,BIOS+42 ;42: Status of list device - A=0 Printer not ready. A=FF -printer ready. 471 | .EQU SECTRAN ,BIOS+45 ;45: Sector translation for skewing DE=Tableaddress (IGNORE DE). BC=Incoming sector (logical). HL=Translated sector (physical) . Return BC. 472 | .EQU SECTRN ,BIOS+45 ;45: Sector translation for skewing DE=Tableaddress (IGNORE DE). BC=Incoming sector (logical). HL=Translated sector (physical) . Return BC. 473 | 474 | 475 | ; CPM Calls as EQU functions. 476 | EQU System_Reset , 0 477 | EQU Console_Input , 1 478 | EQU Console_Output , 2 479 | EQU Reader_Input , 3 480 | EQU Punch_Output , 4 481 | EQU List_Output , 5 482 | EQU Direct_Console_IO , 6 483 | EQU Get_IO_Byte , 7 484 | EQU Set_IO_Byte , 8 485 | EQU Print_String , 9 486 | EQU Read_Console_String , 10 487 | EQU Get_Console_Status , 11 488 | EQU Return_Version , 12 489 | EQU Reset_Disk_System , 13 490 | EQU Select_Disk , 14 491 | EQU Open_File , 15 492 | EQU Close_File , 16 493 | EQU Search_For_First , 17 494 | EQU Search_For_Next , 18 495 | EQU Delete_File , 19 496 | EQU Read_Sequential , 20 497 | EQU Write_Sequential , 21 498 | EQU Make_File , 22 499 | EQU Rename_File , 23 500 | EQU Return_Login_Vector , 24 501 | EQU Return_Current_Disk , 25 502 | EQU Set_DMA_Address , 26 503 | EQU Get_ADDR_ALLOC , 27 504 | EQU Write_Protect_Disk , 28 505 | EQU Get_RO_Vector , 29 506 | EQU Set_File_Attributes , 30 507 | EQU Get_ADDR_DiskParms , 31 508 | EQU Set_Get_User_Code , 32 509 | EQU Read_Random , 33 510 | EQU Write_Random , 34 511 | EQU Compute_File_Size , 35 512 | EQU Set_Random_Record , 36 513 | EQU Reset_Drive , 37 514 | EQU Access_Drive , 38 515 | EQU Free_Drive , 39 516 | EQU Write_Random_Fill , 40 517 | ; Special. 518 | EQU Write_Dry , 41 ; Dry write - Not a real function. 519 | 520 | 521 | FBC: DW $00 ; File Control Block location Save. First is a label in memory. Rest are offset for IX. 522 | EQU DR, $00 ; Offset 0 523 | EQU EX, $0C ; Extent counter. 00, 01 etc. First extent is 00. 524 | EQU S1, $0D 525 | EQU S2, $0E 526 | EQU RC, $0F ; Record counter. File is RC * 128 bytes. 527 | EQU ALLOC1, $10 ; First byte of allocation data or other contents. Copied from Directory for this extent. 528 | EQU ALLOC2, $11 ; Second byte of the allocation data or other contents. 529 | EQU CR, $20 ; Current record within extent. 0= first record. 530 | EQU R0, $21 ; Low byte first. 16 bit counter for Random Access Record Number. Record =128 block. 531 | EQU R1, $22 ; Random Access Record high byte. 532 | EQU R2, $23 ; Random Access Record Count Overflow. 533 | 534 | 535 | DPH: DB $00 ; Disk Parameter Header file. Get temorarily to 536 | EQU TRANS0, $00 ; Put in label for vector here if translation table. 00=no translate 537 | EQU SCRATCH1, $02 ; Scratchpad. 538 | EQU SCRATCH2, $04 ; Scratchpad. 539 | EQU SCRATCH3, $06 ; Scratchpad. 540 | EQU DIRBF, $08 ; 128 byte scratchpad buffer for file directory. ( used by BDOS ) 541 | EQU DIRBF_H, $09 ; High byte. 542 | EQU DPBLK, $0A ; Disk Parameter Block. 543 | EQU CHK00, $0C ; 544 | EQU ALL00, $0E ; Allocation table. 545 | EQU ALL00_H, $0FH ; High byte allocation table. 546 | 547 | ;DISK PARAMETER BLOCK Offsets. 548 | EQU SPT, $00 ; SPT Sectors per track. ie, 0 to 31. 549 | EQU SPT_H, $01 ; SPT High Byte Offset 550 | EQU BSF, $02 ; BSF Block Shift Factor - 3 bits over Bit6 ( Bit6 = 128 = allocation ). Bit7 = 256., Bit 8=512 and Bit9 is 1024 ( 0 to 1023 ). Number of bits past Bit6. 551 | EQU BLM, $03 ; BLM Block Mask - Related directly to the above. =2^BSF-1... 7 is a 3 bit mask, so BSF must be 3. 552 | EQU EXM, $04 ; EXM - Extent Mask - 553 | EQU DSM, $05 ; DSM - Disk size -1 - ie, 242= 243 BLOCKS. if the block size=1024k then 248,832 bytes on disk. Block=Allocation Unit. 554 | EQU DSM_H, $06 ; DSM High Byte - If not zero, two byte allocations. 555 | EQU DRM, $07 ; DRM - Directory Max - Directory Entries = 64 entries. If 1024K allocation holds 32 directory entries then this means 2 allocations for directory. 556 | EQU DRM_H, $08 ; DRM High Byte offset. 557 | EQU AL0, $09 ; AL0 - Alloc 0 ROTATE LEFT, One bit set per directory allocation. something like RL(IX+AL0) ??? 558 | EQU AL1, $0A ; AL1 - Yet, they used 16 bits for this - is it to quick-set the ALL00? I think it might be. 559 | EQU CKS, $0B ; CKS - Check size = (DRM+1) /4 for removable media. = 64/4 = 16... In this case, If fixed then DRM=0 = Do not check directory records - Not sure why. 560 | EQU OFF, $0D ; OFF - Track Offset. Number of "Reserved" tracks. 561 | 562 | ;.NOTES 563 | ;Everything after this point can just be text. It's just general notes. The assembly should stop at end or notes. 564 | ;5.6 System Function Summary 565 | ;Function 566 | ;Number Function 567 | ; Name Input Output 568 | ;Dec Hex 569 | ;0 0 System Reset none none 570 | ;1 1 Console Input none A = ASCII char 571 | ;2 2 Console Output E = char none 572 | ;3 3 Reader Input none A = ASCII char 573 | ;4 4 Punch Output E = char none 574 | ;5 5 List Output E = char none 575 | ;6 6 Direct Console I/O E = $FF (input) A = char 576 | ; E = $FE (status) A = status 577 | ; E = char none 578 | ;7 7 Get I/O Byte none A = I/O byte value 579 | ;8 8 Set I/O Byte E = I/O byte none 580 | ;9 9 Print String DE = Buffer Address none 581 | ;10 A Read Console String DE = Buffer Console characters in Buffer 582 | ;11 B Get Console Status none A = 00/non zero 583 | ;12 C Return Version # none HL = Version # 584 | ;13 D Reset Disk System none none 585 | ;14 E Select Disk E = Disk # none 586 | ;15 F Open File DE = FCB address A = FF if not found 587 | ;16 10 Close File DE = FCB address A = FF if not found 588 | ;17 11 Search For First DE = FCB address A = Directory Code 589 | ;18 12 Search For Next none A = Directory Code 590 | ;19 13 Delete File DE = FCB address A = none 591 | ;20 14 Read Sequential DE = FCB address A = Error Code 592 | ;21 15 Write Sequential DE = FCB Address A = Error Code 593 | ;22 16 Make File DE = FCB address A = FF if no DIR Space 594 | ;23 17 Rename File DE = FCB address A = FF if not found 595 | ;24 18 Return Login Vector none HL = Login Vector* 596 | ;25 19 Return Current Disk none A = Current Disk Number 597 | ;26 1A Set DMA Address DE = DMA address none 598 | ;27 1B Get ADDR (ALLOC) none HL = ALLOC address* 599 | ;28 1C Write Protect Disk none none 600 | ;29 1D Get Read/only Vector none HL = ALLOC address* 601 | ;30 1E Set File Attributes DE = FCB address A = none 602 | ;31 1F Get ADDR (Disk Parms) none HL = DPB address 603 | ;32 20 Set/Get User Code E = $FF for Get A = User Number 604 | ; E = 00 to $0F for Set none 605 | ;33 21 Read Random DE = FCB address A = Error 606 | ;34 22 Write Random DE = FCB address A = Error Code 607 | ;35 23 Compute File Size DE = FCB address r0, r1, r2 608 | ;36 24 Set Random Record DE = FCB address r0, r1, r2 609 | ;37 25 Reset Drive DE = Drive Vector A = 0 610 | ;38 26 Access Drive not supported not supported 611 | ;39 27 Free Drive not supported not supported 612 | ;40 28 Write Random w/Fill DE = FCB A = error code 613 | -------------------------------------------------------------------------------- /loki.cfg: -------------------------------------------------------------------------------- 1 | # Config file for z80 emulator. 2 | debug=0 3 | maxinstructions=0 4 | # Maxinstructions = 0 means forever. Otherwise stop after this many instructions. 5 | showstart=0 6 | showend=255 7 | savememory=0 8 | savestart=0 9 | saveend=65535 10 | savefile=test.z80 11 | #loadbios=bios.bin 12 | #startbios=986112 13 | # 14 | #loadbdos=My-bdos.bin 15 | #startbdos=983296 16 | # 17 | #loadccp=ccp.bin 18 | #startccp=53248 D000 19 | #startccp=57344 E000 20 | #startccp=49152 21 | # Should I make this 100? 22 | #loadtpa=tpa.bin 23 | #starttpa=256 24 | fixed=20237 25 | # Fixed is the "fixed" data ( 16 bits ) at address fixed, as though HL=FIXED and we see (HL) 26 | 27 | # Set the start position ( 0 = reset or warm boot? ) 28 | start=0 29 | # 30 | 31 | 32 | #end -------------------------------------------------------------------------------- /lokidisk.bas: -------------------------------------------------------------------------------- 1 | rem LOKIDISK - Creates a DISK for LOKI L: from a series of files, offset from F1000 - As L: 2 | rem 3 | rem 17-3-23 Fixed bug in record writing that didn't reduce number of records to write for very large files in multiple extents. 4 | 5 | dim shared DISK as string : rem 27512 ROM = 64K. That's the size of this disk. Actual disk includes BOOTSTRAP.BIN from 0000 to 0FFF also, then directory, then files. 6 | 7 | 8 | dim shared DIRECTORY as string : rem As we build the directory. 9 | 10 | dim shared FILESPACE as string : rem Temporary space to store the files.... 11 | dim shared nullfile as string : rem Construction of a NULL FILE - eg, Blank File followed by FF for Eprom Use. So we can add files later and reburn. 12 | 13 | 14 | dim shared BOOTFILE as string 15 | dim shared BDOSFILE as string 16 | dim shared BIOSFILE as string 17 | 18 | dim shared FILEIN as string : rem DOS. 19 | dim shared fileout as string : rem 8+3 format. 20 | dim shared filecontent as string : rem the actual file itself. 21 | dim shared thisfile as string : rem Contents of the single opened file. 22 | dim shared ALLOCATIONS as integer 23 | dim shared filesize as integer : rem 24 | dim shared records as integer : rem How many records have we written to the file? 25 | dim shared erecords as integer : rem How many records in just this extent? 26 | dim shared blocks as integer : rem How many blocks in the file? 27 | dim shared extents as integer : rem Which extent are we writing? 28 | dim shared nextalloc as integer : rem Current allocation ( base ) 29 | 30 | dim shared eloop as integer : rem Extent LOOP. 31 | dim shared aloop as integer : rem Allocation Loop. 32 | dim shared anum as integer : rem Allocation Number for the loop. 33 | dim shared allopoint as integer : rem allocation pointer. 34 | 35 | dim shared alloc(16) as integer : rem Array of allocations we want to write into the current extent. 36 | 37 | 38 | dim shared missing as integer : rem missing padding on end of file. 39 | 40 | BOOTFILE="bootstrap.bin" 41 | BDOSFILE="my-bdos.bin" 42 | BIOSFILE="bios.bin" 43 | FILEIN="ccp.bin" 44 | FILEOUT="ccp bin" 45 | 46 | rem File Name Structure 47 | rem UU F1 F2 F3 F4 F5 F6 F7 F8 T1 T2 T3 EX S1 S2 RC .FILENAMETYP.... 48 | rem AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL ................ 49 | 50 | rem Initialise variables. 51 | 52 | 53 | Function Printhex (Byref image as string, Byref numhex as integer) as integer 54 | 55 | rem Image is the string you want to dump as HEX. 56 | rem numhex is the number of characters you want to display ( won't be less than 32 displayed... Might adjust that later. ) 57 | 58 | dim a as integer 59 | dim b as integer 60 | dim c as integer 61 | 62 | print " ! ! ! ! ! ! ! ! " 63 | 64 | for a=0 to int(((numhex-1)/32)) 65 | 66 | print hex$(A*32,4);" - "; : rem Print relative location in Hex at start of line. 67 | 68 | for b=1 to 32 69 | print hex$(asc(mid$(image,(a*32)+b,1)),2);" "; : rem Print out the hex of the bytes. 70 | next b 71 | 72 | print " - "; 73 | 74 | for b=1 to 32 : rem Now print out the ASCII characters. 75 | c=asc(mid$(image,(a*32)+b,1)) 76 | if c<32 then c=asc("+") 77 | if c>127 then c=asc("+") 78 | print chr$(c); 79 | next b 80 | 81 | print 82 | 83 | next a 84 | 85 | Return len (image) 86 | 87 | End function 88 | 89 | 90 | Function LoadFile(ByRef filename As String) As String : rem Load in a file from the hard disk. Used to load in DISK DSK files. 91 | 92 | Dim h As Integer 93 | Dim txt As String 94 | 95 | h = FreeFile 96 | 97 | If Open( filename For Binary Access Read As #h ) <> 0 Then Return "" 98 | 99 | If LOF(h) > 0 Then 100 | 101 | txt = String(LOF(h), 0) 102 | If Get( #h, ,txt ) <> 0 Then txt = "" 103 | 104 | endIf 105 | 106 | Close #h 107 | 108 | Return txt 109 | 110 | end function 111 | 112 | 113 | Function PadFile (Byref filename as string, Byref Thislong as integer ) as string 114 | rem Call Loadfile, then pad with spaces until it's "thislong". 115 | dim result as string 116 | 117 | result=LoadFile (filename) 118 | if len(result) > Thislong then print "##### ERROR - Input file is bigger than allocated space. Truncation will occur #####" 119 | print "Padding:";filename;len(result);" bytes - Segment is"; 120 | result=result+string(Thislong,0) 121 | result=left(result,Thislong) 122 | print len(result);" bytes" 123 | return result 124 | end function 125 | 126 | function COPYBOOT (Byref DISK as string) as integer 127 | 128 | dim result as integer 129 | dim block as string 130 | result=0 131 | block=padfile(BOOTFILE,256) 132 | print "Loading:";BOOTFILE;" Length:";LEN(BLOCK) 133 | 134 | close #2 135 | 136 | return result 137 | end function 138 | 139 | function PAD (Byref text as string, Byref Length as integer ) as string 140 | rem Pad out any space to achieve LENGTH size. 141 | dim result as string 142 | 143 | result=text+string(Length,0) 144 | result=left(result,Length) 145 | 146 | return result 147 | end function 148 | 149 | function PADE5 (Byref text as string, Byref Length as integer ) as string 150 | rem Pad out any space to achieve LENGTH size. 151 | dim result as string 152 | 153 | result=text+string(Length,&h0E5) 154 | result=left(result,Length) 155 | 156 | return result 157 | end function 158 | 159 | function SECTPAD (Byref text as string) as string 160 | rem Pad a file to the allocation size ( allocation = 1024bytes) 161 | dim result as string 162 | dim size as integer 163 | dim resize as integer 164 | 165 | rem Pad the sector to allocation size blocks, and records records, allocations and extents. 166 | 167 | size=len(text) 168 | resize=(int((size-1)/1024)+1)*1024 169 | records=(int((size-1)/128)+1) 170 | result=text+string(1023,0) 171 | result=left(result,resize) 172 | ALLOCATIONS=(resize/1024) 173 | extents=(int((allocations-1)/16)+1) 174 | Print "File size:";size;" Resizing to:";resize ;" = ";resize/1024;" allocations contained in"; records;" records to write in";extents;" Extents - SCHECK:";len(RESULT) 175 | 176 | 177 | return result 178 | end function 179 | 180 | 181 | function transfer (Byref DISKFILE as string, Byref Fileout as string) as integer 182 | dim result as integer 183 | rem ALL GLOBAL VARIABLES. 184 | 185 | thisfile=sectpad(loadfile(DISKFILE)) 186 | 187 | print "Adding file:";fileout 188 | filecontent=filecontent+thisfile : rem File is now added as allocations to the filecontent ( written to disk last ). 189 | 190 | for eloop=0 to extents-1 : rem As many allocation filenames as we need to add. 191 | 192 | directory=directory+chr(0)+fileout+chr(eloop)+chr(0)+chr(0) 193 | 194 | if allocations>16 then 195 | allocations=allocations-16 196 | records=records-128 197 | anum=16 198 | directory=directory+chr(&h80) 199 | else 200 | anum=allocations 201 | directory=directory+chr(records) 202 | endif 203 | 204 | for aloop = 1 to 16 : rem number of allocations. 205 | if aloop <= anum then 206 | directory=directory+chr(nextalloc) 207 | nextalloc=nextalloc+1 : rem Increment allocation number 208 | rem print "Writing allocation:";aloop;"with block:";nextalloc 209 | else 210 | directory=directory+chr(0) 211 | rem print "Writing allocation:";aloop;"with block:";0 212 | endif 213 | next aloop 214 | next eloop 215 | 216 | 217 | return result 218 | end function 219 | 220 | MAIN: 221 | 222 | disk=disk+padfile(bootfile,256) 223 | disk=disk+padfile(bdosfile,2816) 224 | disk=disk+padfile(biosfile,1024) 225 | 226 | print "Disk Boot Image Created - LOKI.DSK ";len(disk);" bytes" 227 | 228 | 229 | 230 | nextalloc=2 : rem Start with allocation 2 - Allocations 0 and 1 are used for the directory. 231 | 232 | 233 | rem FILE TRANSFER LIST - THIS IS FIXED SINCE IT GENERATES AN EPROM OUTPUT. 234 | transfer ("ccp.bin","CCP BIN") 235 | 236 | transfer ("startup.bin","LOKI COM") 237 | 238 | transfer ("stat.com","STAT COM") 239 | 240 | transfer ("altccp.bin","ALTCCP BIN") 241 | transfer ("altbdos.bin","ALTBDOS BIN") 242 | transfer ("moveccp.bin","MOVECCP COM") 243 | transfer ("movebdos.bin","MOVEBDOSCOM") 244 | transfer ("halt.bin","HALT COM") 245 | transfer ("loadcpm.bin","LOADCPM COM") 246 | transfer ("altcpm.bin","ALTCPM BIN") 247 | transfer ("reset.bin","RESET COM") 248 | 249 | transfer ("pip.com","PIP COM") 250 | transfer ("fsck.com","FSCK COM") 251 | 252 | rem print "Directory:";directory 253 | 254 | 255 | print "Base Directory Size:";len(directory) 256 | directory=pade5(directory,2048) : rem size of 2 directory allocations. 257 | directory=left(directory,2048) 258 | print "Final Directory Size:";len(directory) 259 | 260 | printhex (Directory,512) 261 | 262 | 263 | rem File Name Structure 264 | rem UU F1 F2 F3 F4 F5 F6 F7 F8 T1 T2 T3 EX S1 S2 RC .FILENAMETYP.... 265 | rem AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL ................ 266 | rem thisfile 267 | rem thissize 268 | rem thisalloc 269 | rem thisextent 270 | rem dim shared FILEIN as string : rem DOS. 271 | rem dim shared fileout as string : rem 8+3 format. 272 | rem dim shared thisfile as string : rem Contents of the single opened file. 273 | rem dim shared filecontent as string : rem the actual file itself. 274 | rem dim shared ALLOCATIONS as integer 275 | rem dim shared filesize as integer : rem 276 | rem dim shared records as integer : rem How many records have we written to the file? 277 | rem dim shared blocks as integer : rem How many blocks in the file? 278 | rem dim shared extents as integer : rem Which extent are we writing? 279 | 280 | DISK=DISK+Directory+filecontent 281 | 282 | 283 | Print "Final disk image size:";len(DISK) 284 | 285 | 286 | rem printhex (filecontent,len(filecontent)) 287 | 288 | open "loki.img" for output as #1 289 | print #1,DISK; 290 | close #1 291 | 292 | 293 | end -------------------------------------------------------------------------------- /lokinvm.bas: -------------------------------------------------------------------------------- 1 | rem LOKIDISK - Creates a DISK for LOKI L: from a series of files, offset from F1000 - As L: 2 | rem 3 | rem 17-3-23 Fixed bug in record writing that didn't reduce number of records to write for very large files in multiple extents. 4 | 5 | dim shared DISK as string : rem 27512 ROM = 64K. That's the size of this disk. Actual disk includes BOOTSTRAP.BIN from 0000 to 0FFF also, then directory, then files. 6 | 7 | 8 | dim shared DIRECTORY as string : rem As we build the directory. 9 | 10 | dim shared FILESPACE as string : rem Temporary space to store the files.... 11 | dim shared nullfile as string : rem Construction of a NULL FILE - eg, Blank File followed by FF for Eprom Use. So we can add files later and reburn. 12 | 13 | 14 | dim shared BOOTFILE as string 15 | dim shared BDOSFILE as string 16 | dim shared BIOSFILE as string 17 | 18 | dim shared FILEIN as string : rem DOS. 19 | dim shared fileout as string : rem 8+3 format. 20 | dim shared filecontent as string : rem the actual file itself. 21 | dim shared thisfile as string : rem Contents of the single opened file. 22 | dim shared ALLOCATIONS as integer 23 | dim shared filesize as integer : rem 24 | dim shared records as integer : rem How many records have we written to the file? 25 | dim shared erecords as integer : rem How many records in just this extent? 26 | dim shared blocks as integer : rem How many blocks in the file? 27 | dim shared extents as integer : rem Which extent are we writing? 28 | dim shared nextalloc as integer : rem Current allocation ( base ) 29 | 30 | dim shared eloop as integer : rem Extent LOOP. 31 | dim shared aloop as integer : rem Allocation Loop. 32 | dim shared anum as integer : rem Allocation Number for the loop. 33 | dim shared allopoint as integer : rem allocation pointer. 34 | 35 | dim shared alloc(16) as integer : rem Array of allocations we want to write into the current extent. 36 | 37 | 38 | dim shared missing as integer : rem missing padding on end of file. 39 | 40 | BOOTFILE="bootstrap.bin" 41 | BDOSFILE="My-Bdos.bin" 42 | BIOSFILE="bios.bin" 43 | FILEIN="ccp.bin" 44 | FILEOUT="ccp bin" 45 | 46 | rem File Name Structure 47 | rem UU F1 F2 F3 F4 F5 F6 F7 F8 T1 T2 T3 EX S1 S2 RC .FILENAMETYP.... 48 | rem AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL ................ 49 | 50 | rem Initialise variables. 51 | 52 | 53 | Function Printhex (Byref image as string, Byref numhex as integer) as integer 54 | 55 | rem Image is the string you want to dump as HEX. 56 | rem numhex is the number of characters you want to display ( won't be less than 32 displayed... Might adjust that later. ) 57 | 58 | dim a as integer 59 | dim b as integer 60 | dim c as integer 61 | 62 | print " ! ! ! ! ! ! ! ! " 63 | 64 | for a=0 to int(((numhex-1)/32)) 65 | 66 | print hex$(A*32,4);" - "; : rem Print relative location in Hex at start of line. 67 | 68 | for b=1 to 32 69 | print hex$(asc(mid$(image,(a*32)+b,1)),2);" "; : rem Print out the hex of the bytes. 70 | next b 71 | 72 | print " - "; 73 | 74 | for b=1 to 32 : rem Now print out the ASCII characters. 75 | c=asc(mid$(image,(a*32)+b,1)) 76 | if c<32 then c=asc("+") 77 | if c>127 then c=asc("+") 78 | print chr$(c); 79 | next b 80 | 81 | print 82 | 83 | next a 84 | 85 | Return len (image) 86 | 87 | End function 88 | 89 | 90 | Function LoadFile(ByRef filename As String) As String : rem Load in a file from the hard disk. Used to load in DISK DSK files. 91 | 92 | Dim h As Integer 93 | Dim txt As String 94 | 95 | h = FreeFile 96 | 97 | If Open( filename For Binary Access Read As #h ) <> 0 Then Return "" 98 | 99 | If LOF(h) > 0 Then 100 | 101 | txt = String(LOF(h), 0) 102 | If Get( #h, ,txt ) <> 0 Then txt = "" 103 | 104 | endIf 105 | 106 | Close #h 107 | 108 | Return txt 109 | 110 | end function 111 | 112 | 113 | Function PadFile (Byref filename as string, Byref Thislong as integer ) as string 114 | rem Call Loadfile, then pad with spaces until it's "thislong". 115 | dim result as string 116 | 117 | result=LoadFile (filename) 118 | if len(result) > Thislong then print "##### ERROR - Input file is bigger than allocated space. Truncation will occur #####" 119 | print "Padding:";filename;len(result);" bytes - Segment is"; 120 | result=result+string(Thislong,0) 121 | result=left(result,Thislong) 122 | print len(result);" bytes" 123 | return result 124 | end function 125 | 126 | function COPYBOOT (Byref DISK as string) as integer 127 | 128 | dim result as integer 129 | dim block as string 130 | result=0 131 | block=padfile(BOOTFILE,256) 132 | print "Loading:";BOOTFILE;" Length:";LEN(BLOCK) 133 | 134 | close #2 135 | 136 | return result 137 | end function 138 | 139 | function PAD (Byref text as string, Byref Length as integer ) as string 140 | rem Pad out any space to achieve LENGTH size. 141 | dim result as string 142 | 143 | result=text+string(Length,0) 144 | result=left(result,Length) 145 | 146 | return result 147 | end function 148 | 149 | function PADE5 (Byref text as string, Byref Length as integer ) as string 150 | rem Pad out any space to achieve LENGTH size. 151 | dim result as string 152 | 153 | result=text+string(Length,&h0E5) 154 | result=left(result,Length) 155 | 156 | return result 157 | end function 158 | 159 | function SECTPAD (Byref text as string) as string 160 | rem Pad a file to the allocation size ( allocation = 1024bytes) 161 | dim result as string 162 | dim size as integer 163 | dim resize as integer 164 | 165 | rem Pad the sector to allocation size blocks, and records records, allocations and extents. 166 | 167 | size=len(text) 168 | resize=(int((size-1)/1024)+1)*1024 169 | records=(int((size-1)/128)+1) 170 | result=text+string(1023,0) 171 | result=left(result,resize) 172 | ALLOCATIONS=(resize/1024) 173 | extents=(int((allocations-1)/16)+1) 174 | Print "File size:";size;" Resizing to:";resize ;" = ";resize/1024;" allocations contained in"; records;" records to write in";extents;" Extents - SCHECK:";len(RESULT) 175 | 176 | 177 | return result 178 | end function 179 | 180 | 181 | function transfer (Byref DISKFILE as string, Byref Fileout as string) as integer 182 | dim result as integer 183 | rem ALL GLOBAL VARIABLES. 184 | 185 | thisfile=sectpad(loadfile(DISKFILE)) 186 | 187 | print "Adding file:";fileout 188 | filecontent=filecontent+thisfile : rem File is now added as allocations to the filecontent ( written to disk last ). 189 | 190 | for eloop=0 to extents-1 : rem As many allocation filenames as we need to add. 191 | 192 | directory=directory+chr(0)+fileout+chr(eloop)+chr(0)+chr(0) 193 | 194 | if allocations>16 then 195 | allocations=allocations-16 196 | records=records-128 197 | anum=16 198 | directory=directory+chr(&h80) 199 | else 200 | anum=allocations 201 | directory=directory+chr(records) 202 | endif 203 | 204 | for aloop = 1 to 16 : rem number of allocations. 205 | if aloop <= anum then 206 | directory=directory+chr(nextalloc) 207 | nextalloc=nextalloc+1 : rem Increment allocation number 208 | rem print "Writing allocation:";aloop;"with block:";nextalloc 209 | else 210 | directory=directory+chr(0) 211 | rem print "Writing allocation:";aloop;"with block:";0 212 | endif 213 | next aloop 214 | next eloop 215 | 216 | 217 | return result 218 | end function 219 | 220 | MAIN: 221 | 222 | rem disk=disk+padfile(bootfile,256) : rem NOT A SYSTEM DISK, so no boot structure here. 223 | rem disk=disk+padfile(bdosfile,2816) 224 | rem disk=disk+padfile(biosfile,1024) 225 | 226 | print "Disk Boot Image Created - LOKI.DSK ";len(disk);" bytes" 227 | 228 | 229 | 230 | nextalloc=4 : rem Start with allocation 4 - Allocations 0,1,2,3 are used for the directory. Allocations are 1K. Similar to L: drive. 231 | 232 | 233 | rem FILE TRANSFER LIST - THIS IS FIXED SINCE IT GENERATES AN EPROM OUTPUT. 234 | transfer ("longtext.txt","TEST TXT") 235 | transfer ("zork2.com","ZORK2 COM") 236 | transfer ("zork2.dat","ZORK2 DAT") 237 | transfer ("zork3.com","ZORK3 COM") 238 | transfer ("zork3.dat","ZORK3 DAT") 239 | transfer ("mbasic.com","MBASIC COM") 240 | transfer ("memdump.bin","MEMDUMP COM") 241 | transfer ("testx.bin","TESTX COM") 242 | transfer ("fsck.com","FSCK COM") 243 | 244 | rem print "Directory:";directory 245 | 246 | 247 | print "Base Directory Size:";len(directory) 248 | directory=pade5(directory,4096) : rem size of 2 directory allocations, but for NVM, Allocation is 2K.... 249 | directory=left(directory,4096) 250 | print "Final Directory Size:";len(directory) 251 | 252 | printhex (Directory,512) 253 | 254 | 255 | rem File Name Structure 256 | rem UU F1 F2 F3 F4 F5 F6 F7 F8 T1 T2 T3 EX S1 S2 RC .FILENAMETYP.... 257 | rem AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL ................ 258 | rem thisfile 259 | rem thissize 260 | rem thisalloc 261 | rem thisextent 262 | rem dim shared FILEIN as string : rem DOS. 263 | rem dim shared fileout as string : rem 8+3 format. 264 | rem dim shared thisfile as string : rem Contents of the single opened file. 265 | rem dim shared filecontent as string : rem the actual file itself. 266 | rem dim shared ALLOCATIONS as integer 267 | rem dim shared filesize as integer : rem 268 | rem dim shared records as integer : rem How many records have we written to the file? 269 | rem dim shared blocks as integer : rem How many blocks in the file? 270 | rem dim shared extents as integer : rem Which extent are we writing? 271 | 272 | DISK=DISK+Directory+filecontent 273 | 274 | 275 | Print "Final disk image size:";len(DISK) 276 | 277 | 278 | rem printhex (filecontent,len(filecontent)) 279 | 280 | open "nvm.img" for output as #1 281 | print #1,DISK; 282 | close #1 283 | 284 | 285 | end -------------------------------------------------------------------------------- /make.bat: -------------------------------------------------------------------------------- 1 | fbc ASM.BAS 2 | fbc LOKI2.BAS 3 | fbc LOKIDISK.BAS 4 | fbc LOKINVM.BAS 5 | fbc BINDISK.BAS 6 | asm BIOS 7 | asm MY-BDOS 8 | asm CCP 9 | asm BOOTSTRAP 10 | asm STARTUP 11 | asm ALTBDOS 12 | asm ALTCCP 13 | asm MOVEBDOS 14 | asm MOVECCP 15 | asm LINSTALL 16 | asm VIDEO512 17 | asm NETWORK 18 | asm NET 19 | 20 | LOKIDISK 21 | LOKINVM 22 | BINDISK -------------------------------------------------------------------------------- /make.linux: -------------------------------------------------------------------------------- 1 | fbc asm.bas 2 | fbc loki2.bas 3 | fbc lokidisk.bas 4 | fbc lokinvm.bas 5 | fbc bindisk.bas 6 | ./asm bios 7 | ./asm my-bdos 8 | ./asm ccp 9 | ./asm bootstrap 10 | ./asm startup 11 | ./asm altbdos 12 | ./asm altccp 13 | ./asm movebdos 14 | ./asm moveccp 15 | ./asm linstall 16 | ./asm video512 17 | ./asm network 18 | ./asm net 19 | 20 | ./lokidisk 21 | ./lokinvm 22 | ./bindisk -------------------------------------------------------------------------------- /movebdos.asm: -------------------------------------------------------------------------------- 1 | ; Move CPM Alternative CCP file to replace existing LOKI CCP file. 2 | ; 3 | 4 | .equ BDOS,$0005 5 | .equ MYBDOS,$F000 6 | .equ DRBDOS,$ED00 7 | 8 | .org $0100 9 | 10 | 11 | ; Let's reuse the CCP load routine from the BDOS to load a replacement CCP. 12 | 13 | ;out ($13),A ; turn on debug 14 | CALL CCP_LOAD 15 | RST 0 16 | 17 | 18 | 19 | 20 | 21 | ;DR F1 F2 F3 F4 F5 F6 F7 F8 T1 T2 T3 EX S1 S2 RC .FILENAMETYP... 22 | ;AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL ............... 23 | ;CR R0 R1 R2 .... 24 | CCP_FCB: DB $00 ; FCB for Command and Control Process. 25 | CCP_FILENAME: DB 'altbdos bin' ; CCP Filename - and it's drive L: 26 | CCP_FILENAME_CNT: DB $00,$00,$00,$00 ; File variable tracking. 27 | CCP_FILENAME_ALL: DB $00,$00,$00,$00, $00,$00,$00,$00, $00,$00,$00,$00, $00,$00,$00,$00 ; For the allocations. 28 | CCP_FILENAME_HND: DB $00,$00,$00,$00 ; Four working bytes. 29 | 30 | CCP_PTR: DW $0000 ; Where in the process are we? How much of the CCP have we written? 31 | 32 | 33 | CCP_LOAD: ; Here's where we load the CCP.... I really want this at the end of the BDOS, and would be nice to have in the BIOS. 34 | LD DE,CCPPRELOAD 35 | LD C,9 ; BDOS Print String. 36 | CALL BDOS ; Message. 37 | 38 | LD DE,$0080 ; Default DMA at 0080 -let's use that to save memory in the BDOS. 39 | LD C,26 ; Function 1A - Set the DMA address to the buffer at 0080. 40 | CALL BDOS ; Set DMA to 0080 - This is because the directory will show up here. Saves us copying the filenames too. 41 | 42 | LD DE,CCP_FCB ; Set up the file open for L:CCP.BIN 43 | LD C,15 ; Function 15 = Open File. 44 | CALL BDOS ; Get the first matching file. 45 | AND %11111100 ; Mask the result bytes. 46 | JR NZ,CCP_ERROR ; If the file didn't open, then exit. AND will clear CF = error. 47 | 48 | 49 | 50 | 51 | ; LD DE,DRBDOS ; BDOS destination starts at DRBDOS - 52 | LD DE,$8000 ; temp store before transfer. 53 | LD (CCP_PTR),DE ; Store the pointer that contains where we will store the file. 54 | CCP_READ: 55 | LD DE,CCP_FCB ; The CCP is located on L: and is usually the first file, but this is enough to find it on L: 56 | LD C,20 ; BDOS Function 20 = read record 57 | CALL BDOS ; Get the first matching file. 58 | OR A ; Check for file status - And exit if we're at EOF ( we already exited earlier for other result other than read OK ) 59 | JP NZ,CCP_RUN ; Once we are fully loaded, try to execute the CCP. 60 | 61 | 62 | ; LD HL,$107F ; Reserved space before we transfer file. 63 | ; LD DE,(CCP_PTR) 64 | ; ADD HL,DE 65 | ; JR C,BDOSFAIL ; We will overwrite original BDOS at $F000 if we proceed - so error out. 66 | 67 | LD HL,$0080 ; Transfer the file to memory. 68 | LD DE,(CCP_PTR) 69 | LD BC,$80 70 | LDIR 71 | LD (CCP_PTR),DE ; Store the destination pointer for the next cycle. 72 | 73 | JR CCP_READ ; Loop until completely loaded - Note: CCP can ALSO include the BDOS and BIOS if both need to be overwritten. Cool eh? 74 | 75 | CCP_ERROR: ; THIS SHOULD NEVER HAPPEN. ROM IS CORRUPT. WTF? Do I even need to do this? 76 | LD DE,CCPERROR ; DE = Error string. 77 | LD C,9 ; Function 9 = Display String pointed to by DE 78 | CALL BDOS 79 | ; HALT ; Nothing else to do... Hopefully they can reburn the boot eprom. Maybe they forgot the file? 80 | JP $0000 ; The halt should not go away. 81 | 82 | CCP_RUN: 83 | LD C,9 84 | LD DE,BDOSSUCCESS 85 | CALL BDOS 86 | 87 | ; JP $0000 ; exit. 88 | 89 | 90 | 91 | LD DE,M1 92 | LD C,PRINT_STRING ; print string. 93 | CALL BDOS 94 | 95 | LD HL,$8000 96 | LD DE,DRBDOS 97 | LD BC,$0E00 ; 2 pages short of a block. 98 | LDIR ; transfer it AS A BLOCK. 99 | 100 | LD HL,DRBDOS+$06 101 | LD ($0006),HL ; write new BDOS location into the BDOS Hook. 102 | LD C,SELECT_DISK 103 | LD E,$0B ; disk L I think. 104 | CALL BDOS 105 | 106 | LD C,RESET_DISK_SYSTEM 107 | CALL BDOS 108 | 109 | LD DE,M2 110 | LD C,PRINT_STRING 111 | CALL BDOS 112 | 113 | ; out ($10),A ; Display debub notice 114 | ; out ($13),A ; turn on debug 115 | 116 | ; CALL OTHERTESTS 117 | 118 | ; out (10),A ; turn off debug 119 | 120 | ret 121 | 122 | 123 | ;; 124 | 125 | M1: DB 'Preparing to migrate BDOS and install hook.',$0D,$0A,'$' 126 | 127 | M2: DB 'BDOS Hook installed...',$0D,$0A,'$' 128 | 129 | 130 | 131 | BDOSFAIL: 132 | LD C,9 ; Print 133 | LD DE,BDOSOVER 134 | CALL BDOS 135 | JP $0000 136 | 137 | 138 | CCPPRELOAD: DB 'Loading ALTBDOS.BIN to E200 - Not forking BDOS hook',$0A,$0D,'$' 139 | CCPERROR: DB 'ALTBDOS.BIN not found. Exiting',$0A,$0D,'$' 140 | 141 | BDOSOVER: DB 'Error: Attempted to overwrite existing BDOS - Exiting.',$0D,$0A,'$' 142 | BDOSSUCCESS: DB 'BDOS Successfully loaded at E200',$0D,$0A,'$' 143 | 144 | 145 | 146 | 147 | ;#################################################################################### 148 | ; 149 | ;# Constants, Notes, Tables, etc. 150 | ; 151 | ;#################################################################################### 152 | 153 | 154 | FBC: DW $00 ; File Control Block location Save. First is a label in memory. Rest are offset for IX. 155 | EQU DR, $00 ; Offset 0 156 | EQU EX, $0C ; Extent counter. 00, 01 etc. First extent is 00. 157 | EQU S1, $0D 158 | EQU S2, $0E 159 | EQU RC, $0F ; Record counter. File is RC * 128 bytes. 160 | EQU ALLOC1, $10 ; First byte of allocation data or other contents. Copied from Directory for this extent. 161 | EQU ALLOC2, $11 ; Second byte of the allocation data or other contents. 162 | EQU CR, $20 ; Current record within extent. 0= first record. 163 | EQU R0, $21 ; Low byte first. 16 bit counter for Random Access Record Number. Record =128 block. 164 | EQU R1, $22 ; Random Access Record high byte. 165 | EQU R2, $23 ; Random Access Record Count Overflow. 166 | 167 | 168 | DPH: DB $00 ; Disk Parameter Header file. Get temorarily to 169 | EQU TRANS0, $00 ; Put in label for vector here if translation table. 00=no translate 170 | EQU SCRATCH1, $02 ; Scratchpad. 171 | EQU SCRATCH2, $04 ; Scratchpad. 172 | EQU SCRATCH3, $06 ; Scratchpad. 173 | EQU DIRBF, $08 ; 128 byte scratchpad buffer for file directory. ( used by BDOS ) 174 | EQU DIRBF_H, $09 ; High byte. 175 | EQU DPBLK, $0A ; Disk Parameter Block. 176 | EQU CHK00, $0C ; 177 | EQU ALL00, $0E ; Allocation table. 178 | EQU ALL00_H, $0FH ; High byte allocation table. 179 | 180 | ;DISK PARAMETER BLOCK Offsets. 181 | EQU SPT, $00 ; SPT Sectors per track. ie, 0 to 31. 182 | EQU SPT_H, $01 ; SPT High Byte Offset 183 | EQU BSF, $02 ; BSF Block Shift Factor - 3 bits over Bit6 ( Bit6 = 128 = allocation ). Bit7 = 256., Bit 8=512 and Bit9 is 1024 ( 0 to 1023 ). Number of bits past Bit6. 184 | EQU BLM, $03 ; BLM Block Mask - Related directly to the above. =2^BSF-1... 7 is a 3 bit mask, so BSF must be 3. 185 | EQU EXM, $04 ; EXM - Extent Mask - 186 | EQU DSM, $05 ; DSM - Disk size -1 - ie, 242= 243 BLOCKS. if the block size=1024k then 248,832 bytes on disk. Block=Allocation Unit. 187 | EQU DSM_H, $06 ; DSM High Byte - If not zero, two byte allocations. 188 | EQU DRM, $07 ; DRM - Directory Max - Directory Entries = 64 entries. If 1024K allocation holds 32 directory entries then this means 2 allocations for directory. 189 | EQU DRM_H, $08 ; DRM High Byte offset. 190 | EQU AL0, $09 ; AL0 - Alloc 0 ROTATE LEFT, One bit set per directory allocation. something like RL(IX+AL0) ??? 191 | EQU AL1, $0A ; AL1 - Yet, they used 16 bits for this - is it to quick-set the ALL00? I think it might be. 192 | EQU CKS, $0B ; CKS - Check size = (DRM+1) /4 for removable media. = 64/4 = 16... In this case, If fixed then DRM=0 = Do not check directory records - Not sure why. 193 | EQU OFF, $0D ; OFF - Track Offset. Number of "Reserved" tracks. 194 | 195 | ;DPBLK2: ; Example. 196 | ; DW $20 ; 00,01 - eg, 32 sectors per track. 197 | ; DB 3 ; 02 - Like block mask, but a binary nuber. 1 less than the blocks. 198 | ; DB 7 ; 03 - 8 records per Allocation. 0 is not an option ( or would mean 128 or 256 records/alloc ) 199 | ; DB 0 ; 04 - Extent mask - Blocks are 1K, so should be zero. If 2K, 1, if 4K, 3 etc. Our extents are fixed 16K 200 | ; DW 242 ; 05,06 - Number of allocation on disk. 242 = 243K. 201 | ; DW 63 ; 07,08 - Directory Max (Max extents in directory allocations ). We could figure this out, but it's here. 202 | ; DB %11000000 ;09 - High bits set first. One bit for each directory allocation (block). 203 | ; DB %00000000 ;0A - More directory bits in case 8 isn't enough. 204 | ; DW 16 ; 0B,0C - Check size, DRM+1/4. or 0 for fixed. Not sure if I'll use this. 205 | ; DW 42 ; 0D,0E - Track Offset - Number of reserved tracks. The BIOS handles this. We can forget it. We just get Track0. 206 | 207 | 208 | 209 | ;CP/M File Control Block 210 | ; 211 | ;The File Control Block is a 36-byte data structure (33 bytes in CP/M 1). It is laid out as follows: 212 | ; 213 | ;DR F1 F2 F3 F4 F5 F6 F7 F8 T1 T2 T3 EX S1 S2 RC .FILENAMETYP... 214 | ;AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL ............... 215 | ;CR R0 R1 R2 .... 216 | ; 217 | ;The bytes in it have the following meanings: 218 | ; 219 | ; 220 | ;FCB+00h DR - Drive. 0 for default, 1-16 for A-P. In DOSPLUS, 221 | ; bit 7 can be set to indicate that the operation should work with 222 | ; subdirectories rather than files. 223 | ; 224 | ;FCB+01h Fn - Filename, 7-bit ASCII. The top bits of the filename bytes 225 | ; (usually referred to as F1' to F8') have the following 226 | ; meanings: 227 | ; F1'-F4' - User-defined attributes. Any program can use 228 | ; them in any way it likes. The filename in the 229 | ; disc directory has the corresponding bits set. 230 | ; F5'-F8' - Interface attributes. They modify the 231 | ; behaviour of various BDOS functions or 232 | ; indicate error conditions. In the directory 233 | ; these bits are always zero. 234 | ;FCB+09h Tn - Filetype, 7-bit ASCII. T1' to T3' have the following 235 | ; meanings: 236 | ; T1' - Read-Only. 237 | ; T2' - System (hidden). System files in user 0 can be 238 | ; opened from other user areas. 239 | ; T3' - Archive. Set if the file has not been changed 240 | ; since it was last copied. 241 | ;FCB+0Ch EX - Set this to 0 when opening a file and then leave it to 242 | ; CP/M. You can rewind a file by setting EX, RC, S2 and CR to 0. 243 | ;FCB+0Dh S1 - Reserved. 244 | ;FCB+0Eh S2 - Reserved. 245 | ;FCB+0Fh RC - Set this to 0 when opening a file and then leave it to 246 | ; CP/M. 247 | ;FCB+10h AL - Image of the second half of the directory entry, 248 | ; containing the file's allocation (which disc blocks it 249 | ; owns). 250 | ;FCB+20h CR - Current record within extent. It is usually best to set 251 | ; this to 0 immediately after a file has been opened and 252 | ; then ignore it. 253 | ;FCB+21h Rn - Random access record number (not CP/M 1). A 16-bit 254 | ; value in CP/M 2 (with R2 used for overflow); an 18-bit 255 | ; value in CP/M 3. 256 | ; 257 | ;How CP/M uses the bytes EX, S2, and CR. Some programs (such as the Digital Research linker, LINK.COM) manipulate these bytes to perform "seek" operations in files without using the random-access calls. 258 | ; 259 | ;CR = current record, ie (file pointer % 16384) / 128 260 | ;EX = current extent, ie (file pointer % 524288) / 16384 261 | ;S2 = extent high byte, ie (file pointer / 524288). The CP/M Plus source 262 | ; code refers to this use of the S2 byte as 'module number'. 263 | 264 | 265 | 266 | 267 | 268 | 269 | ; ########################################## Equates. 270 | 271 | 272 | ; CPM Calls as EQU functions. 273 | EQU System_Reset , 0 274 | EQU Console_Input , 1 275 | EQU Console_Output , 2 276 | EQU Reader_Input , 3 277 | EQU Punch_Output , 4 278 | EQU List_Output , 5 279 | EQU Direct_Console_IO , 6 280 | EQU Get_IO_Byte , 7 281 | EQU Set_IO_Byte , 8 282 | EQU Print_String , 9 283 | EQU Read_Console_String , 10 284 | EQU Get_Console_Status , 11 285 | EQU Return_Version , 12 286 | EQU Reset_Disk_System , 13 287 | EQU Select_Disk , 14 288 | EQU Open_File , 15 289 | EQU Close_File , 16 290 | EQU Search_For_First , 17 291 | EQU Search_For_Next , 18 292 | EQU Delete_File , 19 293 | EQU Read_Sequential , 20 294 | EQU Write_Sequential , 21 295 | EQU Make_File , 22 296 | EQU Rename_File , 23 297 | EQU Return_Login_Vector , 24 298 | EQU Return_Current_Disk , 25 299 | EQU Set_DMA_Address , 26 300 | EQU Get_ADDR_ALLOC , 27 301 | EQU Write_Protect_Disk , 28 302 | EQU Get_RO_Vector , 29 303 | EQU Set_File_Attributes , 30 304 | EQU Get_ADDR_DiskParms , 31 305 | EQU Set_Get_User_Code , 32 306 | EQU Read_Random , 33 307 | EQU Write_Random , 34 308 | EQU Compute_File_Size , 35 309 | EQU Set_Random_Record , 36 310 | EQU Reset_Drive , 37 311 | EQU Access_Drive , 38 312 | EQU Free_Drive , 39 313 | EQU Write_Random_Fill , 40 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | .END 323 | 324 | -------------------------------------------------------------------------------- /moveccp.asm: -------------------------------------------------------------------------------- 1 | ; Move CPM Alternative CCP file to replace existing LOKI CCP file. 2 | ; 3 | 4 | .equ BDOS,$0005 5 | .equ MYBDOS,$F000 6 | .equ DRCCP,$E500 7 | .equ BIOSJUMP,$FC3E ; BIOS Jump Vector for warm boot. 8 | 9 | .org $0100 10 | 11 | 12 | ; Let's reuse the CCP load routine from the BDOS to load a replacement CCP. 13 | 14 | ;out ($13),A ; turn on debug 15 | CALL CCP_LOAD 16 | RST 0 17 | 18 | 19 | 20 | 21 | 22 | ;DR F1 F2 F3 F4 F5 F6 F7 F8 T1 T2 T3 EX S1 S2 RC .FILENAMETYP... 23 | ;AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL ............... 24 | ;CR R0 R1 R2 .... 25 | CCP_FCB: DB $00 ; FCB for Command and Control Process. 26 | CCP_FILENAME: DB 'ALTCCP BIN' ; CCP Filename - and it's drive L: 27 | CCP_FILENAME_CNT: DB $00,$00,$00,$00 ; File variable tracking. 28 | CCP_FILENAME_ALL: DB $00,$00,$00,$00, $00,$00,$00,$00, $00,$00,$00,$00, $00,$00,$00,$00 ; For the allocations. 29 | CCP_FILENAME_HND: DB $00,$00,$00,$00 30 | 31 | CCP_PTR: DW $0000 ; Where in the process are we? How much of the CCP have we written? 32 | 33 | 34 | CCP_LOAD: ; Here's where we load the CCP.... I really want this at the end of the BDOS, and would be nice to have in the BIOS. 35 | LD DE,CCPPRELOAD 36 | LD C,9 ; BDOS Print String. 37 | CALL BDOS ; Message. 38 | 39 | LD DE,$0080 ; Default DMA at 0080 -let's use that to save memory in the BDOS. 40 | LD C,26 ; Function 1A - Set the DMA address to the buffer at 0080. 41 | CALL BDOS ; Set DMA to 0080 - This is because the directory will show up here. Saves us copying the filenames too. 42 | 43 | LD DE,CCP_FCB ; Set up the file open for L:CCP.BIN 44 | LD C,15 ; Function 15 = Open File. 45 | CALL BDOS ; Get the first matching file. 46 | AND %11111100 ; Mask the result bytes. 47 | JR NZ,CCP_ERROR ; If the file didn't open, then exit. AND will clear CF = error. 48 | 49 | 50 | 51 | 52 | ; LD DE,DRBDOS ; BDOS destination starts at DRBDOS - 53 | LD DE,$8000 ; temp store before transfer. 54 | LD (CCP_PTR),DE ; Store the pointer that contains where we will store the file. 55 | CCP_READ: 56 | LD DE,CCP_FCB ; The CCP is located on L: and is usually the first file, but this is enough to find it on L: 57 | LD C,20 ; BDOS Function 20 = read record 58 | CALL BDOS ; Get the first matching file. 59 | OR A ; Check for file status - And exit if we're at EOF ( we already exited earlier for other result other than read OK ) 60 | JP NZ,CCP_RUN ; Once we are fully loaded, try to execute the CCP. 61 | 62 | 63 | ; LD HL,$107F ; Reserved space before we transfer file. 64 | ; LD DE,(CCP_PTR) 65 | ; ADD HL,DE 66 | ; JR C,BDOSFAIL ; We will overwrite original BDOS at $F000 if we proceed - so error out. 67 | 68 | LD HL,$0080 ; Transfer the file to memory. 69 | LD DE,(CCP_PTR) 70 | LD BC,$80 71 | LDIR 72 | LD (CCP_PTR),DE ; Store the destination pointer for the next cycle. 73 | 74 | JR CCP_READ ; Loop until completely loaded - Note: CCP can ALSO include the BDOS and BIOS if both need to be overwritten. Cool eh? 75 | 76 | CCP_ERROR: ; THIS SHOULD NEVER HAPPEN. ROM IS CORRUPT. WTF? Do I even need to do this? 77 | LD DE,CCPERROR ; DE = Error string. 78 | LD C,9 ; Function 9 = Display String pointed to by DE 79 | CALL BDOS 80 | ; HALT ; Nothing else to do... Hopefully they can reburn the boot eprom. Maybe they forgot the file? 81 | JP $0000 ; The halt should not go away. 82 | 83 | CCP_RUN: 84 | LD C,9 85 | LD DE,BDOSSUCCESS 86 | CALL BDOS 87 | 88 | ; JP $0000 ; exit. 89 | 90 | 91 | 92 | LD DE,M1 93 | LD C,PRINT_STRING ; print string. 94 | CALL BDOS 95 | 96 | LD HL,$8000 97 | LD DE,DRCCP 98 | LD BC,$0806 ; half a block. Plus 6 for the serial. 99 | LDIR ; transfer it AS A BLOCK. 100 | 101 | LD HL,DRCCP+$03 ; clean boot. 102 | ;LD ($FC04),HL ; write new CCP location into BIOS WBOOT jump. 103 | LD (BIOSJUMP),HL ; write new CCP location into BIOS WBOOT jump vector. 104 | 105 | ; out ($10),A ; Display debub notice 106 | ; out ($13),A ; turn on debug 107 | 108 | ; CALL OTHERTESTS 109 | 110 | ; out (10),A ; turn off debug 111 | 112 | ret 113 | 114 | 115 | ;; 116 | 117 | M1: DB 'Preparing to migrate CCP to E500 and install hook.',$0D,$0A,'$' 118 | 119 | M2: DB 'CCP Hook installed...',$0D,$0A,'$' 120 | 121 | 122 | 123 | BDOSFAIL: 124 | LD C,9 ; Print 125 | LD DE,BDOSOVER 126 | CALL BDOS 127 | JP $0000 128 | 129 | 130 | CCPPRELOAD: DB 'Loading ALTCCP.BIN to buffer',$0A,$0D,'$' 131 | CCPERROR: DB 'ALTBDOS.BIN not found. Exiting',$0A,$0D,'$' 132 | 133 | BDOSOVER: DB 'Error: Attempted to overwrite existing CCP - Exiting.',$0D,$0A,'$' 134 | BDOSSUCCESS: DB 'CCP Successfully loaded at E500',$0D,$0A,'$' 135 | 136 | 137 | 138 | 139 | ;#################################################################################### 140 | ; 141 | ;# Constants, Notes, Tables, etc. 142 | ; 143 | ;#################################################################################### 144 | 145 | 146 | FBC: DW $00 ; File Control Block location Save. First is a label in memory. Rest are offset for IX. 147 | EQU DR, $00 ; Offset 0 148 | EQU EX, $0C ; Extent counter. 00, 01 etc. First extent is 00. 149 | EQU S1, $0D 150 | EQU S2, $0E 151 | EQU RC, $0F ; Record counter. File is RC * 128 bytes. 152 | EQU ALLOC1, $10 ; First byte of allocation data or other contents. Copied from Directory for this extent. 153 | EQU ALLOC2, $11 ; Second byte of the allocation data or other contents. 154 | EQU CR, $20 ; Current record within extent. 0= first record. 155 | EQU R0, $21 ; Low byte first. 16 bit counter for Random Access Record Number. Record =128 block. 156 | EQU R1, $22 ; Random Access Record high byte. 157 | EQU R2, $23 ; Random Access Record Count Overflow. 158 | 159 | 160 | DPH: DB $00 ; Disk Parameter Header file. Get temorarily to 161 | EQU TRANS0, $00 ; Put in label for vector here if translation table. 00=no translate 162 | EQU SCRATCH1, $02 ; Scratchpad. 163 | EQU SCRATCH2, $04 ; Scratchpad. 164 | EQU SCRATCH3, $06 ; Scratchpad. 165 | EQU DIRBF, $08 ; 128 byte scratchpad buffer for file directory. ( used by BDOS ) 166 | EQU DIRBF_H, $09 ; High byte. 167 | EQU DPBLK, $0A ; Disk Parameter Block. 168 | EQU CHK00, $0C ; 169 | EQU ALL00, $0E ; Allocation table. 170 | EQU ALL00_H, $0FH ; High byte allocation table. 171 | 172 | ;DISK PARAMETER BLOCK Offsets. 173 | EQU SPT, $00 ; SPT Sectors per track. ie, 0 to 31. 174 | EQU SPT_H, $01 ; SPT High Byte Offset 175 | EQU BSF, $02 ; BSF Block Shift Factor - 3 bits over Bit6 ( Bit6 = 128 = allocation ). Bit7 = 256., Bit 8=512 and Bit9 is 1024 ( 0 to 1023 ). Number of bits past Bit6. 176 | EQU BLM, $03 ; BLM Block Mask - Related directly to the above. =2^BSF-1... 7 is a 3 bit mask, so BSF must be 3. 177 | EQU EXM, $04 ; EXM - Extent Mask - 178 | EQU DSM, $05 ; DSM - Disk size -1 - ie, 242= 243 BLOCKS. if the block size=1024k then 248,832 bytes on disk. Block=Allocation Unit. 179 | EQU DSM_H, $06 ; DSM High Byte - If not zero, two byte allocations. 180 | EQU DRM, $07 ; DRM - Directory Max - Directory Entries = 64 entries. If 1024K allocation holds 32 directory entries then this means 2 allocations for directory. 181 | EQU DRM_H, $08 ; DRM High Byte offset. 182 | EQU AL0, $09 ; AL0 - Alloc 0 ROTATE LEFT, One bit set per directory allocation. something like RL(IX+AL0) ??? 183 | EQU AL1, $0A ; AL1 - Yet, they used 16 bits for this - is it to quick-set the ALL00? I think it might be. 184 | EQU CKS, $0B ; CKS - Check size = (DRM+1) /4 for removable media. = 64/4 = 16... In this case, If fixed then DRM=0 = Do not check directory records - Not sure why. 185 | EQU OFF, $0D ; OFF - Track Offset. Number of "Reserved" tracks. 186 | 187 | ;DPBLK2: ; Example. 188 | ; DW $20 ; 00,01 - eg, 32 sectors per track. 189 | ; DB 3 ; 02 - Like block mask, but a binary nuber. 1 less than the blocks. 190 | ; DB 7 ; 03 - 8 records per Allocation. 0 is not an option ( or would mean 128 or 256 records/alloc ) 191 | ; DB 0 ; 04 - Extent mask - Blocks are 1K, so should be zero. If 2K, 1, if 4K, 3 etc. Our extents are fixed 16K 192 | ; DW 242 ; 05,06 - Number of allocation on disk. 242 = 243K. 193 | ; DW 63 ; 07,08 - Directory Max (Max extents in directory allocations ). We could figure this out, but it's here. 194 | ; DB %11000000 ;09 - High bits set first. One bit for each directory allocation (block). 195 | ; DB %00000000 ;0A - More directory bits in case 8 isn't enough. 196 | ; DW 16 ; 0B,0C - Check size, DRM+1/4. or 0 for fixed. Not sure if I'll use this. 197 | ; DW 42 ; 0D,0E - Track Offset - Number of reserved tracks. The BIOS handles this. We can forget it. We just get Track0. 198 | 199 | 200 | 201 | ;CP/M File Control Block 202 | ; 203 | ;The File Control Block is a 36-byte data structure (33 bytes in CP/M 1). It is laid out as follows: 204 | ; 205 | ;DR F1 F2 F3 F4 F5 F6 F7 F8 T1 T2 T3 EX S1 S2 RC .FILENAMETYP... 206 | ;AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL ............... 207 | ;CR R0 R1 R2 .... 208 | ; 209 | ;The bytes in it have the following meanings: 210 | ; 211 | ; 212 | ;FCB+00h DR - Drive. 0 for default, 1-16 for A-P. In DOSPLUS, 213 | ; bit 7 can be set to indicate that the operation should work with 214 | ; subdirectories rather than files. 215 | ; 216 | ;FCB+01h Fn - Filename, 7-bit ASCII. The top bits of the filename bytes 217 | ; (usually referred to as F1' to F8') have the following 218 | ; meanings: 219 | ; F1'-F4' - User-defined attributes. Any program can use 220 | ; them in any way it likes. The filename in the 221 | ; disc directory has the corresponding bits set. 222 | ; F5'-F8' - Interface attributes. They modify the 223 | ; behaviour of various BDOS functions or 224 | ; indicate error conditions. In the directory 225 | ; these bits are always zero. 226 | ;FCB+09h Tn - Filetype, 7-bit ASCII. T1' to T3' have the following 227 | ; meanings: 228 | ; T1' - Read-Only. 229 | ; T2' - System (hidden). System files in user 0 can be 230 | ; opened from other user areas. 231 | ; T3' - Archive. Set if the file has not been changed 232 | ; since it was last copied. 233 | ;FCB+0Ch EX - Set this to 0 when opening a file and then leave it to 234 | ; CP/M. You can rewind a file by setting EX, RC, S2 and CR to 0. 235 | ;FCB+0Dh S1 - Reserved. 236 | ;FCB+0Eh S2 - Reserved. 237 | ;FCB+0Fh RC - Set this to 0 when opening a file and then leave it to 238 | ; CP/M. 239 | ;FCB+10h AL - Image of the second half of the directory entry, 240 | ; containing the file's allocation (which disc blocks it 241 | ; owns). 242 | ;FCB+20h CR - Current record within extent. It is usually best to set 243 | ; this to 0 immediately after a file has been opened and 244 | ; then ignore it. 245 | ;FCB+21h Rn - Random access record number (not CP/M 1). A 16-bit 246 | ; value in CP/M 2 (with R2 used for overflow); an 18-bit 247 | ; value in CP/M 3. 248 | ; 249 | ;How CP/M uses the bytes EX, S2, and CR. Some programs (such as the Digital Research linker, LINK.COM) manipulate these bytes to perform "seek" operations in files without using the random-access calls. 250 | ; 251 | ;CR = current record, ie (file pointer % 16384) / 128 252 | ;EX = current extent, ie (file pointer % 524288) / 16384 253 | ;S2 = extent high byte, ie (file pointer / 524288). The CP/M Plus source 254 | ; code refers to this use of the S2 byte as 'module number'. 255 | 256 | 257 | 258 | 259 | 260 | 261 | ; ########################################## Equates. 262 | 263 | 264 | ; CPM Calls as EQU functions. 265 | EQU System_Reset , 0 266 | EQU Console_Input , 1 267 | EQU Console_Output , 2 268 | EQU Reader_Input , 3 269 | EQU Punch_Output , 4 270 | EQU List_Output , 5 271 | EQU Direct_Console_IO , 6 272 | EQU Get_IO_Byte , 7 273 | EQU Set_IO_Byte , 8 274 | EQU Print_String , 9 275 | EQU Read_Console_String , 10 276 | EQU Get_Console_Status , 11 277 | EQU Return_Version , 12 278 | EQU Reset_Disk_System , 13 279 | EQU Select_Disk , 14 280 | EQU Open_File , 15 281 | EQU Close_File , 16 282 | EQU Search_For_First , 17 283 | EQU Search_For_Next , 18 284 | EQU Delete_File , 19 285 | EQU Read_Sequential , 20 286 | EQU Write_Sequential , 21 287 | EQU Make_File , 22 288 | EQU Rename_File , 23 289 | EQU Return_Login_Vector , 24 290 | EQU Return_Current_Disk , 25 291 | EQU Set_DMA_Address , 26 292 | EQU Get_ADDR_ALLOC , 27 293 | EQU Write_Protect_Disk , 28 294 | EQU Get_RO_Vector , 29 295 | EQU Set_File_Attributes , 30 296 | EQU Get_ADDR_DiskParms , 31 297 | EQU Set_Get_User_Code , 32 298 | EQU Read_Random , 33 299 | EQU Write_Random , 34 300 | EQU Compute_File_Size , 35 301 | EQU Set_Random_Record , 36 302 | EQU Reset_Drive , 37 303 | EQU Access_Drive , 38 304 | EQU Free_Drive , 39 305 | EQU Write_Random_Fill , 40 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | .END 315 | 316 | -------------------------------------------------------------------------------- /net.asm: -------------------------------------------------------------------------------- 1 | ; IP Networking Module, LOKI - Simple IP stack using 20 byte headers. Unreliable comms. 2 | ; Uses SLIP for transmission over serial port. 3 | ; Can form IP headers 4 | ; Supports simple protocols for network drive access and serial comms to another system. 5 | ; 6 | ; Conventions. 7 | ; 1. MTU and MRU is 256 bytes AT THE IP LEVEL. There is not MAC level. 8 | ; 2. Maximum payload should be 128 bytes to match CP/M's DMA size, however other information can exist within the packet. 9 | ; 3. 10 | 11 | 12 | 13 | IPHEADER: ; NOTE it's BIG ENDIAN. 14 | ; 32 bits 15 | DB $45 ; Version and IHL. ( packet header is 20 bytes ) 16 | DB $00 ; TOS is 0 17 | DW $0000 ; Big endian packet length. 18 | 19 | ; 32 bits 20 | DB $00,$00 ; Identification. Update for each packet or frame - eg, frame counter. 21 | DB %010 00000, %00000000 ; Zero, Don't Fragment, Last Fragment 22 | 23 | ; 32 bits 24 | DB $20 ; 32 hops TTL. 25 | DB $11 ; UDP? 26 | DB $00,$00 ; Checksum. 27 | 28 | ; 32 bits 29 | DB 192, 168, 0, 1 ; Source Range default. 30 | 31 | ; 32 bits 32 | DB 203, 13, 16, 30 ; Destination Range default. "Loki Network". 33 | 34 | 35 | 36 | EQU VERSION , $00 ; $45 Version 4, IHL 5 (IHL5 is 20 bytes for header ) 37 | EQU TOS , $01 ; $00 Type of service (eg, IP Precedence) 38 | EQU LENGTH , $02 ; Big Endian 2 bytes. 39 | EQU ID , $04 ; Identification. Big endian, but doesn't really matter. 40 | EQU FLAGS , $06 ; Fragmentation. 41 | EQU TTL , $08 42 | EQU PROTOCOL, $09 43 | EQU CHECKSUM, $0A 44 | EQU SOURCE , $0C 45 | EQU DEST , $10 46 | ;EQU OPTIONS , $14 ; No options, since we will use header length 5. 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /network.asm: -------------------------------------------------------------------------------- 1 | ; Loki Driver Installation Routine - Installs Driver code and hooks entry point to driver. 2 | ; Standard Drivers are at 0040 (Video) and 0048 (Network) 3 | ; Drivers have common pages in the first and last 4K page locations ( 0000-0FFF at Page01 and F000-FFFF at Page 10) with the ROOT-CPM process 0. 4 | ; Install driver uses random write to copy the driver then hooks the BIOS and exits. It can also unload a driver ( replace with ret ). 5 | ; Syntax: 6 | ; A>LINSTALL M: 7 | 8 | 9 | 10 | .EQU BDOS, $0005 11 | 12 | .equ ADRIVE, $00 13 | .equ LDRIVE, $0B 14 | .equ MDRIVE, $0C 15 | 16 | .equ TRACKPORT ,$06 17 | .equ SECTORPORT ,$07 18 | .equ MADPORT ,$0C 19 | 20 | .equ MMU,$0F ; The port to read/write MMU bytes to the MMU RAM. 21 | .equ PID,$0E ; The PID port - Which process is the MMU reflecting? 22 | 23 | .equ DMA,$0080 ; This DMA is suitable. 24 | .equ NETWORK,$0048 ; Network, Audio, Drives and Comms Hook. 25 | 26 | ; Memory Map. 27 | ; Common Lower Page - common to all drivers. 4K in size. 28 | ; $0000 - Common memory, entry points in the Zero Page. Existing applications from $0100 to $0FFF. 29 | ; $1000 - Driver entry, Code and Bitmaps. Resident. 30 | ; $3000 - Video Map - 8 bits - 2040 bytes * 2 ( 8 bits of attributes ). 5x ADM attributes. 31 | ; 32 | 33 | .ORG $100 ; Start as a COM file. 34 | 35 | INITIALISE: ; Let's initialise the screen, Character map, etc. 36 | ; Constantly changing as I get it working under LokiOS and CP/M. 37 | ; Question: Should standard drivers exist already? Eg, Interrupts, Video, Network? 38 | 39 | LD (SAVEHL),HL ; Save HL in case, if, for some reason, we actually want to save it. 40 | LD HL,$0000 41 | ADD HL,SP ; save the SP 42 | LD (SAVESTACK),HL 43 | LD SP,TEMP_STACK ; New stack location for this routine in location 1. 44 | 45 | 46 | CALL MAKE_NETWORK_DRIVER ; Create the driver file on M: 47 | ; Need to put some error checking in here. 48 | ; Once created, we need to set up the MMU to follow the file. 49 | 50 | CALL SET_MMU ; Set the MMU to match the FCB. 51 | 52 | 53 | 54 | CALL TRANSFER ; Transfer the BIOS code to the Process. Later do this in CREATE_FILE (Transfer at same time and write it in) 55 | ; It switches to the process, then just loads the driver at $1000 and executes with C=0. 56 | 57 | CALL INTHOOK ; Install the interrupt hook. No going back now. 58 | ; This sets a hook at $0040 or other interrupt vectors the change the PID and then jump to $1000. 59 | 60 | LD A,$00 ; Back to process 0. 61 | OUT (PID),A 62 | 63 | 64 | CALL RESET_MMU ; Drop back to the default MMU process 0. 65 | 66 | 67 | LD C,$00 ; Reset NETWORK. 68 | CALL NETWORK 69 | 70 | 71 | LD DE,SUCCESS 72 | LD C,PRINT_STRING 73 | CALL BDOS 74 | ; CALL ERROR_MESSAGE 75 | 76 | LD (SAVEHL),HL 77 | LD HL,(SAVESTACK) 78 | LD SP,HL ; Restore the SP. 79 | LD HL,(SAVEHL) 80 | 81 | 82 | RET ; Just exit here. 83 | 84 | SAVESTACK: DW $0000 ; Stack location store. 85 | SAVEHL: DW $0000 ; Temporary location for HL. 86 | 87 | 88 | 89 | 90 | 91 | RESET_MMU: ; Return to Process 0. 92 | LD A,$00 ; Process ID for the driver we are installing. 93 | OUT (PID),A ; SHOULD be safe.. SHOULD have the same process information. Unless someone's changed it. 94 | LD HL,DRVBDOS ; END of driver pages. 95 | LD B,$0E ; Second last page... We'll transfer them backwards. 96 | LD C,MMU ; C is the port address for the MMU. 97 | ret 98 | 99 | SET_MMU: 100 | 101 | LD DE,DRVFCB 102 | LD C,OPEN_FILE 103 | CALL BDOS ; Get current memory unit allocations from FCB after opening file. 104 | 105 | LD A,$08 ; Process ID for the driver we are installing. 106 | OUT (PID),A ; SHOULD be safe.. SHOULD have the same process information. Unless someone's changed it. 107 | 108 | LD HL,FCBCR ; END of driver pages. We'll deduct 1 each time, so will be the last allocation. 109 | LD B,$0F ; last page... We'll transfer them backwards. 110 | LD C,MMU ; C is the port address for the MMU. 111 | 112 | SET_MMU_LOOP: 113 | DEC HL 114 | PUSH BC 115 | LD A,B 116 | RLC A 117 | RLC A 118 | RLC A 119 | RLC A 120 | LD B,A 121 | LD A,(HL) ; Pick up allocation from FCB. 122 | 123 | OUT (C),A ; Set a block of memory... MMU is set for this page. 124 | POP BC 125 | DJNZ SET_MMU_LOOP ; Because we use DJNZ, we won't set page 0, which is reserved for the common zero page. 126 | ret 127 | 128 | 129 | 130 | 131 | 132 | INTHOOK: ; Code to hook the Interrupt, Install the device driver, rebuild the handler and write it all to M: 133 | LD DE,NETWORK 134 | LD HL,INT48 135 | LD BC,$08 136 | LDIR ; Install RST40 hook. 137 | 138 | LD DE,$0050 139 | LD HL,RET50 140 | LD BC,$05 141 | LDIR ; Install RET50 return hook. 142 | 143 | LD DE,$0055 144 | LD HL,RET55 145 | LD BC,$07 146 | LDIR 147 | 148 | ret 149 | 150 | 151 | 152 | INT48: ; Standing to copy relative code to move to $0048 (NETWORK). 8 bytes. 153 | PUSH AF ; Protect the A register. 154 | LD A,$09 ; We're going to run NETWORK as Process 9 (reserved) 155 | OUT (PID),A ; Switch to the NETWORK Process. 156 | JP $1000 ; Jump to the next segment at the 4K Boundary where the real handler lies. 157 | ; Map - $0000 to $0FFF - Handler, Install routines, Initialise routines. 158 | ; $1000 to $1FFF - Routines. 159 | ; $2000 to $2FFF - More Routines. 160 | ; $3000 to $3FFF - More Routines. 161 | ; $4000 to $7FFF - Shared Memory and Buffers. 162 | 163 | ; RET50 is 5 bytes exactly. 164 | RET50: ; Return location for 50 where we install the "Return to previous call". 165 | OUT (PID),A ; Switch to the calling process. ( A contains the process - don't set it here. ) 166 | POP AF ; Recover A ( and maybe other registers ) 167 | ret ; And return to where we were called from. 168 | ; Note - Return programs from 50 to 5B... 169 | NOP ; So I can see a single 0. 170 | ; DO NOT CHANGE THE ABOVE....... below follows on. 171 | 172 | RET55: ; RET55 is exactly 7 bytes. 173 | OUT (PID),A ; Switch to the calling process. 174 | POP AF 175 | RETI ; And the RETI version if it's called by an interrupt. 176 | NOP 177 | NOP 178 | 179 | 180 | 181 | RETEND: 182 | ; Marker for the end. 183 | 184 | LASTDMA: DW $0080 ; Place to store the DMA. 185 | 186 | TRANSFER: 187 | ; Later need to change process ID here to map in Process 08 - This memory segment will be stable at 0000 to 0FFF 188 | ; reuse TYPE routine. 189 | LD DE,$1000 190 | LD (LASTDMA),DE ; Store the DMA. 191 | LD C,Set_DMA_Address ; Function 1A - Set the DMA address to the buffer at 0080. 192 | CALL BDOS ; Set DMA to 0080 - This is because the directory will show up here. Saves us copying the filenames too. 193 | 194 | LD DE,VIDFCB ; The filename we want is located in the first FCB in lower memory from the Command Line 195 | LD C,Open_File ; Open File. 196 | CALL BDOS ; Get the first matching file. 197 | 198 | AND %11111100 ; Mask the result bytes. 199 | JR NZ,FILE_OPEN_ERROR 200 | 201 | TRANSFER_READ: 202 | LD DE,VIDFCB ; The filename we want remains located in the first FCB in lower memory from the Command Line 203 | LD C,Read_Sequential ; read record 204 | CALL BDOS ; Get the first matching file. 205 | OR A ; Check for file status - And exit if we're at EOF ( we already exited earlier for other result other than read OK ) 206 | 207 | JR NZ,TRANSFER_EXIT ; Nothing more to read. 208 | ;;; CALL PRINT_RECORD ; Print the record 209 | 210 | LD HL,(LASTDMA) ; Step the DMA pointer along. 211 | LD DE,$0080 212 | ADD HL,DE 213 | LD (LASTDMA),HL ; DMA is not further along and saved. 214 | EX DE,HL ; Set DMA back in DE. 215 | 216 | LD C,Set_DMA_Address ; Function 1A - Set the DMA address to the buffer at 0080. 217 | CALL BDOS ; Set DMA to 0080 - This is because the directory will show up here. Saves us copying the filenames too. 218 | 219 | JR TRANSFER_READ 220 | 221 | TRANSFER_EXIT: 222 | CALL CRLF ; Start next on a new line. 223 | ret 224 | 225 | 226 | CRLF: LD E,$0D 227 | LD C,Console_Output 228 | CALL BDOS 229 | LD E,$0A 230 | LD C,Console_Output 231 | CALL BDOS 232 | ret 233 | 234 | PRINT_RECORD: 235 | LD B,128 236 | LD HL,$0080 ; DMA address. 237 | PRINT_RECORD_LOOP: 238 | PUSH HL 239 | PUSH BC 240 | LD E,(HL) 241 | LD C,Console_Output 242 | CALL BDOS 243 | POP BC 244 | POP HL 245 | 246 | LD A,(HL) 247 | CP $1A ; CTRL'Z' 248 | RET Z ; Is end of file. Exit here now if that happens. 249 | 250 | INC HL 251 | DJNZ PRINT_RECORD_LOOP 252 | 253 | ret 254 | 255 | 256 | DESTINATION_MISSING_ERROR: 257 | LD DE,NODEST_ERROR 258 | JP ERROR_MESSAGE 259 | 260 | FILE_READ_ERROR: 261 | LD DE,FILEREAD_ERROR 262 | JP ERROR_MESSAGE 263 | 264 | FILE_OPEN_ERROR: 265 | LD DE,FILEOPEN_ERROR 266 | JP ERROR_MESSAGE ; Exit via the error message. 267 | ; ret 268 | 269 | CANT_OPEN_PROCESS: 270 | LD DE,PROCESS_ERROR 271 | JP ERROR_MESSAGE 272 | 273 | ERROR_MESSAGE: 274 | LD C,Print_String 275 | CALL BDOS 276 | RST 0 ; restart - do not continue. 277 | RET 278 | 279 | COMMAND_BAD: db 'Command Unknown or Executable not located',$0D,$0A,'$' 280 | ret 281 | FILENAME_BAD: db 'Filename had unexpected charactors or was malformed',$0D,$0A,'$' 282 | 283 | COMMAND_HINT: db 'Type HELP for a list of commands',$0D,$0A,'$' 284 | 285 | FILEOPEN_ERROR: db 'File could not be opened',$0D,$0A,'$' 286 | 287 | FILEREAD_ERROR: db 'Error reading file',$0D,$0A,'$' 288 | 289 | NODEST_ERROR: db 'Bad Source or Destination',$0D,$0A,'$' 290 | 291 | FILE_COPY_ERROR:db 'Error copying file',$0D,$0A,'$' 292 | 293 | SUCCESS: db 'New network driver installed successfully.',$0D,$0A,'$' 294 | 295 | PROCESS_ERROR: DB 'Could not open process file on M:',$0D,$0A,'$' 296 | 297 | 298 | 299 | 300 | 301 | 302 | MAKE_NETWORK_DRIVER: 303 | LD DE,DRVFCB ; Location of new file FCB. 304 | LD C,MAKE_FILE ; Open the file on M: 305 | CALL BDOS 306 | AND $FC 307 | JP NZ,CANT_OPEN_PROCESS 308 | 309 | LD C,CLOSE_FILE 310 | LD DE,DRVFCB 311 | CALL BDOS ; Now close the file, so it's written. 312 | 313 | LD C,OPEN_FILE 314 | LD DE,DRVFCB 315 | CALL BDOS ; And make sure we can open it. 316 | AND $FC 317 | JP NZ,CANT_OPEN_PROCESS 318 | 319 | LD A,$01 ; Make sure the first segment is mapped to the same as CP/M root, and the last to BDOS/BIOS, 320 | LD (DRVZERO),A ; Set the first page. 321 | LD A,$10 322 | LD (DRVBDOS),A ; Set the BDOS page. 323 | LD A,$0F 324 | LD (DRVDRIBDOS),A ; And set it just in case the DRI BDOS is in place. Software can reallocate this anyway. 325 | ; Next we need to allocate 3 blocks of free memory. 326 | 327 | LD DE,DRVFCB 328 | LD C,CLOSE_FILE 329 | CALL BDOS 330 | 331 | 332 | ; Did we get it working? Let's random write it. 333 | 334 | LD A,$3F ; Next 4K boundary. 335 | LD (FCBR1),A 336 | CALL SET_ALLOCATION 337 | 338 | LD A,$5F ; Next 4K boundary. 339 | LD (FCBR1),A 340 | CALL SET_ALLOCATION 341 | 342 | LD A,$7F ; Next 4K boundary. Note - separate transfer and allocation setting to avoid short files failing to allocate enough. 343 | LD (FCBR1),A ; Anything in the range of $60 to $7F is suitable, however the RC field will reflect the final random write. 344 | CALL SET_ALLOCATION 345 | 346 | 347 | 348 | 349 | LD DE,DRVFCB 350 | LD C,CLOSE_FILE 351 | CALL BDOS 352 | 353 | ret 354 | 355 | SET_ALLOCATION: 356 | LD DE,DRVFCB 357 | LD C,WRITE_RANDOM 358 | CALL BDOS 359 | ret 360 | 361 | ; Incoming Driver File. 362 | VIDFCB: ; Source is VIDEO512BIN on L: 363 | DB $0A ; Driver happens to be on L: but later can load from anywhere. Use this to change video modes too. 364 | DB 'NET BIN',$00,$00,$00,$00 365 | DB 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 366 | DB $00 367 | DB $00 368 | DB $00 369 | DB $00 370 | 371 | ; Driver File to build on M: 372 | DRVFCB: ; Random FCB to write driver to MAD space at $1000... 373 | DB $0D ; Process will ALWAYS be M: drive. 374 | DB 'NETWORK DRV',$00,$00,$00 375 | DRVRC: DB $0 ; Set RC Record Count to $20 - ie, all initial locations taken. We will subsequently assign three more allocations. 376 | DRVZERO: DB $0 ; Establish the first file location. 377 | DRV0: DB 0 ; Default driver locations. 378 | DRV1: DB 0 ; Default driver locations. 379 | DRV2: DB 0 ; Default driver locations. 380 | DRV_PAGES: DB 0,0,0,0, 0,0,0,0, 0,0 ; Reserved for paging in NETWORK for direct access. ( Eg, Scroll etc. ) but we don't need them yet. 381 | DRVDRIBDOS: DB $00 ; Because the DRI BDOS is too fat, if it's installed. 382 | DRVBDOS: DB $00 ; Standard LOKI FDOS. 383 | FCBCR: DB $00 384 | FCBR1: DB $00 385 | FCBR2: DB $00 386 | FCBR3: DB $00 387 | 388 | 389 | 390 | .ORG $0FF0 391 | TEMP_STACK: 392 | ; Put a temporary stack right at the end. 393 | 394 | 395 | ; Wildcard for FCB.... 396 | ;DR F1 F2 F3 F4 F5 F6 F7 F8 T1 T2 T3 EX S1 S2 RC .FILENAMETYP... 397 | ;AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL ............... 398 | ;CR R0 R1 R2 399 | ; 400 | ;The bytes in it have the following meanings: 401 | ; 402 | ; 403 | ;FCB+00h DR - Drive. 0 for default, 1-16 for A-P. In DOSPLUS, 404 | ; bit 7 can be set to indicate that the operation should work with 405 | ; subdirectories rather than files. 406 | ; 407 | ;FCB+01h Fn - Filename, 7-bit ASCII. The top bits of the filename bytes 408 | ; (usually referred to as F1' to F8') have the following 409 | ; meanings: 410 | ; F1'-F4' - User-defined attributes. Any program can use 411 | ; them in any way it likes. The filename in the 412 | ; disc directory has the corresponding bits set. 413 | ; F5'-F8' - Interface attributes. They modify the 414 | ; behaviour of various BDOS functions or 415 | ; indicate error conditions. In the directory 416 | ; these bits are always zero. 417 | ;FCB+09h Tn - Filetype, 7-bit ASCII. T1' to T3' have the following 418 | ; meanings: 419 | ; T1' - Read-Only. 420 | ; T2' - System (hidden). System files in user 0 can be 421 | ; opened from other user areas. 422 | ; T3' - Archive. Set if the file has not been changed 423 | ; since it was last copied. 424 | ;FCB+0Ch EX - Set this to 0 when opening a file and then leave it to 425 | ; CP/M. You can rewind a file by setting EX, RC, S2 and CR to 0. 426 | ;FCB+0Dh S1 - Reserved. But otherwise upper bits of RC ( Remember, Bit 7 is something else ) 427 | ;FCB+0Eh S2 - Reserved. But otherwise upper bits of EX ( Remember, Bit 7 is something else ) 428 | ;FCB+0Fh RC - Set this to 0 when opening a file and then leave it to 429 | ; CP/M. 430 | ;FCB+10h AL - Image of the second half of the directory entry, 431 | ; containing the file's allocation (which disc blocks it 432 | ; owns). 433 | ;FCB+20h CR - Current record within extent. It is usually best to set 434 | ; this to 0 immediately after a file has been opened and 435 | ; then ignore it. 436 | ;FCB+21h Rn - Random access record number (not CP/M 1). A 16-bit 437 | ; value in CP/M 2 (with R2 used for overflow); an 18-bit 438 | ; value in CP/M 3. 439 | ; 440 | ; CR - Current Record ( Sequential Read ) 441 | ; R0, R1, R2 - Random read ( R0, R1 = 16 bit. R2 Overflow. ) 442 | 443 | 444 | 445 | ;************************************************************** 446 | ; I really wish I knew why this bios has a -3 jump... Would be good to figure that out. Might be due to CP/M 3??? 447 | ; Jump table here - Define the BIOS entry points. 448 | .EQU BIOS ,$FC03 ; Location of BIOS. Though in reality, we have boot. Need to understand if BOOT=0 or BOOT=-3. 449 | .EQU BOOT ,BIOS-3 ;-3: Cold Boot - Set up system. 450 | .EQU WBOOT ,BIOS+0 ; 0: Warm boot - reload command processor 451 | .EQU WBOOTE ,BIOS+0 452 | .EQU CONST ,BIOS+3 ; 3: Console status - A=0 No character ready, A=FF character waiting to be read. 453 | .EQU CONIN ,BIOS+6 ; 6: Console input - Wait until character then A=character. 454 | .EQU CONOUT ,BIOS+9 ; 9: Console output - Write C register to screen. 455 | .EQU LIST ,BIOS+12 ;12: Printer output - Wait until ready, then write C register to printer. 456 | .EQU PUNCH ,BIOS+15 ;15: Paper tape punch output - Wait until ready then write C to Punch Reader ( or AUX ). 457 | .EQU READER ,BIOS+18 ;18: Paper tape reader input - Wait until ready, then return A=Character. If not implemented, return 26 ( Ctrl Z ) 458 | .EQU HOME ,BIOS+21 ;21: Move disc head to track 0 459 | .EQU SELDSK ,BIOS+24 ;24: Select disc drive - C Register = disk 0...F. Enter with E=0 or E=FF. 460 | ; If bit 0 of E is 0, then the disc is logged in as if new; if the format has to be determined from the boot sector, for example, this will be done. 461 | ;If bit 0 if E is 1, then the disc has been logged in before. The disc is not accessed; the DPH address (or zero) is returned immediately. 462 | ;SELDSK returns the address of a Disc Parameter Header in HL. The exact format of a DPH varies between CP/M versions; note that under CP/M 3, 463 | ; the DPH is in memory bank 0 and probably not visible to programs. If the disc could not be selected it returns HL=0. 464 | .EQU SETTRK ,BIOS+27 ;27: Set track number - Track in BC - Is a word - Starts at 0. 465 | .EQU SETSEC ,BIOS+30 ;30: Set sector number - Sector in BC - Is a word. Starts at 1. I think. Will find out. 466 | .EQU SETDMA ,BIOS+33 ;33: Set DMA address - Set DMA address. 467 | .EQU READ ,BIOS+36 ;36: Read a sector - To DMA address. A=0 success. A=1 unrecoverable error. A=FF Media changed. 468 | .EQU WRITE ,BIOS+39 ;39: Write a sector - C contains blocking code. 0=deferred. 1=immediate. 2=Can be deferred. No preread necessary. A=0 success. 469 | ; A=1 unrecoverable error. A=FF Media changed. 470 | ;In CP/M 2 and later, the following extra jumps appear: 471 | .EQU LISTST ,BIOS+42 ;42: Status of list device - A=0 Printer not ready. A=FF -printer ready. 472 | .EQU SECTRAN ,BIOS+45 ;45: Sector translation for skewing DE=Tableaddress (IGNORE DE). BC=Incoming sector (logical). HL=Translated sector (physical) . Return BC. 473 | .EQU SECTRN ,BIOS+45 ;45: Sector translation for skewing DE=Tableaddress (IGNORE DE). BC=Incoming sector (logical). HL=Translated sector (physical) . Return BC. 474 | 475 | 476 | ; CPM Calls as EQU functions. 477 | EQU System_Reset , 0 478 | EQU Console_Input , 1 479 | EQU Console_Output , 2 480 | EQU Reader_Input , 3 481 | EQU Punch_Output , 4 482 | EQU List_Output , 5 483 | EQU Direct_Console_IO , 6 484 | EQU Get_IO_Byte , 7 485 | EQU Set_IO_Byte , 8 486 | EQU Print_String , 9 487 | EQU Read_Console_String , 10 488 | EQU Get_Console_Status , 11 489 | EQU Return_Version , 12 490 | EQU Reset_Disk_System , 13 491 | EQU Select_Disk , 14 492 | EQU Open_File , 15 493 | EQU Close_File , 16 494 | EQU Search_For_First , 17 495 | EQU Search_For_Next , 18 496 | EQU Delete_File , 19 497 | EQU Read_Sequential , 20 498 | EQU Write_Sequential , 21 499 | EQU Make_File , 22 500 | EQU Rename_File , 23 501 | EQU Return_Login_Vector , 24 502 | EQU Return_Current_Disk , 25 503 | EQU Set_DMA_Address , 26 504 | EQU Get_ADDR_ALLOC , 27 505 | EQU Write_Protect_Disk , 28 506 | EQU Get_RO_Vector , 29 507 | EQU Set_File_Attributes , 30 508 | EQU Get_ADDR_DiskParms , 31 509 | EQU Set_Get_User_Code , 32 510 | EQU Read_Random , 33 511 | EQU Write_Random , 34 512 | EQU Compute_File_Size , 35 513 | EQU Set_Random_Record , 36 514 | EQU Reset_Drive , 37 515 | EQU Access_Drive , 38 516 | EQU Free_Drive , 39 517 | EQU Write_Random_Fill , 40 518 | ; Special. 519 | EQU Write_Dry , 41 ; Dry write - Not a real function. 520 | 521 | 522 | FBC: DW $00 ; File Control Block location Save. First is a label in memory. Rest are offset for IX. 523 | EQU DR, $00 ; Offset 0 524 | EQU EX, $0C ; Extent counter. 00, 01 etc. First extent is 00. 525 | EQU S1, $0D 526 | EQU S2, $0E 527 | EQU RC, $0F ; Record counter. File is RC * 128 bytes. 528 | EQU ALLOC1, $10 ; First byte of allocation data or other contents. Copied from Directory for this extent. 529 | EQU ALLOC2, $11 ; Second byte of the allocation data or other contents. 530 | EQU CR, $20 ; Current record within extent. 0= first record. 531 | EQU R0, $21 ; Low byte first. 16 bit counter for Random Access Record Number. Record =128 block. 532 | EQU R1, $22 ; Random Access Record high byte. 533 | EQU R2, $23 ; Random Access Record Count Overflow. 534 | 535 | 536 | DPH: DB $00 ; Disk Parameter Header file. Get temorarily to 537 | EQU TRANS0, $00 ; Put in label for vector here if translation table. 00=no translate 538 | EQU SCRATCH1, $02 ; Scratchpad. 539 | EQU SCRATCH2, $04 ; Scratchpad. 540 | EQU SCRATCH3, $06 ; Scratchpad. 541 | EQU DIRBF, $08 ; 128 byte scratchpad buffer for file directory. ( used by BDOS ) 542 | EQU DIRBF_H, $09 ; High byte. 543 | EQU DPBLK, $0A ; Disk Parameter Block. 544 | EQU CHK00, $0C ; 545 | EQU ALL00, $0E ; Allocation table. 546 | EQU ALL00_H, $0FH ; High byte allocation table. 547 | 548 | ;DISK PARAMETER BLOCK Offsets. 549 | EQU SPT, $00 ; SPT Sectors per track. ie, 0 to 31. 550 | EQU SPT_H, $01 ; SPT High Byte Offset 551 | EQU BSF, $02 ; BSF Block Shift Factor - 3 bits over Bit6 ( Bit6 = 128 = allocation ). Bit7 = 256., Bit 8=512 and Bit9 is 1024 ( 0 to 1023 ). Number of bits past Bit6. 552 | EQU BLM, $03 ; BLM Block Mask - Related directly to the above. =2^BSF-1... 7 is a 3 bit mask, so BSF must be 3. 553 | EQU EXM, $04 ; EXM - Extent Mask - 554 | EQU DSM, $05 ; DSM - Disk size -1 - ie, 242= 243 BLOCKS. if the block size=1024k then 248,832 bytes on disk. Block=Allocation Unit. 555 | EQU DSM_H, $06 ; DSM High Byte - If not zero, two byte allocations. 556 | EQU DRM, $07 ; DRM - Directory Max - Directory Entries = 64 entries. If 1024K allocation holds 32 directory entries then this means 2 allocations for directory. 557 | EQU DRM_H, $08 ; DRM High Byte offset. 558 | EQU AL0, $09 ; AL0 - Alloc 0 ROTATE LEFT, One bit set per directory allocation. something like RL(IX+AL0) ??? 559 | EQU AL1, $0A ; AL1 - Yet, they used 16 bits for this - is it to quick-set the ALL00? I think it might be. 560 | EQU CKS, $0B ; CKS - Check size = (DRM+1) /4 for removable media. = 64/4 = 16... In this case, If fixed then DRM=0 = Do not check directory records - Not sure why. 561 | EQU OFF, $0D ; OFF - Track Offset. Number of "Reserved" tracks. 562 | 563 | ;.NOTES 564 | ;Everything after this point can just be text. It's just general notes. The assembly should stop at end or notes. 565 | ;5.6 System Function Summary 566 | ;Function 567 | ;Number Function 568 | ; Name Input Output 569 | ;Dec Hex 570 | ;0 0 System Reset none none 571 | ;1 1 Console Input none A = ASCII char 572 | ;2 2 Console Output E = char none 573 | ;3 3 Reader Input none A = ASCII char 574 | ;4 4 Punch Output E = char none 575 | ;5 5 List Output E = char none 576 | ;6 6 Direct Console I/O E = $FF (input) A = char 577 | ; E = $FE (status) A = status 578 | ; E = char none 579 | ;7 7 Get I/O Byte none A = I/O byte value 580 | ;8 8 Set I/O Byte E = I/O byte none 581 | ;9 9 Print String DE = Buffer Address none 582 | ;10 A Read Console String DE = Buffer Console characters in Buffer 583 | ;11 B Get Console Status none A = 00/non zero 584 | ;12 C Return Version # none HL = Version # 585 | ;13 D Reset Disk System none none 586 | ;14 E Select Disk E = Disk # none 587 | ;15 F Open File DE = FCB address A = FF if not found 588 | ;16 10 Close File DE = FCB address A = FF if not found 589 | ;17 11 Search For First DE = FCB address A = Directory Code 590 | ;18 12 Search For Next none A = Directory Code 591 | ;19 13 Delete File DE = FCB address A = none 592 | ;20 14 Read Sequential DE = FCB address A = Error Code 593 | ;21 15 Write Sequential DE = FCB Address A = Error Code 594 | ;22 16 Make File DE = FCB address A = FF if no DIR Space 595 | ;23 17 Rename File DE = FCB address A = FF if not found 596 | ;24 18 Return Login Vector none HL = Login Vector* 597 | ;25 19 Return Current Disk none A = Current Disk Number 598 | ;26 1A Set DMA Address DE = DMA address none 599 | ;27 1B Get ADDR (ALLOC) none HL = ALLOC address* 600 | ;28 1C Write Protect Disk none none 601 | ;29 1D Get Read/only Vector none HL = ALLOC address* 602 | ;30 1E Set File Attributes DE = FCB address A = none 603 | ;31 1F Get ADDR (Disk Parms) none HL = DPB address 604 | ;32 20 Set/Get User Code E = $FF for Get A = User Number 605 | ; E = 00 to $0F for Set none 606 | ;33 21 Read Random DE = FCB address A = Error 607 | ;34 22 Write Random DE = FCB address A = Error Code 608 | ;35 23 Compute File Size DE = FCB address r0, r1, r2 609 | ;36 24 Set Random Record DE = FCB address r0, r1, r2 610 | ;37 25 Reset Drive DE = Drive Vector A = 0 611 | ;38 26 Access Drive not supported not supported 612 | ;39 27 Free Drive not supported not supported 613 | ;40 28 Write Random w/Fill DE = FCB A = error code 614 | -------------------------------------------------------------------------------- /startup.asm: -------------------------------------------------------------------------------- 1 | ; Loki startup routines - COM - Can execute scripts or other activities - As a COM. 2 | ; THIS is the program that configured the system, loads up startup files, etc. 3 | ; 4 | ; 5 | .EQU BDOS, $0005 6 | 7 | .equ ADRIVE, $00 8 | .equ LDRIVE, $0B 9 | .equ MDRIVE, $0C 10 | 11 | .equ TRACKPORT ,$06 12 | .equ SECTORPORT ,$07 13 | .equ MADPORT ,$0C 14 | 15 | .ORG $100 ; Start as a COM file. 16 | 17 | LD DE,Startup_Message 18 | LD C,Print_String 19 | CALL BDOS 20 | 21 | LD C,SELECT_DISK ; Select M: as the disk. 22 | LD E,MDRIVE 23 | CALL BDOS 24 | 25 | 26 | CALL RST1 27 | CALL RST2 28 | CALL RST3 29 | CALL RST4 30 | CALL RST5 31 | CALL RST6 32 | CALL RST7 33 | 34 | CALL BIOS1 35 | CALL BIOS2 36 | CALL BIOS3 37 | CALL BIOS4 38 | 39 | 40 | ; Multi Extent and Multi-Entry Files to mark. 41 | CALL NVMDRIVE ; Non Volatile Memory ( 256 bytes ) 42 | CALL NVMD2 43 | CALL NVMD3 44 | CALL NVMD4 ; Set aside 256K for NVM. 45 | 46 | ; CALL VIDEODRV ; Set up video driver blank. 47 | ; CALL NETWORK ; Set up video driver blank. 48 | 49 | ; CALL UPPERMEMORY ; Mark Upper Memory as unavailable. - Can leave out - It's all marked now. 50 | 51 | 52 | LD C,Reset_Disk_System ; Force re-reading the disk, mark allocations as used etc. 53 | CALL BDOS 54 | 55 | LD DE,TESTMESSAGE 56 | LD C,PRINT_STRING 57 | CALL BDOS ; Print initialisation string. 58 | 59 | ret 60 | 61 | TESTMESSAGE: DB '2022 Sinclair LOKI project.$' 62 | TESTEND: DB '$' ; in case I forget. 63 | 64 | 65 | 66 | 67 | MARK_FCB: DW $0000 ; The FCB we are storing this form. 68 | AV_TABLE: DW $0000 ; Where the AV table is for the current disk ( Memory M: Disk ) 69 | BLOCK: DB $00 ; Block in memory 70 | ALLOCATION: DB $00 ; Allocation in the extent(s) 71 | 72 | 73 | 74 | RSTENTRIES: 75 | 76 | ret 77 | 78 | VIDEODRV: 79 | LD DE,VID 80 | LD (MARK_FCB),DE 81 | LD C,MAKE_FILE ; Create the file entry with BDOS. 82 | CALL BDOS 83 | LD D,$00 ; First Allocation 84 | LD E,$01 ; Block 1 85 | LD B,$01 ; 1 block presently assigned to each file. 86 | JP VXLOOP 87 | 88 | 89 | NETWORK: 90 | LD DE,NET 91 | LD (MARK_FCB),DE 92 | LD C,MAKE_FILE ; Create the file entry with BDOS. 93 | CALL BDOS 94 | LD D,$00 ; First Allocation 95 | LD E,$01 ; Block 1 96 | LD B,$01 ; 1 block presently assigned to each file. 97 | JP VXLOOP 98 | 99 | RST1: ; Place stand in files for each interrupt handler. 100 | LD DE,RST8 101 | LD (MARK_FCB),DE 102 | LD C,MAKE_FILE ; Create the file entry with BDOS. 103 | CALL BDOS 104 | LD D,$00 ; First Allocation 105 | LD E,$01 ; Block 1 106 | LD B,$01 ; 1 block presently assigned to each file. 107 | JP VXLOOP 108 | RST2: ; Place stand in files for each interrupt handler. 109 | LD DE,RST10 110 | LD (MARK_FCB),DE 111 | LD C,MAKE_FILE ; Create the file entry with BDOS. 112 | CALL BDOS 113 | LD D,$00 ; First Allocation 114 | LD E,$01 ; Block 1 115 | LD B,$01 ; 1 block presently assigned to each file. 116 | JP VXLOOP 117 | RST3: ; Place stand in files for each interrupt handler. 118 | LD DE,RST18 119 | LD (MARK_FCB),DE 120 | LD C,MAKE_FILE ; Create the file entry with BDOS. 121 | CALL BDOS 122 | LD D,$00 ; First Allocation 123 | LD E,$01 ; Block 1 124 | LD B,$01 ; 1 block presently assigned to each file. 125 | JP VXLOOP 126 | RST4: ; Place stand in files for each interrupt handler. 127 | LD DE,RST20 128 | LD (MARK_FCB),DE 129 | LD C,MAKE_FILE ; Create the file entry with BDOS. 130 | CALL BDOS 131 | LD D,$00 ; First Allocation 132 | LD E,$01 ; Block 1 133 | LD B,$01 ; 1 block presently assigned to each file. 134 | JP VXLOOP 135 | RST5: ; Place stand in files for each interrupt handler. 136 | LD DE,RST28 137 | LD (MARK_FCB),DE 138 | LD C,MAKE_FILE ; Create the file entry with BDOS. 139 | CALL BDOS 140 | LD D,$00 ; First Allocation 141 | LD E,$01 ; Block 1 142 | LD B,$01 ; 1 block presently assigned to each file. 143 | JP VXLOOP 144 | RST6: ; Place stand in files for each interrupt handler. 145 | LD DE,RST30 146 | LD (MARK_FCB),DE 147 | LD C,MAKE_FILE ; Create the file entry with BDOS. 148 | CALL BDOS 149 | LD D,$00 ; First Allocation 150 | LD E,$01 ; Block 1 151 | LD B,$01 ; 1 block presently assigned to each file. 152 | JP VXLOOP 153 | RST7: ; Place stand in files for each interrupt handler. 154 | LD DE,RST38 155 | LD (MARK_FCB),DE 156 | LD C,MAKE_FILE ; Create the file entry with BDOS. 157 | CALL BDOS 158 | LD D,$00 ; First Allocation 159 | LD E,$01 ; Block 1 160 | LD B,$01 ; 1 block presently assigned to each file. 161 | JP VXLOOP 162 | 163 | 164 | BIOS1: ; Place stand in files for each interrupt handler. 165 | LD DE,BIOS1FCB 166 | LD (MARK_FCB),DE 167 | LD C,MAKE_FILE ; Create the file entry with BDOS. 168 | CALL BDOS 169 | LD D,$00 ; First Allocation 170 | LD E,$E0 ; Block E0 171 | LD B,$04 ; 4 block presently assigned to each extension bios file. 172 | JP VLOOP 173 | 174 | BIOS2: ; Place stand in files for each interrupt handler. 175 | LD DE,BIOS2FCB 176 | LD (MARK_FCB),DE 177 | LD C,MAKE_FILE ; Create the file entry with BDOS. 178 | CALL BDOS 179 | LD D,$00 ; First Allocation 180 | LD E,$E4 ; Block E0 181 | LD B,$04 ; 4 block presently assigned to each extension bios file. 182 | JP VLOOP 183 | 184 | BIOS3: ; Place stand in files for each interrupt handler. 185 | LD DE,BIOS3FCB 186 | LD (MARK_FCB),DE 187 | LD C,MAKE_FILE ; Create the file entry with BDOS. 188 | CALL BDOS 189 | LD D,$00 ; First Allocation 190 | LD E,$E8 ; Block E0 191 | LD B,$04 ; 4 block presently assigned to each extension bios file. 192 | JP VLOOP 193 | 194 | BIOS4: ; Place stand in files for each interrupt handler. 195 | LD DE,BIOS4FCB 196 | LD (MARK_FCB),DE 197 | LD C,MAKE_FILE ; Create the file entry with BDOS. 198 | CALL BDOS 199 | LD D,$00 ; First Allocation 200 | LD E,$EC ; Block E0 201 | LD B,$04 ; 4 block presently assigned to each extension bios file. 202 | JP VLOOP 203 | 204 | UPPERMEMORY: ; Place stand in files for each interrupt handler. 205 | LD DE,UPPER 206 | LD (MARK_FCB),DE 207 | LD C,MAKE_FILE ; Create the file entry with BDOS. 208 | CALL BDOS 209 | LD D,$00 ; First Allocation 210 | LD E,$80 ; Block E0 211 | LD B,$80 ; 4 block presently assigned to each extension bios file. 212 | JP VLOOP 213 | 214 | NVMDRIVE: ; Assume Video1 at least is used by the system. Video1 sits at 80000 to 9FFFF 215 | 216 | LD DE,NVM1 217 | LD (MARK_FCB),DE 218 | LD C,MAKE_FILE ; Create the file entry with BDOS. 219 | CALL BDOS 220 | 221 | LD D,$00 ; First Allocation 222 | LD E,$80 ; Block 80 = 512K boundary. 223 | LD B,$10 ; 40 blocks for NVM = 256K 224 | JP VLOOP 225 | 226 | NVMD2: 227 | LD DE,NVM2 228 | LD (MARK_FCB),DE 229 | LD C,MAKE_FILE ; Create the file entry with BDOS. 230 | CALL BDOS 231 | 232 | LD D,$10 ; First Allocation + 10.... 233 | LD E,$90 ; Block 80 = 512K boundary. 234 | LD B,$10 ; 40 blocks for NVM = 256K 235 | JP VLOOP 236 | 237 | NVMD3: 238 | LD DE,NVM3 239 | LD (MARK_FCB),DE 240 | LD C,MAKE_FILE ; Create the file entry with BDOS. 241 | CALL BDOS 242 | 243 | LD D,$20 ; First Allocation + 10.... 244 | LD E,$A0 ; Block 80 = 512K boundary. 245 | LD B,$10 ; 40 blocks for NVM = 256K 246 | JP VLOOP 247 | 248 | NVMD4: 249 | LD DE,NVM4 250 | LD (MARK_FCB),DE 251 | LD C,MAKE_FILE ; Create the file entry with BDOS. 252 | CALL BDOS 253 | 254 | LD D,$30 ; First Allocation + 10.... 255 | LD E,$B0 ; Block 80 = 512K boundary. 256 | LD B,$10 ; 40 blocks for NVM = 256K 257 | JP VLOOP 258 | 259 | 260 | VXLOOP: ; Also include the BDOS/BIOS blocks. 261 | CALL VLOOP 262 | LD IX,(MARK_FCB) ; FCB location. 263 | LD A,$0FH 264 | LD (IX+ALLOC15),A 265 | INC A 266 | LD (IX+ALLOC16),A 267 | 268 | LD C,CLOSE_FILE ; And close the file to save the BDOS / BIOS allocation. 269 | LD DE,(MARK_FCB) 270 | CALL BDOS 271 | ret 272 | 273 | VLOOP: 274 | PUSH BC 275 | PUSH DE 276 | LD (BLOCK),DE ; Store Block and Allocation. 277 | CALL Mark_Allocation 278 | POP DE 279 | POP BC 280 | INC D 281 | INC E 282 | DJNZ VLOOP 283 | 284 | ret 285 | 286 | 287 | 288 | Mark_Allocation: ; Marks an allocation as used by a specified block, and makes sure that block is marked in the Allocation Vector tables. 289 | ; D is FIRST ALLOCATION (Usually $00) 290 | ; E is the Memory Block number (eg, typically 1 to FF ) 291 | ; B is the number of blocks to allocate. 292 | LD A,D 293 | LD (ALLOCATION),A ; Set Allocation as per D. 294 | 295 | LD A,(ALLOCATION) 296 | RRA 297 | RRA ; Divide by four - Four allocations per extent. ( Because we use 32 records / allocation ) 298 | 299 | AND %00111111 ; 64 possible extent numbers. 300 | 301 | LD IX,(MARK_FCB) ; FCB location. 302 | LD (IX+EX),A ; Store the Extent number. 303 | 304 | 305 | ;LD DE,(MARK_FCB) ; Debug should show the FCB pre-change. 306 | ;OUT (20),A ; debug - show FCB. 307 | 308 | 309 | LD A,(ALLOCATION) 310 | AND $0F 311 | INC A 312 | LD B,A ; Set allocation into B. (1,2,3 or 4) 313 | 314 | LD IX,(MARK_FCB) ; FCB 315 | LD A,(IX+CR) ; Current Record, not Record Counter. 316 | ADD A,$20 ; 32 records per allocation 317 | DEC A 318 | AND $7F ; Only 128 records/allocation. 319 | INC A 320 | LD (IX+CR),A ; Add them to the FCB 321 | 322 | LD A,(BLOCK) ; Which block should we set in the allocation?. 323 | DEC IX 324 | MARK_LP1: 325 | INC IX 326 | DJNZ MARK_LP1 ; Shuffle IX so that ALLOC1 points to the correct allocation. 327 | LD (IX+ALLOC1),A ; Write the block vector to the allocation. 328 | 329 | ; DON'T USE BELOW CODE - It was to mark the AV table - But we can do that post-all-changes with a DISK RESET. 330 | ; DISK RESET POPULATES THE AV TABLES for Drive M: 331 | ; ; Next we determine the bit to set in the AV tables. 332 | ; LD A,(BLOCK) 333 | ; RRA 334 | ; RRA 335 | ; RRA 336 | ; AND %00011111 ; Position in AV table for block. 337 | ; LD E,A 338 | ; LD D,$00 ; DE is loaded with the offset for the vector byte. 339 | ; LD HL,(AV_TABLE) 340 | ; ADD HL,DE ; HL holds the absolute vector byte location in memory. 341 | ; 342 | ; 343 | ; LD A,(BLOCK) 344 | ; AND %00000111 ; Get the bit within the byte for the block. 345 | ; INC A 346 | ; LD B,A 347 | ; XOR A 348 | ; SCF 349 | ; 350 | ;MARK_LP2: 351 | ; RRA 352 | ; DJNZ MARK_LP2 ; Move the bit along. 353 | ; ; A now holds the B 354 | ; 355 | ; OR (HL) ; Or A with the vector byte. 356 | ; LD (HL),A ; And update the byte in memory. 357 | 358 | XOR A ; These five lines are installed as without them, the "Close" operation fails to save the extent under DRI BDOS. 359 | LD IX,(MARK_FCB) ; FCB location. 360 | LD (IX+S1),A ; Note sure why CP/M 2.2 plays with these "reserved" values. 361 | LD (IX+S2),A 362 | LD A,(IX+CR) 363 | LD (IX+RC),A ; Transfer the CR to RC. 364 | 365 | LD C,CLOSE_FILE ; And close the file to save the allocation. 366 | LD DE,(MARK_FCB) 367 | 368 | CALL BDOS 369 | ret 370 | 371 | Startup_Message: db $0D,$0A,'LokiOS running. Building MAD system',$0D,$0A,'$' 372 | 373 | 374 | ;LDRIVEFILE: ; FCB for the initial OS. 375 | ; DB $0D,'L:_DRIVE','DSK',0,0,0,0 ; Root System - Process 0. 376 | ; DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 377 | ; DB 0,0,0,0 378 | 379 | 380 | ;VIDEO1: ; FCB for the initial OS. 381 | ; DB $0D,'Screen_1','IMG',0,0,0,0 ; Root System - Process 0. 382 | ; DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 383 | ; DB 0,0,0,0 384 | 385 | NVM1: ; FCB for the initial OS. 386 | DB $0D,'NVMemory','DSK',0,0,0,0 ; NVRAM Disk 0. 387 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 388 | DB 0,0,0,0 389 | 390 | 391 | NVM2: ; FCB for the initial OS. 392 | DB $0D,'NVMemory','DSK',4,0,0,0 ; NVRAM Disk 1. 393 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 394 | DB 0,0,0,0 395 | 396 | 397 | NVM3: ; FCB for the initial OS. 398 | DB $0D,'NVMemory','DSK',8,0,0,0 ; NVRAM Disk 1. 399 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 400 | DB 0,0,0,0 401 | 402 | 403 | NVM4: ; FCB for the initial OS. 404 | DB $0D,'NVMemory','DSK',$0C,0,0,0 ; NVRAM Disk 1. 405 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 406 | DB 0,0,0,0 407 | 408 | 409 | ; RST FCBs. 410 | RST8: ; FCB for the initial OS. 411 | DB $0D,'RST-#08H','INT',0,0,0,0 ; Root System - Process 1. 412 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 413 | DB 0,0,0,0 414 | 415 | RST10: ; FCB for the initial OS. 416 | DB $0D,'RST-#10H','INT',0,0,0,0 ; Root System - Process 2. 417 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 418 | DB 0,0,0,0 419 | 420 | RST18: ; FCB for the initial OS. 421 | DB $0D,'RST-#18H','INT',0,0,0,0 ; Root System - Process 3. 422 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 423 | DB 0,0,0,0 424 | 425 | RST20: ; FCB for the initial OS. 426 | DB $0D,'RST-#20H','INT',0,0,0,0 ; Root System - Process 4. 427 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 428 | DB 0,0,0,0 429 | 430 | RST28: ; FCB for the initial OS. 431 | DB $0D,'RST-#28H','INT',0,0,0,0 ; Root System - Process 5. 432 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 433 | DB 0,0,0,0 434 | 435 | RST30: ; FCB for the initial OS. 436 | DB $0D,'RST-#30H','INT',0,0,0,0 ; Root System - Process 6. 437 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 438 | DB 0,0,0,0 439 | 440 | RST38: ; FCB for the initial OS. 441 | DB $0D,'RST-#38H','INT',0,0,0,0 ; Root System - Process 7. 442 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 443 | DB 0,0,0,0 444 | 445 | BIOS1FCB: ; FCB for the initial OS. 446 | DB $0D,'VIDEOROM','DSK',0,0,0,0 ; Root System - Process 7. 447 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 448 | DB 0,0,0,0 449 | 450 | BIOS2FCB: ; FCB for the initial OS. 451 | DB $0D,'DISK_ROM','DSK',0,0,0,0 ; Root System - Process 7. 452 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 453 | DB 0,0,0,0 454 | 455 | BIOS3FCB: ; FCB for the initial OS. 456 | DB $0D,'UNASSIG1','DSK',0,0,0,0 ; Root System - Process 7. 457 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 458 | DB 0,0,0,0 459 | 460 | BIOS4FCB: ; FCB for the initial OS. 461 | DB $0D,'UNASSIG2','DSK',0,0,0,0 ; Root System - Process 7. 462 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 463 | DB 0,0,0,0 464 | 465 | 466 | UPPER: ; FCB for the initial OS, upper memory. 467 | DB $0D,'UPPERMEM','BLK',0,0,0,0 ; Upper Memory above $80000 468 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 469 | DB 0,0,0,0 470 | 471 | VID: 472 | DB $0D,'VIDEO ','DRV',0,0,0,$80 ; Video Driver Blank. 473 | DB $01,0,0,0,0,0,0,0,0,0,0,0,0,0,$0F,$10 474 | DB $80,0,0,0 475 | 476 | NET: 477 | DB $0D,'NETWORK ','DRV',0,0,0,0 ; Network Driver Blank. 478 | DB 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 479 | DB 0,0,0,0 480 | ; Wildcard for FCB.... 481 | ;DR F1 F2 F3 F4 F5 F6 F7 F8 T1 T2 T3 EX S1 S2 RC .FILENAMETYP... 482 | ;AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL AL ............... 483 | ;CR R0 R1 R2 484 | ; 485 | ;The bytes in it have the following meanings: 486 | ; 487 | ; 488 | ;FCB+00h DR - Drive. 0 for default, 1-16 for A-P. In DOSPLUS, 489 | ; bit 7 can be set to indicate that the operation should work with 490 | ; subdirectories rather than files. 491 | ; 492 | ;FCB+01h Fn - Filename, 7-bit ASCII. The top bits of the filename bytes 493 | ; (usually referred to as F1' to F8') have the following 494 | ; meanings: 495 | ; F1'-F4' - User-defined attributes. Any program can use 496 | ; them in any way it likes. The filename in the 497 | ; disc directory has the corresponding bits set. 498 | ; F5'-F8' - Interface attributes. They modify the 499 | ; behaviour of various BDOS functions or 500 | ; indicate error conditions. In the directory 501 | ; these bits are always zero. 502 | ;FCB+09h Tn - Filetype, 7-bit ASCII. T1' to T3' have the following 503 | ; meanings: 504 | ; T1' - Read-Only. 505 | ; T2' - System (hidden). System files in user 0 can be 506 | ; opened from other user areas. 507 | ; T3' - Archive. Set if the file has not been changed 508 | ; since it was last copied. 509 | ;FCB+0Ch EX - Set this to 0 when opening a file and then leave it to 510 | ; CP/M. You can rewind a file by setting EX, RC, S2 and CR to 0. 511 | ;FCB+0Dh S1 - Reserved. But otherwise upper bits of RC ( Remember, Bit 7 is something else ) 512 | ;FCB+0Eh S2 - Reserved. But otherwise upper bits of EX ( Remember, Bit 7 is something else ) 513 | ;FCB+0Fh RC - Set this to 0 when opening a file and then leave it to 514 | ; CP/M. 515 | ;FCB+10h AL - Image of the second half of the directory entry, 516 | ; containing the file's allocation (which disc blocks it 517 | ; owns). 518 | ;FCB+20h CR - Current record within extent. It is usually best to set 519 | ; this to 0 immediately after a file has been opened and 520 | ; then ignore it. 521 | ;FCB+21h Rn - Random access record number (not CP/M 1). A 16-bit 522 | ; value in CP/M 2 (with R2 used for overflow); an 18-bit 523 | ; value in CP/M 3. 524 | ; 525 | ; CR - Current Record ( Sequential Read ) 526 | ; R0, R1, R2 - Random read ( R0, R1 = 16 bit. R2 Overflow. ) 527 | 528 | 529 | 530 | ;************************************************************** 531 | ; I really wish I knew why this bios has a -3 jump... Would be good to figure that out. Might be due to CP/M 3??? 532 | ; Jump table here - Define the BIOS entry points. 533 | .EQU BIOS ,$FC03 ; Location of BIOS. Though in reality, we have boot. Need to understand if BOOT=0 or BOOT=-3. 534 | .EQU BOOT ,BIOS-3 ;-3: Cold Boot - Set up system. 535 | .EQU WBOOT ,BIOS+0 ; 0: Warm boot - reload command processor 536 | .EQU WBOOTE ,BIOS+0 537 | .EQU CONST ,BIOS+3 ; 3: Console status - A=0 No character ready, A=FF character waiting to be read. 538 | .EQU CONIN ,BIOS+6 ; 6: Console input - Wait until character then A=character. 539 | .EQU CONOUT ,BIOS+9 ; 9: Console output - Write C register to screen. 540 | .EQU LIST ,BIOS+12 ;12: Printer output - Wait until ready, then write C register to printer. 541 | .EQU PUNCH ,BIOS+15 ;15: Paper tape punch output - Wait until ready then write C to Punch Reader ( or AUX ). 542 | .EQU READER ,BIOS+18 ;18: Paper tape reader input - Wait until ready, then return A=Character. If not implemented, return 26 ( Ctrl Z ) 543 | .EQU HOME ,BIOS+21 ;21: Move disc head to track 0 544 | .EQU SELDSK ,BIOS+24 ;24: Select disc drive - C Register = disk 0...F. Enter with E=0 or E=FF. 545 | ; If bit 0 of E is 0, then the disc is logged in as if new; if the format has to be determined from the boot sector, for example, this will be done. 546 | ;If bit 0 if E is 1, then the disc has been logged in before. The disc is not accessed; the DPH address (or zero) is returned immediately. 547 | ;SELDSK returns the address of a Disc Parameter Header in HL. The exact format of a DPH varies between CP/M versions; note that under CP/M 3, 548 | ; the DPH is in memory bank 0 and probably not visible to programs. If the disc could not be selected it returns HL=0. 549 | .EQU SETTRK ,BIOS+27 ;27: Set track number - Track in BC - Is a word - Starts at 0. 550 | .EQU SETSEC ,BIOS+30 ;30: Set sector number - Sector in BC - Is a word. Starts at 1. I think. Will find out. 551 | .EQU SETDMA ,BIOS+33 ;33: Set DMA address - Set DMA address. 552 | .EQU READ ,BIOS+36 ;36: Read a sector - To DMA address. A=0 success. A=1 unrecoverable error. A=FF Media changed. 553 | .EQU WRITE ,BIOS+39 ;39: Write a sector - C contains blocking code. 0=deferred. 1=immediate. 2=Can be deferred. No preread necessary. A=0 success. 554 | ; A=1 unrecoverable error. A=FF Media changed. 555 | ;In CP/M 2 and later, the following extra jumps appear: 556 | .EQU LISTST ,BIOS+42 ;42: Status of list device - A=0 Printer not ready. A=FF -printer ready. 557 | .EQU SECTRAN ,BIOS+45 ;45: Sector translation for skewing DE=Tableaddress (IGNORE DE). BC=Incoming sector (logical). HL=Translated sector (physical) . Return BC. 558 | .EQU SECTRN ,BIOS+45 ;45: Sector translation for skewing DE=Tableaddress (IGNORE DE). BC=Incoming sector (logical). HL=Translated sector (physical) . Return BC. 559 | 560 | 561 | ; CPM Calls as EQU functions. 562 | EQU System_Reset , 0 563 | EQU Console_Input , 1 564 | EQU Console_Output , 2 565 | EQU Reader_Input , 3 566 | EQU Punch_Output , 4 567 | EQU List_Output , 5 568 | EQU Direct_Console_IO , 6 569 | EQU Get_IO_Byte , 7 570 | EQU Set_IO_Byte , 8 571 | EQU Print_String , 9 572 | EQU Read_Console_String , 10 573 | EQU Get_Console_Status , 11 574 | EQU Return_Version , 12 575 | EQU Reset_Disk_System , 13 576 | EQU Select_Disk , 14 577 | EQU Open_File , 15 578 | EQU Close_File , 16 579 | EQU Search_For_First , 17 580 | EQU Search_For_Next , 18 581 | EQU Delete_File , 19 582 | EQU Read_Sequential , 20 583 | EQU Write_Sequential , 21 584 | EQU Make_File , 22 585 | EQU Rename_File , 23 586 | EQU Return_Login_Vector , 24 587 | EQU Return_Current_Disk , 25 588 | EQU Set_DMA_Address , 26 589 | EQU Get_ADDR_ALLOC , 27 590 | EQU Write_Protect_Disk , 28 591 | EQU Get_RO_Vector , 29 592 | EQU Set_File_Attributes , 30 593 | EQU Get_ADDR_DiskParms , 31 594 | EQU Set_Get_User_Code , 32 595 | EQU Read_Random , 33 596 | EQU Write_Random , 34 597 | EQU Compute_File_Size , 35 598 | EQU Set_Random_Record , 36 599 | EQU Reset_Drive , 37 600 | EQU Access_Drive , 38 601 | EQU Free_Drive , 39 602 | EQU Write_Random_Fill , 40 603 | ; Special. 604 | EQU Write_Dry , 41 ; Dry write - Not a real function. 605 | 606 | 607 | FBC: DW $00 ; File Control Block location Save. First is a label in memory. Rest are offset for IX. 608 | EQU DR, $00 ; Offset 0 609 | EQU EX, $0C ; Extent counter. 00, 01 etc. First extent is 00. 610 | EQU S1, $0D 611 | EQU S2, $0E 612 | EQU RC, $0F ; Record counter. File is RC * 128 bytes. 613 | EQU ALLOC1, $10 ; First byte of allocation data or other contents. Copied from Directory for this extent. 614 | EQU ALLOC2, $11 ; Second byte of the allocation data or other contents. 615 | EQU ALLOC15,$1E 616 | EQU ALLOC16,$1F 617 | EQU CR, $20 ; Current record within extent. 0= first record. 618 | EQU R0, $21 ; Low byte first. 16 bit counter for Random Access Record Number. Record =128 block. 619 | EQU R1, $22 ; Random Access Record high byte. 620 | EQU R2, $23 ; Random Access Record Count Overflow. 621 | 622 | 623 | DPH: DB $00 ; Disk Parameter Header file. Get temorarily to 624 | EQU TRANS0, $00 ; Put in label for vector here if translation table. 00=no translate 625 | EQU SCRATCH1, $02 ; Scratchpad. 626 | EQU SCRATCH2, $04 ; Scratchpad. 627 | EQU SCRATCH3, $06 ; Scratchpad. 628 | EQU DIRBF, $08 ; 128 byte scratchpad buffer for file directory. ( used by BDOS ) 629 | EQU DIRBF_H, $09 ; High byte. 630 | EQU DPBLK, $0A ; Disk Parameter Block. 631 | EQU CHK00, $0C ; 632 | EQU ALL00, $0E ; Allocation table. 633 | EQU ALL00_H, $0FH ; High byte allocation table. 634 | 635 | ;DISK PARAMETER BLOCK Offsets. 636 | EQU SPT, $00 ; SPT Sectors per track. ie, 0 to 31. 637 | EQU SPT_H, $01 ; SPT High Byte Offset 638 | EQU BSF, $02 ; BSF Block Shift Factor - 3 bits over Bit6 ( Bit6 = 128 = allocation ). Bit7 = 256., Bit 8=512 and Bit9 is 1024 ( 0 to 1023 ). Number of bits past Bit6. 639 | EQU BLM, $03 ; BLM Block Mask - Related directly to the above. =2^BSF-1... 7 is a 3 bit mask, so BSF must be 3. 640 | EQU EXM, $04 ; EXM - Extent Mask - 641 | EQU DSM, $05 ; DSM - Disk size -1 - ie, 242= 243 BLOCKS. if the block size=1024k then 248,832 bytes on disk. Block=Allocation Unit. 642 | EQU DSM_H, $06 ; DSM High Byte - If not zero, two byte allocations. 643 | EQU DRM, $07 ; DRM - Directory Max - Directory Entries = 64 entries. If 1024K allocation holds 32 directory entries then this means 2 allocations for directory. 644 | EQU DRM_H, $08 ; DRM High Byte offset. 645 | EQU AL0, $09 ; AL0 - Alloc 0 ROTATE LEFT, One bit set per directory allocation. something like RL(IX+AL0) ??? 646 | EQU AL1, $0A ; AL1 - Yet, they used 16 bits for this - is it to quick-set the ALL00? I think it might be. 647 | EQU CKS, $0B ; CKS - Check size = (DRM+1) /4 for removable media. = 64/4 = 16... In this case, If fixed then DRM=0 = Do not check directory records - Not sure why. 648 | EQU OFF, $0D ; OFF - Track Offset. Number of "Reserved" tracks. 649 | 650 | ;.NOTES 651 | ;Everything after this point can just be text. It's just general notes. The assembly should stop at end or notes. 652 | ;5.6 System Function Summary 653 | ;Function 654 | ;Number Function 655 | ; Name Input Output 656 | ;Dec Hex 657 | ;0 0 System Reset none none 658 | ;1 1 Console Input none A = ASCII char 659 | ;2 2 Console Output E = char none 660 | ;3 3 Reader Input none A = ASCII char 661 | ;4 4 Punch Output E = char none 662 | ;5 5 List Output E = char none 663 | ;6 6 Direct Console I/O E = $FF (input) A = char 664 | ; E = $FE (status) A = status 665 | ; E = char none 666 | ;7 7 Get I/O Byte none A = I/O byte value 667 | ;8 8 Set I/O Byte E = I/O byte none 668 | ;9 9 Print String DE = Buffer Address none 669 | ;10 A Read Console String DE = Buffer Console characters in Buffer 670 | ;11 B Get Console Status none A = 00/non zero 671 | ;12 C Return Version # none HL = Version # 672 | ;13 D Reset Disk System none none 673 | ;14 E Select Disk E = Disk # none 674 | ;15 F Open File DE = FCB address A = FF if not found 675 | ;16 10 Close File DE = FCB address A = FF if not found 676 | ;17 11 Search For First DE = FCB address A = Directory Code 677 | ;18 12 Search For Next none A = Directory Code 678 | ;19 13 Delete File DE = FCB address A = none 679 | ;20 14 Read Sequential DE = FCB address A = Error Code 680 | ;21 15 Write Sequential DE = FCB Address A = Error Code 681 | ;22 16 Make File DE = FCB address A = FF if no DIR Space 682 | ;23 17 Rename File DE = FCB address A = FF if not found 683 | ;24 18 Return Login Vector none HL = Login Vector* 684 | ;25 19 Return Current Disk none A = Current Disk Number 685 | ;26 1A Set DMA Address DE = DMA address none 686 | ;27 1B Get ADDR (ALLOC) none HL = ALLOC address* 687 | ;28 1C Write Protect Disk none none 688 | ;29 1D Get Read/only Vector none HL = ALLOC address* 689 | ;30 1E Set File Attributes DE = FCB address A = none 690 | ;31 1F Get ADDR (Disk Parms) none HL = DPB address 691 | ;32 20 Set/Get User Code E = $FF for Get A = User Number 692 | ; E = 00 to $0F for Set none 693 | ;33 21 Read Random DE = FCB address A = Error 694 | ;34 22 Write Random DE = FCB address A = Error Code 695 | ;35 23 Compute File Size DE = FCB address r0, r1, r2 696 | ;36 24 Set Random Record DE = FCB address r0, r1, r2 697 | ;37 25 Reset Drive DE = Drive Vector A = 0 698 | ;38 26 Access Drive not supported not supported 699 | ;39 27 Free Drive not supported not supported 700 | ;40 28 Write Random w/Fill DE = FCB A = error code 701 | --------------------------------------------------------------------------------