*
* IEC.asm - IEC-Bus-Routinen, 1541-Emulation
*
* Copyright (C) 1994-1996 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.
*
* DRVDATA/drv_Type/drv_#?Proc:
*  - Jedem Laufwerk (8-11) ist eine Datenstruktur DRVDATA zugeordnet, die
*    u.a. den Typ des Laufwerks enthält (Amiga-Dateisystem, .d64-Datei,
*    IEC-Kabel).
*  - Je nach Typ werden die sechs Prozedurzeiger drv_InitProc, drv_ExitProc,
*    drv_OpenProc, drv_CloseProc, drv_ReadProc und drv_WriteProc gesetzt, um
*    entweder die Emulation im Amiga-Dateisystem oder in einer .d64-Datei
*    auszuwählen. Nur für Geräte am IEC-Kabel erfolgt die Ein-/Ausgabe
*    direkt hier, für die anderen Typen ist sie in den Include-Files
*    "1541fs.i" und "1541d64.i" gekapselt.
*  - Die drv_#?Proc-Zeiger bilden eine transparente Schnittstelle zur
*    Implementation eines Gerätes (im Prinzip ließe sich so auch eine
*    Druckeremulation einbauen):
*      drv_InitProc bereitet die Emulation vor
*      drv_ExitProc beendet die Emulation
*      drv_OpenProc öffnet einen Kanal
*      drv_CloseProc schließt einen Kanal
*      drv_ReadProc liest aus einem Kanal
*      drv_WriteProc schreibt in einen Kanal
*
* Inkompatibilitäten/Verbesserungen:
*  - Es kann nur ein Listener gleichzeitig aktiv sein
*

		MACHINE	68020

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

		XREF	_SysBase
		XREF	_DOSBase
		XREF	_CiaBase
		XREF	_MiscBase
		XREF	_UtilityBase

		XREF	_ciaaprb
		XREF	_ciaaddrb

		XREF	_FS_Init	;1541fs.o
		XREF	_FS_Exit
		XREF	_FS_Open
		XREF	_FS_Close
		XREF	_FS_Read
		XREF	_FS_Write

		XREF	_D64_Init	;1541d64.o
		XREF	_D64_Exit
		XREF	_D64_Open
		XREF	_D64_Close
		XREF	_D64_Read
		XREF	_D64_Write

		XDEF	InitIEC
		XDEF	ExitIEC
		XDEF	ChangedIECPrefs
		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	Drv8Type
		XDEF	Drv9Type
		XDEF	Drv10Type
		XDEF	Drv11Type
		XDEF	OtherIEC
		XDEF	MapSlash

		XREF	PDir8		;Für ChangedIECPrefs
		XREF	PDir9
		XREF	PDir10
		XREF	PDir11
		XREF	PDrv8Type
		XREF	PDrv9Type
		XREF	PDrv10Type
		XREF	PDrv11Type

		SECTION	"text",CODE


**
** Definitionen
**

; 1541-Fehlercodes (genauer: Die Nummer des zugeh. Fehlertextes)
ERR_OK		= 0	;00: OK
ERR_WRITEERROR	= 1	;25: WRITE ERROR
ERR_WRITEPROTECT = 2	;26: WRITE PROTECT ON
ERR_SYNTAX30	= 3	;30: SYNTAX ERROR (Unbekannter Befehl)
ERR_SYNTAX33	= 4	;33: SYNTAX ERROR (Wildcards beim Schreiben)
ERR_WRITEFILEOPEN = 5	;60: WRITE FILE OPEN
ERR_FILENOTOPEN	= 6	;61: FILE NOT OPEN
ERR_FILENOTFOUND = 7	;62: FILE NOT FOUND
ERR_ILLEGALTS	= 8	;67: ILLEGAL TRACK OR SECTOR
ERR_NOCHANNEL	= 9	;70: NO CHANNEL
ERR_STARTUP	= 10	;73: Einschaltmeldung

