
*********************************
*				*
*  Function 59 -- Program Load	*
*   Assembly language version	*
*				*
*        June  8, 1982		*
*				*
*********************************

.globl  _pgmld		* this routine is public

secsize = 128		* CP/M sector size

* d0 always contains the return parameter from pgmld
* d1 is the return register from local subroutines
* a0 contains the pointer to the Load Parm Block passed to pgmld

* Return parameters in d0 are:
*	00 - function successful
*	01 - insufficient memory or bad header in file
*	02 - read error on file
*	03 - bad relocation information in file


* Entry point for Program Load routine
_pgmld:
	movem.l	d1-d7/a0-a6, -(sp) * save everything, just to be safe
	move.l	60(sp),a0	* get pointer to LPB
	clr.l	d0		* start with return parm cleared
	bsr	gethdr		* get header
	tst	d0
	bne	lddone		* if unsuccessful, return
	bsr	setaddr		* set up load addresses
	tst	d0
	bne	lddone		* if unsuccessful, return
	bsr	rdtxt		* read code and data text segments into mem
	tst	d0
	bne	lddone		* if unsuccessful, return
	move.l	tstart,d7
	cmp.l	cseg,d7
	beq	noreloc
	bsr	reloc		* do relocation if necessary
noreloc:
	tst	d0
	bne	lddone
	bsr	setrtn		* set up return parameters
lddone:
	move.l	64(sp), d1
	bsr	setdma		* restore dma address
	movem.l	(sp)+,d1-d7/a0-a6
	rts

* Subroutines

readseq:
* CP/M read sequential function
	move.l	d0,-(sp)	* save return parm
	move.l	FCBPtr(a0),d1
	moveq	#20,d0		* read seq function
	trap	#2		* call bdos
	move.l	d0,d1		* return parm in d1
	move.l	(sp)+,d0
	rts


setdma:
* CP/M set dma function
	move.l	d0,-(sp)	* save return parm
	moveq	#26,d0		* set dma function
	trap	#2		* call bdos
	move.l	(sp)+,d0	* restore d0
	rts


gethdr:
* Get header into buffer in data segment
	move.l	LoAdr(a0),d1
	bsr	setdma
	bsr	readseq
	tst	d1		* read ok?
	bne	badhdr		* if no, return bad
	moveq	#18,d7
	movea.l	LoAdr(a0),a5
	movea.l	#hdr,a6
geth1:	move.w	(a5)+,(a6)+	* move header into hdr
	dbf	d7,geth1
	rts
badhdr:	moveq	#2,d0
	rts


conflict:
* input parms: d2, d3 = 4 * segment nmbr
* if segment d2/4 overlaps segment d3/4, then return 1 in d1
* else return 0 in d1
* uses d7, a2, a3
	clr.l	d1		* assume it will work
	movea.l	#cseg,a2	* a2 points to start of segment addresses
	movea.l	#csize,a3	* a3 points to start of segment lengths
	move.l	0(a2,d2),d7	* get 1st seg start
	cmp.l	0(a2,d3),d7	* is 1st seg above 2nd seg?
	bge	conf1
	add.l	0(a3,d2),d7	* yes, find top of 1st seg
	cmp.l	0(a2,d3),d7	* above start of 2nd seg?
	bgt	confbd		* if yes, we have a conflict
	rts			* else, return good
conf1:
	move.l	0(a2,d3),d7
	add.l	0(a3,d3),d7	* find top of 2nd seg
	cmp.l	0(a2,d2),d7	* above start of 1st seg?
	ble	confgd		* if no, we're ok
confbd:	moveq.l	#1,d1
confgd:	rts


trymemtp:
* entry: d2 is a segment nmbr [0..4]
* try to fit it at top of memory
* uses d3, d6, d7, a5, a6
* returns 0 in d1 if ok
	move.l	d2,d6		* d6 is loop counter for chksegs
	subq	#1,d6
	lsl	#2,d2		* multiply d2 by 4
	move.l	HiAdr(a0),d7	* top of mem to d7

chksegs:
* entry: d2 = 4 * (segment nmbr to try)
*	 d6 = (d2/4) - 1  (loop counter)
*	 d7 = address below which to try it
* check for conflicts with segments [0..d6] and low memory boundary
* return 0 in d1 if no conflicts, else d1 = 1
* uses d3, a5, a6
	movea.l	#cseg,a5
	movea.l	#csize,a6
	sub.l	0(a6,d2),d7	* subtract size of segment to try
	bclr	#0,d7		* make it even address
	move.l	d7,0(a5,d2)	* insert address in segment table
	cmp.l	LoAdr(a0),d7	* check for conflict with low memory
	blt	confbd
	clr.l	d3		* check for conflicts with 0..d6
