runcpm: after last nights hacks it will now run ZORK
authorAlan Cox <alan@linux.intel.com>
Wed, 15 Jun 2016 14:37:25 +0000 (15:37 +0100)
committerAlan Cox <alan@linux.intel.com>
Wed, 15 Jun 2016 14:37:25 +0000 (15:37 +0100)
Applications/cpm/emulator.s
Applications/cpm/runcpm.c

index 0e87076..cfcffbb 100644 (file)
@@ -8,7 +8,6 @@
     ; TODO
     ; - Recheck all syscall translations
     ; - Fix up directory mapping a bit more
-    ; - Move some of the scratch bytes into the CP/M work area
     ; - Save the syscall vector on entry and go via the saved copy as the
     ;   RST could be re-used by CP/M programs
     ; - Make the code use indirect pointers to the directory buffer etc
@@ -32,6 +31,18 @@ LF   .EQU    0x0A            ; ASCII Line Feed
 CR     .EQU    0x0D            ; ASCII Carriage Return
 ESC    .EQU    0x1B            ; ASCII ESCape Char
 
+;
+;      Reserved space items
+;
+char   .EQU    0x3F            ; Byte storage for Conin/Conout
+SysSP  .EQU    0x40            ; System stack pointer
+UserSP .EQU    0x42            ; User process stack
+dmaSav .EQU    0x44            ; Saved DMA pointer
+dmaadr .EQU    0x46            ; DMA address
+srchFD .EQU    0x48            ; Directory search fd
+LSeekData .EQU 0x4A            ; 4 bytes
+cnt    .EQU    0x4E            ; Count of waiting keys
+
 TCGETS .EQU    1               ; Fuzix - get tty data
 TCSETS .EQU    2               ; Fuzix - set tty data
 TIOCINQ        .EQU    5               ; Fuzix - characters pending
@@ -39,36 +50,21 @@ TIOCINQ     .EQU    5               ; Fuzix - characters pending
 STDIN  .EQU    0       ; file descriptor value of keyboard
 STDOUT .EQU    1       ; file descriptor value of display
 
-;
-; FIXME: set up phase can be in the runcpm tool
-;
-; 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              ;  Get Ptr to argv[]
-       INC     IX              ;  Skip "runcpm"
-       INC     IX
+EStart:
 ;
 ; Load the binary of argv[1] at 0x0100 (obliterating runcpm) (passed as fd
 ; 2)
 ;
-       LD      DE,#__bdos - 0x100      ; largest possible binary space
+       LD      HL, #startaddr          ; just a cheap way to let runcpm
+                                       ; check a valid load address
+       LD      (SysSP), SP             ; save stack pointer for cold starts
+       LD      DE,#EStart - 0x100      ; largest possible binary space
        PUSH    DE
-       LD      DE, #0x0100
+       LD      DE, #0x0100     ; address
        PUSH    DE
        LD      DE,#3           ; file handle
        PUSH    DE
-       LD      DE,#7           ; read()
+       LD      DE,#7           ; read(3, 0x100, size)
        PUSH    DE
        RST     0x30
        POP     DE
@@ -82,52 +78,7 @@ EStart:      LD      HL,#fcbDat      ; Move initial FCB
        RST     0x30
        POP     DE
        POP     DE
-       INC     IX              ;  Skip binary name
-       INC     IX
-;
-;      Turn the following arguments into the CP/M tail and default FCB
-;
-       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
@@ -138,70 +89,6 @@ Cold2:      XOR     A
 
        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
 ;==========================================================
@@ -268,8 +155,8 @@ _bdos0:     LD      (_arg),DE
        LD      A,#0xFF         ;  Prepare Error code
        LD      L,A
        RET     NC              ; ..return if Illegal
-       LD      (_userSP),SP
-       LD      SP,#_Bstack
+       LD      (UserSP),SP
+       LD      SP,(SysSP)      ; return to system stack
        PUSH    IX
        PUSH    IY
        LD      B,#0            ; Fcn # to Word
@@ -286,7 +173,7 @@ _bdos0:     LD      (_arg),DE
 
 _bdosX:        POP     IY
        POP     IX
-       LD      SP,(_userSP)
+       LD      SP,(UserSP)
        LD      DE,(_arg)       ; Return Orig contents of DE
        LD      A,(_call)
        LD      C,A             ; Return Orig contents of C
@@ -611,7 +498,7 @@ 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      A,#24           ;  Getdirent
        LD      DE,#32          ;  Len of Dir entries
        CALL    RdWrt0          ;   Read an Entry
        JR      C,#Fcn18E       ; Error if Carry Set
@@ -1371,14 +1258,18 @@ BWrit:  JP      Write           ; 14 Write 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
+__cold:
+
+       LD      A,#0xC3
+       LD      SP,(SysSP)      ; Set CP/M Stack for execution
        LD      (0x0005),A      ;  Set Bdos Vector
+       LD      HL,#__bdos
        LD      (0x0006),HL
        LD      HL,#WBoot
        LD      (0x0000),A      ;   Set Bios Warm Boot Vector
        LD      (0x0001),HL
+       LD      HL, #0x80
+       LD      (dmaadr), HL
 
        LD      HL,#ttTermios0  ; & buf
        LD      DE,#TCGETS      ;  ioctl fcn to Get Parms