; Datenstruktur für ein Laufwerk
 STRUCTURE DRVDATA,0
	BYTE	drv_Type		;Laufwerks-Typ (DRTYPE_*)
	BYTE	drv_ReadChar		;Byte-Puffer zum Lesen aus Dateien
	BYTE	drv_NameLen		;Bisher empfangene Länge des Dateinamens
	BYTE	drv_ErrorLen		;Restliche Länge der Fehlermeldung
	APTR	drv_NamePtr		;Zeiger in Dateinamen, für Open/IECOut
	APTR	drv_ErrorPtr		;Zeiger in Fehlermeldung
	APTR	drv_Lock		;Lock des Verzeichnisses des Laufwerks
					; bzw. Handle der .d64-Datei

					;Prozeduren:
	APTR	drv_InitProc		; Initialisieren
	APTR	drv_ExitProc		; Beenden
	APTR	drv_OpenProc		; Kanal öffnen
	APTR	drv_CloseProc		; Kanal schließen
	APTR	drv_ReadProc		; Byte lesen
	APTR	drv_WriteProc		; Byte schreiben

	STRUCT	drv_CmdBuffer,44	;Eingabepuffer für Kommandos
	LONG	drv_CmdLen		;Länge des bisher empfangenen Kommandos

	STRUCT	drv_Handle,16*4		;FS: FileHandles für alle 16 Kanäle

	STRUCT	drv_ChanMode,16		;D64: Kanalmodus
	STRUCT	drv_ChanBufNum,16	;D64: Nummer des Puffers des Kanals
	STRUCT	drv_ChanBuf,16*4	;D64: Zeiger auf Puffer des Kanals
	STRUCT	drv_BufPtr,16*4		;D64: Aktuelle Position im Puffer
	STRUCT	drv_BufLen,16*4		;D64: Restliche Anzahl Bytes im Puffer

	APTR	drv_RAM			;D64: 2KB RAM für das Laufwerk
	STRUCT	drv_BufFree,4		;D64: Puffer 0..3 frei?

	LONG	drv_ImageHeader		;D64: Länge des Headers der Image-Datei (.d64: 0, .x64: 64)

	APTR	drv_BAM			;D64: Zeiger auf Puffer für BAM (Puffer 4, $700)
	STRUCT	drv_DIR,256		;D64: Temporärer Puffer zum Directory-Lesen und -Scannen
 LABEL drv_SIZEOF

; Drv?Type
DRTYPE_DIR	= 0	;1541-Emulation in Amiga-Verzeichnis
DRTYPE_D64	= 1	;1541-Emulation in .d64-Datei
DRTYPE_IEC	= 2	;Echte 1541 am IEC-Kabel

; 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

; Länge des Namenspuffers, muß zwischen 32 und 256 liegen
NAMEBUF_LENGTH	= 256

; 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


**
** IEC-Emulation starten
**
** Rückgabe: d0=0 Alles OK
**           d0=1 Parallelport belegt
**           d0=2 Kein Timer frei
**

; Emulation aller 4 Laufwerke vorbereiten
InitIEC		lea	Drv8Data,a0
		move.w	Drv8Type,d0
		bsr	SetDrvProcs

		pea	Dir8
		pea	Drv8Data
		move.l	Drv8Data+drv_InitProc,a0
		jsr	(a0)
		addq.l	#8,sp

		lea	Drv9Data,a0
		move.w	Drv9Type,d0
		bsr	SetDrvProcs

		pea	Dir9
		pea	Drv9Data
		move.l	Drv9Data+drv_InitProc,a0
		jsr	(a0)
		addq.l	#8,sp

		lea	Drv10Data,a0
		move.w	Drv10Type,d0
		bsr	SetDrvProcs

		pea	Dir10
		pea	Drv10Data
		move.l	Drv10Data+drv_InitProc,a0
		jsr	(a0)
		addq.l	#8,sp

		lea	Drv11Data,a0
		move.w	Drv11Type,d0
		bsr	SetDrvProcs

		pea	Dir11
		pea	Drv11Data
		move.l	Drv11Data+drv_InitProc,a0
		jsr	(a0)
		addq.l	#8,sp

; Flag setzen
		st.b	IECInited

; Resourcen für das IEC-Kabel belegen, wenn nötig
		bra	CheckCablePrefs


**
** IEC-Emulation beenden
**

; Resourcen des IEC-Kabels freigeben
ExitIEC		bsr	CloseIECBus

; Emulation aller 4 Laufwerke beenden
		tst.b	IECInited
		beq	1$

		pea	Drv8Data
		move.l	Drv8Data+drv_ExitProc,a0
		jsr	(a0)
		addq.l	#4,sp

		pea	Drv9Data
		move.l	Drv9Data+drv_ExitProc,a0
		jsr	(a0)
		addq.l	#4,sp

		pea	Drv10Data
		move.l	Drv10Data+drv_ExitProc,a0
		jsr	(a0)
		addq.l	#4,sp

		pea	Drv11Data
		move.l	Drv11Data+drv_ExitProc,a0
		jsr	(a0)
		addq.l	#4,sp
1$

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


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

