;
; netprint.device
;
; Timo Rossi, 1995
;

;DEBUG		set	1

		include	'netprint_dev.i'

		section	DeviceCode,CODE

SafeExit	moveq	#-1,d0
		rts

DevRomTag	dc.w	RTC_MATCHWORD	;rt_MatchWord (ILLEGAL)
		dc.l	DevRomTag	;rt_MatchTag
		dc.l	DevSectionEnd	;rt_EndSkip
		dc.b	RTF_AUTOINIT	;rt_Flags
		dc.b	DevVersion	;rt_Version
		dc.b	NT_DEVICE	;rt_Type
		dc.b	0		;rt_Pri
		dc.l	DevName		;rt_Name
		dc.l	DevIDString	;rt_IDString
		dc.l	DevInit		;rt_Init

DevVersion	equ	1
DevRevision	equ	0

DevName		DEVNAME
		dc.b	'$VER: '
DevIDString	dc.b	'netprint 1.0 (05.01.95)',CR,LF,0

utility_name	dc.b	'utility.library',0
dos_name	dc.b	'dos.library',0
process_name	dc.b	'NetPrint_Process',0
default_dir	dc.b	'T:',0
filename_fmt	dc.b	'netprint-%04lx',0
lpr_cmd		dc.b	'AmiTCP:bin/lpr '
lpr_cmd_len	equ	*-lpr_cmd

		EVEN

;
; The RomTag for this device has the AUTOINIT flag set, so DevInit contains
; parameters for MakeLibrary()
;
DevInit		dc.l	DevBase_SIZE
		dc.l	DevFuncInit
		dc.l	DevStructInit
		dc.l	DevInitRoutine

devfunc		macro
		dc.w	Dev\1-DevFuncInit
		endm

DevFuncInit	dc.w	-1	;relative vectors
		devfunc	Open
		devfunc	Close
		devfunc	Expunge
		devfunc	Reserved
		devfunc	BeginIO
		devfunc	AbortIO
		dc.w	-1

DevStructInit	INITBYTE LN_TYPE,NT_DEVICE
		INITBYTE LIB_FLAGS,LIBF_CHANGED!LIBF_SUMUSED
		INITLONG LN_NAME,DevName
		INITLONG LIB_IDSTRING,DevIDString
		INITLONG LIB_VERSION,((DevVersion<<16)|DevRevision)
		dc.l	0

;
; d0 - device base
; a0 - seglist
; a6 - execbase
;
; return d0: device base (success) or zero (failure)
;
DevInitRoutine
		ifd	DEBUG
		DMSG	<'Device init entry',LF>
		endc

		move.l	a4,-(sp)
		move.l	d0,a4
		move.l	a0,dv_SegList(a4)
		move.l	a6,dv_SysBase(a4)

;
; Allocate message port for inter-process communication
; The dos process sets up MP_SIGBIT, MP_SIGTASK & MP_FLAGS
;
		moveq	#MP_SIZE,d0
		move.l	#MEMF_CLEAR!MEMF_PUBLIC,d1
		libcall	AllocMem
		move.l	d0,dv_MsgPort(a4)
		beq	dev_init_end

		ifd	DEBUG
		DMSG	<'Allocated message port at 0x%lx',LF>
		endc

		move.l	d0,a0
		move.b	#PA_IGNORE,MP_FLAGS(a0)
		lea	MP_MSGLIST(a0),a0
		NEWLIST	a0

;
; Open utility library
;
		lea	utility_name(pc),a1
		moveq	#37,d0
		libcall	OpenLibrary
		move.l	d0,dv_UtilityBase(a4)
		beq	dev_init_fail1

;
; Open DOS library
;
		lea	dos_name(pc),a1
		moveq	#37,d0			;need at least kick 2.04
		libcall	OpenLibrary
		move.l	d0,dv_DosBase(a4)
		beq	dev_init_fail2

