mirror of
https://github.com/SEPPDROID/Digital-Research-Source-Code.git
synced 2025-10-23 08:24:18 +00:00
433 lines
12 KiB
C
433 lines
12 KiB
C
/****************************************************************
|
||
* *
|
||
* CP/M-8000 BDOS Program Loader *
|
||
* *
|
||
* Copyright (c) 1982 Zilog Incorporated *
|
||
* *
|
||
* This module contains routines for loading the file *
|
||
* naked "CPM.SYS" into memory, at the address specified *
|
||
* by the first entry in the MRT. If we are properly *
|
||
* linked with the Bootstrap BIOS, this is a loader- *
|
||
* specific MRT which actually contains the address of *
|
||
* system space. *
|
||
* *
|
||
****************************************************************/
|
||
|
||
#include "stdio.h" /* Standard declarations for BDOS, BIOS */
|
||
|
||
#include "bdosdef.h" /* Type and structure declarations for BDOS */
|
||
|
||
#include "biosdef.h" /* Declarations of BIOS functions */
|
||
|
||
#include "basepage.h" /* Base page structure */
|
||
|
||
#include "x.out.h" /* structure of x.out (".Z8[KS] file") */
|
||
|
||
#define SEP_ID 0x4000 /* Separate I/D flag */
|
||
|
||
#define PRNTSTR 9 /* Print String BDOS call */
|
||
#define SETDMA 26 /* Set DMA Address BDOS call */
|
||
|
||
#define BGETMRT 18 /* Number of the BIOS call */
|
||
|
||
#define open(fcbp) _bdos(15, 0, map_adr( (XADDR)fcbp, MYDATA) )
|
||
/* BDOS open call */
|
||
|
||
extern UWORD bdos(); /* To do I/O into myself */
|
||
extern XADDR bios(); /* To get MRT pointer */
|
||
extern VOID xfer(); /* Transfer control to user program */
|
||
#define NPSEG 1 /* Number of physical segs available */
|
||
/* for CP/M kernel */
|
||
#define NSEG 16 /* Maximum number of x.out segments */
|
||
#define SEGLEN 0x10000 /* Length of a Z8000 segment */
|
||
#define BPLEN (sizeof (struct b_page))
|
||
#define DEFSTACK 0x100 /* Default stack length */
|
||
#define NREGIONS 1 /* Number of regions in the Loader MRT */
|
||
|
||
/* return values */
|
||
|
||
#define GOOD 0 /* good return value */
|
||
#define BADHDR 1 /* bad header */
|
||
#define NOMEM 2 /* not enough memory */
|
||
#define READERR 3 /* read error */
|
||
|
||
#define MYDATA 0 /* Argument for map_adr */
|
||
#define SYSPROG 3 /* Argument for map_adr */
|
||
#define SYSDATA 2 /* Argument for map_adr */
|
||
/* Get actual code segment (as opposed */
|
||
/* to segment where it can be accessed*/
|
||
/* as data) */
|
||
#define TRUE_SYSPROG (SYSPROG | 0x100)
|
||
|
||
struct m_rt { /* The Memory Region Table */
|
||
int entries;
|
||
struct {
|
||
XADDR m_low;
|
||
XADDR m_len;
|
||
} m_reg[NREGIONS];
|
||
};
|
||
|
||
struct context /* Startup context for user's program */
|
||
{
|
||
short regs[14];
|
||
XADDR segstkptr;
|
||
short ignore;
|
||
short FCW;
|
||
XADDR PC;
|
||
};
|
||
extern struct context context; /* defined in bios.c */
|
||
|
||
|
||
|
||
/* Define the data structures used in ldcpm */
|
||
|
||
struct fcb cpmfcb =
|
||
{ (BYTE)0, /* drive code */
|
||
'C','P','M',' ',' ',' ',' ',' ','S','Y','S', /* file name */
|
||
0,0,0,0,0,0,0,0,0,0, /* the rest of the fcb */
|
||
0,0,0,0,0,0,0,0,0,0,0
|
||
};
|
||
|
||
|
||
#define NSPREG 0 /* The MRT region for non-split programs */
|
||
|
||
#define READ 20 /* Read Sequential BDOS call */
|
||
extern UWORD bdos(); /* To do I/O into myself (note this */
|
||
/* function does not map 2nd param - */
|
||
/* see mbdos macro below) */
|
||
|
||
XADDR textloc, /* Physical locations of pgm sections. */
|
||
dataloc,
|
||
bssloc,
|
||
stkloc;
|
||
|
||
XADDR textsiz, /* Sizes of the various sections. */
|
||
datasiz,
|
||
bsssiz,
|
||
stksiz;
|
||
|
||
UWORD split, /* Tells if split I/D or not */
|
||
seg; /* Tells if segmented or not */
|
||
|
||
char *gp; /* Buffer pointer for char input */
|
||
char *mydma; /* Local address of read buffer */
|
||
|
||
struct x_hdr x_hdr; /* Object File Header structure */
|
||
struct x_sg x_sg[NSEG]; /* Segment Header structure */
|
||
|
||
XADDR segsiz[NPSEG]; /* Segment lengths */
|
||
XADDR seglim[NPSEG]; /* Segment length limits */
|
||
XADDR segloc[NPSEG]; /* Segment base physical addresses */
|
||
|
||
short textseg, /* Logical seg # of various segments */
|
||
dataseg,
|
||
bssseg,
|
||
stkseg;
|
||
|
||
|
||
/**************************/
|
||
/* Utility Routines */
|
||
/**************************/
|
||
|
||
pstring(p)
|
||
/* print string */
|
||
REG BYTE *p;
|
||
{
|
||
while (*p) bconout(*p++);
|
||
}
|
||
|
||
|
||
badload(errtype)
|
||
/* Load failed. Print message and try again */
|
||
REG WORD errtype;
|
||
{
|
||
REG WORD i;
|
||
|
||
switch (errtype)
|
||
{
|
||
case 0: pstring("\r\nBoot error.");
|
||
startld();
|
||
|
||
case 1: pstring("\n\rOpen or Read error on ");
|
||
break;
|
||
|
||
case 2: pstring("\n\rBad file format on ");
|
||
}
|
||
pstring("CPM.SYS");
|
||
startld();
|
||
}
|
||
|
||
/***************************
|
||
* ldcpm main routine *
|
||
***************************/
|
||
|
||
|
||
/********************************/
|
||
/* */
|
||
/* l d c p m */
|
||
/* */
|
||
/********************************/
|
||
|
||
|
||
VOID /* Load a program from */
|
||
ldcpm() /* info in cpmfcb */
|
||
{
|
||
register unsigned k;
|
||
register XADDR physaddr;
|
||
register char *tp;
|
||
|
||
if (seldsk(0)) badload(0);
|
||
|
||
k = open(&cpmfcb);
|
||
if( k >= 255 ) badload(1);
|
||
|
||
k = load(); /* load the CP/M system */
|
||
if(k != GOOD) badload(2);
|
||
|
||
pstring("\r\n\nCP/M-Z8K(tm) Version 1.3 ");
|
||
pstring("\n\rCopyright (c) 1983 Zilog Inc, Digital Research, Inc.\r\n\n");
|
||
|
||
/* Finally, ready to transfer control. Must complete context first. */
|
||
xfer(map_adr((XADDR) &context, MYDATA)); /* Go for it! */
|
||
}
|
||
/********************************/
|
||
/* */
|
||
/* Start of load function */
|
||
/* */
|
||
/********************************/
|
||
|
||
load() /* Load a program from FCB info */
|
||
{ register int i,j; /* Temporary counters etc. */
|
||
struct m_rt *mrp; /* Pointer to a MRT structure */
|
||
char mybuf[SECLEN]; /* Local buffer for file reading*/
|
||
|
||
mydma = mybuf; /* Initialize addr for local DMA*/
|
||
gp = &mybuf[SECLEN]; /* Point beyond end of buffer */
|
||
|
||
mrp = (struct m_rt *) bios(BGETMRT);/* Get address of memory region */
|
||
/* table (note segment # lost)*/
|
||
if (readhdr() == EOF) /* Get x.out file header */
|
||
return (READERR); /* Read error on header */
|
||
|
||
if (x_hdr.x_magic == X_NXN_MAGIC) /* Is this acceptable x.out file*/
|
||
{ /* CP/M kernel must be nonseg nonsplit*/
|
||
split = FALSE;
|
||
seg = FALSE;
|
||
}
|
||
else
|
||
return (BADHDR); /* Sorry, can't load it! */
|
||
|
||
/* Set the user space segment number, from the low address in the */
|
||
/* appropriate entry of the MRT. */
|
||
/* m_reg[SPREG] is the region used for split I/D programs in the MRT */
|
||
/* m_reg[NSPREG] is used for non-split. */
|
||
|
||
/* NOTE -- the tpa limits passed in the MRT are really system space */
|
||
/* limits. It is assumed that the system is non-split. */
|
||
|
||
for (i = 0; i < x_hdr.x_nseg; i++) { /* For each segment... */
|
||
if( readxsg(i) == EOF) /* ...get segment hdr */
|
||
return(READERR);
|
||
seglim[i] = SEGLEN; /* ...set max length */
|
||
segsiz[i] = 0L; /* ...and current size */
|
||
}
|
||
|
||
/* Set section base addresses */
|
||
|
||
textloc = dataloc = bssloc = stkloc = 0L;
|
||
|
||
/* Zero section sizes */
|
||
textsiz = datasiz = bsssiz = 0L;
|
||
stksiz = DEFSTACK;
|
||
|
||
stkseg = bssseg = dataseg= textseg = 0;
|
||
|
||
/* assign locations */
|
||
segloc[textseg] = mrp->m_reg[NSPREG].m_low;
|
||
|
||
/* Assign limits */
|
||
seglim[textseg] = mrp->m_reg[NSPREG].m_len - stksiz;
|
||
|
||
/* Assign stack location */
|
||
stkloc = segloc[dataseg] + seglim[dataseg] + stksiz;
|
||
|
||
|
||
for (i = 0; i < x_hdr.x_nseg; i++) /* For each segment... */
|
||
if( (j = loadseg(i)) != GOOD) /* ...load memory. If */
|
||
return (j); /* error return, pass */
|
||
/* it back. */
|
||
return (GOOD);
|
||
}
|
||
|
||
|
||
/* Macro to call BDOS. First parameter is passed unchanged, second */
|
||
/* is cast into an XADDR, then mapped to caller data space. */
|
||
|
||
#define mbdos(func, param) (bdos((func), map_adr((XADDR) (param), MYDATA)))
|
||
|
||
|
||
/* Macro to read the next character from the input file (much faster */
|
||
/* than having to make a function call for each byte) */
|
||
|
||
#define fgetch() ((gp<mydma+SECLEN) ? (int)*gp++&0xff : fillbuff())
|
||
|
||
|
||
/* Routine to fill input buffer when fgetch macro detects it is empty */
|
||
|
||
|
||
int fillbuf() /* Returns first char in buffer */
|
||
{ /* or EOF if read fails */
|
||
/* Set up address to read into */
|
||
mbdos(SETDMA, mydma);
|
||
if (mbdos(READ, &cpmfcb) != 0) /* Have BDOS do the read*/
|
||
return (EOF);
|
||
gp = mydma; /* Initialize buffer pointer */
|
||
return ((int)*gp++ & 0xff); /* Return first character */
|
||
}
|
||
|
||
/* Routine to read the file header */
|
||
|
||
int readhdr()
|
||
{
|
||
register int n, k;
|
||
register char *p;
|
||
|
||
p = (char *) &x_hdr;
|
||
for (n = 0; n < sizeof (struct x_hdr); n++) {
|
||
if( (k = fgetch()) == EOF)
|
||
return (k);
|
||
*p++ = (char) k;
|
||
}
|
||
return (GOOD);
|
||
}
|
||
|
||
/* Routine to read the header for segment i */
|
||
|
||
int readxsg(i)
|
||
int i;
|
||
{
|
||
register int n, k;
|
||
register char *p;
|
||
|
||
p = (char *) &x_sg[i];
|
||
for(n = 0; n < sizeof (struct x_sg); n++) {
|
||
if ( (k = fgetch()) == EOF)
|
||
return (READERR);
|
||
*p++ = (char) k;
|
||
}
|
||
return (GOOD);
|
||
}
|
||
|
||
/* Routine to load segment number i */
|
||
/* This assumes that the segments occur in load order in the file, and */
|
||
/* that all initialized data and, in the case of combined I/D programs, */
|
||
/* text segments, precede all bss segments. */
|
||
|
||
/* In the case of segmented programs, the stack segment must exist, */
|
||
/* and all segments are presumed to be of maximum length. */
|
||
/* Text, data, bss, and stack lengths are sum of lengths of all such */
|
||
/* segments, and so may be bigger than maximum segment length. */
|
||
|
||
int loadseg(i)
|
||
int i;
|
||
{
|
||
register UWORD l, length; /* Total, incremental length */
|
||
register int type; /* Type of segment loaded */
|
||
register short lseg; /* logical segment index */
|
||
register XADDR phystarg; /* physical target load address */
|
||
|
||
l = x_sg[i].x_sg_len; /* number of bytes to load */
|
||
type = x_sg[i].x_sg_typ; /* Type of segment */
|
||
|
||
lseg = textseg; /* try putting in text space */
|
||
|
||
phystarg = segloc[lseg] + segsiz[lseg]; /* physical target addr */
|
||
|
||
switch (type) /* Now load data, if necessary */
|
||
/* save physical address & size */
|
||
{
|
||
case X_SG_BSS: /* BSS gets cleared by runtime */
|
||
/* startup. */
|
||
stkloc = (phystarg & 0xffff0000L) + SEGLEN - BPLEN - stksiz;
|
||
/* ...in case no stack segment */
|
||
if (bssloc == 0L) bssloc = phystarg;
|
||
bsssiz += l;
|
||
if ((segsiz[lseg] += l) >= seglim[lseg])
|
||
return (NOMEM);
|
||
return (GOOD); /* Transfer no data */
|
||
|
||
case X_SG_STK: /* Stack segment: */
|
||
if (stkloc == 0L) { /* if segmented, we now */
|
||
/* know where to put */
|
||
seglim[lseg] -= BPLEN; /* the base page */
|
||
stkloc = segloc[lseg] + seglim[lseg];
|
||
}
|
||
|
||
stkseg = lseg;
|
||
stksiz += l; /* adjust size and */
|
||
seglim[lseg] -= l; /* memory limit */
|
||
if (segsiz[lseg] >= seglim[lseg])
|
||
return (NOMEM);
|
||
return (GOOD); /* Transfer no data */
|
||
|
||
case X_SG_COD: /* Pure text segment */
|
||
case X_SG_MXU: /* Dirty code/data (better not)*/
|
||
case X_SG_MXP: /* Clean code/data (be sep I/D)*/
|
||
if (textloc == 0L) textloc = phystarg;
|
||
textsiz += l;
|
||
break;
|
||
|
||
case X_SG_CON: /* Constant (clean) data */
|
||
case X_SG_DAT: /* Dirty data */
|
||
stkloc = (phystarg & 0xffff0000L) + SEGLEN - BPLEN - stksiz;
|
||
/* ...in case no stack or */
|
||
/* bss segments */
|
||
if (dataloc == 0L) dataloc = segloc[i];
|
||
datasiz += l;
|
||
break;
|
||
}
|
||
/* Check seg overflow */
|
||
if ((segsiz[lseg] += l) >= seglim[lseg])
|
||
return (NOMEM);
|
||
/* load data from file */
|
||
|
||
/* Following loop is optimized for load speed. It knows*/
|
||
/* about three conditions for data transfer: */
|
||
/* 1. Data in read buffer: */
|
||
/* Transfer data from read buffer to target */
|
||
/* 2. Read buffer empty and more than 1 sector of data */
|
||
/* remaining to load: */
|
||
/* Read data direct to target */
|
||
/* 3. Read buffer empty and less than 1 sector of data */
|
||
/* remaining to load: */
|
||
/* Fill read buffer, then proceed as in 1 above */
|
||
|
||
while (l) /* Until all loaded */
|
||
{ /* Data in disk buffer? */
|
||
if (gp < mydma + SECLEN)
|
||
{
|
||
length = min(l, mydma + SECLEN - gp);
|
||
cpy_out(gp, phystarg, length);
|
||
gp += length;
|
||
}
|
||
else if (l < SECLEN) /* Less than 1 sector */
|
||
{ /* remains to transfer*/
|
||
length = 0;
|
||
mbdos(SETDMA, mydma);
|
||
fillbuf();
|
||
gp = mydma;
|
||
}
|
||
else /* Read full sector */
|
||
{ /* into target space */
|
||
length = SECLEN;
|
||
bdos(SETDMA, phystarg);
|
||
mbdos(READ, &cpmfcb);
|
||
}
|
||
|
||
phystarg += length;
|
||
l -= length;
|
||
}
|
||
|
||
return (GOOD);
|
||
}
|