*****************************************************************
*								*
*		UNIX Hardware Support Routines			*
*								*
*		Perkin-Elmer 7/32, 8/32, 3220, 3240		*
*								*
*		(C) 1980, Richard Miller			*
*		    University of Wollongong			*
*								*
*****************************************************************

	entry	u
	entry	start,idle,waitloc
	entry	initf,icode,szicode
	entry	fuword,fubyte,fuiword,fuibyte
	entry	suword,subyte,suiword,suibyte
	entry	save,resume
	entry	copyin,copyiin,copyout,copyiout
	entry	clearseg,copyseg
	entry	addupc
	entry	spl0,spl1,spl4,spl5,spl6,spl7,splx
	entry	ss,oc,wd,wh,wdh,rd,rh,rdh,rel
	entry	abort
	entry	lra,lraddr
	extrn	kisa0,useg
	extrn	printf
	extrn	uisa,kisa
	extrn	memtop
	extrn	dumpsw
	extrn	clockaddr
	extrn	ksp,maxsp
	extrn	addrsw
	extrn	main
	extrn	end,edata
    ifnz	PIC
	extrn	rdpic,sytim
    endc
*
* psw bit definitions
*
ps.wait equ	x'8000'	wait state
ps.io	equ	x'4000'	immediate interrupt mask
ps.mm	equ	x'2000'	machine malfunction interrupt mask
ps.af	equ	x'1000'	arith fault interrupt mask
ps.il	equ	x'0800'	interrupt levels (8/32)
ps.rp	equ	x'0400'	memory relocation / protection
ps.sq	equ	x'0200'	system queue service mask
ps.prot equ	x'0100'	protect mode
ps.ureg equ	x'00f0'	user register set
*
* psw definitions
*
ps.user equ	ps.io+ps.mm+ps.af+ps.rp+ps.prot+ps.ureg
ps.idle equ	ps.wait+ps.io+ps.mm+ps.af+ps.rp+ps.ureg
ps.kern equ	ps.io+ps.mm+ps.af+ps.rp+ps.ureg
ps.disb equ	ps.mm+ps.af+ps.rp+ps.ureg
ps.trap equ	ps.mm+ps.af

*
* register definitions
*
r0	equ	0
r1	equ	1
r2	equ	2
r3	equ	3
r4	equ	4
r5	equ	5
r6	equ	6
r7	equ	7
r8	equ	8
r9	equ	9
r10	equ	10
r11	equ	11
r12	equ	12
r13	equ	13
r14	equ	14
r15	equ	15
rf	equ	15
sp	equ	r7

    ifz M3240
CLICK	equ	256
CSHIFT	equ	8
    else
CLICK	equ	2048
CSHIFT	equ	11
    endc

*
* per-process data area for current process - always mapped to segment f
*
u	equ	y'f0000'
usize	equ	2048/CLICK

*****************************************************************
*								*
*  start:	System Initialization				*
*								*
*	Clear bss and user memory				*
*	Establish kernel memory relocation and stack		*
*	Call 'main' to complete initialization			*
*	Enter process 1 ('init') in user mode			*
*								*
*****************************************************************

	pure
	entry	start
start	equ	*

* on entry from bootstrap loader, disable mac & interrupts

	lhi	r1,ps.ureg	disable everything
	epsr	r0,r1
    ifnz	WCS
      ifz  M3240

* initialize writeable control store to vector to illegal instruction handler

	la	r2,wcs.ill	vector to illegal instruction handler
	li	r0,x'800'	wcs starts at word 800
wcslp	lis	r1,0		transfer one word
	wdcs	r2		write to wcs
	ais	r0,1		next wcs word
	ci	r0,x'1000'	total of 2k words
	bne	wcslp
	impur
	align	adc
wcs.ill	dc	y'0e000070'	wcs branch to illegal instruction handler
	pure
      endc
    endc


* clear per-process data area and bss to zeroes

	lis	r0,0
	la	r5,end		end of kernel
	ahi	r5,CLICK-1		round up to 256 byte block
	nhi	r5,-CLICK
	la	r2,CLICK*usize(r5)	end of u area
	la	r1,edata		start of bss