chk1:
	bsr	conflict
	addq.l	#4,d3
	tst.l	d1		* conflict with this seg?
	dbne	d6,chk1		* if no, try next
	rts


fndseg:
* entry: d2 is a segment nmbr [0..4]
* try to fit segment d2 directly below segments 0..(d2-1)
* uses d3-d7, a5, a6
	move.l	d2,d5		* d5 is loop counter to find fit
	subq.l	#1,d5
	move.l	d5,temp
	lsl.l	#2,d2		* multiply segment by 4
	clr.l	d4		* d4 is segment to try to fit below
fnd1:
	move.l	temp,d6		* d6 is loop counter for chksegs
	movea.l	#cseg,a5
	move.l	0(a5,d4),d7	* segment address to d7
	bsr	chksegs		* check for conflicts
	addq.l	#4,d4
	tst.l	d1
	dbeq	d5,fnd1		* if conflict, try next
	rts


setaddr:
* Set up load addresses for cseg, dseg, bss, basepg, and stack
	move.w	magic,d6
	andi.w	#$fffe,d6
	cmpi.w	#$601a,d6
	bne	badadr		* if magic nmbr <> 601a or 601b, skip
	move.l	bpsize,symsize
	move.l	#256,d7
	move.l	d7,bpsize	* base page is 256 bytes
	lea	stksize,a2
	cmp	(a2),d7
	blt	set0		* if stack size < 256, set to 256
	move.l	d7,(a2)
set0:	cmpi.w	#$601b,magic
	beq	seta
	tst.w	rlbflg
	beq	set1
seta:	move.l	tstart,cseg	* if not relocatable or hdr = $601b,
	bra	set2		* cseg starts at tstart
set1:	btst	#0,Flags(a0)
	bne	sldhi
* relocatable, load low
	move.l	LoAdr(a0),d7
	add.l	#$101,d7	* leave room for base page
	bclr	#0,d7
	move.l	d7,cseg		* cseg is bottom of mem + $100 (even boundary)
	bra	set2
sldhi:
* relocatable, load high
	move.l	HiAdr(a0),d7
	sub.l	csize,d7
	sub.l	dsize,d7
	sub.l	bsize,d7
	subq.l	#4,d7
	bclr	#0,d7		* put cseg at next even address below
	move.l	d7,cseg		*  high memory - (sum of sizes)
set2:
* Cseg has been set up.  Now do dseg, bseg
	cmpi.w	#$601b,magic
	bne	set3
* if magic # = 601b, take addr from hdr
	move.l	dstart,dseg
	move.l	bstart,bseg
	bra	set4
set3:
* if short header, dseg and bseg follow cseg
	move.l	cseg,d7
	add.l	csize,d7
	addq.l	#1,d7
	bclr	#0,d7
	move.l	d7,dseg
	add.l	dsize,d7
	addq.l	#1,d7
	bclr	#0,d7
	move.l	d7,bseg
set4:
* cseg, dseg, bseg set up
* now find a place for the base page and stack
	moveq.l	#3,d2
	bsr	fndseg		* try to fit base page below cseg, dseg, bseg
	tst.l	d1
	beq	set5		* if found, skip
	moveq.l	#3,d2
	bsr	trymemtp	* else, try top of memory
	tst.l	d1
	bne	badadr		* if fail, exit
set5:	moveq.l	#4,d2
	bsr	trymemtp	* try to fit stack at top of memory
	tst.l	d1
	beq	set6		* if ok, skip
	moveq.l	#4,d2
	bsr	fndseg		* else, try to fit below other segs
	tst.l	d1
	bne	badadr
set6:
* now check all segments for conflicts with low and high memory boundaries
	movea.l	#cseg,a5
	movea.l	#csize,a6
	clr.l	d2
	moveq	#4,d3		* loop counter
set7:	move.l	0(a5,d2),d7	* get segment base
	cmp.l	LoAdr(a0),d7	* above bottom of memory?
	blt	badadr
	add.l	0(a6,d2),d7	* find top of segment
	cmp.l	HiAdr(a0),d7	* below top of memory?
	bgt	badadr
	addq.l	#4,d2		* point to next segment
	dbf	d3,set7
	rts
badadr:	moveq.l	#1,d0
	rts


movebuf:
* move (d3) bytes from the base page buffer to (a2)
* uses d6
	movea.l	basepg,a1
	move.l	#secsize,d6
	sub.w	bufbyts,d6	* address to move from =
	adda.w	d6,a1		*	(basepg) + secsize - (bufbyts)
	sub.w	d3,bufbyts	* update # bytes buffered
	bra	moveb2