@@ -1421,7 +1312,7 @@ Exit:     LD      C,#0x0D
        LD      HL,#ttTermios0  ; & buf
        LD      DE,#TCSETS      ;  ioctl fcn to Set Parms
        CALL    IoCtl           ;   Execute ioctl Fcn on STDIN
-
+Quit:
        LD      HL,#0           ; Exit Good Status
        PUSH    HL
        PUSH    HL              ;  UZI Fcn 0 (_exit)
@@ -1592,23 +1483,12 @@ dpb:    .dw     64              ; Dummy Disk Parameter Block
 
 ;----------------------- 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
+dir:   .ds     128             ; Directory Buffer (can cut to 48 bytes?)
 
 BIOSIZ .EQU    .-__bios
 CPMSIZ .EQU    .-__bdos
index f3b8735..6fecd2a 100644 (file)
@@ -2,6 +2,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
+#include <ctype.h>
 #include <fcntl.h>
 
 static char path[] = "/usr/lib/cpm/emulator.bin";
@@ -11,24 +12,58 @@ void writes(const char *p)
   write(2, p, strlen(p));
 }
 
-static struct stat st;
+/* Stack space is valuable constant space is almost free */
+struct stat st;
 unsigned char buf[4];
 unsigned char *basep;
-void (*exec)(char *argv[]);
+void (*exec)(void);
+char pathbuf[512];
+char *pathp;
+int fd;
+int len, len2;
+uint8_t *fcb = (uint8_t *)0x5C;
+uint8_t *tail = (uint8_t *)0x81;
+uint8_t *tailsize = (uint8_t *)0x80;
+char **argp;
+int nfcb;
+
+void prepare_fcb(char *p)
+{
+  int n = 0;
+  char c = toupper(*p);
+  /* We don't parse user codes : FIXME ? */
+  if (p[1] == ':' && c <='A' && c >='Z') {
+    *fcb = c - 'A' + 1;
+    p += 2;
+  }
+  while(*p && !isspace(*p) && *p != '.' && n < 8)
+    fcb[++n] = *p++;
+  if (*p == '.') {
+    p++;
+    n = 8;
+    while(*p && !isspace(*p) && *p != '.' && n < 11)
+      fcb[++n] = *p++;
+  }
+  fcb += 16;
+}
 
 int main(int argc, char *argv[])
 {
 
-  int fd;
-  
   if (argc < 2) {
     writes("runcpm [app.com] [args...]\n");
     exit(1);
   }
-  
+
+  /* Quick idiot trap */
+  if ((uint16_t)main >= 0x1000U) {
+    writes("runcpm: low memory base platform needed.\n");
+    exit(1);
+  }
+
   fd = open(path, O_RDONLY);
   if (fd == -1) {
-    perror(path);
+    writes("runcpm: emulator.bin missing\n");
     exit(1);
   }
   if (read(fd, buf, 4) != 4 || buf[2] < 0x80) {
@@ -44,13 +79,36 @@ int main(int argc, char *argv[])
   }
 
   memcpy(basep, buf, 4);
-  if (read(fd, basep + 4, 0x2FC) != 0x2FC) {
+  if (read(fd, basep + 4, 0xBFC) != 0xBFC) {
     writes("runcpm: emulator.bin too short\n");
     exit(1);
   }
   close(fd);
 
-  fd = open(argv[1], O_RDONLY);
+  /* FIXME: worth having a CP/M search path ? */
+
+  pathp = strrchr(argv[1], '/');
+  if (pathp == NULL)
+    pathp = argv[1];
+  pathp = strrchr(argv[1], '.');
+
+  /* Has a .com extension so honour it */
+  if (pathp && strcasecmp(pathp, ".com") == 0)
+    fd = open(argv[1], O_RDONLY);
+  else {
+    len = strlen(argv[1]);
+    pathp = pathbuf + strlcpy(pathbuf, argv[1], sizeof(pathbuf));
+    strlcpy(pathp, ".com", sizeof(pathbuf) - len);
+    fd = open(path, O_RDONLY);
+    if (fd == -1) {
+      strlcpy(pathp, ".COM", sizeof(pathbuf) - len);
+      fd = open(path, O_RDONLY);
+      if (fd == -1) {
+        writes("runcpm: unable to find .COM file\n");
+        exit(1);
+      }
+    }
+  }
   if (fd == -1 || fstat(fd, &st) == -1) {
     perror(argv[1]);
     exit(1);
@@ -66,10 +124,31 @@ int main(int argc, char *argv[])
     dup2(fd, 3);
     close(fd);
   }
+  /* Now build the FCB and argument area in C so we can keep as much in portable
+     discardable code as we can */
+  memset(fcb, 0, 16);
+  memset(fcb + 1, ' ', 11);
+
+  argp = argv + 2;
+  len = 0;
+
+  /* Linearize arguments that will fit */
+  while(*argp) {
+    len2 = strlen(*argp);
+    if (len + len2 > 126)
+      break;
+    if (nfcb++ < 2)
+      prepare_fcb(*argp);
+    memcpy(tail, *argp, len2);
+    tail += len2;
+    *tail++ = ' ';
+    len += len2 + 1;
+    argp++;
+  }
+  *tailsize = len;
+
   /* If this works it blows us away and we never return */
-  (*exec)(argv);
+  (*exec)();
   writes("runcp: exec failure\n");
   exit(1);
 }
-
-  
\ No newline at end of file