;
; Create the process for DOS file I/O
;
		movem.l	a5/a6,-(sp)
		move.l	d0,a6		;DosBase
		move.l	sp,a5

		clr	-(sp)			;end of tag list

		pea	process_start(pc)	;process entry point
		move.l	#NP_Entry,-(sp)

		pea	1
		move.l	#NP_Priority,-(sp)

		pea	process_name(pc)
		move.l	#NP_Name,-(sp)

		move.l	sp,d1			;taglist ptr
		libcall	CreateNewProc

		move.l	a5,sp
		movem.l	(sp)+,a5/a6
		move.l	d0,dv_Process(a4)
		beq	dev_init_fail3

		ifd	DEBUG
		DMSG	<'Created process, ptr at 0x%lx',LF>
		endc

;
; Send a startup message to the DOS process
;
		move.l	d0,a0
		lea	pr_MsgPort(a0),a0
		lea	dv_InitMsg(a4),a1
		move.l	a4,LN_NAME(a1)
		libcall	PutMsg

		move.l	a4,d0

dev_init_end	move.l	(sp)+,a4
		rts

dev_init_fail3	move.l	dv_UtilityBase(a4),a1
		libcall	CloseLibrary

dev_init_fail2	move.l	dv_DosBase(a4),a1
		libcall	CloseLibrary

dev_init_fail1	move.l	dv_MsgPort(a4),a1
		move.l	#MP_SIZE,d0
		libcall	FreeMem
		moveq	#0,d0
		bra.b	dev_init_end

;

;
; a6 - device base
; a1 - iorequest
; d0 - unit number
; d1 - flags
;
; return d0: zero(success) or error code
;
DevOpen		movem.l	a2/a3/a5/a6,-(sp)
		move.l	a1,a2
		move.l	a6,a5

		ifd	DEBUG
		DMSG	<'Device open',LF>
		endc

		move.l	#DevUnit_SIZE,d0
		move.l	#MEMF_CLEAR!MEMF_PUBLIC,d1
		move.l	dv_SysBase(a6),a6
		libcall	AllocMem
		move.l	d0,IO_UNIT(a2)
		beq	dev_open_fail1
		move.l	d0,a3

		ifd	DEBUG
		DMSG	<'Allocated unit at 0x%lx',LF>
		endc

		move.l	dv_UtilityBase(a5),a6
		libcall	GetUniqueID

		movem.l	d0/a2/a3,-(sp)
		lea	du_FileName(a3),a3
		move.l	dv_SysBase(a5),a6
		lea	filename_fmt(pc),a0
		move.l	sp,a1
		lea	put_ch(pc),a2
		libcall	RawDoFmt
		movem.l	(sp)+,d0/a2/a3

		lea	du_FileName(a3),a0

		ifd	DEBUG
		move.l	a0,d0
		DMSG	<'Temp filename is "%s"',LF>
		endc

		move.l	a0,d1
		moveq	#DOSCMD_OPEN,d0
		move.l	a5,a6
		bsr	SendDosRequest
		move.l	d0,du_FileHandle(a3)
		beq	dev_open_fail2

		add.w	#1,LIB_OPENCNT(a5)
		bclr	#LIBB_DELEXP,LIB_FLAGS(a5)

		moveq	#0,d0

dev_open_exit	movem.l	(sp)+,a2/a3/a5/a6
		rts

dev_open_fail2	move.l	a3,a1
		move.l	#DevUnit_SIZE,d0
		move.l	dv_SysBase(a5),a6
		libcall	FreeMem

dev_open_fail1	moveq	#IOERR_OPENFAIL,d0
		bra.b	dev_open_exit

put_ch		move.b	d0,(a3)+
		rts

;
; a6 - device base
; a1 - iorequest
;
; return d0: zero (normal) or seglist (expunged)
;
DevClose	movem.l	a2/a3/a4,-(sp)
		move.l	a1,a2
		move.l	IO_UNIT(a2),a3

		ifd	DEBUG
		DMSG	<'Device close',LF>
		endc

		moveq	#DOSCMD_CLOSE,d0
		move.l	du_FileHandle(a3),d1
		bsr	SendDosRequest

		tst.l	du_BytesWritten(a3)
		beq.b	del_file		;don't try to print empty files

		lea	lpr_cmd(pc),a0
		lea	du_FileName(a3),a4
		lea	-lpr_cmd_len(a4),a1