moveb1:	move.b	(a1)+,(a2)+	* do the move
moveb2:	dbf	d3,moveb1
	rts


rdtxt:
* Read code and data text into memory
* during this routine, a2 is always the load address,
*		       d2 is number of bytes left to load
	moveq	#63,d7
	movea.l	LoAdr(a0),a5
	movea.l	basepg,a6
rdtxt1:	move.w	(a5)+,(a6)+	* move header sector to base page
	dbf	d7,rdtxt1
	move.w	#secsize-28,d7
	cmpi.w	#$601a,magic	* short header?
	beq	rdtxt2
	subq.w	#8,d7
rdtxt2:	move.w	d7,bufbyts	* indicate # bytes of text in buffer
	move.w	#2,loop		* do for code, data segments
	move.l	cseg,a2		* start at cseg
	move.l	csize,d2	* for csize bytes
rdtxt3:
	clr.l	d3
	move.w	bufbyts,d3
	cmp.l	d2,d3		* # bytes in buffer >= # bytes to load?
	blt	rdtxt4
	move.l	d2,d3
	bsr	movebuf		* if yes, move # bytes to load
	bra	finrd
rdtxt4:
	sub.l	d3,d2		* if no, update # bytes to load
	bsr	movebuf		* move remainder of buffer
	move.l	#secsize,d3	* d3 = secsize fo following loop
rdtxt5:
	cmp.l	d3,d2		* have at least one more full sector?
	blt	rdtxt6
	move.l	a2,d1
	bsr	setdma		* if yes, set up dma address
	bsr	readseq		* read next sector
	tst.w	d1
	bne	rdbad		* if no good, exit
	sub.l	d3,d2		* decrement # bytes to load
	adda.l	#secsize,a2	* increment dma address
	bra	rdtxt5
rdtxt6:
	tst.l	d2		* any more bytes to read?
	beq	finrd
	move.l	basepg,d1
	bsr	setdma
	bsr	readseq		* if yes, read into base page
	tst.w	d1
	bne	rdbad
	move.w	d3,bufbyts	* indicate that we've buffered a sector
	move.l	d2,d3
	bsr	movebuf		* move remainder of segment
finrd:
	move.l	dseg,a2		* set up to load data segment
	move.l	dsize,d2
	sub.w	#1,loop
	bne	rdtxt3
	move.l	bseg,a2		* clear the bss segment
	move.l	bsize,d2
	beq	rdtxt8
rdtxt7:	clr.b	(a2)+
	subq.l	#1,d2
	bne	rdtxt7
rdtxt8:	rts

rdbad:	moveq.l	#2,d0
	rts


relocword:
* relocate word at (a2) based on reloc bits at (a3)
* lsb of d2 indicates whether previous word was 1st half of long-word
	move.w	(a3)+,d7	* get relocation info
	andi.w	#7,d7		* strip off symbol table bits
	lsl	#1,d7		* multiply by 2
	jmp	2(pc,d7)

	bra	relabs
	bra	reldata
	bra	relcode
	bra	relbss
	bra	relbad
	bra	rellong
	bra	relbad
	bra	relop

relbad:	move.l	(sp)+,d0	* pop return address
	moveq	#3,d0		* return bad relocation to main routine
	rts

relabs:
relop:	bclr	#0,d2		* reset long word flag
	tst.w	(a2)+		* point to next word of segment
	rts

rellong:
	bset	#0,d2		* set long word flag
	tst.w	(a2)+		* point to next word of segment
	rts

reldata:
relbss:
relcode:
	bclr	#0,d2		* long word flag set?
	bne	relc1		* if yes, skip
	move.w	(a2),d6
	add.w	d5,d6
	move.w	d6,(a2)+
	rts

relc1:	tst.w	-(a2)		* point to first word of long
	move.l	(a2),d6
	add.l	d5,d6
	move.l	d6,(a2)+	* note that a2 points past long word
	rts


reloc:
* Modify address references of code and data segments based on relocation bits
* During this routine,
* a2 points to text file to relocate
* a3 points to relocation word in basepg
* lsb of d2 is long word flag (set on reloc type 5, reset on next word)
* d3 is # words in relocation buffer
* d4 is nmbr of words left to relocate
* d5 is relocation offset

	move.l	basepg,d1
	bsr	setdma		* we will always read into base page
* skip past the symbol table
	move.l	symsize,d7
	divu	#secsize,d7	* calculate how many sectors to skip