clp	equ	*
	st	r0,0(r1)	clear a word
	ais	r1,4		next word
	cr	r1,r2		finished?
	bl	clp		no - repeat

* Find out how much memory is available

	lr	r1,r2		end of u area
	li	r2,y'fedcba98'	test data value
memlp	equ	*
	st	r2,0(r1)	try storing word
	c	r2,0(r1)	did it work?
	bne	memend		no - end of memory
	ahi	r1,CLICK		yes - try next 256 byte block
	c	r1,memtop	reached upper limit?
	bl	memlp		no - keep looking
memend	equ	*
	sis	r1,1		highest valid address
	st	r1,memtop	save it
	ais	r1,1
	srl	r1,CSHIFT		max memory (in 256-byte blocks)
    ifz M3240

* initialize prototype mac registers for kernel address space:

* segments 0-n: map real low-core addressess (up to n*64k)
*           f : maps per-process data area for current process
* all others  : invalid

	la	r2,kisa0	kernel seg regs
	li	r0,y'0ff00010'	prototype seg reg value
seglp	equ	*
	chi	r1,CLICK		more than full segment left?
	bm	seglast		no - set last segment
	st	r0,0(r2)	set seg reg
	ais	r2,4
	ai	r0,y'10000'	origin of next segment
	shi	r1,CLICK		memory left
	b	seglp

seglast equ	*
	sis	r1,1		last segment length - 1
	bm	segx		no partial segment - skip
	sll	r1,20		move to seg length field
	ni	r0,y'fffff'
	or	r0,r1
	st	r0,0(r2)	set seg reg
	ais	r2,4

segx	equ	*
	la	r2,useg		per-process data segment
	lr	r0,r5		address of end of kernel
	oi	r0,usize*y'100000'+x'10'	size & protection
	st	r0,0(r2)	set as segment for pda

    else

* initialize kernel segment table:

* segments 0-n: map real low-core addressess (up to n*64k)
*           f : maps per-process data area for current process
* all others  : invalid

	la	r2,kisa0	kernel seg regs
	li	r0,y'5c3e0000'	prototype seg reg: present, rd, wrt, exe, len=31
seglp	equ	*
	chi	r1,32		more than full segment left?
	bm	seglast		no - set last segment
	st	r0,0(r2)	set seg reg
	ais	r2,8
	ai	r0,512		origin of next segment (shifted <<4)
	shi	r1,32		memory left
	b	seglp

seglast equ	*
	sis	r1,1		last segment length - 1
	bm	segx		no partial segment - skip
	sll	r1,17		move to seg length field
	ni	r0,-1-y'3e0000'	remove the length field from proto
	or	r0,r1
	st	r0,0(r2)	set seg reg
	ais	r2,8

segx	equ	*
	la	r2,useg		per-process data segment
	lr	r0,r5		address of end of kernel
	srl	r0,7
	oi	r0,usize*y'20000'+y'5c000000'	size, present, rd, wrt, exe
	st	r0,0(r2)	set as segment for pda
    endc


* start kernel stack pointer at top of per process area

	la	sp,CLICK*usize+u-4
	st	sp,ksp		save sp for interrupts
	st	sp,maxsp	save as 'empty stack' ptr

* load hardware mac registers from kernel prototypes, and enable
* memory relocation / protection and interrupts

	l	r1,kisa		kernel seg regs
	bal	r6,addrsw	load into mac regs
	li	r1,ps.kern	enable mac & interrupts
	epsr	r0,r1

* call main() c routine to complete initialization

	bal	rf,main

* on return from main, enter 'user state' at user address 0 to exec
*  init process

	epsr	r3,r3		disable the mac
	nhi	r3,x'ffff'-ps.rp
	epsr	r0,r3
	l	r1,uisa		user seg regs
	bal	r6,addrsw	switch address space
	lhi	r0,ps.user	user state psw
	lis	r1,0		address 0 in user space
	lpswr	r0		enter process 1

* Bootstrap program to load in init process:
*
*	exec("/etc/init", "/etc/init");
*	for (;;)
*		;

	align	adc