; Laufwerke schließen und neu öffnen, wenn sich die Einstellungen geändert haben
ChangedIECPrefs	lea	Drv8Data,a3
		lea	Dir8,a4
		lea	PDir8,a5
		move.w	PDrv8Type,d4
		bsr	DrvReInit

		lea	Drv9Data,a3
		lea	Dir9,a4
		lea	PDir9,a5
		move.w	PDrv9Type,d4
		bsr	DrvReInit

		lea	Drv10Data,a3
		lea	Dir10,a4
		lea	PDir10,a5
		move.w	PDrv10Type,d4
		bsr	DrvReInit

		lea	Drv11Data,a3
		lea	Dir11,a4
		lea	PDir11,a5
		move.w	PDrv11Type,d4
		bsr	DrvReInit

; Einstellungen übertragen
CheckCablePrefs	moveq	#0,d1

		cmp.w	#DRTYPE_IEC,PDrv8Type
		seq	d0
		or.b	d0,d1

		cmp.w	#DRTYPE_IEC,PDrv9Type
		seq	d0
		or.b	d0,d1

		cmp.w	#DRTYPE_IEC,PDrv10Type
		seq	d0
		or.b	d0,d1

		cmp.w	#DRTYPE_IEC,PDrv11Type
		seq	d0
		or.b	d0,d1

		or.w	OtherIEC,d1	;d1: Flag: IEC-Kabel wird benötigt

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


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

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

; Parallelport belegen
		move.l	_MiscBase,d0
		beq	OIECNoPort
		move.l	d0,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
*

CloseIECBus	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


*
* Laufwerk neu initialisieren, falls nötig
*
* a3.l: Zeiger auf DRVDATA
* a4.l: Alter Verzeichnis-String
* a5.l: Neuer Verzeichnis-String
* d4.w: Neues Drv?Type
*

DrvReInit	move.l	_UtilityBase,a6

		move.l	a4,a0		;Hat sich das Verzeichnis/die Datei geändert?
		move.l	a5,a1
		move.l	#255,d0
		JSRLIB	Strnicmp
		tst.l	d0
		bne	1$

		cmp.b	drv_Type(a3),d4	;Nein, hat sich der Typ geändert?
		beq	2$

1$		move.l	a3,-(sp)	;Ja, Exit aufrufen
		move.l	drv_ExitProc(a3),a0
		jsr	(a0)
		addq.l	#4,sp

		move.l	a3,a0		;Neue Prozeduren setzen
		move.w	d4,d0
		bsr	SetDrvProcs

		move.l	a5,-(sp)	;Und neues Init aufrufen
		move.l	a3,-(sp)
		move.l	drv_InitProc(a3),a0
		jsr	(a0)
		addq.l	#8,sp
2$		rts


*
* Prozeduren in DRVDATA je nach Typ setzen
*
* a0.l: Zeiger auf DRVDATA
* d0.w: Drv?Type
*

SetDrvProcs	move.b	d0,drv_Type(a0)		;Typ setzen

		cmp.w	#DRTYPE_D64,d0
		beq	1$

		move.l	#_FS_Init,drv_InitProc(a0)
		move.l	#_FS_Exit,drv_ExitProc(a0)
		move.l	#_FS_Open,drv_OpenProc(a0)
		move.l	#_FS_Close,drv_CloseProc(a0)
		move.l	#_FS_Read,drv_ReadProc(a0)
		move.l	#_FS_Write,drv_WriteProc(a0)
		rts

1$		move.l	#_D64_Init,drv_InitProc(a0)
		move.l	#_D64_Exit,drv_ExitProc(a0)
		move.l	#_D64_Open,drv_OpenProc(a0)
		move.l	#_D64_Close,drv_CloseProc(a0)
		move.l	#_D64_Read,drv_ReadProc(a0)
		move.l	#_D64_Write,drv_WriteProc(a0)
		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
		cmp.b	#DRTYPE_IEC,drv_Type(a0) ;IEC-Gerät?
		beq	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
		cmp.b	#DRTYPE_IEC,drv_Type(a0) ;IEC-Gerät?
		beq	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)
		clr.b	drv_NameLen(a0)
		moveq	#ST_OK,d0
		rts


*
* Close-Befehl: Kanal schließen
*

SecListenClose	move.l	ListenerData,a0
		moveq	#0,d0
		move.b	SecAddr,d0
		move.l	d0,-(sp)
		move.l	a0,-(sp)
		move.l	drv_CloseProc(a0),a0
		jsr	(a0)
		addq.l	#8,sp
		rts


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

