;********************************
; P2000GET - Dieses CP/M-Programm empfaengt eine Datei von einem PC ueber eine
; serielle Schnittstelle. Fuer den Transfer wird das XMODEM-Protokoll genutzt.
;
; Urheber des Programms ist Ward Christensen.
;
; Diese Version fuer Philips P2000C bedient nur den SIO-Kanal A.
; Die Initialisierung der Schnittstelle (9600 Bd, 8 Datenbits, 
; keine Paritaet, 1 Stopbit) wird durch das Betriebssystem erledigt. 
;
;********************************
;
; ASCII-Zeichen fuer Datentransfer und Anzeige
;
SOH	EQU	01H		; Blockanfang
CTRLC	EQU	03H		; Abbruch
EOT	EQU	04H		; Transferende
ACK	EQU	06H		; positive Bestaetigung
LF	EQU	0AH		; Zeilenvorschub
CR	EQU	0DH		; Wagenruecklauf
NAK	EQU	15H		; negative Bestaetigung
;
; Hardwareadressen und -parameter
;
SIOACR	EQU	029H		; SIO Kanal A Steuerregister
SIOADR	EQU	028H		; SIO Kanal A Datenregister
XMTMSK	EQU	4		; Sendepuffer leer
RCVMSK	EQU	1		; Byte vorhanden
;
; Systemadressen
;
BDOS	EQU	0005H		; BDOS-Adresse
FCB	EQU	005CH		; Standard-Dateisteuerblock
PARAM	EQU	FCB+1		; Kommando-Parameter 1
BUFFER	EQU	0080H		; CP/M-Standarpuffer
TPA	EQU	0100H		; CP/M-Programmanfang
;
; BDOS-Funktionen
;
PRINT	EQU	9		; Zeichenkette ausgeben
CONST	EQU	11		; Konsolenstatus
OPEN	EQU	15		; Datei oeffnen
CLOSE	EQU	16		; Datei schliessen
SRCHF	EQU	17		; Dateieintrag suchen
ERASE	EQU	19		; Datei loeschen
READ	EQU	20		; Sektor lesen
WRITE	EQU	21		; Sektor schreiben
MAKE	EQU	22		; Datei anlegen
;
	ASEG			;
	ORG	TPA		;
;
; Parameter pruefen
;
	LD	A,(PARAM)	; 1. Zeichen des Parameters
	CP	' '		; Zeichen vorhanden?
	JR	NZ,DOXFER	; ja, weiter
	LD	DE,HLPMSG	; Benutzungshinweis
	LD	C,PRINT		;  ausgeben
	CALL	BDOS		;
	RET			; zurueck zum CP/M
;
; Dateinamen gefunden
; Lokalen Stapel einrichten und
; Uebertragung starten
;
DOXFER:	LD	HL,0		; HL=0
	ADD	HL,SP		; CP/M-Stapelzeiger
	LD	(STACK),HL	;  retten
	LD	SP,STACK	; Lokalen Stapel benutzen
	XOR	A		;
	LD	(SECTNO),A	; Blocknummer initialisieren
	LD	DE,SNDMSG	; Sendeaufforderung
	LD	C,PRINT		;  anzeigen
	CALL	BDOS		;
;
; noch anstehende Bytes entfernen
;
PURGE:	LD	B,1		; max. 1s warten
	CALL	RECV		; Byte empfangen
	JR	C,RCVFILE	; kein Byte, Uebertragung beginnen
	CP	CTRLC		; Abbruch angefordert?
	JR	NZ,PURGE	; nein, Byte ignorieren
;
; Uebertragung abgebrochen
;
ABORT:	CALL	ERXIT		;
	DEFB	13,10,10,'Abbruch',13,10,'$'
;
; Datei empfangen
;
RCVFILE:CALL	ERAFILE		; evt. vorhandene Datei loeschen
	CALL	MAKFILE		; neue Datei anlegen
	LD	A,NAK		; Start der Uebertragung
	CALL	SEND		;  senden
;
; Hauptschleife
;
RCVLOOP:LD	B,3		; max. 3s warten
	CALL	RECV		; Byte empfangen
	JR	C,RCVTMO	; Zeitueberschreitung?
;
; erstes Zeichen auswerten
;
	CP	SOH		; neuer Block?
	JR	Z,GOTSOH	; ja
	CP	CTRLC		; Abbruch?
	JR	Z,ABORT		; ja
	CP	EOT		; Dateiende?
	JR	NZ,RCVTMO	; nein, Fehler
;
; Dateiende erreicht, Datei schliessen
;
GOTEOT:	LD	A,ACK		; positive Bestaetigung
	CALL	SEND		;  senden
	LD	DE,FCB		; und Datei
	LD	C,CLOSE		;  schliessen
	CALL	BDOS		;
	INC	A		; Fehler?
	JR	Z,CLSERR	; ja, Meldung
;
; Uebertragung beendet
;
	CALL	ERXIT		;
	DEFB	13,10,10,'Uebertragung erfolgreich',13,10,'$'
;
; Zeitueberschreitung oder Fehler beim Empfangen
;
RCVTMO: LD	B,1		; max. 1s warten
	CALL	RECV		; Byte empfangen
	JR	NC,RCVTMO	; wiederholen bis Sender fertig
	LD	A,NAK		; negative Bestaetigung
	CALL	SEND		;  senden
	JR	RCVLOOP		; erneut versuchen
;
CLSERR:	CALL	ERXIT		;
	DEFB	13,10,10,'Fehler beim Schliessen der Datei',13,10,'$'
