*
* 1541.asm - 1541-Emulation, Dateihandling
*
* Copyright (C) 1994-1995 by Christian Bauer
*

*
* Anmerkungen:
* ------------
*
* Aufrufkonventionen:
*  - Die Routinen IECOut#? und IECIn geben in d0 den Status zurück.
*    Dieser wird von den aufrufenden Routinen mit dem C64-Statusbyte
*    ($90) verodert.
*  - Die Routinen dürfen nur d0-d1/a0-a1 verändern
*
* Funktionsweise:
*  - Am IEC-Bus gibt es drei Sorten von Geräten: Controller, Listener
*    und Talker. Der Controller sind immer wir, zusätzlich können wir
*    wahlweise Talker und Listener sein. Es kann immer nur einen Talker
*    und einen Listener geben (am echten IEC-Bus sind mehrere Listener
*    erlaubt, aber hier nicht).
*  - Die Geräte, die angesprochen werden, sind entweder real am IEC-Kabel
*    oder werden hier emuliert. Für jedes emulierte Laufwerk existiert
*    eine eigene Datenstruktur (Drv?Data). Ein Zeiger darauf wird bei
*    Talk/Listen in TalkerData/ListenerData gespeichert und steht den
*    nachfolgend aufgerufenen Routinen zur Verfügung, damit sich diese
*    um das jeweils angesprochene Laufwerk kümmern.
*  - Bei Geräten am Kabel wird #?Data nicht benutzt, da die Routinen
*    dann die Daten im wesentlichen einfach an den IEC-Bus durchreichen
*
* EOI/EOF:
*  - Im Gegensatz zum Amiga, wo der EOF-Zustand erst nach dem Lesen
*    des letzten Bytes erkannt wird, wird beim IEC-Bus das EOF
*    gleichzeitig mit dem letzten Byte gesendet. Daher verwenden die
*    Leseroutinen einen Puffer, in dem immer ein Byte bereitsteht.
*
* Directory-Emulation:
*  - Wird das Directory geöffnet (Dateiname "$"), wird in T: eine
*    temporäre Datei angelegt, die vom Aufbau genau einem 1541-Directory
*    entspricht und diese Datei geöffnet. Sie kann dann mit den ganz
*    normalen Lesebefehlen verarbeitet werden.
*
* Inkompatibilitäten/Verbesserungen:
*  - Es kann nur ein Listener gleichzeitig aktiv sein
*  - Kein Kommandokanal implementiert
*  - Beim Empfang des Dateinamens sollte er auf Pufferüberlauf prüfen
*

		MACHINE	68020

		INCLUDE	"exec/types.i"
		INCLUDE	"exec/macros.i"
		INCLUDE	"dos/dos.i"
		INCLUDE	"resources/misc.i"
		INCLUDE	"hardware/cia.i"

		XREF	_SysBase
		XREF	_DOSBase
		XREF	_CiaBase
		XREF	_MiscBase

		XREF	_ciaaprb
		XREF	_ciaaddrb

		XDEF	Open1541
		XDEF	Close1541
		XDEF	ChangedDirs
		XDEF	CloseIEC
		XDEF	ChangedIEC
		XDEF	IECOut
		XDEF	IECOutATN
		XDEF	IECOutSec
		XDEF	IECIn
		XDEF	IECSetATN
		XDEF	IECRelATN
		XDEF	IECTurnaround
		XDEF	IECRelease
		XDEF	IECIsOpen

		XDEF	Dir8		;Prefs
		XDEF	Dir9
		XDEF	Dir10
		XDEF	Dir11
		XDEF	Drv8IsIEC
		XDEF	Drv9IsIEC
		XDEF	Drv10IsIEC
		XDEF	Drv11IsIEC
		XDEF	OtherIEC

		SECTION	"CODE",CODE


**
** Definitionen
**