icode	equ	*
	svc	0,11		* exec *
	dc	a(initf-icode)
	dc	a(initp-icode)
	b	*		didn't work -- loop forever
initp	dc	a(initf-icode)
	dc	0
initf	db	c'/etc/init',0
* extra patch space in case of emergency
	db	0,0,0,0,0,0,0,0,0,0
	align	adc		must be even words for copyout
szicode	dc	*-icode		size of icode

*****************************************************************
*								*
* dump:		Memory dump to disk or magtape			*
*								*
*	Save register sets 0 and F so they will appear in dump	*
*	Call (*dumpsw)() to dump all of memory			*
*								*
*****************************************************************

	pure
	entry	dump
dump	equ	*
	lpsw	dump.ps1	reg set F, disabled
dump1	stm	r0,uregs	save reg set F
	lpsw	dump.ps2	reg set 0, disabled
dump2	stm	r0,eregs	save reg set 0

dloop	equ	*
	l	sp,memtop	start stack pointer at top of physical memory
	ni	sp,-4
	l	r1,dumpsw	address of dump routine
	balr	rf,r1
	lpsw	dump.ps3	wait
dump3	b	dloop		if restarted, dump again

	align	adc
dump.ps1 dc	y'00f0',dump1
dump.ps2 dc	y'0000',dump2
dump.ps3 dc	y'8000',dump3

	impur
	entry	uregs,eregs
uregs	das	16
eregs	das	16

*****************************************************************
*								*
* display:	Display Panel Driver  ( 7/32 and 8/32 only )	*
*								*
* Called by clock() at each tick to read display switch		*
* register, and display value addressed by csw on display panel	*
*								*
*****************************************************************

	pure
	entry	display,csw
display	equ	*
    ifz M3200
*
* read console switch register
*
	lis	r1,1		display address always 01
	oc	r1,dis.norm	set 'normal' mode
	rhr	r1,r2		read halfword
	exbr	r2,r2		reverse bytes
	st	r2,csw		save for getswit()
*
* if switches are zero, display pc
*
	lr	r2,r2		zero?
	bnz	disp.ad		no - use as address
	l	r2,0(sp)	yes - display parm (pc)
	b	disp.wr
*
* use switch contents as an address in kernel space
*
disp.ad equ	*
	thi	r2,1		odd address ?
	bz	disp.ev		no - skip
	ai	r2,y'10000'	yes - use segment 1
disp.ev equ	*
	ni	r2,-4		make sure of word boundary
	l	r2,0(r2)	load value
*
* display *(csw) on display panel
*
disp.wr equ	*
	oc	r1,dis.incr	set 'incremental' mode
	exbr	r2,r2		reverse first two bytes
	whr	r1,r2		write lower two bytes
	exhr	r2,r2		get upper two bytes
	exbr	r2,r2		reverse them
	whr	r1,r2		write upper two bytes
    endc
	br	rf		return
*
* commands for display panel
*
dis.norm	db	x'80'	set normal mode
dis.incr	db	x'40'	set incremental mode
*
* saved switch value
*
	impur
	align	adc
csw	dc	0

*****************************************************************
*								*
* clkstart:	Clock Initialization				*
*								*
* Called only once, by main(), to enable interrupts from the	*
* precision clock, which generates interrupts every 10 ms.	*
*								*
*****************************************************************


	pure
	entry	clkstart
clkstart equ	*
	lb	r1,clockaddr	pic address
	oc	r1,clk.disb	disarm interrupts
clkloop	wh	r1,clk.intv	set interval
	ssr	r1,r0		check overflow
	btc	8,clkloop	if overflow, do it again
	oc	r1,clk.enab	enable clock interrupts
	br	rf		return
clk.intv	dc	x'2000'+z(1000)		interval = 10us * 1000
clk.disb	db	x'c0'		disarm
clk.enab	db	x'60'		enable + start

*****************************************************************
*								*
*	Inter-address-space Transfer Routines			*
*								*
* fubyte(i) - fetch a byte from user address <i>		*
* fuword(i) - fetch a word from user address <i>		*
* subyte(i,a) - store byte <a> at user address <i>		*
* suword(i,a) - store word <a> at user address <i>		*
*								*
*   - if <i> is an illegal address (or write-protected for	*
*     subyte or suword) the routine returns (-1)		*
*								*
*****************************************************************

	pure