1$		move.b	(a0)+,(a1)+
		cmp.l	a4,a1
		bcs.b	1$

		ifd	DEBUG
		lea	du_FileName-lpr_cmd_len(a3),a0
		move.l	a0,d0
		DMSG	<'Executing command: "%s"',LF>
		endc

		moveq	#DOSCMD_EXECUTE,d0
		lea	du_FileName-lpr_cmd_len(a3),a0
		move.l	a0,d1
		bsr	SendDosRequest

del_file	moveq	#DOSCMD_DELETE,d0
		lea	du_FileName(a3),a0
		move.l	a0,d1
		bsr	SendDosRequest

		move.l	a6,-(sp)
		move.l	dv_SysBase(a6),a6
		move.l	a3,a1
		move.l	#DevUnit_SIZE,d0
		libcall	FreeMem
		move.l	(sp)+,a6

		moveq	#-1,d0
		move.l	d0,IO_DEVICE(a2)
		move.l	d0,IO_UNIT(a2)
		subq.w	#1,LIB_OPENCNT(a6)
		bne.s	2$
		btst	#LIBB_DELEXP,LIB_FLAGS(a6)
		beq.s	2$
		bsr.b	DevExpunge
		bra.b	dev_close_exit

2$		moveq	#0,d0

dev_close_exit	movem.l	(sp)+,a2/a3/a4
		rts

;
; a6 - device base
;
; return d0: seglist (expunged) or zero (still open)
;
DevExpunge
		ifd	DEBUG
		DMSG	<'Device expunge',LF>
		endc

		movem.l	d2/a2/a6,-(sp)
		move.l	a6,a2
		move.l	dv_SysBase(a2),a6

		tst.w	LIB_OPENCNT(a2)
		beq.s	remove_device

		bset	#LIBB_DELEXP,LIB_FLAGS(a2)
		moveq	#0,d0
		bra.s	expunge_end

remove_device	move.l	dv_SegList(a2),d2
		move.l	a2,a1
		libcall	Remove