* note that max # symbols is 8k, which is 896 sectors of 128 bytes
	move.w	d7,d6		* d6 is nmbr sectors to skip
	swap	d7		* d7 is nmbr bytes to skip
	move.w	bufbyts,d3
	sub.w	d7,d3		* subtract bytes to skip from buffer
	bge	skip1
	addi	#secsize,d3	*if amt in buffer < # bytes to skip,
	addq	#1,d6		*  read in 1 extra sector
skip1:	move.l	basepg,a3
	adda	#secsize,a3
	suba.w	d3,a3		* set up a3 to point to buffer
	lsr	#1,d3		* d3 is nmbr words in buffer
	bra	skip3
skip2:
	bsr	readseq		* read next symbol table sector
	tst.w	d1
	bne	rdbad
skip3:	dbf	d6,skip2
* we got past symbol table
* a3, d3 are set up
	move.l	cseg,d5
	move.l	d5,a2		* relocate cseg first
	sub.l	tstart,d5	* d5 contains the relocation offset
	move.l	csize,d4	* nmbr of bytes to relocate
	move.w	#2,loop		* we're going to relocate 2 segments
reloc1:
* relocate one segment
	clr.l	d2		* clear long word flag
	lsr.l	#1,d4		* make d4 indicate # words
	bra	reloc4
reloc2:
	subq.w	#1,d3
	bpl	reloc3
	bsr	readseq		* if no more words in buffer, refill it
	tst.w	d1
	bne	rdbad
	move.l	basepg,a3
	move.w	#(secsize/2)-1,d3
reloc3:
	bsr	relocword	* relocate one word
	subq.l	#1,d4
reloc4:
	tst.l	d4		* any more to relocate in this segment?
	bne	reloc2		* if yes, do it
	move.l	dseg,a2		* else, set up for dseg
	move.l	dsize,d4
	sub.w	#1,loop
	bne	reloc1
	rts


setrtn:
* Set up the return parameters in Ld Parm Blk and Base Page
	move.l	basepg,BasPage(a0)
	move.l	stk,d7
	add.l	stksize,d7
	bclr	#0,d7
	move.l	d7,Stack(a0)
	move.l	basepg,a1
	move.l	LoAdr(a0),(a1)+
	move.l	HiAdr(a0),(a1)+
	move.l	cseg,(a1)+
	move.l	csize,(a1)+
	move.l	dseg,(a1)+
	move.l	dsize,(a1)+
	move.l	bseg,(a1)+
	move.l	bsize,(a1)
* find size of free memory after bss segment
	move.l	HiAdr(a0),d7	* d7 contains next segment above bss
	move.l	-4(a1),d6
	add.l	(a1)+,d6	* d6 points to start of free mem after bss
	movea.l	#cseg,a6	* a6 points to segment to try
	moveq	#4,d5		* try for all segments
	clr.l	bseg		*    but force bss not to appear
setb1:	cmp.l	(a6),d6		* segment above bss?
	bhi	setb2
	cmp.l	(a6),d7		* segment is above bss. Is it below previous?
	bls	setb2
	move.l	(a6),d7
setb2:	tst.l	(a6)+		* point to next segment
	dbf	d5,setb1
	sub.l	d6,d7		* diff between bss top and next segment abv
	move.l	d7,(a1)+
*			now put disk number that we loaded from into base page
	movea.l FCBPtr(a0),a2
	move.b	(a2),d0		* get disk select byte
	bne	setb3		* if not auto-select, skip
	move	#25,d0
	trap	#2		* get default disk
	addq	#1,d0		* we want it in range of 1..16
setb3:	move.b	d0,(a1)+	* move disk number into base page
	clr.l	d0		* function OK
	rts


	.bss

* offsets from start of parameter block
FCBPtr  = 0
LoAdr   = 4
HiAdr   = 8
BasPage = 12		* return parameters
Stack   = 16
Flags   = 21

hdr:
				* load file header is read into here
magic:		.ds.w	1
csize:		.ds.l	1
dsize:		.ds.l	1
bsize:		.ds.l	1
bpsize:		.ds.l	1	* symb tbl size is swapped with base page size
stksize:	.ds.l	1
tstart:		.ds.l	1
rlbflg:		.ds.w	1
dstart:		.ds.l	1
bstart:		.ds.l	1

cseg:		.ds.l	1
dseg:		.ds.l	1
bseg:		.ds.l	1
basepg:		.ds.l	1
stk:		.ds.l	1

symsize:	.ds.l	1
temp:		.ds.l	1
loop:		.ds.w	1
bufbyts:	.ds.w	1

	.end