; Datenstruktur für ein Laufwerk
 STRUCTURE DRVDATA,0
	BYTE	drv_ErrorCode		;Letzter Fehlercode (ERR_#?)
	BYTE	drv_ReadBuf		;Byte-Puffer zum Lesen aus Dateien
	BYTE	drv_IsIEC		;Laufwerk wird über IEC-Kabel angesprochen
	BYTE	pad0
	STRUCT	drv_Handles,16*4	;FileHandles für alle 16 Sekundäradressen
	APTR	drv_NamePtr		;Zeiger in Dateinamen, für Open/IECOut
	APTR	drv_ErrorPtr		;Zeiger in Fehlermeldung
	APTR	drv_Lock		;Lock des Verzeichnisses des Laufwerks
 LABEL drv_SIZEOF

; Register (müssen mit den Definitionen in 6510.asm übereinstimmen!)
RAMPTR		EQUR	a4	;Zeiger auf C64-RAM

; C64-Status-Codes
ST_OK		= 0	;Alles klar
ST_READ_TIMEOUT	= $02	;Timeout beim Lesen
ST_TIMEOUT	= $03	;Timeout
ST_EOF		= $40	;End of file
ST_NOTPRESENT	= $80	;Device not present

; IEC-Befehlscodes
CMD_DATA	= $60
CMD_CLOSE	= $e0
CMD_OPEN	= $f0

; Fehlercodes (genauer: Die Nummer des zugeh. Fehlertextes)
ERR_OK		= 0	;00: OK
ERR_STARTUP	= 1	;Einschaltmeldung
ERR_FILENOTFOUND = 2	;62: FILE NOT FOUND

; Makros für IEC-Kabel
DATA_HI		MACRO
		bclr	#5,_ciaaprb
		ENDM

DATA_LO		MACRO
		bset	#5,_ciaaprb
		ENDM

CLOCK_HI	MACRO
		bclr	#4,_ciaaprb
		ENDM

CLOCK_LO	MACRO
		bset	#4,_ciaaprb
		ENDM

ATN_HI		MACRO
		bclr	#3,_ciaaprb
		ENDM

ATN_LO		MACRO
		bset	#3,_ciaaprb
		ENDM

LINE_RELEASE	MACRO
		ATN_HI
		bsr	Wait40us
		CLOCK_HI
		DATA_HI
		ENDM

; Data -> Carry, Clock -> Negative
GET_DATA_CLOCK	MACRO
\@1$		move.b	_ciaaprb,d0
		cmp.b	_ciaaprb,d0
		bne	\@1$
		add.b	d0,d0
		ENDM

WAIT_CLOCK_HI	MACRO
\@1$		GET_DATA_CLOCK
		bpl	\@1$
		ENDM

WAIT_CLOCK_LO	MACRO
\@1$		GET_DATA_CLOCK
		bmi	\@1$
		ENDM

WAIT_DATA_HI	MACRO
\@1$		GET_DATA_CLOCK
		bcc	\@1$
		ENDM

WAIT_DATA_LO	MACRO
\@1$		GET_DATA_CLOCK
		bcs	\@1$
		ENDM


**
** 1541-Emulation starten
**

; Einschaltmeldung bereitstellen
Open1541	move.b	#ERR_STARTUP,Drv8Data+drv_ErrorCode
		move.b	#ERR_STARTUP,Drv9Data+drv_ErrorCode
		move.b	#ERR_STARTUP,Drv10Data+drv_ErrorCode
		move.b	#ERR_STARTUP,Drv11Data+drv_ErrorCode

; Locks der Verzeichnisse holen
		move.l	_DOSBase,a6
		bra	OpenLocks


**
** 1541-Emulation beenden
**

; Alle Dateien schließen
Close1541	move.l	_DOSBase,a6

		lea	Drv8Data+drv_Handles,a0
		bsr	FreeHandles
		lea	Drv9Data+drv_Handles,a0
		bsr	FreeHandles
		lea	Drv10Data+drv_Handles,a0
		bsr	FreeHandles
		lea	Drv11Data+drv_Handles,a0
		bsr	FreeHandles

; Alle Locks freigeben
		bsr	FreeLocks

; Temporäre Datei löschen
		move.l	#TempFileName,d1
		JMPLIB	DeleteFile

FreeHandles	moveq	#15,d7		;16 Kanäle
2$		move.l	(a0)+,d1
		beq	1$
		clr.l	-4(a0)		;FileHandle löschen
		JSRLIB	Close
1$		dbra	d7,2$
		rts


**
** Die Verzeichnisse haben sich geändert: Neue Locks holen
**

ChangedDirs	move.l	_DOSBase,a6
		bsr	FreeLocks
		bra	OpenLocks


**
** IEC-Einstellungen wurden verändert
** Rückgabe: d0=0 Alles OK
**           d0=1 Parallelport belegt
**           d0=2 Kein Timer frei
**

; Einstellungen übertragen
ChangedIEC	moveq	#0,d1
		move.w	Drv8IsIEC,d0
		or.b	d0,d1
		move.b	d0,Drv8Data+drv_IsIEC
		move.w	Drv9IsIEC,d0
		or.b	d0,d1
		move.b	d0,Drv9Data+drv_IsIEC
		move.w	Drv10IsIEC,d0
		or.b	d0,d1
		move.b	d0,Drv10Data+drv_IsIEC
		move.w	Drv11IsIEC,d0
		or.b	d0,d1
		move.b	d0,Drv11Data+drv_IsIEC
		or.w	OtherIEC,d1

; IEC öffnen, wenn mindestens ein Gerät mit IEC laufen soll,
; sonst schließen
		tst.b	d1
		beq	CloseIEC
		bra	OpenIEC


**
** Auf Benutzung des IEC-Kabels vorbereiten
** Rückgabe: d0=0 Alles OK
**           d0=1 Parallelport belegt
**           d0=2 Kein Timer frei
**

OpenIEC		move.l	a6,-(sp)
		tst.b	IECIsOpen	;Kann auch mehrmals aufgerufen werden
		bne	OIECOK

; Parallelport belegen
		move.l	_MiscBase,a6
		moveq	#MR_PARALLELPORT,d0
		lea	ParPortName,a1
		JSRLIB	AllocMiscResource
		tst.l	d0
		bne	OIECNoPort

		move.b	#$00,_ciaaprb	;Port inaktiv
		move.b	#$38,_ciaaddrb	;DDR konfigurieren

; Timer A belegen
		move.l	_CiaBase,a6
		lea	TimerInterrupt,a1
		moveq	#CIAICRB_TA,d0
		JSRLIB	AddICRVector
		tst.l	d0
		bne	1$

; Gelungen
		move.b	#CIAICRB_TA,WhichTimer
		move.b	#CIAICRF_TA,WhichTimerMask
		move.l	#$bfe401,CiaTimerReg
		move.l	#$bfee01,CiaControlReg
		bra	3$

; Nicht gelungen, dann Timer B probieren
1$		lea	TimerInterrupt,a1
		moveq	#CIAICRB_TB,d0
		JSRLIB	AddICRVector
		tst.l	d0
		bne	2$

; Gelungen
		move.b	#CIAICRB_TB,WhichTimer
		move.b	#CIAICRF_TB,WhichTimerMask
		move.l	#$bfe601,CiaTimerReg
		move.l	#$bfef01,CiaControlReg
		bra	3$

; Nicht gelungen, dann Parallelport wieder freigeben
2$		move.b	#$00,_ciaaddrb	;Port auf Eingabe

		move.l	_MiscBase,a6
		moveq	#MR_PARALLELPORT,d0
		JSRLIB	FreeMiscResource

		moveq	#2,d0		;Kein Timer frei
		move.l	(sp)+,a6
		rts

; Alles klar, Timer-Interrupt abschalten und Timer stoppen
3$		move.b	WhichTimerMask,d0
		JSRLIB	AbleICR

		move.l	CiaControlReg,a0
		and.b	#$80,(a0)

		st.b	IECIsOpen
OIECOK		moveq	#0,d0		;Alles OK
		move.l	(sp)+,a6
		rts

OIECNoPort	moveq	#1,d0		;Parallelport belegt
		move.l	(sp)+,a6
		rts


**
** Benutzung des IEC-Kabels beenden
** Rückgabe: d0=0
**

CloseIEC	tst.b	IECIsOpen
		beq	1$

		move.b	#$00,_ciaaddrb	;Port auf Eingabe

		move.l	a6,-(sp)	;Parallelport freigeben
		move.l	_MiscBase,a6
		moveq	#MR_PARALLELPORT,d0
		JSRLIB	FreeMiscResource

		move.l	CiaControlReg,a0 ;Timer stoppen
		and.b	#$80,(a0)

		move.l	_CiaBase,a6
		lea	TimerInterrupt,a1
		move.b	WhichTimer,d0
		JSRLIB	RemICRVector

		move.l	(sp)+,a6
		clr.b	IECIsOpen

1$		moveq	#0,d0
		rts


*
* Locks freigeben
*

FreeLocks	move.l	Drv8Data+drv_Lock,d1
		beq	1$
		JSRLIB	UnLock
1$		move.l	Drv9Data+drv_Lock,d1
		beq	2$
		JSRLIB	UnLock
2$		move.l	Drv10Data+drv_Lock,d1
		beq	3$
		JSRLIB	UnLock
3$		move.l	Drv11Data+drv_Lock,d1
		beq	4$
		JSRLIB	UnLock
4$		rts

*
* Locks holen
*

OpenLocks	move.l	#Dir8,d1
		move.l	#ACCESS_READ,d2
		JSRLIB	Lock
		move.l	d0,Drv8Data+drv_Lock

		move.l	#Dir9,d1
		move.l	#ACCESS_READ,d2
		JSRLIB	Lock
		move.l	d0,Drv9Data+drv_Lock

		move.l	#Dir10,d1
		move.l	#ACCESS_READ,d2
		JSRLIB	Lock
		move.l	d0,Drv10Data+drv_Lock

		move.l	#Dir11,d1
		move.l	#ACCESS_READ,d2
		JSRLIB	Lock
		move.l	d0,Drv11Data+drv_Lock
		rts


**
** IECOut - Ein Byte ausgeben
** d0.b: Byte
** d1.b: <0: EOI-Flag
**

IECOut		tst.b	ListenerIsIEC	;Listener am IEC-Kabel?
		bne	IECByteOut	;Ja, dann Byte über Kabel senden
		tst.b	ListenerActive	;Wurde ein Gerät angesprochen?
		beq	1$

		cmp.b	#CMD_OPEN,ReceivedCmd
		beq	OpenOut
		cmp.b	#CMD_DATA,ReceivedCmd
		beq	DataOut

1$		move.b	#ST_TIMEOUT,d0	;Kein Listener aktiv oder kein gültiger Befehl
		rts


**
** IECOutATN - Ein Byte mit ATN ausgeben (Talk/Listen/Untalk/Unlisten)
** d0.b: Byte
**

IECOutATN	clr.b	SecAddr		;Befehl kommt mit der Sekundäradresse danach
		clr.b	ReceivedCmd

		move.b	d0,d1
		and.b	#$0f,d0		;d0.b: Gerätenummer
		and.b	#$f0,d1		;d1.b: Funktion

		st.b	Listening	;Funktion auswerten
		cmp.b	#$20,d1		;Bei Listen ist das Flag Listening gesetzt
		beq	Listen
		clr.b	Listening
		cmp.b	#$30,d1
		beq	Unlisten
		cmp.b	#$40,d1
		beq	Talk
		cmp.b	#$50,d1
		beq	Untalk

		move.b	#ST_TIMEOUT,d0	;Ungültige Funktion
		rts


**
** IECOutSec - Sekundäradresse ausgeben
** d0.b: Sekundäradresse
**

IECOutSec	tst.b	Listening	;Nach Listen oder nach Talk?
		beq	1$

		tst.b	ListenerIsIEC	;Nach Listen: Listener am IEC-Kabel?
		bne	IECByteOutSec
		tst.b	ListenerActive	;Wurde ein Gerät angesprochen?
		beq	2$

		move.b	d0,d1
		and.b	#$0f,d0		;Sekundäradresse
		move.b	d0,SecAddr
		and.b	#$f0,d1		;und Befehlscode speichern
		move.b	d1,ReceivedCmd
		bra	SecListen	;Sek.adr nach Listen

1$		tst.b	TalkerIsIEC	;Nach Talk: Talker am IEC-Kabel?
		bne	IECByteOutSec
		tst.b	TalkerActive	;Wurde ein Gerät angesprochen?
		beq	2$

		move.b	d0,d1
		and.b	#$0f,d0		;Sekundäradresse
		move.b	d0,SecAddr
		and.b	#$f0,d1		;und Befehlscode speichern
		move.b	d1,ReceivedCmd
		bra	SecTalk		;Sek.adr nach Talk

2$		move.b	#ST_TIMEOUT,d0	;Es erfolgte kein Listen/Talk
		rts


**
** IECIn - Ein Byte einlesen
** RÜckgabe: d1.b: Byte
**

IECIn		tst.b	TalkerIsIEC	;Talker am IEC-Kabel?
		bne	IECByteIn
		tst.b	TalkerActive	;Wurde ein Gerät angesprochen?
		beq	1$

		cmp.b	#CMD_DATA,ReceivedCmd
		beq	DataIn

1$		move.b	#ST_TIMEOUT,d0	;Kein Talk bzw. kein CMD_DATA
		clr.b	d1
		rts


**
** IECSetATN - ATN setzen (für Untalk)
**

IECSetATN	tst.b	TalkerIsIEC	;Nur bei IEC-Geräten etwas machen
		beq	1$
		CLOCK_LO
		ATN_LO
1$		rts


**
** IECRelATN - ATN wegnehmen
**

IECRelATN	tst.b	IECIsOpen
		beq	1$
		ATN_HI
		bsr	Wait20us
1$		rts


**
** IECTurnaround - Talk-attention turn around
**

IECTurnaround	tst.b	TalkerIsIEC	;Nur bei IEC-Geräten etwas machen
		beq	1$
		move.l	a6,-(sp)
		move.l	_SysBase,a6
		JSRLIB	Disable
		DATA_LO
		bsr	Wait20us
		ATN_HI
		CLOCK_HI
		WAIT_CLOCK_LO
		bsr	Wait60us
		JSRLIB	Enable
		move.l	(sp)+,a6
1$		rts


**
** IECRelease - System line release
**

IECRelease	tst.b	IECIsOpen
		beq	1$
		LINE_RELEASE
1$		rts


*
* Listen
* d0.b: Gerätenummer
* d1.b: Funktion
*

Listen		cmp.b	#8,d0		;Gerät zwischen 8 und 11?
		blo	ListenOther
		cmp.b	#11,d0
		bhi	ListenOther

		moveq	#0,d1		;Ja, Zeiger auf Datenbereich holen
		move.b	d0,d1
		subq.b	#8,d1
		move.l	(DrvTab,d1.w*4),a0
		tst.b	drv_IsIEC(a0)	;IEC-Gerät?
		bne	ListenIEC
		tst.l	drv_Lock(a0)	;Nein, Verzeichnis vorhanden?
		beq	ListenNotPr

		move.l	a0,ListenerData	;Ja, Zeiger auf Datenbereich sichern
		st.b	ListenerActive	;Listener ist aktiv
		clr.b	ListenerIsIEC
		moveq	#ST_OK,d0
		rts

; Anderes Gerät außer 8..11
ListenOther	tst.w	OtherIEC
		beq	ListenNotPr

; Listen über IEC-Kabel
ListenIEC	move.l	a0,ListenerData	;Zeiger auf Datenbereich sichern

		moveq	#$20,d1		;Listen über Kabel senden
		bsr	IECByteOutATN
		cmp.b	#ST_NOTPRESENT,d0
		beq	ListenNotPr
		st.b	ListenerIsIEC	;Gelungen, Listener ist aktiv
		rts

; Gerät nicht vorhanden
ListenNotPr	move.b	#ST_NOTPRESENT,d0
		clr.b	ListenerActive
		clr.b	ListenerIsIEC
		rts


*
* Talk
* d0.b: Gerätenummer
* d1.b: Funktion
*

Talk		cmp.b	#8,d0		;Gerät zwischen 8 und 11?
		blo	TalkOther
		cmp.b	#11,d0
		bhi	TalkOther

		moveq	#0,d1		;Ja, Zeiger auf Datenbereich holen
		move.b	d0,d1
		subq.b	#8,d1
		move.l	(DrvTab,d1.w*4),a0
		tst.b	drv_IsIEC(a0)	;IEC-Gerät?
		bne	TalkIEC
		tst.l	drv_Lock(a0)	;Nein, Verzeichnis vorhanden?
		beq	TalkNotPr

		move.l	a0,TalkerData	;Ja, Zeiger auf Datenbereich sichern
		st.b	TalkerActive	;Talker ist aktiv
		clr.b	TalkerIsIEC
		moveq	#ST_OK,d0
		rts

; Anderes Gerät außer 8..11
TalkOther	tst.w	OtherIEC
		beq	TalkNotPr

; Talk über IEC-Kabel
TalkIEC		move.l	a0,TalkerData	;Zeiger auf Datenbereich sichern

		moveq	#$40,d1		;Talk über Kabel senden
		bsr	IECByteOutATN
		cmp.b	#ST_NOTPRESENT,d0
		beq	TalkNotPr
		st.b	TalkerIsIEC	;Gelungen, Talker ist aktiv
		rts

; Gerät nicht vorhanden
TalkNotPr	move.b	#ST_NOTPRESENT,d0
		clr.b	TalkerActive
		clr.b	TalkerIsIEC
		rts


*
* Unlisten
* d0.b: Gerätenummer
* d1.b: Funktion
*

Unlisten	tst.b	ListenerIsIEC	;Listener am IEC-Kabel?
		beq	1$
		bsr	IECByteOutATN	;Ja, Unlisten über Kabel senden
		bra	2$

1$		moveq	#ST_OK,d0
2$		clr.b	ListenerActive	;Gerät nicht mehr aktiv
		clr.b	ListenerIsIEC
		rts

*
* Untalk
* d0.b: Gerätenummer
* d1.b: Funktion
*

Untalk		tst.b	TalkerIsIEC	;Talker am IEC-Kabel?
		beq	1$
		bsr	IECByteOutATN	;Ja, Untalk über Kabel senden
		bra	2$

1$		moveq	#ST_OK,d0
2$		clr.b	TalkerActive	;Gerät nicht mehr aktiv
		clr.b	TalkerIsIEC
		rts


*
* SecListen - Sekundäradresse nach Listen
* d0.b: Sekundäradresse
* d1.b: Befehlscode
*

SecListen	cmp.b	#CMD_OPEN,d1
		beq	SecListenOpen
		cmp.b	#CMD_CLOSE,d1
		beq	SecListenClose

		moveq	#ST_OK,d0
		rts


*
* Open-Befehl: Auf Empfang des Dateinamens vorbereiten
*

SecListenOpen	move.l	ListenerData,a0
		move.l	#NameBuf,drv_NamePtr(a0)
		moveq	#ST_OK,d0
		rts


*
* Close-Befehl: Datei schließen
*

SecListenClose	movem.l	a2/a4/a6,-(sp)
		move.l	_DOSBase,a6
		move.l	ListenerData,a4

		moveq	#0,d0		;Zeiger auf FileHandle holen
		move.b	SecAddr,d0
		lea	drv_Handles(a4,d0.w*4),a2
		move.l	(a2),d1		;Datei schließen, wenn geöffnet
		beq	1$
		JSRLIB	Close
		clr.l	(a2)

1$		movem.l	(sp)+,a2/a4/a6
		moveq	#ST_OK,d0
		rts


*
* SecTalk - Sekundäradresse nach Talk
* d0.b: Sekundäradresse
* d1.b: Befehlscode
*

SecTalk		cmp.b	#15,d0		;Kanal 15 (Fehlerkanal)?
		beq	SecTalk15

		moveq	#ST_OK,d0
		rts


*
* Kanal 15: Fehlermeldung bereitstellen
*

SecTalk15	move.l	TalkerData,a1
		lea	ErrorTab,a0	;Zeiger auf Fehlermeldung laden
		moveq	#0,d0
		move.b	drv_ErrorCode(a1),d0
		move.l	(a0,d0.w*4),drv_ErrorPtr(a1)
		move.b	#ERR_OK,drv_ErrorCode(a1) ;Fehlercode löschen
		moveq	#ST_OK,d0
		rts


*
* Byte nach Open-Befehl: Zeichen im Dateinamen speichern, bei EOI Datei öffnen
* d0.b: Byte
* d1.b: EOI-Flag
*

OpenOut		cmp.b	#15,SecAddr	;Kanal 15 (Kommandokanal)?
		beq	1$

		move.l	ListenerData,a0	;Nein, Zeichen speichern
		move.l	drv_NamePtr(a0),a1
		bsr	ConvTo64	;Zeichensatz wandeln
		move.b	d0,(a1)+
		move.l	a1,drv_NamePtr(a0)

		tst.b	d1		;EOI: Datei öffnen
		bmi	OpenFile

1$		moveq	#ST_OK,d0
		rts


*
* Dateiname nach Open-Befehl empfangen: Datei öffnen
*

OpenFile	movem.l	d2-d4/a2/a4/a6,-(sp)
		move.l	_DOSBase,a6
		move.l	ListenerData,a4

; Zeiger auf FileHandle nach a2 holen
		moveq	#0,d0
		move.b	SecAddr,d0
		lea	drv_Handles(a4,d0.w*4),a2

; Vorige Datei schließen, wenn noch offen
		move.l	(a2),d1
		beq	1$
		JSRLIB	Close
		clr.l	(a2)
1$

; Erstes Zeichen des Dateinamens='$'? Dann Directory öffnen
		cmp.b	#'$',NameBuf
		beq	OpenDir

; In das Verzeichnis des Laufwerks wechseln
		move.l	drv_Lock(a4),d1
		JSRLIB	CurrentDir
		move.l	d0,d3

; Dateinamen mit Nullbyte abschließen
		move.l	drv_NamePtr(a4),a0
		clr.b	(a0)

; Modus ermitteln (Sek.adr 0: Laden, Sek.adr 1: Speichern, sonst R/W)
		cmp.b	#0,SecAddr	;Load?
		bne	2$
		move.l	#MODE_OLDFILE,d2
		bra	4$
2$		cmp.b	#1,SecAddr	;Save?
		bne	3$
		move.l	#MODE_NEWFILE,d2
		bra	4$
3$		move.l	#MODE_READWRITE,d2
4$

; Datei öffnen und erstes Zeichen lesen
		move.b	#ERR_FILENOTFOUND,drv_ErrorCode(a4) ;Vorgabe: Datei nicht gefunden
		move.l	#NameBuf,d1
		JSRLIB	Open
		move.l	d0,(a2)
		beq	5$

		move.b	#ERR_OK,drv_ErrorCode(a4) ;Datei doch gefunden

		move.l	d0,d1
		JSRLIB	FGetC
		move.b	d0,drv_ReadBuf(a4)
5$

; In das ursprüngliche Verzeichnis wechseln
		move.l	d3,d1
		JSRLIB	CurrentDir

		movem.l	(sp)+,d2-d4/a2/a4/a6
		moveq	#ST_OK,d0
		rts


*
* Directory wird geöffnet, temporäre Datei erstellen
*

; Verzeichnis examinieren
OpenDir		move.l	drv_Lock(a4),d1
		move.l	#TheFIB,d2
		JSRLIB	Examine
		tst.l	d0
		beq	ODError

; Gelungen, temp. Datei öffnen
		move.l	#TempFileName,d1
		move.l	#MODE_NEWFILE,d2
		JSRLIB	Open
		move.l	d0,d4		;d4: FileHandle der temp. Datei
		beq	ODError

; Directory-Titel erzeugen und schreiben
		lea	NameBuf,a0
		move.l	#$01040101,(a0)+ ;Ladeadresse und Verkettung
		clr.w	(a0)+		;Laufwerksnummer (0) als Zeilennummer
		move.w	#$1222,(a0)+	;RVS ON und '"'

		lea	TheFIB+fib_FileName,a1
		moveq	#15,d1		;Max. 16 Zeichen Verzeichnisname wandeln
1$		move.b	(a1)+,d0
		bsr	ConvTo64
		move.b	d0,(a0)+
		dbeq	d1,1$
		bne	2$
		subq.w	#1,a0		;Den Rest mit Leerzeichen auffüllen
3$		move.b	#' ',(a0)+
		dbra	d1,3$
		
2$		move.w	#$2220,(a0)+	;'" '
		move.l	#'00 2',(a0)+	;ID und Formatkennzeichen
		move.w	#$4100,(a0)+	;Abschließendes Nullbyte

		move.l	d4,d1		;Zeile schreiben
		move.l	#NameBuf,d2
		moveq	#32,d3
		JSRLIB	Write

; Schleife für alle Verzeichniseinträge
; Nächsten Eintrag holen
ODLoop		move.l	drv_Lock(a4),d1
		move.l	#TheFIB,d2
		JSRLIB	ExNext
		tst.l	d0
		beq	ODLoopEnd

; Directory-Zeile erzeugen und schreiben
		lea	NameBuf,a0	;Zeile mit Leerzeichen löschen
		moveq	#30,d1
20$		move.b	#' ',(a0)+
		dbra	d1,20$
		clr.b	(a0)		;und mit Nullbyte abschließen

		lea	NameBuf,a0
		move.w	#$0101,(a0)+	;Verkettung
		move.l	TheFIB+fib_Size,d0 ;Größe in Blocks berechnen
		add.l	#254,d0
		divu.w	#254,d0
		rol.w	#8,d0		;MSB und LSB vertauschen
		move.w	d0,(a0)+	;Als Zeilennummer eintragen
		rol.w	#8,d0		;Bytefolge korrigieren
		addq.l	#1,a0		;Ein Leerzeichen mindestens
		cmp.w	#10,d0		;Kleiner als 10?
		bhs	10$
		addq.l	#1,a0		;Ja, ein Leerzeichen dazunehmen
10$		cmp.w	#100,d0		;Kleiner als 100?
		bhs	11$
		addq.l	#1,a0		;Ja, noch ein Leerzeichen dazunehmen
11$		move.b	#'"',(a0)+

		move.l	a0,-(sp)
		lea	TheFIB+fib_FileName,a1
		moveq	#15,d1		;Max. 16 Zeichen Dateiname wandeln
1$		move.b	(a1)+,d0
		bsr	ConvTo64
		move.b	d0,(a0)+
		dbeq	d1,1$
		bne	2$
		subq.w	#1,a0		;Nullbyte überschreiben
2$		move.b	#'"',(a0)+	;Mit '"' abschließen
		move.l	(sp)+,a0
		lea	18(a0),a0	;An festgelegter Spalte weitermachen

		tst.l	TheFIB+fib_DirEntryType ;Typ ermitteln und eintragen
		bmi	ODIsFile
		move.l	#'DIR ',(a0)+
		bra	ODLineEnd
ODIsFile	btst	#FIBB_EXECUTE,TheFIB+fib_Protection+3
		beq	ODIsPRG
		move.l	#'SEQ ',(a0)+
		bra	ODLineEnd
ODIsPRG		move.l	#'PRG ',(a0)+

ODLineEnd	move.l	d4,d1		;Zeile schreiben
		move.l	#NameBuf,d2
		moveq	#32,d3
		JSRLIB	Write
		bra	ODLoop

; Abschlußzeile mit "BLOCKS FREE." erzeugen und schreiben
ODLoopEnd	lea	NameBuf,a0	;Zeile mit Leerzeichen löschen
		moveq	#28,d1
20$		move.b	#' ',(a0)+
		dbra	d1,20$
		clr.w	(a0)+		;Und mit drei Nullbytes abschließen
		clr.b	(a0)

		lea	NameBuf,a0
		move.w	#$0101,(a0)+	;Verkettung
		clr.w	(a0)+		;Anzahl freier Blöcke (0)
		move.l	#'BLOC',(a0)+
		move.l	#'KS F',(a0)+
		move.l	#'REE.',(a0)+

		move.l	d4,d1		;Zeile schreiben
		move.l	#NameBuf,d2
		moveq	#32,d3
		JSRLIB	Write

; Temp. Datei schließen
		move.l	d4,d1
		JSRLIB	Close

; Und wieder zum Lesen öffnen und das erste Zeichen lesen
		move.l	#TempFileName,d1
		move.l	#MODE_OLDFILE,d2
		JSRLIB	Open
		move.l	d0,(a2)
		beq	ODError

		move.l	d0,d1
		JSRLIB	FGetC
		move.b	d0,drv_ReadBuf(a4)

ODError		movem.l	(sp)+,d2-d4/a2/a4/a6
		moveq	#ST_OK,d0
		rts


*
* Byte nach Data-Befehl: Zeichen in Datei schreiben
* d0.b: Byte
* d1.b: EOI-Flag
*

DataOut		cmp.b	#15,SecAddr	;Kanal 15 (Kommandokanal)?
		beq	2$

		movem.l	d2/a2/a4/a6,-(sp)
		move.l	_DOSBase,a6
		move.l	ListenerData,a4
		moveq	#0,d2
		move.b	d0,d2		;d2: Byte

		moveq	#0,d0		;Zeiger auf FileHandle holen
		move.b	SecAddr,d0
		lea	drv_Handles(a4,d0.w*4),a2
		move.l	(a2),d1		;Datei geöffnet?
		beq	1$

		JSRLIB	FPutC		;Zeichen schreiben
		tst.l	d0
		bmi	1$

		moveq	#ST_OK,d0
		movem.l	(sp)+,d2/a2/a4/a6
		rts

1$		move.b	#ST_TIMEOUT,d0	;Fehler aufgetreten
		movem.l	(sp)+,d2/a2/a4/a6
		rts

2$		moveq	#ST_OK,d0
		rts


*
* Zeichen aus Datenkanal lesen
* RÜckgabe: d1.b: Byte
*

DataIn		cmp.b	#15,SecAddr	;Kanal 15 (Fehlerkanal)?
		beq	Channel15In

		movem.l	d2/a2/a4/a6,-(sp)
		move.l	_DOSBase,a6
		move.l	TalkerData,a4

		moveq	#0,d0		;Zeiger auf FileHandle holen
		move.b	SecAddr,d0
		lea	drv_Handles(a4,d0.w*4),a2
		move.l	(a2),d1		;Datei geöffnet?
		beq	1$

		move.b	drv_ReadBuf(a4),d2 ;Zeichen aus dem Puffer holen

		JSRLIB	FGetC		;Nächstes Zeichen lesen
		move.b	d0,drv_ReadBuf(a4) ;und Puffern
		tst.l	d0
		bmi	2$		;EOF? Dann Status setzen

		moveq	#ST_OK,d0
		bra	3$
2$		move.b	#ST_EOF,d0
3$		move.b	d2,d1		;Zeichen wieder holen
		movem.l	(sp)+,d2/a2/a4/a6
		rts

1$		move.b	#ST_READ_TIMEOUT,d0	;Fehler aufgetreten
		clr.b	d1
		movem.l	(sp)+,d2/a2/a4/a6
		rts


*
* Aus Kanal 15 lesen
*

Channel15In	move.l	TalkerData,a0
		move.l	drv_ErrorPtr(a0),a1 ;Nächstes Zeichen holen
		move.b	(a1)+,d1
		cmp.b	#13,d1		;CR (letztes Zeichen)?
		beq	1$
		move.l	a1,drv_ErrorPtr(a0) ;Nein, Zeiger merken
		moveq	#ST_OK,d0
		rts

1$		move.b	#ST_EOF,d0	;Letztes Zeichen: EOF
		move.l	#OKText,drv_ErrorPtr(a0) ;Falls jemand noch mehr will...
		rts


*
* Umwandlung ASCII->PETSCII
* Zeichen in d0
*

ConvTo64	cmp.b	#'A',d0
		blo	1$
		cmp.b	#'Z',d0
		bhi	2$
		or.b	#$20,d0		;Großbuchstaben
		bra	1$
2$		cmp.b	#'a',d0
		blo	1$
		cmp.b	#'z',d0
		bhi	1$
		and.b	#$df,d0		;Kleinbuchstaben
1$		rts


*
* Byte mit ATN über IEC-Kabel senden
* d0.b: Gerätenummer
* d1.b: Funktion
*

IECByteOutATN	tst.b	IECIsOpen
		bne	1$
		move.b	#ST_NOTPRESENT,d0
		rts

1$		movem.l	d2/d7/a6,-(sp)
		move.b	d0,d2
		or.b	d1,d2		;d2: Auszugebendes Byte

		move.l	_SysBase,a6
		JSRLIB	Disable

		DATA_HI
		ATN_LO
		bra	IECByteOutSec2	;ATN response abwarten und Byte ausgeben


*
* Sekundäradresse über IEC-Kabel senden
* d0.b: Sekundäradresse
*

IECByteOutSec	tst.b	IECIsOpen
		bne	1$
		move.b	#ST_NOTPRESENT,d0
		rts

1$		movem.l	d2/d7/a6,-(sp)
		move.b	d0,d2		;d2: Auszugebendes Byte

		move.l	_SysBase,a6
		JSRLIB	Disable

IECByteOutSec2	CLOCK_LO
		DATA_HI
		bsr	Wait1ms		;Max ATN response abwarten
		moveq	#0,d1		;Kein EOI
		bra	IECByteOut2	;Byte ausgeben


*
* Byte über IEC-Kabel senden
* d0.b: Byte
* d1.b: EOI-Flag
*

IECByteOut	tst.b	IECIsOpen
		bne	1$
		move.b	#ST_NOTPRESENT,d0
		rts

1$		movem.l	d2/d7/a6,-(sp)
		move.b	d0,d2		;d2: Auszugebendes Byte

		move.l	_SysBase,a6
		JSRLIB	Disable

; Übertragung einleiten
IECByteOut2	move.b	d1,d7		;d7: EOI-Flag
		DATA_HI
		GET_DATA_CLOCK		;Data bleibt high -> Device not present
		bcs	IBODevNotPr
		CLOCK_HI		;Talker ready

		WAIT_DATA_HI		;Warten auf Listener ready for data

		tst.b	d7		;EOI?
		bpl	1$

		WAIT_DATA_LO		;Ja, EOI-Handshake abwarten
		WAIT_DATA_HI

1$		CLOCK_LO		;Talker sending

		bsr	Wait40us	;Wichtige Verzögerung, aber wozu?

; 8 Bits übertragen
		moveq	#7,d7		;d7: Bitzähler
IBOBitLoop	GET_DATA_CLOCK		;Data low -> Time out
		bcc	IBOTimeOut

		lsr.b	#1,d2		;Nächstes Bit holen
		bcc	1$		;und ausgeben
		DATA_HI
		bra	2$
1$		DATA_LO
2$
		bsr	Wait40us	;Bit set-up talker delay

		CLOCK_HI		;Data valid

		bsr	Wait20us	;Data valid delay

		DATA_HI
		CLOCK_LO

		dbra	d7,IBOBitLoop	;Nächstes Bit

; 1ms auf Bestätigung warten
		bsr	Start1ms	;Timer starten

IBODACLoop	bsr	TimerDoneQ	;Timer abgelaufen?
		bne	IBOTimeOut	;Ja, dann Time out
		GET_DATA_CLOCK		;Auf Listener data accepted warten
		bcs	IBODACLoop

; Alles OK
		moveq	#ST_OK,d0
		bra	IBODone

; Fehlerbehandlung
IBODevNotPr	LINE_RELEASE
		move.b	#ST_NOTPRESENT,d0
		bra	IBODone

IBOTimeOut	LINE_RELEASE
		move.b	#ST_TIMEOUT,d0

IBODone		JSRLIB	Enable

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


*
* Byte über IEC-Kabel empfangen
* Rückgabe: d1.b: Empfangenes Byte
*

IECByteIn	tst.b	IECIsOpen
		bne	1$
		move.b	#ST_NOTPRESENT,d0
		rts

1$		movem.l	d2/d3/d7/a6,-(sp)
		moveq	#ST_OK,d3	;d3: Status
		move.l	_SysBase,a6
		JSRLIB	Disable

		CLOCK_HI
		WAIT_CLOCK_HI

; Auf Beginn der Übertragung warten
		bsr	Start250us	;Timer starten
		DATA_HI			;Listener ready for data

IBIWaitTalker	bsr	TimerDoneQ	;Timer abgelaufen?
		bne	IBIEOI		;Ja, EOI empfangen
		GET_DATA_CLOCK		;Warten auf talker sending
		bmi	IBIWaitTalker
		bra	IBIReceive

; EOI empfangen, Handshake senden und erneut auf Beginn warten
IBIEOI		move.b	#ST_EOF,d3
		DATA_LO			;EOI handshake
		CLOCK_HI
		bsr	Wait60us	;EOI response hold time

		bsr	Start250us	;Timer starten
		DATA_HI			;Listener ready for data

IBIWaitTalker2	bsr	TimerDoneQ	;Timer abgelaufen?
		bne	IBITimeOut	;Ja, dann Time out
		GET_DATA_CLOCK		;Warten auf talker sending
		bmi	IBIWaitTalker2

; 8 Bits empfangen
IBIReceive	moveq	#7,d7		;d7: Bitzähler
		moveq	#0,d2		;d2: Empfangenes Byte

IBIBitLoop	WAIT_CLOCK_HI		;Auf Data valid warten
		roxr.b	#1,d2		;Datenbit holen
		WAIT_CLOCK_LO
		dbra	d7,IBIBitLoop

; Empfang bestätigen
		DATA_LO			;Listener data accepted
		btst	#6,d3		;EOI empfangen?
		beq	IBIDone
		LINE_RELEASE		;Ja, Leitung freigeben
		bra	IBIDone

; Fehlerbehandlung
IBITimeOut	or.b	#ST_READ_TIMEOUT,d3
		LINE_RELEASE

IBIDone		move.b	d3,d0		;Status holen
		move.b	d2,d1		;Byte holen
		JSRLIB	Enable
		movem.l	(sp)+,d2/d3/d7/a6
		rts


*
* 20µs/40µs/60µs/1ms warten
*

; Timer auf 20µs setzen
Wait20us	move.l	CiaControlReg,a0
		and.b	#$80,(a0)
		move.l	CiaTimerReg,a0
		move.b	#14,(a0)
		move.b	#0,$100(a0)
		bra	StartTimer

; Timer auf 40µs setzen
Wait40us	move.l	CiaControlReg,a0
		and.b	#$80,(a0)
		move.l	CiaTimerReg,a0
		move.b	#29,(a0)
		move.b	#0,$100(a0)
		bra	StartTimer

; Timer auf 60µs setzen
Wait60us	move.l	CiaControlReg,a0
		and.b	#$80,(a0)
		move.l	CiaTimerReg,a0
		move.b	#43,(a0)
		move.b	#0,$100(a0)
		bra	StartTimer

; Timer auf 1ms setzen
Wait1ms		move.l	CiaControlReg,a0
		and.b	#$80,(a0)
		move.l	CiaTimerReg,a0
		move.b	#$cc,(a0)
		move.b	#2,$100(a0)
		move.l	CiaControlReg,a0

; Timer anwerfen (One-Shot) und auf Ablauf warten
StartTimer	move.l	a6,-(sp)
		move.l	_CiaBase,a6

		move.b	WhichTimerMask,d0
		JSRLIB	SetICR

		move.l	CiaControlReg,a0
		move.b	#CIACRAF_LOAD|CIACRAF_RUNMODE|CIACRAF_START,(a0)

1$		moveq	#0,d0
		JSRLIB	SetICR
		and.b	WhichTimerMask,d0
		beq	1$

		move.l	(sp)+,a6
		rts


*
* Timer starten, 1ms/250µs
*

Start1ms	move.l	a6,-(sp)
		move.l	_CiaBase,a6
		move.l	CiaControlReg,a0
		and.b	#$80,(a0)

		move.l	CiaTimerReg,a0
		move.b	#$cc,(a0)
		move.b	#2,$100(a0)

		move.b	WhichTimerMask,d0
		JSRLIB	SetICR

		move.l	CiaControlReg,a0
		or.b	#CIACRAF_LOAD|CIACRAF_RUNMODE|CIACRAF_START,(a0)
		move.l	(sp)+,a6
		rts

Start250us	move.l	a6,-(sp)
		move.l	_CiaBase,a6
		move.l	CiaControlReg,a0
		and.b	#$80,(a0)

		move.l	CiaTimerReg,a0
		move.b	#$b3,(a0)
		move.b	#0,$100(a0)

		move.b	WhichTimerMask,d0
		JSRLIB	SetICR

		move.l	CiaControlReg,a0
		or.b	#CIACRAF_LOAD|CIACRAF_RUNMODE|CIACRAF_START,(a0)
		move.l	(sp)+,a6
		rts


*
* Timer abgelaufen?
* bne: Ja
*

TimerDoneQ	move.l	a6,-(sp)
		move.l	_CiaBase,a6
		moveq	#0,d0
		JSRLIB	SetICR
		and.b	WhichTimerMask,d0
		move.l	(sp)+,a6
		tst.b	d0
		rts


*
* Dummy-Timer-Interrupt-Routine
*

IntTimer	moveq	#0,d0
		rts


**
** Konstanten
**

; Tabelle für Fehlermeldungen (Index: ErrorCode)
		CNOP	0,4
ErrorTab	dc.l	OKText
		dc.l	StartupText
		dc.l	FileNotFText

; Fehlertexte
OKText		dc.b	"00, OK,00,00",13
StartupText	dc.b	"73,CBM DOS V2.6 1541,00,00",13
FileNotFText	dc.b	"62, FILE NOT FOUND,00,00",13

; Tabelle mit Zeigern auf die Laufwerks-Datenstrukturen
		CNOP	0,4
DrvTab		dc.l	Drv8Data
		dc.l	Drv9Data
		dc.l	Drv10Data
		dc.l	Drv11Data

; Name der temporären Datei für das Directory
TempFileName	dc.b	"T:Frodo$File",0

; Name für AllocMiscResource
ParPortName	dc.b	"Frodo Parallel Port",0

; Name des Timer-Interrupts
TimerIntName	dc.b	"Frodo Timer Int",0
		CNOP	0,4


**
** Initialisierte Daten
**

		SECTION	"DATA",DATA

TimerInterrupt	dc.l	0,0
		dc.b	NT_INTERRUPT,0
		dc.l	TimerIntName
		dc.l	0
		dc.l	IntTimer


**
** Nicht initialisierte Daten
**

		SECTION	"BSS",BSS

; Global
ListenerActive	ds.b	1	;Flag: Listener angewählt, ListenerData ist gültig
ListenerIsIEC	ds.b	1	;Flag: Aktiver Listener hängt am IEC-Kabel (impliziert ListenerActive)
TalkerActive	ds.b	1	;Flag: Talker angewählt, TalkerData ist gültig
TalkerIsIEC	ds.b	1	;Flag: Aktiver Talker hängt am IEC-Kabel (impliziert TalkerActive)

Listening	ds.b	1	;Flag: Letzter ATN-Befehl war Listen (für SecListen/SecTalk-Unterscheidung)
SecAddr		ds.b	1	;Aktuelle Sekundäradresse ($0x)
ReceivedCmd	ds.b	1	;Empfangener Befehlscode ($x0)

IECIsOpen	ds.b	1	;Flag: Timer und Port belegt, IEC-Kabel kann benutzt werden

WhichTimer	ds.b	1	;Welcher Timer wurde belegt? (0: Timer A, 1: Timer B)
WhichTimerMask	ds.b	1	;Dto. als Bit-Maske für ICR

		CNOP	0,4
CiaTimerReg	ds.l	1	;Zeiger auf Cia-Timer-Register
CiaControlReg	ds.l	1

ListenerData	ds.l	1	;Zeiger auf Struktur des aktiven Listeners
TalkerData	ds.l	1	;Zeiger auf Struktur des aktiven Talkers

; Datenstruktur pro Laufwerk
Drv8Data	ds.b	drv_SIZEOF
Drv9Data	ds.b	drv_SIZEOF
Drv10Data	ds.b	drv_SIZEOF
Drv11Data	ds.b	drv_SIZEOF

; Puffer für die Verzeichnisse der 4 Laufwerke
Dir8		ds.b	256
Dir9		ds.b	256
Dir10		ds.b	256
Dir11		ds.b	256

; Puffer für Dateinamen
NameBuf		ds.b	64

; Einstellungen
Drv8IsIEC	ds.w	1
Drv9IsIEC	ds.w	1
Drv10IsIEC	ds.w	1
Drv11IsIEC	ds.w	1
OtherIEC	ds.w	1

		CNOP	0,4
TheFIB		ds.b	fib_SIZEOF	;FileInfoBlock

		END