;
; terminate the DOS process
; (this could fail in extremely low-memory conditions, or if all signal
; bits are in use. however, there isn't much we can do for it)
;
		moveq	#DOSCMD_QUITPROC,d0
		move.l	a2,a6
		bsr	SendDosRequest

		move.l	dv_MsgPort(a2),a1
		move.l	#MP_SIZE,d0
		move.l	dv_SysBase(a2),a6
		libcall	FreeMem

		move.l	dv_DosBase(a2),a1
		libcall	CloseLibrary

		move.l	dv_UtilityBase(a2),a1
		libcall	CloseLibrary

;
; free the device base/jump table memory block
;
		move.l	a2,a1
		moveq	#0,d0
		move.w	LIB_NEGSIZE(a2),d0
		sub.l	d0,a1
		moveq	#0,d1
		move.w	LIB_POSSIZE(a2),d1
		add.l	d1,d0
		libcall	FreeMem
		move.l	d2,d0

expunge_end
		movem.l	(sp)+,d2/a2/a6
		rts

DevReserved	moveq	#0,d0
		rts

;
; a6 - device base
; a1 - iorequest
;
DevBeginIO	movem.l	a2/a3,-(sp)
		move.l	a1,a2
		move.b	#NT_MESSAGE,LN_TYPE(a2)
		move.l	IO_UNIT(a2),a3
		moveq	#0,d0
		move.b	IO_COMMAND+1(a2),d0

		ifd	DEBUG
		DMSG	<'BeginIO, command = 0x%lx',LF>
		endc

		cmp.b	#DEV_NUMCMD,d0
		bcc.s	invalid_cmd

		clr.b	IO_ERROR(a2)

;
; a2 - iorequest
; a3 - unit
; a6 - device
; d0.l - device command
;
PerformIO	add.l	d0,d0
		lea	DevCmdTable(pc),a0
		add.w	0(a0,d0.l),a0
		jsr	(a0)

beginio_end	movem.l	(sp)+,a2/a3
		rts

invalid_cmd	bsr.s	DevCmd_Invalid
		bra.s	beginio_end

;
; a6 - device base
; a1 - iorequest
;
DevAbortIO	;AbortIO() simply returns an error...
		moveq	#IOERR_NOCMD,d0
		rts

;
; invalid commands
;
DevCmd_Invalid
DevCmd_Reset
DevCmd_Read
DevCmd_Start
DevCmd_Stop
DevCmd_Flush
DevCmd_Query
		move.b	#IOERR_NOCMD,IO_ERROR(a1)
		bra.s	TermIO

;
; commands that do nothing
;
DevCmd_Clear
DevCmd_Update	clr.b	IO_ERROR(a1)
		clr.l	IO_ACTUAL(a1)

;
; a6 - device base
; a3 - unit
; a1 - iorequest
;
TermIO		btst	#IOB_QUICK,IO_FLAGS(a1)
		bne.s	termio_exit
		move.l	a6,-(sp)
		libcall	dv_SysBase(a6),ReplyMsg
		move.l	(sp)+,a6
termio_exit	rts

dev_cmd		macro
		dc.w	DevCmd_\1-DevCmdTable
		endm

DevCmdTable	dev_cmd	Invalid
		dev_cmd	Reset
		dev_cmd	Read
		dev_cmd	Write
		dev_cmd	Update
		dev_cmd	Clear
		dev_cmd	Start
		dev_cmd	Stop
		dev_cmd	Flush
		dev_cmd	Query
DEV_NUMCMD	equ	(*-DevCmdTable)/2

;
; the actually working device commands
;
DevCmd_Write
		ifd	DEBUG
		DMSG	<'DevCmd_Write',LF>
		endc

		movem.l	d2/d3,-(sp)
		tst.l	du_BytesWritten(a3)
		bne.b	do_write1
		move.l	IO_LENGTH(a2),d0
		beq.b	write_act1
		move.l	IO_DATA(a2),a0
		cmp.b	#$1b,(a0)	;ESC?
		beq.b	do_write1

		moveq	#DOSCMD_WRITE,d0
		move.l	du_FileHandle(a3),d1
		lea	dummy_esc_seq(pc),a0
		move.l	a0,d2
		moveq	#dummy_esc_seq_len,d3
		bsr	SendDosRequest

do_write1	moveq	#DOSCMD_WRITE,d0
		move.l	du_FileHandle(a3),d1
		move.l	IO_DATA(a2),d2
		move.l	IO_LENGTH(a2),d3
		bsr	SendDosRequest
		movem.l	(sp)+,d2/d3
write_act1	move.l	d0,IO_ACTUAL(a2)
		bpl.b	1$
		move.b	#1,IO_ERROR(a2)		;error code?
		bra.b	2$
1$		add.l	d0,du_BytesWritten(a3)
2$		move.l	a2,a1
		bra	TermIO

dummy_esc_seq	dc.b	$1b,"(8U"
dummy_esc_seq_len	equ	*-dummy_esc_seq

;
; Send a request to the DOS process
;
; Entry:
;	D0 - command
;	D1..D3 - arguments
;	A6 - device base
;
; Exit:
;	D0: -1 if sending failed, else return code from DOS process
;
SendDosRequest	movem.l	d4/d5/a2/a5/a6,-(sp)
		move.l	d0,d4
		move.l	d1,d5

		move.l	a6,a5
		move.l	dv_SysBase(a6),a6

		ifd	DEBUG
		DMSG	<'SendDosRequest -- '>
		endc

		moveq	#DosCmd_SIZE,d0
		move.l	#MEMF_CLEAR!MEMF_PUBLIC,d1
		libcall	AllocMem
		tst.l	d0
		beq	sendreq_fail1
		move.l	d0,a2

		moveq	#-1,d0
		move.l	d0,dcmd_Res(a2)		
		libcall	AllocSignal
		move.b	d0,dcmd_ReplyPort+MP_SIGBIT(a2)
		bmi	sendreq_freemsg

		suba.l	a1,a1
		libcall	FindTask
		move.l	d0,dcmd_ReplyPort+MP_SIGTASK(a2)

		lea	dcmd_ReplyPort(a2),a0
		move.l	a0,MN_REPLYPORT(a2)
		move.w	#dcmd_ReplyPort,MN_LENGTH(a2)

		lea	dcmd_ReplyPort+MP_MSGLIST(a2),a0
		NEWLIST	a0

		move.l	d4,dcmd_Command(a2)
		move.l	d5,dcmd_Args(a2)
		movem.l	d2/d3,dcmd_Args+4(a2)

		ifd	DEBUG
		movem.l	d2/d3,-(sp)
		move.l	dcmd_Command(a2),d0
		movem.l	dcmd_Args(a2),d1/d2/d3
		DMSG	<'Cmd = 0x%lx, Args: 0x%lx 0x%lx 0x%lx',LF>
		movem.l	(sp)+,d2/d3
		endc

		move.l	dv_MsgPort(a5),a0
		move.l	a2,a1
		libcall	PutMsg
		lea	dcmd_ReplyPort(a2),a0
		libcall	WaitPort

		moveq	#0,d0
		move.b	dcmd_ReplyPort+MP_SIGBIT(a2),d0
		libcall	FreeSignal

sendreq_freemsg	move.l	dcmd_Res(a2),-(sp)
		move.l	a2,a1
		moveq	#DosCmd_SIZE,d0
		libcall	FreeMem
		move.l	(sp)+,d0

sendreq_end	movem.l	(sp)+,d4/d5/a2/a5/a6
		rts

sendreq_fail1	moveq	#-1,d0
		bra.b	sendreq_end

;
; The DOS process
;
process_start
		ifd	DEBUG
		DMSG	<'Device process started',LF>
		endc

		suba.l	a1,a1
		move.l	4,a6			;AbsExecBase
		libcall	FindTask		;find out process base
		move.l	d0,a3
		lea	pr_MsgPort(a3),a0
		libcall	WaitPort		;wait for startup message

		ifd	DEBUG
		DMSG	<'Got startup message',LF>
		endc

		move.l	d0,a1
		move.l	d0,a2
		libcall	Remove			;remove the message from port
		move.l	LN_NAME(a2),a5		;get device base address
		move.l	dv_DosBase(a5),d7	;get DosBase
		move.l	dv_MsgPort(a5),a5	;get message port addr.

		exg	d7,a6
		lea	default_dir(pc),a0
		move.l	a0,d1
		moveq	#SHARED_LOCK,d2
		libcall	Lock
		move.l	d0,d1
		beq.b	1$
		libcall	CurrentDir
1$		move.l	d0,-(sp)
		exg	d7,a6

		move.l	a3,MP_SIGTASK(a5)	;setup message port
		moveq	#-1,d0
		libcall	AllocSignal		;this really can't fail
		move.b	d0,MP_SIGBIT(a5)
		clr.b	MP_FLAGS(a5)		;set flags to PA_SIGNAL

		bra.b	process_msgloop

process_wait	moveq	#0,d0
		move.b	MP_SIGBIT(a5),d1
		bset	d1,d0
		libcall	Wait

process_msgloop	move.l	a5,a0
		libcall	GetMsg
		tst.l	d0
		beq.b	process_wait
		move.l	d0,a4

		move.l	dcmd_Command(a4),d0

		ifd	DEBUG
		movem.l	dcmd_Args(a4),d1/d2/d3
		DMSG	<'Process: Got command 0x%lx, Args 0x%lx 0x%lx 0x%lx',LF>
		endc

		moveq	#DOSCMD_LAST,d1
		cmp.l	d1,d0
		bhi.b	process_err

		add.l	d0,d0
		lea	DosCmdTable(pc),a0
		add.w	0(a0,d0.l),a0
		jmp	(a0)

process_err
		ifd	DEBUG
		DMSG	<'Invalid command 0x%lx',LF>
		endc
		moveq	#-1,d0
		move.l	d0,dcmd_Res(a4)

proc_replymsg	move.l	a4,a1
		libcall	ReplyMsg
		bra	process_msgloop

DosCmd_QuitProcess
		ifd	DEBUG
		DMSG	<'Process: requested to exit',LF>
		endc

		move.l	(sp)+,d1
		beq.b	1$
		exg	d7,a6
		libcall	CurrentDir
		move.l	d0,d1
		libcall	UnLock
1$		exg	d7,a6

		libcall	Forbid	;make sure no other task can delay us...
		move.l	a4,a1
		jmp	_LVOReplyMsg(a6)

DosCmd_Open
		ifd	DEBUG
		move.l	dcmd_Args(a4),d0
		DMSG	<'DosCmd_Open, arg = 0x%lx -- '>
		endc

		move.l	dcmd_Args(a4),d1
		move.l	#MODE_NEWFILE,d2	;always use MODE_NEWFILE
		exg	d7,a6
		libcall	Open
		exg	d7,a6
		move.l	d0,dcmd_Res(a4)

		ifd	DEBUG
		DMSG	<'result = 0x%lx',LF>
		endc

		bra	proc_replymsg

DosCmd_Close
		ifd	DEBUG
		move.l	dcmd_Args(a4),d0
		DMSG	<'DosCmd_Close, arg = 0x%lx',LF>
		endc

		exg	d7,a6		;get DosBase to a6
		move.l	dcmd_Args(a4),d1
		libcall	Close
		exg	d7,a6
		clr	dcmd_Res(a4)
		bra	proc_replymsg

DosCmd_Write
		movem.l	dcmd_Args(a4),d1/d2/d3

		ifd	DEBUG
		moveq	#0,d0
		DMSG	<'DosCmd_Write%lc fh = 0x%lx, data = 0x%lx, len = 0x%lx',LF>
		endc

		exg	d7,a6
		libcall	Write
		exg	d7,a6
		move.l	d0,dcmd_Res(a4)
		bra	proc_replymsg

DosCmd_Execute
		ifd	DEBUG
		move.l	dcmd_Args(a4),d0
		DMSG	<'DosCmd_Execute "%s"',LF>
		endc

		move.l	dcmd_Args(a4),d1
		moveq	#0,d2
		exg	d7,a6
		libcall	SystemTagList
		exg	d7,a6
		move.l	d0,dcmd_Res(a4)
		bra	proc_replymsg

DosCmd_Delete
		ifd	DEBUG
		move.l	dcmd_Args(a4),d0
		DMSG	<'DosCmd_Delete "%s"',LF>
		endc

		move.l	dcmd_Args(a4),d1
		exg	d7,a6
		libcall	DeleteFile
		exg	d7,a6
		tst.l	d0
		seq	d0
		ext.w	d0
		ext.l	d0
		move.l	d0,dcmd_Res(a4)
		bra	proc_replymsg


dos_cmd		macro
		dc.w	DosCmd_\1-DosCmdTable
		endm

DosCmdTable	dos_cmd	QuitProcess
		dos_cmd	Open
		dos_cmd	Close
		dos_cmd	Write
		dos_cmd	Execute
		dos_cmd	Delete

;
; Debugging support
;

		ifd	DEBUG
;
; Printf-like formatting with output to serial port (with RawPutChar)
; format string in a0, arguments in d0-d7
;
DPrintf		movem.l	a2/a3/a6,-(sp)
		movem.l	d0-d7,-(sp)
		move.l	sp,a1
		lea	PutCh(pc),a2
		move.l	4,a6
		move.l	a6,a3
		libcall	RawDoFmt
		lea	8*4(sp),sp
		movem.l	(sp)+,a2/a3/a6
		rts

PutCh		move.l	a6,-(sp)
		move.l	a3,a6
		libcall	RawPutChar
		move.l	(sp)+,a6
		rts

		endc	;DEBUG

DevSectionEnd
		end