;
; neuer Block
;
GOTSOH: LD	B,1		; max. 1s warten
	CALL	RECV		; Blocknummer empfangen
	JR	C,RCVTMO	; Zeitueberschreitung?
	LD	D,A		; nein, Blocknummer retten
	LD	B,1		;
	CALL	RECV		; Komplement empfangen
	JR	C,RCVTMO	; Zeitueberschreitung?
	CPL			; Komplement bilden und 
	CP	D		; mit Blocknummer vergleichen
	JR	NZ,RCVTMO	; Fehler?
;
; Block empfangen
;
RCVSEC:	LD	A,D		; Blocknummer
	LD	(RSECTNO),A	;  abspeichern
	LD	C,0		; Pruefsumme loeschen
	LD	HL,BUFFER	; Puffer adressieren
RCVCHR:	LD	B,1		; max. 1s warten
	CALL	RECV		; Byte empfangen
	JR	C,RCVTMO	; Fehler?
	LD	(HL),A		; nein, Byte abspeichern
	INC	L		; Pufferzeiger erhoehen
	JR	NZ,RCVCHR	; naechstes Byte, wenn
				; Puffer noch nicht voll
;
; Pruefsumme verifizieren
;
	LD	D,C		; Pruefsumme retten
	LD	B,1		; 
	CALL	RECV		; Pruefsumme empfangen
	JR	C,RCVTMO	; Zeitueberschreitung?	
	CP	D		; nein, vergleichen
	JR	NZ,RCVTMO	; Fehler?
;
; Block empfangen, speichern, wenn
; die Blocknummer um 1 erhoeht ist.
;
	LD	A,(RSECTNO)	; neue Blocknummer
	LD	B,A		;  retten
	LD	A,(SECTNO)	; vorige Sektnummer
	INC	A		;  erhoehen
	CP	B		; und vergleichen
	JR	NZ,DO$ACK	;
;
; neuen Block in Datei schreiben
;
	LD	DE,FCB		; Dateisteuerblock anwaehlen
	LD	C,WRITE		;  und Block schreiben
	CALL	BDOS		;
	OR	A		; erfolgreich?
	JR	NZ,WRTERR	; nein, Schreibfehler
	LD	A,(RSECTNO)	; neue Blocknummer 
	LD	(SECTNO),A	;  abspeichern
DO$ACK:	LD	A,ACK		; positive Bestaetigung
	CALL	SEND		;  senden
	JP	RCVLOOP		; und weiter
;
WRTERR:	CALL	ERXIT		; Schreibfehler ausgeben
	DEFB	13,10,10,'Fehler beim Schreiben der Datei',13,10,'$'
;
; evt. vorhandene Datei loeschen
;
ERAFILE:LD	DE,FCB		; Datei
	LD	C,SRCHF		;  suchen
	CALL	BDOS		;
	INC	A		; Datei vorhanden?
	RET	Z		; nein, zurueck
ERAY:	LD	DE,FCB		; sonst Datei
	LD	C,ERASE		;  loeschen
	JP	BDOS		;
;
; Datei anlegen
;
MAKFILE:LD	DE,FCB		; Datei
	LD	C,MAKE		;  anlegen
	CALL	BDOS		;
	INC	A		; Fehler?
	RET	NZ		; nein, zurueck
;
; Dateiverzeichnis voll
;
	CALL	ERXIT		;
	DEFB	13,10,10,'Fehler beim Anlegen der Datei',CR,LF,'$'
;
; Ausgabe einer Meldung und Rueckkehr zum CP/M
;
ERXIT:	POP	DE		; Adresse holen
	LD	C,PRINT		;  und Meldung ausgeben
	CALL	BDOS		;
EXIT:	LD	HL,(STACK)	; CP/M-Stapelzeiger holen
	LD	SP,HL		;  und zurueckspeichern
	RET			; zurueck zum CP/M
;
; Byte empfangen
;
RECV:	PUSH	DE		; DE retten
MSEC:	LD	DE,159 SHL 7	; 1s einstellen, hardwareabhaengig!	
;
;  Eingabe vom Kanal A
;
MWTI:	IN	A,(SIOACR)	; Status holen
	AND	RCVMSK		; Byte vorhanden?
	JR	NZ,MCHAR	; ja
	DEC	E		; Zeit herunterzehlen
	JR	NZ,MWTI		;
	DEC	D		;
	JR	NZ,MWTI		;
	DEC	B		; Sekunden abgelaufen?
	JR	NZ,MSEC		; nein, warten
;
; Zeitueberschreitung
;
	POP	DE		; DE wiederherstellen
	SCF			; Uebertrag setzen (Zeitueberschreitung)
	RET			;
;
; Byte empfangen
;
MCHAR:	IN	A,(SIOADR)	; Byte holen
	POP	DE		; DE wiederherstellen
	PUSH	AF		; Byte retten
	ADD	A,C		; zur Pruefsumme addieren
	LD	C,A		;  und abspeichern
	POP	AF		; Byte zurueckholen
	OR	A		; Uebertrag loeschen (keine Zeitueberschreitung)
	RET			;
;
; Byte senden
;
SEND:	PUSH	AF		; Byte retten
	ADD	A,C		; Pruefsumme berechnen
	LD	C,A		;  und merken
SENDW:	IN	A,(SIOACR)	; Status holen
	AND	XMTMSK		; Sendepuffer leer?
	JR	Z,SENDW		; nein, warten
	POP	AF		; Byte zurueckholen
	OUT	(SIOADR),A	;  und senden
	RET			;
;
SNDMSG:	DEFB	'Senden beginnen...$'
HLPMSG:	DEFB	'Kommando: P2000GET dateiname',CR,LF,'$'
;
	DEFS	40		; Stapelspeicher
STACK:	DEFS	2		; CP/M-Stapelzeiger
RSECTNO:DEFS	1		; empfangene Blocknummer
SECTNO:	DEFS	1		; aktuelle Blocknummer 
;
	END			;