fubyte	equ	*
fuibyte	equ	*
	l	r1,0(sp)	user address
	bal	r6,lrau		relocate
	btc	x'c',illaddr	illegal address
	lb	r0,0(r1)	fetch byte
	br	rf

fuword	equ	*
fuiword	equ	*
	l	r1,0(sp)	user address
	bal	r6,lrau		relocate
	btc	x'c',illaddr	illegal address - exit
	lhl	r0,0(r1)	fetch word as 2 halfwords
	exhr	r0,r0		... in case not on word boundary
	lhl	r1,2(r1)	... (8/32)
	or	r0,r1
	br	rf

subyte	equ	*
suibyte	equ	*
	l	r1,0(sp)	user address
	bal	r6,lrau		relocate
	btc	x'e',illaddr	illegal or protected address - exit
	l	r0,4(sp)	byte to be stored
	stb	r0,0(r1)	store it
	lis	r0,0		normal return
	br	rf

suword	equ	*
suiword	equ	*
	l	r1,0(sp)	user address
	bal	r6,lrau		relocate
	btc	x'e',illaddr	illegal or write protected - exit
	l	r0,4(sp)	word to be stored
	sth	r0,2(r1)	store it as 2 halfwords
	exhr	r0,r0		... in case not on word boundary
	sth	r0,0(r1)	... (8/32)
	lis	r0,0	normal return
	br	rf

* copyin (uaddr, kaddr, n)
* copyout (kaddr, uaddr, n)
*	- copy <n> bytes from/to user address <uaddr> to/from kernel
*	  address <kaddr>

* - <n> must be a multiple of wordsize

copyin	equ	*
copyiin	equ	*
	l	r1,0(sp)	user source address
	bal	r6,lrau
	btc	x'c',illaddr	illegal address - exit
	l	r2,4(sp)	kernel target address
	b	copy

copyout	equ	*
copyiout equ	*
	l	r1,4(sp)	user target address
	bal	r6,lrau
	btc	x'e',illaddr	illegal address - exit
	lr	r2,r1
	l	r1,0(sp)	kernel source address
copy	equ	*
	l	r4,8(sp)	no. of bytes
	sis	r4,4		start at last word
copylp	equ	*
	l	r0,0(r1,r4)	move a word
	st	r0,0(r2,r4)
	sis	r4,4		back up
	bnm	copylp		repeat

	lis	r0,0
	br	rf

* Illegal or write-protected address - return (-1)

illaddr	equ	*
	lcs	r0,1	return -1
	br	rf

* clearseg(i) : clear block number <i> in kernel space to zeroes

clearseg	equ	*
	l	r1,0(sp)	block no.
	sll	r1,CSHIFT		block origin address
	lis	r0,0
	lhi	r2,CLICK-4	start at last word
clearlp	equ	*
	st	r0,0(r1,r2)	clear a word
	sis	r2,4		back up
	bnm	clearlp		repeat
	br	rf

* copyseg (i,j) : copy kernel block number <i> into kernel block <j>

copyseg	equ	*
	l	r2,4(sp)	target block no.
	sll	r2,CSHIFT		block origin
	l	r1,0(sp)	source block no.
	sll	r1,CSHIFT
	lhi	r3,CLICK-4	start at last word
copyslp	equ	*
	l	r0,0(r1,r3)	move a word
	st	r0,0(r2,r3)
	sis	r3,4		back up
	bnm	copyslp		repeat
	lis	r0,0
	br	rf

*****************************************************************
*								*
*	User - kernel Address Space Mapping Routines		*
*								*
*								*
*****************************************************************

* lraddr - C interface to lra

* lraddr(aaddr, seg)
* char **addr;		/* pointer to unrelocated address */
* int seg[];		/* pointer to segment table descriptor */
*
*	- replaces *addr by relocated address
*	- returns condition code from lra instruction

