Revert "kernel: remove obsolete CP/M bits"
authorAlan Cox <alan@linux.intel.com>
Tue, 3 Oct 2017 15:47:56 +0000 (16:47 +0100)
committerAlan Cox <alan@linux.intel.com>
Tue, 3 Oct 2017 15:47:56 +0000 (16:47 +0100)
Oops.. deleted cpm-loader as well in error

This reverts commit 876590b88adcf31cebcdc8c2bec3f3d1f0612dd8.

Kernel/cpm-emulator/README.WRS [new file with mode: 0644]
Kernel/cpm-emulator/emulator.s [new file with mode: 0644]
Kernel/cpm-loader/.gitignore [new file with mode: 0644]
Kernel/cpm-loader/Makefile [new file with mode: 0644]
Kernel/cpm-loader/cpmload.s [new file with mode: 0644]
Kernel/cpm-loader/fuzixload.s [new file with mode: 0644]
Kernel/cpm-loader/makecpmloader.c [new file with mode: 0644]

diff --git a/Kernel/cpm-emulator/README.WRS b/Kernel/cpm-emulator/README.WRS
new file mode 100644 (file)
index 0000000..eb5d878
--- /dev/null
@@ -0,0 +1,3 @@
+Assemble with "zmac emulator.s"
+
+Copy output file zout/emulator.cim to /usr/cpm/emulator on target filesystem
diff --git a/Kernel/cpm-emulator/emulator.s b/Kernel/cpm-emulator/emulator.s
new file mode 100644 (file)
index 0000000..5699f1a
--- /dev/null
@@ -0,0 +1,1560 @@
+    ; must begin on a page boundary (address % 256 == 0) to ensure
+    ; the jump table is correctly aligned
+    ;
+    ; This varies by platform so we really want to make this relocatable
+    ; not linked per platform somehow
+    ;
+       .area   ASEG(ABS)
+       .org    0xf000
+startaddr:
+
+fcb    .EQU    0x005C          ; Default CP/M FCB
+buff   .EQU    0x0080          ; Default CP/M Buffer
+BS     .EQU    0x08            ; ASCII BackSpace
+TAB    .EQU    0x09            ; ASCII Horizontal Tab
+LF     .EQU    0x0A            ; ASCII Line Feed
+CR     .EQU    0x0D            ; ASCII Carriage Return
+ESC    .EQU    0x1B            ; ASCII ESCape Char
+
+TCGETS .EQU    1               ; Fuzix - get tty data
+TCSETS .EQU    2               ; Fuzix - set tty data
+TIOCINQ        .EQU    5               ; Fuzix - characters pending
+
+STDIN  .EQU    0       ; file descriptor value of keyboard
+STDOUT .EQU    1       ; file descriptor value of display
+
+; Initialize both FCB entries to blank values
+
+EStart:        LD      HL,#fcbDat      ; Move initial FCB
+       PUSH    HL
+       LD      DE,#fcb         ;  into
+       LD      BC,#16          ;   position
+       LDIR
+       POP     HL
+       LD      C,#16           ;    init 2nd entry
+       LDIR
+
+; Catenate argv[] elements into default buffer
+
+       POP     IX              ; Skip argc
+       POP     IX              ;  Get Ptr to argv[]
+       LD      DE,#buff+1      ; Pt to CP/M Dflt Buffer
+       LD      C,#0            ;  Cnt to 0
+       INC     IX              ;   Skip Argv[0]
+       INC     IX
+Cold0: LD      L, 0(IX)        ; Get Ptr to Arg element
+       INC     IX
+       LD      H, 0(IX)
+       INC     IX
+       LD      A,H
+       OR      L               ; End?
+       JR      Z,Cold2         ; ..exit if Yes
+       LD      A,#' '          ; Add space separator for args
+       LD      (DE),A
+       INC     DE
+       INC     C               ;   bump count
+Cold1: LD      A,(HL)
+       OR      A               ; End of string?
+       JR      Z,Cold0         ; ..try next if Yes
+       CP      #'a'            ; ensure
+       JR      C,NoCap         ;  it
+       CP      #'z'+1          ;   is
+       JR      NC,NoCap        ;    UCase
+       AND     #0x5F
+NoCap: LD      (DE),A          ; Move a byte
+       INC     HL
+       INC     C               ;   bump count
+       INC     E               ;  bump ptr
+       JR      NZ,Cold1        ; ..get next byte if no bfr ovfl
+                       ;..else 0FF->100H, terminate
+       DEC     E               ;  (back up for Null-termination)
+Cold2: XOR     A
+       LD      (DE),A          ;   Null-terminate for safety
+
+       LD      HL,#buff        ; Pt to count loc'n in buff
+       LD      (HL),C          ;  save total arg count
+       INC     HL              ;   advance to 1st char
+       LD      DE,#fcb+1
+       CALL    FilNm           ; Get Name/Typ in 1st FCB
+       OR      A               ;  (set End flag)
+       LD      DE,#fcb+17      ;   (prepare)
+       CALL    NZ,FilNm        ;    Get Tame/Typ in 2nd FCB if present
+
+       LD      DE,#dir
+       LD      B,#128
+       CALL    ZeroDE          ; Clear Directory Buffer
+
+       LD      HL,#0
+       LD      (0x0003),HL     ; Clear IOBYTE and Default Drive/User
+
+       JP      __bios          ; Go to Cold Start setup
+
+; Fill FCB Name.Typ fields with any present data
+
+FilNm: LD      A,(HL)          ; Get char
+       INC     HL              ;   bump
+       OR      A               ; End of String?
+       RET     Z
+       CP      #' '            ; "Whitespace"?
+       JR      Z,FilNm0        ; ..jump if Yes
+       CP      #TAB
+       JR      NZ,FilNm1       ; ..jump if No
+FilNm0:        DEC     C               ; Count down total length
+       LD      A,C             ;  (prepare)
+       JR      NZ,FilNm        ; ..loop if Not End
+       RET                     ;  ..else Exit showing EOL
+
+FilNm1:        LD      B,#8            ; Set length of Name field
+       PUSH    DE              ;   save Ptr to Name[0]
+       CALL    FilFl0          ;  Get Name
+       POP     DE              ;   restore Ptr to Name
+       OR      A
+       RET     Z               ; ..return if End-of-Line
+       CP      #' '
+       RET     Z               ; ..return if separator
+       CP      #'.'
+       JR      Z,FilNm2        ; ..bypass char skip
+
+FilNm3:        LD      A,(HL)
+       INC     HL
+       OR      A
+       RET     Z               ; Exit if End of Line
+       CP      #' '
+       RET     Z               ;  or End of Field
+       CP      #'.'
+       JR      NZ,FilNm3       ; ..loop til End or period
+
+FilNm2:        LD      A,E
+       ADD     A,#8            ; Adjust FCB ptr to type field
+       LD      E,A
+       LD      B,#3
+                       ;..fall thru to get next char..
+; Move bytes from (HL) to (DE) for Count in C, Count in B or Ch in {' ','.',0}
+
+FilFld:        LD      A,(HL)          ; Get Char
+       INC     HL              ;   bump ptr
+       OR      A               ; End of String?
+       RET     Z               ; ..return if Yes
+FilFl0:        CP      #'.'            ; Period?
+       RET     Z
+       CP      #' '            ; Space?
+       RET     Z
+       LD      (DE),A          ; Else Store byte
+       INC     DE              ;   bump dest ptr
+       DEC     C               ; End of Input String?
+       LD      A,C             ;  (prepare)
+       RET     Z               ; .return End if Yes
+       DJNZ    FilFld          ; ..loop til field counter ends
+       OR      #0x0FF          ; Return flag
+       RET
+
+fcbDat:        .db     0
+       .ascii  '           '
+       .db     0,0,0,0
+
+;==========================================================
+;     Resident Portion of Basic Disk Operating System
+;==========================================================
+; bdos()
+; {
+__bdos:        JP      _bdos0
+; }
+
+;.....
+; BDOS Function Dispatch Table
+
+fcnTbl:        .dw     Fcn0            ; Warm Boot
+       .dw     Fcn1            ; ConIn
+       .dw     Fcn2            ; ConOut
+       .dw     Fcn3            ; Reader In
+       .dw     Fcn4            ; Punch Out
+       .dw     Fcn5            ; List Output
+       .dw     Fcn6            ; Direct Console IO
+       .dw     Fcn7            ; Get IOBYTE
+       .dw     Fcn8            ; Set IOBYTE
+       .dw     Fcn9            ; WrBuf
+       .dw     Fcn10           ; RdBuf
+       .dw     Fcn11           ; Get Console Status
+       .dw     Fcn12           ; Return Version #
+       .dw     Fcn13           ; Reset Disk Drive
+       .dw     Fcn14           ; Select Disk
+       .dw     Fcn15           ; Open File
+       .dw     Fcn16           ; Close File
+       .dw     Fcn17           ; Search First Occurance
+       .dw     Fcn18           ; Search Next Occurance
+       .dw     Fcn19           ; Delete File
+       .dw     Fcn20           ; Read File
+       .dw     Fcn21           ; Write File
+       .dw     Fcn22           ; Create File
+       .dw     Fcn23           ; Rename File
+       .dw     Fcn24           ; Return Disk Login Vector
+       .dw     Fcn25           ; Return Current Disk
+       .dw     Fcn26           ; Set DMA
+       .dw     Fcn27           ; Get Allocation Map
+       .dw     Fcn28           ; Write Protect Disk
+       .dw     Fcn29           ; Get R/O Vector Address
+       .dw     Fcn30           ; Set File Attributes
+       .dw     Fcn31           ; Get Disk Parameter Table Address
+       .dw     Fcn32           ; Set/Get User Code
+       .dw     Fcn33           ; Read Random
+       .dw     Fcn34           ; Write Random
+       .dw     Fcn35           ; Compute File Size
+       .dw     Fcn36           ; Set Random Record Field in FCB
+;      .dw     Fcn37           ; Reset Multiple Drives
+;      .dw     null            ; (Fcn38 not implemented)
+;      .dw     Fcn39           ; Get Fixed Disk Vector
+;      .dw     Fcn40           ; Write Random
+TBLSZ   .EQU   .-fcnTbl
+MAXFCN  .EQU   TBLSZ/2
+
+;------------------------------------------------
+; bdos0()
+; {
+
+_bdos0:        LD      (_arg),DE
+       LD      A,C
+       LD      (_call),A
+       CP      #MAXFCN         ; Legal Function?
+       LD      A,#0xFF         ;  Prepare Error code
+       LD      L,A
+       RET     NC              ; ..return if Illegal
+       LD      (_userSP),SP
+       LD      SP,#_Bstack
+       PUSH    IX
+       PUSH    IY
+       LD      B,#0            ; Fcn # to Word
+       LD      HL,#_bdosX
+       PUSH    HL              ;  (ret Addr to Stack)
+       LD      HL,#fcnTbl
+       ADD     HL,BC
+       ADD     HL,BC           ;   Pt to Fcn entry in Table
+       LD      A,(HL)
+       INC     HL
+       LD      H,(HL)
+       LD      L,A
+       JP      (HL)            ; "Call" Function #
+
+_bdosX:        POP     IY
+       POP     IX
+       LD      SP,(_userSP)
+       LD      DE,(_arg)       ; Return Orig contents of DE
+       LD      A,(_call)
+       LD      C,A             ; Return Orig contents of C
+       LD      A,L
+       LD      B,H             ; Strict compatibility
+       OR      A
+       RET
+; }
+
+;------------------------------------------------
+;         case 0: _exit();                     /* Warm Boot */
+
+Fcn0:  JP      WBoot
+
+;------------------------------------------------
+;         case 6: if (arg < 0xfe)              /* Direct Console I/O */
+;                     goto conout;
+;                 else if (arg == 0xfe)
+;                     return (ConSt);
+;                 else if (ConSt)       /* 0xff */
+;                     goto conout;
+;                 else return (0);
+
+Fcn6:  LD      A,E             ; _arg in DE
+       CP      #0x0FE          ; < 0FE ?
+       JR      C,Fcn2          ; ..jump if Write if Yes
+       PUSH    AF
+       CALL    BConSt          ; Else get Console Status
+       POP     AF
+       INC     A
+       RET     NZ              ; ..exit with 0ffh if 0feh
+       LD      A,H
+       OR      L               ; Any char ready?
+       RET     Z               ; ..exit if Nothing available
+                       ;..else fall thru to Fcn1..
+;------------------------------------------------
+;         case 1:                              /* Console Input */
+;         conin:  read (0, &c, 1);
+;                 if (c == '\n')
+;                     c = '\r';
+;                 return (c);
+
+Fcn1:  CALL    BConIn          ; Get Char from Bios
+Fcn1A: LD      H,#0
+       CP      #0x0A           ; \n?
+       LD      L,A             ;  (prepare for return
+       RET     NZ              ; ..return if Not
+       LD      L,#0x0D         ; Else return CR
+       RET
+
+;------------------------------------------------
+;         case 3:                              /* Reader (Aux) Input */
+;         conin:  read (0, &c, 1);
+;                 if (c == '\n')
+;                     c = '\r';
+;                 return (c);
+
+Fcn3:  CALL    AuxIn           ; Get Char from Bios
+       JR      Fcn1A           ; ..exit via common code
+
+;------------------------------------------------
+;         case 2:                              /* Console Output */
+;         conout: if (arg == '\r')
+;                     return (0);
+;                 c = arg;
+;                 write (1, &c, 1);
+;                 break;
+
+Fcn2:  LD      C,E             ; _arg in DE, need char in C
+       JP      BConOu
+
+;------------------------------------------------
+;         case 4:                              /* Punch (Aux) Output */
+;         conout: if (arg == '\r')
+;                     return (0);
+;                 c = arg;
+;                 write (1, &c, 1);
+;                 break;
+
+Fcn4:  LD      C,E             ; _arg in DE, need char in C
+       JP      AuxOut
+
+;------------------------------------------------
+;         case 5: if (arg == '\r')             /* List (Prntr) Output */
+;                     return (0);
+;                 c = arg;
+;                 write (2, &c, 1);
+;                 break;
+
+Fcn5:  LD      A,E             ; _arg in DE
+       CP      #13             ; \r?
+       RET     Z
+       JP      List            ; ..go to Bios
+
+;------------------------------------------------
+;         case 9: ptr = (char *)arg;           /* Print '$'-term String */
+;                 while (*ptr != '$')
+;                 {
+;                     if (*ptr != '\r')
+;                         write (1, ptr, 1);
+;                     ++ptr;
+;                 }
+;                 break;
+                               ; Enter: DE -> String (arg)
+Fcn9:  LD      A,(DE)          ; Get char
+       INC     DE              ;   pt to Next
+       CP      #'$'            ; End?
+       RET     Z               ; ..quit if Yes
+       LD      C,A
+       PUSH    DE
+       CALL    BConOu
+       POP     DE
+       JR      Fcn9            ; ..loop Til done
+       
+;------------------------------------------------
+;         case 10: rdbuf (arg);
+;                  break;
+; rdbuf (arg)
+; char *arg;
+; {
+;     int nread;
+
+;     nread = read (0, arg+2, *arg & 0xff);
+
+Fcn10:
+       push    de
+       ex      de,hl           ; hl - buffer
+       ld      e,(hl)          ; e - max chars
+       inc     hl
+       inc     hl
+       ld      d,#0            ; d - char cnt
+get:   push    hl
+       push    de
+       call    BConIn
+       pop     de
+       pop     hl
+       cp      #8
+       jr      z,del
+       cp      #0x7F
+       jr      z,del
+       cp      #3
+       jp      z,0
+       push    hl
+       push    de
+       push    af
+       ld      c,a
+       call    BConOu
+       pop     af
+       pop     de
+       pop     hl
+       ld      (hl),a
+       cp      #CR
+       jr      z,eol
+       ld      a,e
+       cp      d
+       jr      z,eol1
+       inc     hl
+       inc     d
+       jr      get
+del:   ld      a,d
+       or      a
+       jr      z,get
+       push    hl
+       push    de
+       ld      c,#8
+       call    BConOu
+       ld      c,#' '
+       call    BConOu
+       ld      c,#8
+       call    BConOu
+       pop     de
+       pop     hl
+       dec     hl
+       dec     d
+       jr      get
+eol:   ld      (hl),#0
+eol1:  ld      a,d
+       pop     de
+       inc     de
+       ld      (de),a
+       ld      hl,#0
+       ret
+
+
+;------------------------------------------------
+;         case 11: return (ConSt);             /* Get Console Status */
+
+Fcn11: JP      BConSt
+
+;------------------------------------------------
+;         case 12:                             /* Return Version # */
+
+Fcn12: LD      HL,#0x0022      ; Say this is CP/M 2.2
+       RET
+
+;------------------------------------------------
+;         case 13:                             /* Reset Disk Drive */
+;              SDma(0x80);
+;              break;
+Fcn13:
+       LD      BC,#0x80
+       JP      BSDma
+
+;------------------------------------------------
+;         case 7:                              /* Get IO Byte */
+;         case 8: break;                       /* Set IO Byte */
+;         case 14: break;                      /* Select Disk
+;         case 25: break;                      /* Return Current Disk */
+;         case 28: break;                      /* Write Protect Disk */
+;         case 30: break;                      /* Set File Attribytes */
+;         case 32: break;                      /* Get/Set User Code */
+Fcn7:
+Fcn8:
+Fcn14:
+Fcn25:                         ; 0 = Drive A
+Fcn28:
+Fcn30:
+Fcn32:                         ; Return User 0
+;         default: break;
+;     }
+;     return (0);
+
+Exit0: LD      HL,#0
+       RET
+
+;------------------------------------------------
+;         case 15: return (openfile (arg));            /* Open File */
+; openfile (blk)
+; {
+;     desc = open (getname (arg), 2);
+                               ; DE -> arg
+Fcn15: CALL    Fcn17           ; Does this file exist?
+       LD      A,H
+       AND     L
+       INC     A               ; File Not Found (-1)?
+       RET     Z               ; ..return -1 if File doesn't exist
+
+Open1: CALL    CkSrch          ;  (Close Search File)
+
+;     arg.recno = 0;
+
+       CALL    ZeroCR
+       LD      13(IY),#0x80    ; use S1 as file open flag
+
+       JR      Exit0           ;  Return Dir Code for Entry
+
+;.....
+; Common File Open Routine.  Used by Read, Write and Search First.
+; Enter: DE = File Mode
+;       HL = Ptr to Null-terminated Path String
+; Exit : A = 0 if Error, HL = -1
+;           File Descriptor, A <> 0 if Ok
+
+OpenF: PUSH    DE              ; Mode
+       PUSH    HL              ;  Path
+       LD      HL,#1           ;   Fuzix Open Fcn #
+       PUSH    HL
+       RST     0x30            ;    _open (Path, Mode);
+       POP     BC              ; Clean Stack
+       POP     BC
+       POP     BC
+       LD      A,H
+       AND     L
+       INC     A               ; FF -> 0?
+       RET                     ; ..return (HL=-1/A=0 if Err, HL=fd/A<>0 of Ok)
+
+;------------------------------------------------
+;         case 16: return (closefile (arg));           /* Close File */
+
+;     if (close (arg->desc) == -1)
+
+Fcn16: LD      IY,(_arg)
+       LD      13(IY),#0       ; clear file open flag
+
+       LD      HL,#11          ; Fuzix sync function #
+       PUSH    HL
+       RST     0x30            ; Execute!
+       POP     BC              ; Clean Stack
+
+       JP      Exit0           ; Return OK
+
+;....
+; Close file descriptor
+
+CloseV:        PUSH    DE
+       LD      HL,#2           ;  Fuzix Close Fcn #
+       PUSH    HL
+       RST     0x30            ;   Execute!
+       POP     BC              ; Clean Stack
+       POP     BC
+       RET
+
+;------------------------------------------------
+;         case 17:                                     /* Search First */
+
+Fcn17: CALL    CkSrch          ; Ensure Search File closed
+       LD      HL,#'.'         ; Open current directory
+       LD      (RName),HL      ;  store name in Secondary work string
+       LD      DE,#0           ; Open Read-Only
+       LD      HL,#RName
+       CALL    OpenF           ;  _open ('.', 0);
+       RET     Z               ; HL = -1, A = 0 if Can't Open
+
+       LD      (srchFD),HL     ; Else Ok, Save File Descriptor
+       LD      (curFil),HL     ;   Duplicate for Reading
+                       ;..fall thru to read one entry..
+;------------------------------------------------
+;         case 18: return (255);                       /* Search Next */
+
+;
+;      FIXME: dir entries need to be bigger ?
+;
+Fcn18: LD      HL,(dmaadr)
+       LD      (dmaSav),HL     ; Save "real" DMA
+Fcn18A:        LD      HL,#dir+16
+       LD      (dmaadr),HL     ;  Set DMA for Dir Op'n
+       LD      A,#16           ;  Getdirent
+       LD      DE,#32          ;  Len of Dir entries
+       CALL    RdWrt0          ;   Read an Entry
+       JR      C,#Fcn18E       ; Error if Carry Set
+       OR      A               ; Read Ok?
+       JR      Z,Fcn18E        ; ..Return HL=-1 if EOF
+       CALL    ChkDir          ; Else Set Dir to CP/M, Check Match
+       OR      A
+       JR      NZ,Fcn18A       ; ..loop if No Match
+
+       LD      A,(_call)
+       CP      #15             ; Is this a File Open internal Call?
+       LD      HL,#0           ;  (set Success, Index 0)
+       JR      Z,Fcn18X        ; ..exit now if Yes
+
+       LD      HL,#dir         ; Else
+       LD      DE,(dmaSav)     ;  Move Dir Buffer to "real" DMA
+       LD      BC,#128
+       LDIR
+       LD      L,B             ; Use 0 in BC
+       LD      H,C             ;   to show Index 0 (success)
+       JR      Fcn18X          ;  ..exit
+
+Fcn18E:        LD      HL,#-1
+Fcn18X:        LD      DE,(dmaSav)
+       LD      (dmaadr),DE     ; Restore "real" DMA Addr
+       RET
+
+;------------------------------------------------
+;         case 19: return (delete (arg));              /* Delete File */
+
+Fcn19: CALL    CkSrch          ; Ensure Search file closed
+
+;     if (unlink (getname (arg)) == -1)
+                               ; DE -> arg
+       CALL    GetNam          ;  Parse to String
+       PUSH    HL              ; String
+       LD      HL,#6           ;  UZI Unlink Fcn #
+       PUSH    HL
+       RST     0x30            ;   Execute!
+       POP     BC              ; Clean Stack
+       POP     BC
+
+;         return (255);
+;     return (0);
+
+       LD      A,H
+       AND     L
+       INC     A               ; FF->0?
+       JP      NZ,Exit0        ;  return Ok if No
+
+ExitM1:        LD      HL,#-1
+       RET
+
+
+;------------------------------------------------
+;         case 33:                                     /* Read File Random */
+;          readrandom (fcb)
+;          {
+
+Fcn33: CALL    RWprep          ; Prepare File for access
+       JP      Z,Exit1
+
+       LD      IY,(_arg)
+       LD      A,33(IY)        ; Set Record Count from
+       LD      32(IY),A        ; Random Record number
+       LD      A,34(IY)        ;
+       LD      12(IY),A        ;
+
+       CALL    DoRead
+       JR      RWEx
+
+;....
+DoRead:
+       CALL    LSeek           ; Seek to Offset (128-byte rec in Block)
+
+       CALL    BRead           ; Read 1 Sector
+
+       PUSH    AF
+       LD      DE,(curFil)
+;;;;   CALL    CloseV          ; Close the file
+       LD      DE,#0
+       LD      (curFil),DE
+       POP     AF
+
+       RET
+
+
+;------------------------------------------------
+;         case 20: return (readfile (arg));            /* Read File */
+; readfile (arg)
+; {
+;     nread = read (blk->desc, dmaadr, 128);
+                               ; DE -> arg (FCB)
+Fcn20: CALL    RWprep          ; Prepare file for access
+       JP      Z,Exit1
+
+       CALL    DoRead          ; Read 1 Sector
+
+;     arg.recno++;
+
+       PUSH    AF
+       CALL    IncCR           ; Bump Current Record #
+       POP     AF
+
+RWEx:  JP      C,Exit1         ; ..Error if Carry Set
+
+;     if (nread == 0)
+;         return (0);
+
+       OR      A               ; Good Read?
+       JP      Z,Exit0         ;   exit w/0 if Yes
+
+;     else  return (1)
+
+       JP      Exit1
+
+;------------------------------------------------
+;         case 34:                                     /* Write File Random */
+;          writerandom (fcb)
+;          {
+;          /* CAUTION the seek calls MUST be in this order */
+;              _seek (f, (int)(fcb+33) % 128, 0);          /* byte  seek */
+;              _seek (f, (int)(fcb+33) / 128, 3);          /* block seek */
+
+Fcn34: CALL    RWprep          ; Prepare file for access
+       JP      Z,Exit1
+
+       LD      IY,(_arg)
+       LD      A,33(IY)        ; Set Record Count from
+       LD      32(IY),A        ; Random Record number
+       LD      A,34(IY)        ;
+       LD      12(IY),A        ;
+
+       CALL    DoWrite
+       JR      RWEx
+
+;....
+DoWrite:
+       CALL    LSeek           ; Seek to Offset (128-byte rec in Block)
+
+       CALL    BWrit           ; Write 1 Sector
+
+       PUSH    AF
+       LD      DE,(curFil)
+;;;;   CALL    CloseV          ; Close the file
+       LD      DE,#0
+       LD      (curFil),DE
+       POP     AF
+
+       RET
+
+;------------------------------------------------
+;         case 21: return (writefile (arg));           /* Write File */
+; writefile (arg)
+; {
+;     if (write (blk->desc, dmaadr, 128) != 128)
+
+                               ; DE -> arg (FCB)
+Fcn21: CALL    RWprep          ; Prepare file for access
+       JP      Z,Exit1
+
+Fcn21A:        CALL    DoWrite         ;   Write
+
+;     arg.recno++;
+
+       PUSH    AF
+       CALL    IncCR           ; Bump Current Record #
+       POP     AF
+
+;         return (255);
+;     return (0);
+
+       JR      RWEx            ; ..exit via Common R/W Code
+; }
+
+;------------------------------------------------
+;         case 22: return (makefile (arg));            /* Create File */
+; makefile (arg)
+; {
+;     desc = creat (getname (blk), 0666);
+
+Fcn22: CALL    CkSrch          ; Ensure Search file closed
+       LD      HL,#0q0666      ; Own/Grp/Oth are Read/Execute
+       PUSH    HL              ; DE -> arg
+       LD      HL,#0x502       ; O_CREAT|O_RDWR|O_TRUNC
+       CALL    GetNam          ;  This name string
+       PUSH    HL
+       LD      HL,#1           ;   Fuzix open Fcn #
+       PUSH    HL
+       RST     0x30            ;    Execute!
+       POP     BC              ; Clean Stack
+       POP     BC
+       POP     BC
+       POP     BC
+
+;     if (desc == -1)
+
+       LD      A,H
+       AND     L
+       INC     A               ; FF -> 0?
+
+;         return (255);
+
+       RET     Z               ; ..return -1 if Yes
+
+;     arg.recno = 0;
+
+       EX      DE,HL
+       CALL    CloseV
+       JP      Open1
+
+;------------------------------------------------
+;         case 23: return (rename (arg));              /* Rename File */
+; rename (arg)
+; {
+;     RName = getname (arg);
+;
+;      FIXME: should use rename() syscall now
+;
+Fcn23: CALL    CkSrch          ; Ensure Search file closed
+       PUSH    DE              ; Save FCB Ptr
+       CALL    GetNam          ;  parse to UZI String
+
+       LD      HL,#FName
+       LD      DE,#RName
+       LD      BC,#12
+       LDIR                    ; Copy to Rename string
+
+;     FName = getname (arg+16);
+
+       POP     DE              ; DE -> _arg
+       LD      HL,#16
+       ADD     HL,DE           ; Offset to New Name
+       EX      DE,HL
+       CALL    GetNam          ;  parse it returning HL -> FName
+
+;     if (link (RName, FName) < 0) {
+
+       PUSH    HL              ; New Name
+       LD      HL,#RName       ;  Old Name
+       PUSH    HL
+       LD      HL,#5           ;   UZI link Fcn #
+       PUSH    HL
+       RST     0x30            ;    Execute!
+       POP     BC              ; Clean Stack
+       POP     BC
+       POP     BC
+
+;         return (-1);
+
+       JP      C,ExitM1        ; Exit w/Err if Bad
+;     }
+;     if (unlink (RName) < 0) {
+
+       LD      HL,#RName       ; Old Name
+       PUSH    HL
+       LD      HL,#6           ;  UZI unlink Fcn #
+       PUSH    HL
+       RST     0x30            ;   Execute!
+       POP     BC              ; Clean Stack
+       POP     BC
+       JP      NC,Exit0        ;   exit w/0 if Ok
+
+;         unlink (FName);
+                               ; Else remove the new iNode
+       LD      HL,#FName       ; New Name
+       PUSH    HL
+       LD      HL,#6           ;  UZI unlink Fcn #
+       PUSH    HL
+       RST     0x30            ;   Execute!
+       POP     BC              ; Clean Stack
+       POP     BC
+
+;         return (-1);
+
+       JP      C,ExitM1        ;  return -1 if Bad
+;     }
+;     return (0);
+
+       JP      Exit0           ;   else return Ok
+; }
+
+;------------------------------------------------
+;         case 24: return (1);                 /* Return Disk Login Vector */
+
+Fcn24:
+Exit1: LD      HL,#1
+       RET
+
+;------------------------------------------------
+;         case 26: dmaadr = (char *)arg;               /* Set DMA Address */
+;                  break;
+                               ; Enter DE = DMA Address
+Fcn26: LD      C,E
+       LD      B,D             ; Move to Bios Regs
+       JP      BSDma           ;  Set in Bios & return
+
+;------------------------------------------------
+;         case 27: return (-1)                 /* Get Allocation Map */
+;         case 29: return (-1)                 /* Get R/O Vector Address */
+Fcn27:
+Fcn29: LD      HL,#-1
+       RET
+
+;------------------------------------------------
+;         case 31: return (&dpb);              /* Get Disk Param Table Addr */
+
+Fcn31: LD      HL,#dpb
+       RET
+; }
+
+;------------------------------------------------
+;         case 35:                             /* Return File Size in FCB */
+;                                 /* Use stat fcn, rounding up to mod-128 */
+;          if (_stat (dname, &statbuf) == 0) {
+                               ; DE -> fcb
+Fcn35: CALL    CkSrch          ; Ensure Search file closed
+       CALL    GetNam          ;  parse to UZI String
+       LD      DE,#stBuf
+       PUSH    DE              ; &statbuf
+       PUSH    HL              ;  dname
+       LD      HL,#15          ;   UZI stat Fcn #
+       PUSH    HL
+       RST     0x30            ;    Execute!
+       POP     BC              ; Clean Stk
+       POP     BC
+       POP     BC
+       LD      IY,(_arg)
+       LD      A,H
+       OR      L               ; 0?
+       JR      NZ,Fcn35X       ; ..jump if Bad
+
+;              (int)fcb+33 = ((512 * statbuf.st_size.o_blkno
+;                            + statbuf.st_size.o_offset)
+;                            + 127)
+;                            >> 7;
+;      FIXME: offset is now a simple number so this is wrong
+;
+       LD      HL,(stBuf+14)   ; Get Offset
+       LD      DE,#127
+       ADD     HL,DE           ;  round up to next 128-byte block
+       ADD     HL,HL           ;   Shift so H has 128-byte blk #
+       LD      E,H             ;    position in DE
+       LD      HL,(stBuf+16)   ; Get Block
+       ADD     HL,HL           ;  * 2
+       ADD     HL,HL           ;   * 4 for 128-byte Block Count
+       ADD     HL,DE           ; Now have CP/M Record Size
+       LD      33(IY),L
+       LD      34(IY),H        ;  Store in RR fields in FCB
+       LD      35(IY),D        ; (D = 0)
+
+;              return (0);
+
+       LD      L,D
+       LD      H,D             ; HL = 0
+       RET
+
+;          else {
+;              (int)fcb+33 = 0;
+
+Fcn35X:        LD      33(IY),#0
+       LD      34(IY),#0
+       LD      35(IY),#0
+
+;              return (-1);
+
+       LD      HL,#-1
+;          }
+       RET
+
+;------------------------------------------------
+;         case 36:                     /* Set Random Record Field in FCB */
+
+Fcn36: LD      IY,(_arg)
+       LD      A,32(IY)        ; Fetch RecNo
+       LD      33(IY),A        ;  place in LSB of RR field (r0)
+       LD      A,12(IY)
+       LD      34(IY),A        ;  set (r1)
+       LD      35(IY),#0       ;  Clear Hi byte of RR (r2)
+
+       LD      HL,#0           ; Return Ok
+       RET
+
+;===========================================================
+;                BDos Support Routines
+;===========================================================
+; char *
+; getname (struct fcb *blk)
+; {
+;     int j;
+;     static char name[16];
+;     char *p;
+
+;     p = name;
+                               ; Enter: DE -> FCB drive byte
+GetNam:        LD      IX,#FName       ; Dest to string
+       EX      DE,HL
+       PUSH    HL              ;   (save)
+       INC     HL              ;  adv to 1st char of FN
+
+;     for (j = 0; j < 8; ++j)
+;     {
+;         if (!blk->name[j] || blk->name[j] == ' ')
+;             break;
+
+       LD      B,#8
+GetN0: LD      A,(HL)
+       INC     HL
+       OR      A
+       JR      Z,GetN1
+       CP      #' '
+       JR      Z,GetN1
+
+;         *p++ = chlower (blk->name[j]);
+
+       CALL    ChLower
+       LD      0(IX),A
+       INC     IX
+       DJNZ    GetN0
+;     }
+
+GetN1: POP     HL
+       LD      DE,#9
+       ADD     HL,DE           ; Pt to 1st char of FT
+       LD      A,(HL)
+       CP      #' '            ; Any Type?
+       JR      Z,GetNX         ; ..quit if Not
+
+;     *p++ = '.';
+
+       LD      0(IX),#'.'
+       INC     IX
+
+;     for (j = 0; j < 3; ++j)
+
+       LD      B,#3
+
+;     {
+;         if (!blk->ext[j] || blk->ext[j] == ' ')
+;             break;
+
+GetN2: LD      A,(HL)
+       INC     HL
+       CP      #' '
+       JR      Z,GetNX
+
+;         *p++ = chlower (blk->ext[j]);
+
+       CALL    ChLower
+       LD      0(IX),A
+       INC     IX
+       DJNZ    GetN2
+
+;     }
+;     *p = '\0';
+
+GetNX: LD      0(IX),#0
+
+;     return (name);
+
+       LD      HL,#FName
+       RET
+; }
+
+;
+;      FIXME: we need to use lseek for Fuzix
+;
+LSeek: LD      BC,#0           ; Push 0 for absolute
+       PUSH    BC
+       LD      IY, (_arg)      ; FCB
+       LD      C, 32(IY)       ; Pull the offset out of the FCB
+       LD      B, 12(IY)       ; This is in records (128 bytes)
+                               ; And may overflow 2^16 bytes
+       XOR     A
+       SLA     C               ; C x 2, into carry
+       RL      B               ; B x 2 picking up carry from C
+       RLA                     ; A x 2 picking up carry from B - now in 64's
+       SLA     C
+       RL      B
+       RLA                     ; now in 32's
+       SLA     C
+       RL      B
+       RLA                     ; now in 16's
+       SLA     C
+       RL      B
+       RLA                     ; now in 8's
+       SLA     C
+       RL      B
+       RLA                     ; now in 4's
+       SLA     C
+       RL      B
+       RLA                     ; now in 2's
+       SLA     C
+       RL      B
+       RLA                     ; now in bytes
+       LD      (LSeekData), BC ; low bits
+       LD      (LSeekData + 2), A      ; high bit (top byte already clear)
+       LD      BC, #LSeekData  ; push pointer
+       PUSH    BC
+       LD      HL, (curFil)
+       PUSH    HL
+       LD      HL,#9           ; _lseek()
+       PUSH    HL
+       RST     0x30            ; Syscall
+       POP     BC              ; Recover stack
+       POP     BC
+       POP     BC
+       POP     BC
+       RET
+
+;.....
+; Perform File Access Preparatory actions:
+; Open file for R/W and Seek to current Record #
+; Enter: DE = Ptr to FCB
+; Exit : A = 0 and HL = -1 if Error, A <> 0 if Ok
+
+RWprep:        CALL    CkSrch          ; Ensure Search file closed
+       LD      HL,#13          ; offset to S1 (file open flag)
+       ADD     HL,DE
+       LD      A,(HL)
+       AND     #0x80
+       LD      HL,#-1
+       RET     Z
+
+       CALL    GetNam          ;  Parse FCB Fn.Ft to String
+
+       CALL    COpen
+       RET     Z               ; ..return -1 on error
+
+       LD      (curFil),HL     ; store file descriptor for Bios
+       RET
+
+COpen: PUSH    HL
+       LD      DE,#CName
+chk:   LD      A,(DE)
+       CP      (HL)            ; compare filename with cached name
+       JR      NZ,differ
+       OR      A
+       JR      Z,same
+       INC     HL
+       INC     DE
+       JR      chk
+same:  POP     DE              ; if same, just return the cached file descr
+       LD      HL,(Cfd)
+       LD      A,H
+       AND     L
+       INC     A
+       RET     NZ
+       EX      DE,HL
+       JR      op1
+differ:        LD      HL,(Cfd)
+       LD      A,H
+       AND     L
+       INC     A
+       EX      DE,HL
+       CALL    NZ,CloseV       ; close old file
+       POP     HL              ; restore file name
+       CALL    Ccopy
+op1:   LD      DE,#2           ; open for R/W
+       CALL    OpenF
+       LD      (Cfd),HL
+       RET
+
+Ccopy: PUSH    HL
+       LD      DE,#CName
+cpy:   LD      A,(HL)
+       LD      (DE),A
+       INC     HL
+       INC     DE
+       OR      A
+       JR      NZ,cpy
+       POP     HL
+       RET
+
+;.....
+; Convert UZI Directory Entry at dir+16 to CP/M FCB entry at dir, Zero rest.
+; Ambiguously compare FCB FN.FT at dir to that passed at arg, returning Zero
+; if Match, Non-Zero if mismatch.
+
+ChkDir:        LD      DE,#dir
+       LD      HL,#dir+16+2    ; Pt to 1st char of Name
+       XOR     A
+       LD      (DE),A          ; Zero Drive field
+       INC     DE              ;  Pt to 1st char of FN
+       LD      B,#8
+       CALL    ChkD0           ;   Fix Name
+       LD      B,#3
+       CALL    ChkD0           ;    & Type
+       LD      B,#21
+       CALL    ZeroDE          ;     Clear rest of Dir entry
+
+       LD      DE,(_arg)
+       INC     DE              ; Pt to 1st char of FN
+       LD      A,(DE)
+       CP      #' '            ; Any Name present?
+       JR      NZ,ChkFN0       ; ..jump if Yes
+       LD      HL,#8
+       ADD     HL,DE           ;  Else offset to 1st char of FT
+       LD      A,(HL)
+       CP      #' '            ;   Type present?
+       LD      A,#0x0FF                ;    (Assume Error)
+       RET     Z               ;     Return w/Err Flag if no Type either
+
+ChkFN0:        LD      HL,#dir+1       ; Else Compare name/type fields
+       LD      B,#11
+                       ; Ambiguous FN.FT compare of (HL) to (DE)
+ChkL:  LD      A,(DE)
+       CP      #'?'            ; Accept anything?
+       JR      Z,ChkL0         ; ..jump if ambiguous
+       XOR     (HL)
+       AND     #0x7F           ; Match?
+       RET     NZ              ; .Return Non-Zero if Not
+ChkL0: INC     HL
+       INC     DE
+       DJNZ    ChkL            ; ..loop til Done
+       XOR     A               ;    return Zero for Match
+       RET
+
+;.....
+; Parse FileSpec addressed by HL into FN.FT Spec addressed by DE.
+
+ChkD0: LD      A,(HL)          ; Get Char
+       CP      #'a'
+       JR      C,ChkD1
+       CP      #'z'+1
+       JR      NC,ChkD1
+       AND     #0x5F           ; Convert to Uppercase
+ChkD1: OR      A               ; End of String?
+       JR      Z,ChkDE         ; ..jump if End
+       INC     HL              ;     (bump Inp Ptr if Not End)
+       CP      #'.'
+       JR      Z,ChkDE         ;  ..or Period field separator
+       LD      (DE),A          ; Store char
+       INC     DE              ;  bump Dest
+       DJNZ    ChkD0           ; ..loop til field done
+ChkD2: LD      A,(HL)          ; Get Next
+       OR      A
+       RET     Z               ;  Exit at End of string
+       INC     HL              ;   (adv to next)
+       CP      #'.'
+       RET     Z               ;   or field separator
+       JR      ChkD2           ;  ..loop til end found
+
+ChkDE: LD      A,#' '          ; Fill rest w/Spaces
+ChkD3: INC     B
+       DEC     B               ; More in field?
+       RET     Z               ; ..exit if Not
+       JR      ZeroL           ;  ..else stuff spaces til field ends
+
+;.....
+; Zero area addressed by DE for B Bytes.  Uses A,B,DE.
+
+ZeroDE:        XOR     A
+ZeroL: LD      (DE),A
+       INC     DE
+       DJNZ    ZeroL
+       RET
+
+;.....
+; Close the Directory if we just exitted a SearchF/SearchN sequence
+
+CkSrch:        PUSH    DE              ; Save Regs
+       PUSH    HL
+       LD      DE,(srchFD)     ; Get File Desc
+       LD      A,D
+       OR      E               ; Anything open?
+       CALL    NZ,CloseV       ;  Close file if Yes
+       LD      HL,#0
+       LD      (srchFD),HL     ;   Mark as closed
+       POP     HL              ;    (ignore Errors)
+       POP     DE
+       RET
+
+;.....
+; Bump current Record # for sequential R/W operations
+
+IncCR: LD      IY,(_arg)
+       INC     32(IY)          ; Bump Lo byte
+       RET     NZ
+       INC     12(IY)          ; Bump Hi byte
+       RET
+
+;.....
+; Init Current Record #
+
+ZeroCR:        LD      IY,(_arg)
+       LD      32(IY),#0       ; Clear Lo Byte
+       LD      12(IY),#0       ; Clear Hi Byte
+       RET
+
+;.....
+; Convert char in A to Lowercase Ascii
+
+ChLower:
+       CP      #'A'
+       RET     C
+       CP      #'Z'+1
+       RET     NC
+       OR      #0x20           ; Convert to Lcase
+       RET
+
+;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
+; Bdos data in Text Segment for treating as single module
+
+; struct fcb {
+;     char drive;
+;     char name[8];
+;     char ext[3];
+;     char junk1[4];
+;     char desc;       /* This byte & 1st byte of Name used for file desc */
+;     char name2[8];
+;     char ext2[3];
+;     char junk2[4];
+;     char junk3;
+; };
+
+_arg:  .dw     00              ; Argument passed to Bdos       (char *arg;)
+_call: .db     0               ; Bdos Function #               (char call;)
+FName: .ascii  '            '  ; Storage for FCB "name" String
+RName: .ascii  '            '  ; 2nd Storage for FCB "name" String (rename)
+
+CName: .ascii  '            '  ; cached filename
+       .db     0
+Cfd:   .dw     -1              ; cached file descriptor
+
+curFil:        .dw     00              ; Storage for File Descriptor of FCB
+                               ;  (set by Bdos, Used by Bios)
+stBuf: .ds     30              ; Buffer for stat() results
+
+DOSSIZ   .EQU  .-EStart
+RESV   .EQU    0x100-(DOSSIZ&0x0FF)
+       .ds     RESV            ; Pad to make Bios start on even mult of 256
+
+;============================================================
+; The Bios Jump table MUST start on an even MOD 256 boundary
+;============================================================
+
+__bios:        JP      __cold          ; 0 Cold Boot
+WBoot: JP      Exit            ; 1 Warm Boot
+BConSt:        JP      ConSt           ; 2 Console Status
+BConIn:        JP      ConIn           ; 3 Console Input
+BConOu:        JP      ConOut          ; 4 Console Output
+       JP      List            ; 5 Printer Output
+       JP      AuxOut          ; 6 Auxiliary Output (Punch)
+       JP      AuxIn           ; 7 Auxiliary Input (Reader)
+       JP      Home            ; 8 Home drive head
+       JP      SelDsk          ; 9 Select Drive
+       JP      SetTrk          ; 10 Set Track
+       JP      SetSec          ; 11 Set Sector
+BSDma: JP      SetDMA          ; 12 Set DMA Address
+BRead: JP      Read            ; 13 Read Sector
+BWrit: JP      Write           ; 14 Write Sector
+       JP      ListSt          ; 15 Printer Status
+       JP      SecTrn          ; 16 Translate Sector
+
+;------------------------------------------------
+; Cold Entry.  Set up CP/M vectors and Stack, Get
+; Current TTY Parms, Save for Exit, and begin
+
+__cold:        LD      A,#0x0C3
+       LD      HL,#__bdos
+       LD      SP,HL           ; Set CP/M Stack for execution
+       LD      (0x0005),A      ;  Set Bdos Vector
+       LD      (0x0006),HL
+       LD      HL,#WBoot
+       LD      (0x0000),A      ;   Set Bios Warm Boot Vector
+       LD      (0x0001),HL
+
+       LD      HL,#ttTermios0  ; & buf
+       LD      DE,#TCGETS      ;  ioctl fcn to Get Parms
+       CALL    IoCtl           ;   Execute ioctl fcn on STDIN
+       LD      HL,#ttTermios0
+       LD      DE,#ttTermios
+       LD      BC, #20
+       LDIR                    ;  Move to Work Area
+               ; Now we need to Change Modes defined in DEVTTY as:
+               ;   RAW    = 20H        (0000040)
+               ;   CRMOD  = 10H        (0000020)
+               ;   ECHO   = 08H        (0000010)
+               ;   CBREAK = 02H        (0000002)
+               ;   COOKED = 00H        (0000000)
+       LD      HL, #0
+       ; Turn all the input and output magic off
+       LD      (ttTermios), HL
+       LD      (ttTermios + 2), HL
+       XOR     A
+       LD      (ttTermios+6), A        ; Echo etch off
+       LD      A, (ttTermios+7)
+       AND     #0xF0           ; canonical processing off
+       LD      (ttTermios+7), A
+       LD      HL, #1                  ; VTIME 0
+       LD      (ttTermios+8), HL       ; VMIN 1
+       LD      HL, #ttTermios
+       LD      DE, #TCSETS
+       CALL    IoCtl                   ; Set terminal bits 
+       
+       CALL    0x0100          ;  ..Execute!
+
+;.....
+; 1 - Warm Boot Vector (Exits back to UZI)             {exit (0);}
+;     TTY Port Settings are restored to original state.
+
+Exit:  LD      C,#0x0D
+       CALL    ConOut
+       LD      C,#0x0A
+       CALL    ConOut
+       LD      HL,#ttTermios0  ; & buf
+       LD      DE,#TCSETS      ;  ioctl fcn to Set Parms
+       CALL    IoCtl           ;   Execute ioctl Fcn on STDIN
+
+       LD      HL,#0           ; Exit Good Status
+       PUSH    HL
+       PUSH    HL              ;  UZI Fcn 0 (_exit)
+       RST     0x30            ;   Execute!
+       DI
+       HALT
+
+;.....
+; 2 - Return Console Input Status
+
+ConSt: LD      HL,#cnt         ; &buf
+       LD      DE,#TIOCINQ     ;  ioctl fcn to read queue count
+       CALL    IoCtl           ;   Execute ioctl on STDIN
+       LD      HL,(cnt)
+       LD      A,H
+       OR      L               ; Anything There?
+       RET     Z               ; ..return Zero if Not
+       OR      #0x0FF          ; Else signify char waiting
+       RET
+
+;.....
+; 3 - Read Console Input Char                  {read (stdin, &char, 1);}
+
+ConIn: call    ConSt
+       jr      z,ConIn
+       LD      HL,#1           ; 1 char
+       PUSH    HL
+       LD      DE,#char                ;  Addr to put char
+       PUSH    DE
+       LD      L,#STDIN                ;   fd
+       PUSH    HL
+       LD      L,#7            ;    UZI Read Fcn
+ChrV0: PUSH    HL
+       RST     0x30            ;     Execute
+       POP     BC
+       POP     BC
+       POP     BC
+       POP     BC
+       LD      A,(char)
+       RET
+
+;.....
+; 4 - Write Char in C to Console               {write (stdout, &char, 1);}
+
+ConOut:        LD      A,C
+       LD      DE,#char
+       LD      (DE),A          ; Stash char
+       LD      HL,#1           ; 1 char
+       PUSH    HL
+       PUSH    DE              ;  Addr to get char
+       LD      L,#STDOUT       ;   fd
+       PUSH    HL
+       LD      L,#8            ;    UZI Write Fcn
+       JR      ChrV0           ;   ..go to common code
+
+;.....
+
+List:                          ; Bios Fcn 5
+AuxOut:                                ; Bios Fcn 6
+AuxIn:                         ; Bios Fcn 7
+Home:                          ; Bios Fcn 8
+SetTrk:                                ; Bios Fcn 10
+SetSec:                                ; Bios Fcn 11
+ListSt:                                ; Bios Fcn 15
+SecTrn:        XOR     A               ; Bios Fcn 16.  These are No-Ops
+       RET
+
+;.....
+; 9 - Select Disk.  Simply return the DPH pointer
+
+SelDsk:        LD      HL,#dph         ; Return DPH Pointer
+       RET
+
+;.....
+; 12 - Set DMA Transfer Address
+
+SetDMA:        LD      (dmaadr),BC     ; Save Address
+       Ret
+
+;.....
+; 13 - Read a "Sector" to DMA Address          {read (curFil, dmaadr, 128);}
+
+Read:  LD      A,#7            ; Set UZI Read Fcn
+       CALL    RdWrt           ;  Do the work
+       RET     C               ; ..exit if Error
+       OR      A               ; 0 bytes Read?
+       JR      Z,XErr1         ; ..Return Error if Yes (EOF)
+       SUB     #128            ; A full 128 bytes Read?
+       RET     Z               ;   return Ok if Yes
+       LD      DE,(dmaadr)
+       ADD     HL,DE           ; Else offset to byte after end
+Feof:  LD      (HL),#0x1A      ;  stuff EOF in case of text
+       INC     HL
+       INC     A
+       JR      NZ,Feof
+       RET                     ;   exit with OK status
+
+;.....
+; 14 - Write a "Sector" from DMA Address       {write (curFil, dmaadr, 128);}
+
+Write: LD      A,#8            ; Set UZI Write Fcn
+       CALL    RdWrt           ;  Do the work
+       RET     C               ; ..exit if Error
+       SUB     #128            ; Good Write?
+       RET     Z               ;   return Ok if Yes
+XErr1: SCF
+       JR      XErr            ;  Else Return Error
+
+; Common Read/Write Support Routine
+
+RdWrt: LD      DE,#128         ; 1 "Sector" char
+                       ; Entry Point accessed by Search Next (BDos)
+RdWrt0:        PUSH    DE
+       LD      HL,(dmaadr)     ;  from here
+       PUSH    HL
+       LD      HL,(curFil)     ;   to this file
+       PUSH    HL
+       LD      E,A             ;    Position R/W Fcn #
+       PUSH    DE
+       RST     0x30            ;     Execute!
+       POP     BC              ; Clear Stack
+       POP     BC
+       POP     BC
+       POP     BC
+       LD      A,L             ; Shuffle possible byte quantity
+       RET     NC              ; ..return if No Error
+XErr:  LD      A,#0x01         ; Else Signal Error (keeping Carry)
+       RET
+
+;==========================================================
+;               Bios Support Utilities
+;==========================================================
+; Execute ioctl Function on STDIN
+; Enter: HL = Addr of Parm Block
+;       DE = ioctl Function to execute
+; Exit : None
+; Uses : AF,BC,DE,HL
+
+IoCtl: PUSH    HL              ; &buf
+       PUSH    DE              ;  ioctl fcn
+       LD      E,#STDIN                ;   fd
+       PUSH    DE
+       LD      E,#29           ;    Fuzix ioctl Fcn #
+       PUSH    DE
+       RST     0x30            ;     Execute!
+       POP     BC              ; Clean Stack
+       POP     BC
+       POP     BC
+       POP     BC
+       RET
+
+;- - - - - - - - - - Data Structures - - - - - - - - -
+
+dph:   .dw     0               ; Ptr to Skew Table
+       .dw     0,0,0           ; Scratch Words for BDos use
+       .dw     dir             ; Ptr to Directory Buffer
+       .dw     dpb             ; Ptr to DPB
+       .dw     0               ; Ptr to Disk Checksum Buffer
+       .dw     0               ; Ptr to ALV Buffer
+
+
+dpb:   .dw     64              ; Dummy Disk Parameter Block
+       .db     4
+       .db     15
+       .dw     0x0FFFF
+       .dw     1023
+       .db     0x0FF,0
+       .db     0,0,0,0
+
+;----------------------- Data -----------------------
+
+dmaadr:        .dw     0x0080          ; Read/Write Transfer Addr   (char *dmaadr;)
+dmaSav:        .dw     0               ; Temp storage of current DMA Address
+srchFD:        .dw     0               ; File Descriptor for Searches
+char:  .db     ' '             ; Byte storage for Conin/Conout
+cnt:   .dw     0               ; Count of waiting keys
+LSeekData:     .dw     0       ; Used for _lseek() syscalls
+               .dw     0
+ttTermios:
+       .ds     20              ; Working TTY Port Settings
+ttTermios0:
+       .ds     20              ; Initial TTY Port Settings
+
+dir:   .ds     128             ; Directory Buffer
+
+       .ds     128
+_Bstack:
+_userSP:.dw    0       ; WRS: important that we write data all the way to the very last byte used
+
+BIOSIZ .EQU    .-__bios
+CPMSIZ .EQU    .-__bdos
+
+;       .end
+
diff --git a/Kernel/cpm-loader/.gitignore b/Kernel/cpm-loader/.gitignore
new file mode 100644 (file)
index 0000000..8e99cca
--- /dev/null
@@ -0,0 +1,5 @@
+fuzixload.bin
+fuzixload.ihx
+cpmload.bin
+cpmload.ihx
+makecpmloader
diff --git a/Kernel/cpm-loader/Makefile b/Kernel/cpm-loader/Makefile
new file mode 100644 (file)
index 0000000..ddc7b56
--- /dev/null
@@ -0,0 +1,16 @@
+all:   makecpmloader cpmload.bin fuzixload.bin
+
+makecpmloader: makecpmloader.c
+
+cpmload.bin:   cpmload.s
+       $(CROSS_AS) $(ASOPTS) cpmload.s
+       sdldz80 -nmi cpmload.rel
+       makebin -p cpmload.ihx > cpmload.bin
+
+fuzixload.bin: fuzixload.s
+       $(CROSS_AS) $(ASOPTS) fuzixload.s
+       sdldz80 -nmi fuzixload.rel
+       makebin -p fuzixload.ihx > fuzixload.bin
+
+clean:
+       rm -f *~ *.rst *.lst *.asm *.bin *.sym *.rel *.map *.ihx makecpmloader
diff --git a/Kernel/cpm-loader/cpmload.s b/Kernel/cpm-loader/cpmload.s
new file mode 100644 (file)
index 0000000..af0904f
--- /dev/null
@@ -0,0 +1,38 @@
+; 2014-12-21 Will Sowerbutts
+
+.module cpmload
+.area _LOADER (ABS)
+
+    ; CP/M will load us at 0x0100
+    ; We want to relocate our payload (the kernel) and jump into it.
+    ; We put a small stub at the very bottom of memory, copy the kernel into
+    ; place above us, then jump into it.
+
+    .org 0x100
+    di
+    ; copy ourselves to the very bottom of memory -- 0x00 upwards
+    ld de, #0
+    ld hl, #(doload)                ; start of loader code
+    ld bc, #(endloader-doload)      ; length of our loader
+    ldir                            ; copy copy copy!
+    ld hl, (load_address)
+    ld sp, hl                       ; stash copy of entry vector in SP for now
+    ex de, hl                       ; load_address to de
+    ld hl, #payload_start
+    ld bc, (load_length)
+    jp 0                            ; jump and perform copy in low memory
+
+    ; this code gets copied to .org 0
+doload:
+    ldir                            ; copy image into correct place
+    ld hl, #0
+    add hl, sp                      ; recover entry vector
+    jp (hl)                         ; run image
+endloader:                          ; end of code to copy
+
+    ; the data is in a trailer, with a 4-byte header:
+load_address:
+    .ds 2
+load_length:
+    .ds 2
+payload_start:
diff --git a/Kernel/cpm-loader/fuzixload.s b/Kernel/cpm-loader/fuzixload.s
new file mode 100644 (file)
index 0000000..63d63ac
--- /dev/null
@@ -0,0 +1,74 @@
+                .module fuzixload
+
+                .area _LOADER (ABS)
+                .org 0x100
+
+start:          ; jump to start of code
+                jp start2
+
+                ; fuzix executable header --------------------------
+                .db 'F'
+                .db 'Z'
+                .db 'X'
+                .db '1'
+
+                .db 0x01                ; page to load at
+                .dw 0                   ; chmem ("0 - 'all'")
+                .dw 0                   ; gives us code size info
+                .dw 0                   ; gives us data size info
+                .dw 0                   ; bss size info
+                .dw 0                   ; spare
+                ; --------------------------------------------------
+
+msg:            .ascii 'booting ...\r\n'
+endmsg:
+
+start2: 
+                ; sync()
+                ld hl, #11              ; syscall #
+                push hl
+                rst #0x30               ; execute
+
+                ; write(0, msg, strlen(msg));
+                ld hl, #(endmsg-msg)    ; count
+                push hl
+                ld hl, #msg             ; buffer
+                push hl
+                ld hl, #0               ; fd
+                push hl
+                ld hl, #8               ; syscall #
+                push hl
+                rst #0x30               ; execute
+
+                ; sync()
+                ld hl, #11              ; syscall #
+                push hl
+                rst #0x30               ; execute
+
+                di                      ; now we steal control of the machine from the old kernel!
+
+                ld de, #0                       ; copy ourselves to bottom of RAM
+                ld hl, #(doload)                ; start of loader code
+                ld bc, #(endloader-doload)      ; length of our loader
+                ldir                            ; copy copy copy!
+                ld hl, (load_address)
+                ld sp, hl                       ; stash copy of entry vector in SP for now
+                ex de, hl                       ; load_address to de
+                ld hl, #payload_start
+                ld bc, (load_length)
+                jp 0                            ; jump and perform copy in low memory
+
+                ; this code gets copied to .org 0
+doload:
+                ldir                            ; copy image into correct place
+                ld hl, #0
+                add hl, sp                      ; recover entry vector
+                jp (hl)                         ; run image
+endloader:                                      ; end of code to copy
+
+                ; the data is in a trailer, with a 4-byte header:
+load_address:
+                .ds 2
+load_length:
+                .ds 2
+payload_start:
diff --git a/Kernel/cpm-loader/makecpmloader.c b/Kernel/cpm-loader/makecpmloader.c
new file mode 100644 (file)
index 0000000..2f28540
--- /dev/null
@@ -0,0 +1,160 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+
+/*
+ * 2014-12-21 William R Sowerbutts
+ *
+ * Create a CP/M program which boots Fuzix.
+ *
+ * The CP/M executable file contains a small loader program, followed by some
+ * data describing where the kernel should be loaded, and then the kernel
+ * itself.
+ *
+ * When run, the CP/M program copies a small (20 byte) program to the very
+ * start of memory, which then copies the kernel to the correct address and
+ * executes it.
+ *
+ *
+ * Syntax: makecpmloader <loader> <kernel> <address> <output>
+ *
+ * loader   -- normally cpmload.bin, assembled from cpmload.s
+ * kernel   -- fuzix.bin from the Fuzix build process
+ * address  -- the kernel's load address (normally 0x88)
+ * output   -- output file name
+ *
+ */
+
+#define LOADER_TRIM       0x100      /* CP/M loads us at 0x100 */
+#define MAX_LOADER_LENGTH 0x200      /* Loader should really be teeny tiny */
+#define MAX_KERNEL_LENGTH 0x10000    /* Kernel can't be larger than 64KB ... yet ... */
+#define BYTESWAP16(x)  (((x>>8) & 0xFF) | ((x & 0xFF) << 8))
+
+char loader_data[MAX_LOADER_LENGTH];
+char kernel_data[MAX_KERNEL_LENGTH];
+
+struct loader_trailer {
+    unsigned short load_address;
+    unsigned short load_length;
+};
+
+int load_file(const char *filename, char *buffer, int buffer_len)
+{
+    int fd, length, r;
+
+    fd = open(filename, O_RDONLY);
+
+    if(fd < 0){
+        fprintf(stderr, "Cannot open \"%s\": %s\n", filename, strerror(errno));
+        return -1;
+    }
+
+    length = 0;
+    while(1){
+        r = read(fd, &buffer[length], buffer_len - length);
+        if(r == 0) /* EOF */
+            break;
+        else if(r > 0)
+            length += r;
+        else{
+            fprintf(stderr, "Cannot read \"%s\": %s\n", filename, strerror(errno));
+            close(fd);
+            return -1;
+        }
+        if(length == buffer_len){
+            fprintf(stderr, "Out of buffer space reading \"%s\"\n", filename);
+            close(fd);
+            return -1;
+        }
+    }
+
+    close(fd);
+    return length;
+}
+
+int parse_int(char *str)
+{
+    int base = 10;
+    long val;
+    char *end;
+
+    end = str;
+    if(strncasecmp(end, "0x", 2) == 0){
+        base = 16;
+        end = end + 2;
+    }
+
+    val = strtol(end, &end, base);
+
+    if(*end == 0 && *str != 0)
+        return val;
+    else{
+        fprintf(stderr, "Cannot parse load address \"%s\"\n", str);
+        return -1;
+    }
+}
+
+int main(int argc, char **argv)
+{
+    int loader_length;
+    int load_address;
+    int kernel_length;
+    int fd;
+    struct loader_trailer trailer;
+
+    if(argc < 5){
+        fprintf(stderr, "%s [loader] [kernel] [address] [output]\n", argv[0]);
+        return 1;
+    }
+
+    loader_length = load_file(argv[1], loader_data, MAX_LOADER_LENGTH);
+    if(loader_length < 0)
+        return 1;
+    if(loader_length <= LOADER_TRIM){
+        fprintf(stderr, "Loader image is too small\n");
+        return 1;
+    }
+
+    kernel_length = load_file(argv[2], kernel_data, MAX_KERNEL_LENGTH);
+    if(kernel_length < 0)
+        return 1;
+
+    load_address = parse_int(argv[3]);
+
+    if(load_address < 0 || load_address > 0xFFFF){
+        fprintf(stderr, "Bad load address.\n");
+        return 1;
+    }
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+    trailer.load_length = kernel_length;
+    trailer.load_address = load_address;
+#else
+    trailer.load_length = BYTESWAP16(kernel_length);
+    trailer.load_address = BYTESWAP16(load_address);
+#endif
+
+    fd = open(argv[4], O_WRONLY | O_CREAT | O_TRUNC, 0666);
+    if(fd < 0){
+        fprintf(stderr, "Cannot open \"%s\": %s\n", argv[4], strerror(errno));
+        return 1;
+    }
+
+    if(write(fd, &loader_data[LOADER_TRIM], loader_length - LOADER_TRIM) != (loader_length - LOADER_TRIM) ||
+       write(fd, &trailer, sizeof(trailer)) != sizeof(trailer) ||
+       write(fd, kernel_data, kernel_length) != kernel_length){
+        fprintf(stderr, "Write to \"%s\" failed: %s\n", argv[4], strerror(errno));
+        close(fd);
+        unlink(argv[4]);
+        return 1;
+    }
+
+    close(fd);
+
+    return 0;
+}