SecTalk		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		move.l	ListenerData,a0
		cmp.b	#NAMEBUF_LENGTH-1,drv_NameLen(a0) ;Noch genügend Platz im NameBuffer?
		bhs	2$
		move.l	drv_NamePtr(a0),a1	;Ja, Zeichen speichern
		move.b	d0,(a1)+
		move.l	a1,drv_NamePtr(a0)
		addq.b	#1,drv_NameLen(a0)

2$		tst.b	d1			;EOI?
		bpl	1$

		move.l	ListenerData,a0		;Ja, Datei öffnen
		move.l	drv_NamePtr(a0),a1	;Dateiname mit Nullbyte abschließen
		clr.b	(a1)
		pea	NameBuf
		moveq	#0,d0
		move.b	SecAddr,d0
		move.l	d0,-(sp)
		move.l	a0,-(sp)
		move.l	drv_OpenProc(a0),a0
		jsr	(a0)
		lea	12(sp),sp

1$		moveq	#ST_OK,d0
		rts


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

DataIn		move.l	TalkerData,a0
		moveq	#0,d0
		move.b	SecAddr,d0
		pea	DataInBuf
		move.l	d0,-(sp)
		move.l	a0,-(sp)
		move.l	drv_ReadProc(a0),a0
		jsr	(a0)
		lea	12(sp),sp
		move.b	DataInBuf,d1
		rts


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

DataOut		move.l	ListenerData,a0
		move.l	d1,-(sp)
		move.l	d0,-(sp)
		moveq	#0,d0
		move.b	SecAddr,d0
		move.l	d0,-(sp)
		move.l	a0,-(sp)
		move.l	drv_WriteProc(a0),a0
		jsr	(a0)
		lea	16(sp),sp
		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


*
* SetError: 1541-Fehlermeldung bereitstellen
*
* a0.l: Zeiger auf DRVDATA
* d0.l: Fehlercode
*

		XDEF	_SetError
_SetError	move.l	4(sp),a0
		move.l	8(sp),d0
		lea	ErrorTab,a1
		move.l	(a1,d0.l*4),a1
		move.l	a1,drv_ErrorPtr(a0)

		move.l	a2,-(sp)
		move.l	a1,a2
1$		tst.b	(a2)+
		bne	1$
		sub.l	a1,a2
		move.l	a2,d0
		subq.l	#1,d0
		move.b	d0,drv_ErrorLen(a0)
		move.l	(sp)+,a2
		rts


**
** Konstanten
**

; Tabelle für Fehlermeldungen (Index: ErrorCode)
		CNOP	0,4
ErrorTab	dc.l	OKText
		dc.l	WriteErrText
		dc.l	WriteProtText
		dc.l	Syntax30Text
		dc.l	Syntax33Text
		dc.l	WrFileOpenText
		dc.l	FileNotOpenText
		dc.l	FileNotFText
		dc.l	IllegalTSText
		dc.l	NoChannelText
		dc.l	StartupText

; Fehlertexte
OKText		dc.b	"00, OK,00,00",13,0
WriteErrText	dc.b	"25,WRITE ERROR,00,00",13,0
WriteProtText	dc.b	"26,WRITE PROTECT ON,00,00",13,0
Syntax30Text	dc.b	"30,SYNTAX ERROR,00,00",13,0
Syntax33Text	dc.b	"33,SYNTAX ERROR,00,00",13,0
WrFileOpenText	dc.b	"60,WRITE FILE OPEN,00,00",13,0
FileNotOpenText	dc.b	"61,FILE NOT OPEN,00,00",13,0
FileNotFText	dc.b	"62,FILE NOT FOUND,00,00",13,0
IllegalTSText	dc.b	"67,ILLEGAL TRACK OR SECTOR,00,00",13,0
NoChannelText	dc.b	"70,NO CHANNEL,00,00",13,0
StartupText	dc.b	"73,CBM DOS V2.6 1541,00,00",13,0
		CNOP	0,4

; 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
IECInited	ds.b	1	;Flag: InitIEC wurde aufgerufen

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

DataInBuf	ds.b	1	;Puffer für DataIn

; Datenstruktur pro Laufwerk
		CNOP	0,4
Drv8Data	ds.b	drv_SIZEOF
Drv9Data	ds.b	drv_SIZEOF
Drv10Data	ds.b	drv_SIZEOF
Drv11Data	ds.b	drv_SIZEOF

; Puffer für Dateinamen/Befehlsstrings
NameBuf		ds.b	NAMEBUF_LENGTH

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

; Einstellungen
Drv8Type	ds.w	1
Drv9Type	ds.w	1
Drv10Type	ds.w	1
Drv11Type	ds.w	1
OtherIEC	ds.w	1
		XDEF	_MapSlash
_MapSlash
MapSlash	ds.w	1
		END