lraddr	equ	*
	l	r5,0(sp)	pointer to virtual addr
	l	r1,0(r5)	virtual address
	l	r2,4(sp)	pointer to seg regs
	bal	r6,lra		relocate
	st	r1,0(r5)	change virtual to real address
    ifnz	LRA!M3200
	epsr	r0,r0		real lra doesn't leave psw in r0
    endc
	nhi	r0,15		isolate cond code in new psw
	br	rf

* lra: load 'real' address
*  input:	r1 - address in user program space
*		r2 - address of segmentation regs
*  output:	r1 - corresponding address in kernel space
*		cc - condition code set as in real lra instruction
* uses:		r0

* lrau : entry point using user seg regs (uisa)

lrau	equ	*
	l	r2,uisa		use uisa for seg regs
lra	equ	*
    ifz M3240
	l	r2,0(r2)	extra level of indirection
	ni	r2,y'fffff'
    endc

    ifnz	LRA!M3200
	lra	r1,0(r2)	use hardware translation
	br	r6
    else
	lr	r0,r1		save input addr
	srl	r1,14		seg no. * 4
	ni	r1,x'3c'	clear extra bits
	l	r1,0(r1,r2)	user seg reg
	thi	r1,x'10'	check 'present' bit
	bz	lrabad		zero - illegal address
	lr	r2,r1
	srl	r2,12		seg limit * 256
	ahi	r2,x'100'	seg length in bytes
	ni	r2,y'fff00'	clear extra bits
	ni	r0,y'ffff'	offset in segment
	cr	r0,r2		within range?
	bnl	lrabad		no - illegal address
	lis	r2,0
	thi	r1,x'60'		write-protected?
	bz	lranwp		no - skip
	lis	r2,2		set G bit in cc
lranwp	equ	*
	ni	r1,y'fff00'	segment origin
	ar	r1,r0		add offset
	b	lraset
* unmapped address
lrabad	equ	*
	lis	r2,8		set C bit
* set condition code
lraset	equ	*
	epsr	r0,r0		current psw
	nhi	r0,-16		mask off old cond code
	or	r0,r2		set new cond code
	epsr	r2,r0		set new psw
	br	r6
    endc


*****************************************************************
*								*
*		Kernel Process-switching Routines		*
*								*
*								*
*****************************************************************

* save(a)
*	- save current kernel process stack environment
*	  in location <a>

save	equ	*
	l	r1,0(sp)	location
	stm	sp,0(r1)	save context
	lis	r0,0		return zero
	br	rf

* resume(p, a)
*	- reset kernel seg reg f to map to block <a>, thus
*	  switching the 'current process'
*	- restore previously-saved kernel process stack environment
*	  from address <a> in new process


    ifz M3240
resume	equ	*
	l	r3,4(sp)	address of saved regs
	si	r3,u		offset in u segment
	l	r0,0(sp)	block number for u segment
	sll	r0,CSHIFT		convert to address
	ar	r3,r0		physical address of saved regs
	oi	r0,usize*y'100000'+x'10'	length = usize
    else
resume	equ	*
	l	r0,0(sp)	block number for u segment
	lr	r3,r0
	sll	r3,CSHIFT		get phys address
	a	r3,4(sp)	address of saved regs
	si	r3,u		offset in u segment
	sll	r0,4		relocation field
	oi	r0,usize*y'20000'+x'5c000000'	length = usize
    endc

	li	r1,ps.disb-ps.rp	disable the mac
	epsr	r2,r1
	lm	sp,0(r3)	restore environment
	st	r0,useg		set new kernel ppd segment

* switch to new ppd segment

	l	r1,kisa		set new seg regs into 
	bal	r6,addrsw	hardware mac regs
	epsr	r0,r2		enable mac again
	br	rf		NOTE: r0 is nonzero

* addupc (pc, prof, n)
* struct \(
*   int base;  int length;  int offset;  int scale;
* \) *prof;
*
*  - called from clock interrupt handler to update user execution profile

addupc	equ	*
	l	r4,4(sp)	base of prof
	l	r3,0(sp)	current program counter
	s	r3,8(r4)	subtract offset
	bmr	rf		< 0 : not within buffer

* scale pc into buffer

	m	r2,12(r4)	scale pc
	ais	r2,1		round to fullword
	slls	r2,1
	nhi	r2,x'fffc'
	c	r2,4(r4)	within buffer?
	bnlr	rf		no : return
	a	r2,0(r4)	address = base + offset

* get physical address of buffer location

	lr	r1,r2		program address
	bal	r6,lrau		get physical address
	btc	x'e',incupcx	illegal or write-protected -- error

* increment profile counter

	l	r0,8(sp)	increment
	am	r0,0(r1)	increment counter in buffer
	br	rf

* memory fault -- turn off profiling

incupcx equ	*
	lis	r0,0		zero prof scale
	st	r0,12(r4)	to turn off profiling
	br	rf

*****************************************************************
*								*
*		PSW Manipulation Routines			*
*								*
*****************************************************************

* splN()
*  - sets 'processor level' to N (i.e. only devices with 
*    priority >N may interrupt
*  - returns previous psw status
*
* splx(p)
*  - restores psw status to <p>
*
*  Note:
*    As currently implemented, spl0 enables interrupts, and any
*    nonzero processor level disables all interrupts.  Eventually
*    the PDP-11 processor level mechanism should be emulated more
*    closely, probably by using Interdata system queue.

spl1	equ	*
spl4	equ	*
spl5	equ	*
spl6	equ	*
spl7	equ	*
	epsr	r1,r1		current psw status
	nhi	r1,x'ffff'-ps.io	mask off immediate interrupts
	epsr	r0,r1		load new psw status
	br	rf

spl0	equ	*
	epsr	r1,r1		current psw status
	ohi	r1,ps.io	turn on immediate interrupts
	epsr	r0,r1		load new psw status
	br	rf

splx	equ	*
	l	r1,0(sp)	new psw status
	epsr	r0,r1		set in psw
	br	rf

* idle():	go into enabled wait state

waitloc	dcf	a(wait)
idle	equ	*
    ifnz	PIC
	bal	r6,rdpic	read and update system time
	am	r5,sytim
    endc
	li	r1,ps.idle	load 'wait' psw
	epsr	r0,r1
wait	equ	*
	epsr	r1,r0		restore previous psw
	br	rf		return to caller


*****************************************************************
*								*
*		I/O Instruction Routines			*
*		Perkin-Elmer 7/32, 8/32, 3220, 3240		*
*								*
*****************************************************************

ss	equ	*	sense status
	l	r1,0(sp)
	ssr	r1,r0
	br	rf

oc	equ	*	output command
	l	r1,0(sp)
	lb	r0,7(sp)	*** do ocr instead of oc
	ocr	r1,r0		*** because of timing bug on 7/32
	br	rf

wd	equ	*	write data
	l	r1,0(sp)
	wd	r1,7(sp)
	br	rf

wh	equ	*	write halfword
	l	r1,0(sp)
	wh	r1,6(sp)
	br	rf

wdh	equ	*	write 3 bytes
	l	r1,0(sp)
	wd	r1,5(sp)
	wh	r1,6(sp)
	br	rf

rd	equ	*	read data
	l	r1,0(sp)
	rdr	r1,r0
	br	rf

rh	equ	*	read halfword
	l	r1,0(sp)
	rhr	r1,r0
	br	rf

rdh	equ	*	read 3 bytes
	l	r1,0(sp)
	rdr	r1,r2
	rhr	r1,r0
	exhr	r2,r2
	or	r0,r2
	br	rf

rel	equ	*	read error logger
    ifz  M3200
	lcs	r0,1
	br	rf
    else
	l	r2,0(sp)
	thi	r2,x'1000'		* test bank ?
	bnz	relchk
	psf	r0,0(r2)		* rel r2
	lr	r0,r3
	nhi	r0,x'ffff'
	br	rf

relchk	equ	*
	psf	0,0(r2)			* rel r2
	bm	relchk1			* at least 1 error
	lis	r0,0
	br	rf

relchk1	equ	*
	lis	r0,1			* errors
	br	rf
    endc


abort	equ	*			* stop dead - like panic, but no update
	bal	15,printf		* print state
	dc	x'8800'			* brk

	end	start
