 ; /*
 ; ##########################################################################
 ; ####                                                                  ####
 ; ####         TurboDevice - A resident RAM disk for the Amiga          ####
 ; ####        =================================================         ####
 ; ####                                                                  ####
 ; #### TurboDev.asm                                                     ####
 ; ####                                                                  ####
 ; #### Version 1.00os  --  May 04, 1991                                 ####
 ; ####                                                                  ####
 ; #### Copyright (C) 1990  Thomas Dreibholz                             ####
 ; ####               1991  Molbachweg 7                                 ####
 ; ####                     51674 Wiehl/Germany                          ####
 ; ####                     EMail: Dreibholz@bigfoot.com                 ####
 ; ####                     WWW:   http://www.bigfoot.com/~dreibholz     ####
 ; ####                                                                  ####
 ; ##########################################################################
 ; */
 ;/***************************************************************************
 ; *                                                                         *
 ; *   This program is free software; you can redistribute it and/or modify  *
 ; *   it under the terms of the GNU General Public License as published by  *
 ; *   the Free Software Foundation; either version 2 of the License, or     *
 ; *   (at your option) any later version.                                   *
 ; *                                                                         *
 ; ***************************************************************************/

 ; #######################################################
 ; ##                                                   ##
 ; ##            Das Turbo-Drive-System                 ##
 ; ##            ======================                 ##
 ; ##                                                   ##
 ; ##  Entwicklungszeit:     21 Monate                  ##
 ; ##  Fertigstellung:       4. Mai 1991  18:10:18 Uhr  ##
 ; ##  Aktuelle Version:     29. Jun 1991               ##
 ; ##                                                   ##
 ; #######################################################

 ; Diese Dateien werden vom Assembler eingebunden

 include "exec/types.i"
 include "exec/initializers.i"
 include "exec/libraries.i"
 include "exec/lists.i"
 include "exec/nodes.i"
 include "exec/resident.i"
 include "exec/alerts.i"
 include "exec/ables.i"
 include "exec/devices.i"
 include "exec/io.i"
 include "exec/memory.i"
 include "exec/errors.i"
 include "libraries/dos.i"
 include "libraries/dosextens.i"

 ; Es folgen 2 Makros zum Aufrufen von Library-Funktionen

CALLSYS  MACRO
 jsr _LVO%1(a6)
 endm
LINKSYS  MACRO
 move.l a6,-(a7)
 move.l %2,a6
 jsr _LVO%1(a6)
 move.l (a7)+,a6
 endm

 ; XLIB wird zum Einbinden der Library-Offsets benutzt

XLIB  MACRO
 XREF _LVO%1
 endm

 ; TURBO_NUMUNITS gibt die Anzahl der verfügbaren Units an.
 ; Das turbo.device stellt 50 Verwaltungseinheiten (Units)
 ; zur Verfügung.

TURBO_NUMUNITS EQU 50

 ; TurboDev ist die Device-Struktur die das Device verwaltet.
 ; in fd_Units befinden sich Zeiger auf die einzelnen Unit-
 ; -Strukturen.

 STRUCTURE TurboDev,LIB_SIZE
  ULONG  td_SysLib                    ; exec.library
  ULONG  td_DosLib                    ; dos.library
  ULONG  td_SegList                   ; Segmentliste
  ULONG  td_Resident                  ; Resident (nicht immer belegt!)
  UBYTE  td_Flags                     ; Flags
  UBYTE  td_DriveNum                  ; nächste Namennummer
  STRUCT td_Units,TURBO_NUMUNITS*4    ; Zeiger auf Units
 LABEL TurboDev_SIZE

 ; TurboDevMsg wird für den Unit-Prozeß benötigt.

 STRUCTURE TurboDevMsg,MN_SIZE
  APTR tdm_Device                     ; Zeiger auf Device
  APTR tdm_Unit                       ; Zeiger auf Unit
 LABEL TurboDevMsg_SIZE

 ; TurboDevUnit ist die Struktur zur Verwaltung der einzelnen
 ; Verwaltungseinheiten (Units).

 STRUCTURE TurboDevUnit,UNIT_SIZE
  UBYTE  tdu_UnitNum                  ; Unit-Nummer
  UBYTE  tdu_pad                      ; Füllbyte
  STRUCT tdu_Msg,TurboDevMsg_SIZE     ; TurboDevMsg
  APTR   tdu_Process                  ; Unit-Prozeß
  LONG   tdu_LastComm                 ; Letztes Kommando für LastComm
  LONG   tdu_Size                     ; Kapazität des Laufwerks (Bytes)
  ULONG  tdu_NumTracks                ; Anzahl der Tracks
  ULONG  tdu_Position                 ; Lese/Schreibkopf-Position
  APTR   tdu_Memory                   ; Zeiger auf den Speicherbereich
 LABEL TurboDevUnit_SIZE

 ; Mit den Funktionen Stop und Start läßt sich die Verarbeitung
 ; anhalten. In UNIT_FLAGS wird FDUF_STOPPED gesetzt.

 BITDEF TDU,STOPPED,2                 ; Stop-Bit

TURBONAME MACRO                       ; Name des Turbo-Devices
 dc.b 'turbo.device',0
 endm

 ; Diese Funktionen werden vom Linker eingebunden.

 XREF _GetMemory                      ; Funktion zum Organisieren des Speichers
 XREF _GetDriveSize                   ; Funktion zur Kapazitätsberechnung
 XLIB OpenLibrary                     ; Library öffnen
 XLIB CloseLibrary                    ; Library schließen
 XLIB Alert                           ; Guru ausgeben
 XLIB FreeMem                         ; Speicher freigeben
 XLIB Remove                          ; Node entfernen
 XLIB FindTask                        ; Task suchen
 XLIB AllocMem                        ; Speicher belegen
 XLIB CreateProc                      ; Prozeß erzeugen
 XLIB PutMsg                          ; Message senden
 XLIB RemTask                         ; Task entfernen
 XLIB ReplyMsg                        ; Message zurücksenden
 XLIB Signal                          ; Signale setzen
 XLIB GetMsg                          ; Message erwarten
 XLIB Wait                            ; Auf Signal warten
 XLIB WaitPort                        ; Auf Message warten
 XLIB AllocSignal                     ; Signal belegen
 XLIB SetTaskPri                      ; Taskpriorität setzen
 XLIB Permit                          ; Multitasking zulassen

 INT_ABLES

 ; Damit das Device, wenn er versehentlich gestartet wurde,
 ; keinen Absturz erzeugt, wird das Device in START wieder
 ; beendet.

START:
 move.l #$20000,d0                    ; $20000 in d0
1$:                                   ; Schleife
 move.w #$0F00,$DFF180                ; Rot in Farbregister 0
 subq.l #1,d0                         ; 1 subtrahieren
 tst.l d0                             ; ist d0=0 ?
 bne 1$                               ; Nein, dann zu 1$
 moveq.l #0,d0                        ; Keine Fehlermeldung
 rts                                  ; Rücksprung

PRIORITY:   EQU 50                    ; Priorität
VERSION:    EQU 35                    ; Version
REVISION:   EQU 0                     ; Revision

 ; Hier beginnt die Resident-Struktur für das Device

InitTable:
 dc.w RTC_MATCHWORD                   ; Code für Resident
 dc.l InitTable                       ; Zeiger auf Anfang
 dc.l EndCode                         ; Zeiger auf Ende
 dc.b RTF_AUTOINIT                    ; Flag für autom. Installation
 dc.b VERSION                         ; Version
 dc.b NT_DEVICE                       ; Flag für Device
 dc.b PRIORITY                        ; Priorität
 dc.l TurboName                       ; Name der Devices
 dc.l idString                        ; Zeiger auf Kommentar
 dc.l Init                            ; Zeiger auf Intallationstabelle

 ; Hier stehen die Namen für die Installation

TurboName:  TURBONAME
idString: dc.b 'turbo.device 35.0 (17 Okt 1991)',13,10,0   ; Kommentar
dosName:  dc.b "dos.library",0        ; Name der dos.library
 ds.w 0                               ; Auf gerade Adresse

EndCode:                              ; Ende der Resident-Struktur

Init:                                 ; Installationstabelle
 dc.l TurboDev_SIZE                   ; Größe der Device-Struktur
 dc.l funcTable                       ; Funktionstabelle
 dc.l dataTable                       ; Datentabelle
 dc.l InitRoutine                     ; Zeiger auf Installationsroutine

funcTable:                            ; Funktionstabelle
 dc.l Open                            ; Öffnen des Devices
 dc.l Close                           ; Schließen des Devices
 dc.l Expunge                         ; Entfernen des Devices
 dc.l Null                            ; Unbenutzte Funktion
 dc.l BeginIO                         ; Kommandohandler
 dc.l AbortIO                         ; Abbruch einer Funktion
 dc.l CopyMemFast                     ; Kopierroutine (512 Bytes/Durchlauf)
 dc.l CopyMemSlow                     ; Kopierroutine (byteweise)
 dc.l CheckUnit                       ; Unit gültig ?
 dc.l -1                              ; Ende der Tabelle

dataTable:                            ; Datentabelle
 INITBYTE LH_TYPE,NT_DEVICE           ; Typ der Tabelle
 INITLONG LN_NAME,TurboName           ; Name
 INITBYTE LIB_FLAGS,LIBF_SUMUSED!LIBF_CHANGED ; Flags
 INITWORD LIB_VERSION,VERSION         ; Version
 INITWORD LIB_REVISION,REVISION       ; Revision
 INITLONG LIB_IDSTRING,idString       ; Kommentar
 dc.l 0                               ; Ende der Tabelle

 ; ========================================================
 ; = Hier steht die Installationsroutine, die beim ersten =
 ; = Zugriff auf das Device aufgerufen wird, um die Stru- =
 ; = ktur vollständig zu installieren.                    =
 ; = Ihe Parameter sind:                                  =
 ; =  => D0 = Zeiger auf die Device-Struktur              =
 ; =  => A0 = Zeiger auf die Segmentliste                 =
 ; =  => A6 = Zeiger auf die exec.library                 =
 ; =                                                      =
 ; =  <= D0 = Zeiger auf die Device-Struktur              =
 ; ========================================================

InitRoutine:
 move.l a5,-(a7)                      ; a5 retten
 move.l d0,a5                         ; Device nach a5
 move.l a6,td_SysLib(a5)              ; exec.library in SysLib
 move.l a0,td_SegList(a5)             ; Segmentliste in SegList
 lea    dosName(pc),a1                ; Name der dos.library
 moveq  #0,d0                         ; Version=0
 CALLSYS OpenLibrary                  ; Library öffnen
 move.l d0,td_DosLib(a5)              ; dos.library in DosLib
 bne.s Dos_OK                         ; Konnte Library geöffnet werden ?
 ALERT AG_OpenLib!AO_DOSLib           ; Nein, dann Reset
Dos_OK:                               ; Ja
 move.b #1,td_DriveNum(a5)            ; Start-Namensnummer ist 1
 move.l a5,d0                         ; Device nach d0
 move.l (a7)+,a5                      ; a5 freigeben
 rts                                  ; Rücksprung

 ; =====================================================
 ; = Die Open-Funktion wird immer aufgerufen, wenn ein =
 ; = Programm das Turbo-Device öffnet. Sie installiert =
 ; = die Unit-Struktur.                                =
 ; = Ihre Parameter sind:                              =
 ; =  => D0 = Unitnummer                               =
 ; =  => D1 = Flags                                    =
 ; =  => A1 = Zeiger auf die IOStdReq-Struktur         =
 ; =  => A6 = Zeiger auf das Device                    =
 ; =====================================================

Open:
 movem.l d2/a2-a4,-(a7)               ; Register retten
 move.l a1,a2                         ; IOStdReq nach a2
 moveq #TURBO_NUMUNITS,d2               ; Unitanzahl nach d2
 cmp.l d2,d0                          ; Unitnummer gültig ?
 bcc.s Open_error                     ; Nein, dann springe zu Open_error
 move.l d0,d2                         ; Unitnummer nach d2
 lsl.l #2,d0                          ; Nummer mit 4 multiplizieren
 lea td_Units(a6,d0.l),a4             ; Unit-Adresse nach a4
 move.l (a4),d0                       ; Adresse nach d0
 bne.s Open_ok                        ; zu Open_ok springen
 bsr InitUnit                         ; Unit installieren
 move.l (a4),d0                       ; Unit nach d0
 beq.s Open_error                     ; Fehler? Dann springe zum Open_error
Open_ok:                              ; Open_ok:
 move.l d0,a3                         ; Unit nach a3
 move.l d0,IO_UNIT(a2)                ; Unit installieren
 addq.w #1,LIB_OPENCNT(a6)            ; Device-Benutzerzähler +1
 addq.w #1,UNIT_OPENCNT(a3)           ; Unit-Benutzerzäher +1
 bclr   #LIBB_DELEXP,td_Flags(a6)     ; Entfernungsflag löschen
Open_end:                             ; Open_end:
 movem.l (a7)+,d2/a2-a4               ; Register freigeben
 rts                                  ; Rücksprung

 ; Wenn ein Fehler aufgetreten ist, dann wird zu
 ; Open_error gesprungen.

Open_error:                           ; Open_error:
 move.b #IOERR_OPENFAIL,IO_ERROR(a2)  ; Fehler in io_Error
 bra.s  Open_end                      ; Zum Ende springen

 ; ========================================================
 ; = Wenn das Device über CloseDevice() geschlossen wird, =
 ; = wird Close aufgerufen. Diese Funktion gibt den       =
 ; = Speicher für das Unit frei, wenn Kein Programm mehr  =
 ; = auf das Unit zugreift. Wird von keinem Programm mehr =
 ; = auf das Device zugegriffen und ist das LIBB_DELEXP-  =
 ; = -Flag gesetzt, dann wird das Device mit Expunge      =
 ; = entfernt.                                            =
 ; = Ihre Parameter sind:                                 =
 ; =  => A1 = Zeiger auf die IOStdReq-Struktur            =
 ; =  => Zeiger auf die Device-Struktur                   =
 ; =                                                      =
 ; =  <= Zeiger auf die Segmentliste                      =
 ; ========================================================

Close:
 movem.l a2-a3,-(a7)                  ; Register retten
 move.l a1,a2                         ; IOStdReq nach a2
 move.l IO_UNIT(a2),a3                ; Unit nach a3
 moveq.l #-1,d0                       ; Segmentlist=-1
 move.l d0,IO_UNIT(a2)                ; Unit=-1
 move.l d0,IO_DEVICE(a2)              ; Device=-1
 subq.w #1,UNIT_OPENCNT(a3)           ; Unit-Zugriffszähler-1
 bne.s 1$                             ; Wenn nicht 0, dann springe zu 1$
 bsr ExpungeUnit                      ; Springe zu ExpungeUnit
1$:                                   ; 1$:
 clr.l d0                             ; Segmentliste=0
 subq.w #1,LIB_OPENCNT(a6)            ; Device-Zugriffszähler+1
 bne.s Close_end                      ; Wenn nicht 0, dann Ende
 btst #LIBB_DELEXP,td_Flags(a6)       ; Ist LIBB_DELEXP gesetzt ?
 beq.s Close_end                      ; Nein, dann Close_end
 bsr Expunge                          ; Springe zu Expunge
Close_end:                            ; Close_end:
 movem.l (a7)+,a2-a3                  ; Register freigeben
 rts                                  ; Rücksprung

 ; =====================================================
 ; = Expunge entfernt das Device und gibt den Speicher =
 ; = wieder frei.                                      =
 ; = Ihre Parameter sind:                              =
 ; =  => A6 = Zeiger auf das Device                    =
 ; =                                                   =
 ; =  <= D0 = Zeiger auf die Segmentliste              =
 ; =====================================================

Expunge:
 movem.l d2/a5-a6,-(a7)               ; Register retten
 move.l a6,a5                         ; Device nach a5
 move.l td_SysLib(a5),a6              ; exec.library nach a6
 tst.w  LIB_OPENCNT(a5)               ; Ist Zugriffszähler=0 ?
 beq 1$                               ; Ja, dann springe zu 1$
 bset #LIBB_DELEXP,td_Flags(a5)       ; LIBB_DELEXP-Flag löschen
 clr.l d0                             ; Segmentliste=0
 bra.s Expunge_end                    ; Springe zu Expunge_end
1$:                                   ; 1$:
 move.l td_SegList(a5),d2             ; SegList nach d2
 move.l a5,a1                         ; Device nach a1
 CALLSYS Remove                       ; Device aus DeviceList entfernen
 move.l td_DosLib(a5),a1              ; dos.library nach a1
 CALLSYS CloseLibrary                 ; Library schließen
 move.l a5,a1                         ; Device nach a1
 clr.l d0                             ; d0=0
 move.w LIB_NEGSIZE(a5),d0            ; Negative Größe nach d0
 sub.w d0,a1                          ; Negative Größe vom Device abziehen
 add.w LIB_POSSIZE(a5),d0             ; Positive Größe nach d0
 CALLSYS FreeMem                      ; Speicher freigeben
 move.l d2,d0                         ; Segmentliste nach d2
Expunge_end: movem.l (a7)+,d2/a5-a6   ; Register freigeben
 rts                                  ; Rücksprung

 ; Diese Funktion (ExtFuncDev) ist nicht belegt.

Null:
 clr.l d0                             ; Rückgabe=0
 rts                                  ; Rücksprung

TURBOPROCSTACK   EQU $200             ; Stackgröße
TURBOPROCPRI     EQU 110              ; Priorität

 ; ===========================================================
 ; = InitUnit erstellt den neuen Prozeß für die Verwaltungs- =
 ; = einheit und die neue Unit-Struktur. Dem Prozeß wird die =
 ; = Adresse des Devices in einer Message mitgeteilt. Die    =
 ; = Message ist in der Unit-Struktur integriert.            =
 ; = Ihre Parameter sind:                                    =
 ; =  => D2 = Unitnummer                                     =
 ; =  => A6 = Zeiger auf Device                              =
 ; ===========================================================

InitUnit:
 movem.l d2-d4,-(a7)                  ; Register retten
 move.l #TurboDevUnit_SIZE,d0         ; Unitgröße is d0
 move.l #MEMF_PUBLIC!MEMF_CLEAR,d1    ; Flags in d1
 LINKSYS AllocMem,td_SysLib(a6)       ; Speicher belegen
 tst.l  d0                            ; Konnte Speicher belegt werden ?
 beq InitUnit_end                     ; Nein,dann Ende
 move.l d0,a3                         ; Speicher nach a3
 move.l #0,tdu_Position(a3)           ; Lese/Schreibkopf steht auf 0
 movem.l d1-d7/a0-a6,-(sp)            ; Register retten
 move.l a3,-(sp)                      ; Unit auf Stapel
 ext.w d2                             ; Unitnummer auf Wortgröße
 ext.l d2                             ; Unitnummer auf Langwortgröße
 move.l d2,-(sp)                      ; Unitnummer auf Stapel
 move.l a6,-(sp)                      ; Device auf Stapel
 move.l $4,-(sp)                      ; ExecBase auf Stapel
 jsr _GetMemory                       ; Speicher organisieren
 add.w #16,sp                         ; Stapel erhöhen
 movem.l (sp)+,d1-d7/a0-a6            ; Register freigeben
 tst.l d0                             ; Konnte Speicher belegt werden ?
 beq InitUnit_end                     ; Nein, dann Ende
 move.l d0,tdu_Memory(a3)             ; Speicher in tdu_Memory
 clr.l tdu_LastComm(a3)               ; tdu_LastComm=0
 move.b d2,tdu_UnitNum(a3)            ; Unitnummer in UnitNum
 move.l #TURBOPROCSTACK,d4            ; Stack in d4
 move.l #TurboProc_SegList,d3         ; SegList in d3
 lsr.l #2,d3                          ; Durch 4 teilen
 moveq #TURBOPROCPRI,d2               ; Priorität in d2
 move.l #TurboName,d1                 ; Name in d1
 LINKSYS CreateProc,td_DosLib(a6)     ; Prozeß erstellen
 tst.l  d0                            ; Konnte Prozeß erstellt werden ?
 beq InitUnit_FreeUnit                ; Nein, dann Ende
 move.l d0,tdu_Process(a3)            ; Prozeß in tdu_Prozeß
 move.l d0,a0                         ; Prozeß nach a0
 lea    -pr_MsgPort(a0),a0            ; MsgPort abziehen
 move.l a0,MP_SIGTASK(a3)             ; Prozeß in MP_SIGTASK
 move.b #PA_IGNORE,MP_FLAGS(a3)       ; MP_FLAGS=PA_IGNORE
 lea    MP_MSGLIST(a3),a1             ; MsgList nach a1
 NEWLIST a1                           ; Neue Liste erstellen
 lea    tdu_Msg(a3),a1                ; Message nach a1
 move.l a3,tdm_Unit(a1)               ; Unit in tdm_Unit
 move.l a6,tdm_Device(a1)             ; Device in tdm_Device
 move.l d0,a0                         ; Messageport nach a0
 LINKSYS PutMsg,td_SysLib(a6)         ; Message senden
 clr.l d0                             ; d0=0
 move.b tdu_UnitNum(a3),d0            ; UnitNum nach d0
 lsl.l #2,d0                          ; Mal 4
 move.l a3,td_Units(a6,d0.l)          ; Unit in td_Unit
InitUnit_end:                         ; Ende:
 movem.l (a7)+,d2-d4                  ; Register freigeben
 rts                                  ; Rücksprung

InitUnit_FreeUnit:                    ; Fehler:
 bsr FreeUnit                         ; Unit freigeben
 bra.s InitUnit_end                   ; Zum Ende springen

 ; #####################################################
 ; # FreeUnit gibt den Speicher der Unit-Struktur frei #
 ; # Ihr Parameter ist:                                #
 ; #  => A3 = Zeiger auf die Unit-Struktur             #
 ; #####################################################

FreeUnit:
 move.l tdu_Memory(a3),a1             ; Speicher in a1
 move.l tdu_Size,d0                   ; Größe in d0
 LINKSYS FreeMem,td_SysLib(a6)        ; Speicher freigeben
 move.l a3,a1                         ; Unit in a3
 move.l #TurboDevUnit_SIZE,d0         ; Unitgröße in d0
 LINKSYS FreeMem,td_SysLib(a6)        ; Speicher freigeben
 rts                                  ; Rücksprung

 ; #####################################################
 ; # ExpungeUnit entfernt eine Verwaltungseinheit, die #
 ; # nicht mehr benötigt wird.                         #
 ; # Ihre Parameter sind:                              #
 ; #  => A3 = Zeiger auf Unit-Struktur                 #
 ; #  => A6 = Zeiger auf Device                        #
 ; #####################################################

ExpungeUnit:
 move.l d2,-(a7)                      ; d2 retten
 move.l tdu_Process(a3),a1            ; Prozeß nach a1
 lea    -pr_MsgPort(a1),a1            ; MsgPort abziehen
 LINKSYS RemTask,td_SysLib(a6)        ; Prozeß beenden
 clr.l d2                             ; d2=0
 move.b tdu_UnitNum(a3),d2            ; Unitnummer nach d2
 bsr.s FreeUnit                       ; Unit freigeben
 lsl.l #2,d2                          ; Unitnummer mit 4 multiplizieren
 clr.l td_Units(a6,d2.l)              ; td_Unit löschen
 move.l (a7)+,d2                      ; d2 freigeben
 rts                                  ; Rücksprung

 ; =================================================
 ; = cmdtable:                                     =
 ; = Hier stehen die Befehle, die durch das Device =
 ; = aufgerufen werden können. Die Befehle sind    =
 ; = der Rehe nach in der Tabelle aufgeführt.      =
 ; = Invalid ist der Befehl 0. Die Befehle Reset   =
 ; = bis Flush sind Befehle, die im jedem Device   =
 ; = vorhanden sind. Die Befehle Reset bis Prot-   =
 ; = status müssen bei jedem Laufwerk vorhanden    =
 ; = sein, da DOS diese Befehle für die Verwaltung =
 ; = begötigt. Die Direkt-Befehle sind in der      =
 ; = Konstante "DirektBefehle" mit oder verknüpft. =
 ; = Alle in dieser Konstante aufgeführten Befehle =
 ; = werden direkt ausgeführt, und nicht an den    =
 ; = Prozeß geschickt.                             =
 ; =================================================

cmdtable:
 ; Hier stehen die Befehle, die alle Devices besitzen.
 ; Die Befehle ab Motor sind für Laufwerke wichtig.
 ; Sie werden vom DOS benutzt und müssen vorhanden
 ; sein.

 dc.l Invalid      ; Invalid       Keine Funktion
 dc.l MyReset      ; Reset         Keine Funktion
 dc.l Read         ; Read          Block lesen
 dc.l Write        ; Write         Block schreiben
 dc.l Update       ; Update        Buffer schreiben
 dc.l Clear        ; Clear         Buffer löschen
 dc.l MyStop       ; Stop          Abarbeitung anhalten
 dc.l Start        ; Start         Abarbeitung wieder starten
 dc.l Flush        ; Flush         Alle Messages als Abgebrochen zurücksenden
 dc.l Motor        ; Motor         Motor ein- und ausschalten
 dc.l Seek         ; Seek          Schreib/Lesekopf positionieren
 dc.l Write        ; Format        Track formatieren
 dc.l Remove       ; Remove        Diskettenwechsel-Interrupt installiern
 dc.l Status       ; ChangeNum:    Diskettenwechsel (0)
 dc.l Status       ; ChangeState:  Diskette eingelegt (ja)
 dc.l Status       ; ProtStatus:   Schreibschutz (aus)

 ; Hier beginnen die eigenen Befehle.
 ; Sie sind für die Arbeit des DOS
 ; mit dem Device unwichtig.

 dc.l RawRead      ; RawRead:      Byteweises lesen
 dc.l RawWrite     ; RawWrite:     Byteweises schreiben
 dc.l GetDriveType ; GetDriveType: Laufwerkstyp (=> 5.25 Zoll)
 dc.l GetNumTracks ; GetNumTracks: Anzahl der Tracks
 dc.l AddChangeInt ; Diskettenwechselinterrupt installieren
 dc.l RemChangeInt ; Diskettenwechselinterrupt entfernen
 dc.l LastComm     ; LastComm:     Letztes Kommando ermitteln
cmdtableend:

DirektBefehle equ $1!$2!$10!$20!$40!$80!$100

FUNKANZ: EQU 23    ; 23 Befehle

 ; ==========================================================
 ; = BeginIO ist die Funktion, die die Befehle verarbeitet. =
 ; = Sie stellt die gültigkeit fest und bestimmt, ob der    =
 ; = Befehl zum Prozeß des Units gesendet oder direkt aus-  =
 ; = geführt wird.                                          =
 ; = Ihre Parameter sind:                                   =
 ; =  => A1 = Zeiger auf die IOStdReq-Strunktur             =
 ; =  => A6 = Zeiger auf die Device-Struktur                =
 ; ==========================================================

BeginIO:
 move.l a3,-(a7)                   ; a3 retten
 move.l IO_UNIT(a1),a3             ; Zeiger auf das Unit in a3
 move.w IO_COMMAND(a1),d0          ; Kommando nach d0
 bclr #15,d0                       ; Extended-Bit löschen
 cmp.w #FUNKANZ,d0                 ; Ist Kommando gültig ?
 bcc BeginIO_NoCmd                 ; Nein? Dann springe zu BeginIO_NoCmd
 DISABLE a0                        ; Interrupts sperren
 move.l #DirektBefehle,d1          ; Ist der Befehl ein Direkt-Befehl ?
 btst d0,d1                        ; Ja ?
 bne.s BeginIO_Immediate           ; Wenn Nein, dann springe
BeginIO_QueueMsg:                  ; Ja.
 bset #UNITB_INTASK,UNIT_FLAGS(a3) ; Unit-Bit setzen
 bclr #IOB_QUICK,IO_FLAGS(a1)      ; Quick-Flag löschen
 ENABLE a0                         ; Interrupts erlauben
 move.l a3,a0                      ; Unit nach a0
 LINKSYS PutMsg,td_SysLib(a6)      ; Message Senden
 bra.s BeginIO_end                 ; Ende

BeginIO_Immediate:
 ENABLE a0                         ; Interrupts erlauben
 bsr PerformIO                     ; Befehl ausführen
BeginIO_end: move.l (a7)+,a3       ; a3 wieder zurückgeben
 rts                               ; Rücksprung

BeginIO_NoCmd:                     ; Kommandofehler
 move.b #IOERR_NOCMD,IO_ERROR(a1)  ; Fehler in io_Error
 bra.s BeginIO_end                 ; Zum Ende springen

 ; ===================================================
 ; = PerformIO:                                      =
 ; = Diese Funktion holt die Adresse des Befehls aus =
 ; = der Tabelle und springt zu der angegebenen      =
 ; = Adresse.                                        =
 ; = Ihre Parameter sind:                            =
 ; =  => A1 = Zeiger auf IOStdReq                    =
 ; =  => A3 = Zeiger auf Unit-Struktur               =
 ; =  => A6 = Zeiger auf Device                      =
 ; ===================================================

PerformIO:
 move.l a2,-(a7)                   ; a2 retten
 move.l a1,a2                      ; IOStdReq nach a2
 move.w IO_COMMAND(a2),d0          ; Kommando nach d0
 lsl.w #2,d0                       ; mal 4
 lea cmdtable(pc),a0               ; Kommandoadresse holen
 move.l 00(a0,d0.w),a0             ; Adresse nach a0
 jsr (a0)                          ; Befehl ausführen
 move.l (a7)+,a2                   ; a2 freigeben
 rts

 ; ====================================================
 ; = TermIO wird zur Beendigung von jeder Funktion    =
 ; = aufgerufen. Sie sendet, wenn der Befehl über den =
 ; = Prozeß lief, die Message zurück.                 =
 ; ====================================================

TermIO:
 move.w IO_COMMAND(a1),d0          ; Kommando nach d0
 move.w d0,d5                      ; und d5
 ext.l d5                          ; auf Langwortgröße erweiten
 move.l d5,tdu_LastComm(a3)        ; Kommando in tdu_LastComm
 move.l #DirektBefehle,d1          ; Direktbefehlsmaske in d1
 btst   d0,d1                      ; Direktbefehl ?
 bne.s TermIO_Direkt               ; Ja
 btst #UNITB_INTASK,UNIT_FLAGS(a3) ; Ging der Befehl über den Prozeß ?
 bne.s TermIO_Direkt               ; Nein
 bclr #UNITB_ACTIVE,UNIT_FLAGS(a3) ; Task freigeben
TermIO_Direkt:
 btst #IOB_QUICK,IO_FLAGS(a1)      ; Quick-Flag gesetzt ?
 bne.s TermIO_end                  ; Nein, dann springe
 LINKSYS ReplyMsg,td_SysLib(a6)    ; Message zurücksenden
TermIO_end: rts                    ; Rücksprung

 ; Diese Routine ist zum Abbrechen eines laufenden Befehls
 ; gedacht. Da das Abbrechen eines Befehls in Laufwerken
 ; ist nicht sinnvoll ist, ist eine Abbruchfunktion nicht
 ; implementiert.

AbortIO:                           ; Funktion zum Abbruch eines Kommandos
 moveq #0,d0                       ; Kein Fehler
 rts                               ; Rücksprung

CheckUnit:                         ; Unit belegt ?
 lsl.l #2,d0                       ; Nummer*4
 lea td_Units(a6,d0.l),a0          ; Unit nach a0
 move.l (a0),d0                    ; a0 in d0
 rts                               ; Rücksprung

 ; ================================================
 ; = Hier beginnen die Device-Befehle, die durch  =
 ; = den vorher installierten Prozeß oder durch   =
 ; = BeginIO angesprungen werden.                 =
 ; = Alle Befehle müssen mit TermIO abgeschlossen =
 ; = werden!                                      =
 ; = Die Parameter sind:                          =
 ; = => A1 = Zeiger auf die IOStdReq-Struktur     =
 ; = => A3 = Zeiger auf die Unit-Struktur         =
 ; = => A6 = Zeiger auf die Device-Struktur       =
 ; ================================================

MyReset:
Invalid:                           ; Abarbeitung eines ungültigen Befehls
 move.b #IOERR_NOCMD,IO_ERROR(a1)  ; IOERR_NOCMD nach io_Error
 bsr TermIO                        ; Zu TermIO springen
 rts                               ; Rücksprung

GetDriveType:                      ; Laufwerkstyp ermitteln
 move.l #2,IO_ACTUAL(a1)           ; In einem Ram-Laufwerk gibt es keine
 bsr TermIO                        ; Typen! Rückgabe als 5.25 Zoll.
 rts

GetNumTracks:                      ; Anzahl der Tracks ermitteln
 move.l tdu_NumTracks(a3),IO_ACTUAL(a1) ; Größe in Actual
 bsr TermIO                        ; Zu TermIO springen
 rts                               ; Rücksprung

LastComm:                          ; Letzten Kommando ermitteln
 move.l tdu_LastComm(a3),IO_ACTUAL(a1)  ; Kommando in Actual
 bsr TermIO                        ; Zu TermIO springen
 rts                               ; Rücksprung

RawRead:                           ; Byteweises lesen
 movem.l a0/a2/d0,-(sp)            ; Register retten
 move.l tdu_Memory(a3),a0          ; Speicher in a0
 add.l IO_OFFSET(a1),a0            ; Offset addieren
 move.l IO_DATA(a1),a2             ; Daten in a2
 move.l IO_LENGTH(a1),d0           ; Lange in d0
 jsr CopyMemSlow
 movem.l (sp)+,a0/a2/d0            ; Register freigeben
 move.l IO_LENGTH(a1),IO_ACTUAL(a1); Länge in Actual
 move.l IO_OFFSET(a1),tdu_Position(a3) ; Lesekopfposition
 clr.b IO_ERROR(a1)                ; Keine Fehler
 bsr TermIO                        ; Zu TermIO springen
 rts                               ; Rücksprung

RawWrite:                          ; Byteweises lesen
 movem.l a0/a2/d0,-(sp)            ; Register retten
 move.l tdu_Memory(a3),a2          ; Speicher in a2
 add.l IO_OFFSET(a1),a2            ; Offset addieren
 move.l IO_DATA(a1),a0             ; Daten in a0
 move.l IO_LENGTH(a1),d0           ; Länge in d0
 jsr CopyMemSlow                   ; Speicher kopieren
 movem.l (sp)+,a0/a2/d0            ; Register freigeben
 move.l IO_LENGTH(a1),IO_ACTUAL(a1); Länge in Actual
 move.l IO_OFFSET(a1),tdu_Position(a3) ; Schreibkopfposition
 clr.b IO_ERROR(a1)                ; Keine Fehler
 bsr TermIO                        ; Zu TermIO springen
 rts                               ; Rücksprung

Read:                              ; Block lesen
 movem.l a0/a2/d0,-(sp)            ; Register retten
 move.l tdu_Memory(a3),a0          ; Speicher in a0
 add.l IO_OFFSET(a1),a0            ; Offset addieren
 move.l IO_DATA(a1),a2             ; Daten in a2
 move.l IO_LENGTH(a1),d0           ; Länge in d0
 jsr CopyMemFast                   ; Zur Kopierroutine springen
 movem.l (sp)+,a0/a2/d0            ; Register freigeben
 move.l IO_LENGTH(a1),IO_ACTUAL(a1); Länge in Actual
 move.l IO_OFFSET(a1),tdu_Position(a3) ; Lesekopfposition
 clr.b IO_ERROR(a1)                ; Keine Fehler
 bsr TermIO                        ; Zu TermIO springen
 rts                               ; Rücksprung

Write:                             ; Block schreiben
 movem.l a0/a2/d0,-(sp)            ; Register retten
 move.l tdu_Memory(a3),a2          ; Speicher in a2
 add.l IO_OFFSET(a1),a2            ; Offset addieren
 move.l IO_DATA(a1),a0             ; Daten in a0
 move.l IO_LENGTH(a1),d0           ; Länge in d0
 jsr CopyMemFast                   ; Zur Kopierroutine springen
 movem.l (sp)+,a0/a2/d0            ; Register freigeben
 move.l IO_LENGTH(a1),IO_ACTUAL(a1); Länge in Actual
 move.l IO_OFFSET(a1),tdu_Position(a3) ; Schreibkopfposition
 clr.b IO_ERROR(a1)                ; Keine Fehler
 bsr TermIO                        ; Zu TermIO springen
 rts                               ; Rücksprung

 ; ================================================
 ; = CopyMemSlow:                                 =
 ; = Diese Routine kopiert den Speicher für die   =
 ; = Raw-Befehle. Sie kopiert byteweise und ist   =
 ; = deshalb auch langsamer.                      =
 ; = Ihre Parameter sind:                         =
 ; =  => D0 = Länge                               =
 ; =  => A0 = Quelladresse                        =
 ; =  => A2 = Zieladresse                         =
 ; ================================================

CopyMemSlow:                       ; Speicher Byteweise kopieren
 tst.l d0                          ; Ist etwas zu kopieren ?
 beq CopyMemSlow_Ende              ; Nein, dann Ende
CopyMemSlow_Loop:                  ; Schleife
 move.b (a0)+,(a2)+                ; a0 nach a2 und erhöhen
 dbra d0,CopyMemSlow_Loop          ; Zurückzählen
CopyMemSlow_Ende:                  ; Ende
 rts                               ; Rücksprung

 ; ================================================
 ; = CopyMemFast:                                 =
 ; = Diese Routine ist das Kernstück des Devices. =
 ; = Über sie werden alle Scheib -und Lesebefehle =
 ; = abgewickelt. Sie kopiert 512 Bytes in einem  =
 ; = Durchlauf!                                   =
 ; = RawRead und RawWrite laufen nicht über diese =
 ; = Funktion, weil sie auch einzelne Bytes lesen =
 ; = können. Sie sind dadurch langsamer.          =
 ; = Ihre Parameter sind:                         =
 ; = => D0 = Länge                                =
 ; = => A0 = Quelladresse                         =
 ; = => A2 = Zieladresse                          =
 ; ================================================

 ; INIT_COPY ist ein Macro, um die Routine zu installieren.
 ; Es wird 8 mal aufgerufen, um 128 befehle zu installieren.

INIT_COPY:  MACRO
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 move.l (a0)+,(a2)+
 endm

CopyMemFast:
 tst.l d0                          ; Ist etwas zu kopieren ?
 beq CopyMemFast_Ende              ; Nein, dann springe zum Ende
 lsr.l #7,d0                       ; Länge durch 128 dividieren
 lsr.l #2,d0                       ; Länge durch 4 dividieren
 subq.l #1,d0                      ; 1 subtrahieren
CopyMemFast_Loop:                  ; Kopierschleife
 INIT_COPY
 INIT_COPY
 INIT_COPY
 INIT_COPY
 INIT_COPY
 INIT_COPY
 INIT_COPY
 INIT_COPY
 dbra d0,CopyMemFast_Loop          ; Zurückzählen
CopyMemFast_Ende:                  ; Ende:
 rts                               ; Rücksprung

MyStop:                            ; Ausführung anhalten
 bset #TDUB_STOPPED,UNIT_FLAGS(a3) ; Flag setzen
 bsr TermIO                        ; Zu TermIO springen
 rts                               ; Rücksprung

Start:                             ; Ausführung weiterführen
 bsr InternalStart                 ; Zu InternalStart springen
 move.l a2,a1                      ; IOStdReq nach a1
 bsr TermIO                        ; Zu TermIO springen
 rts                               ; Rücksprung

InternalStart:                     ; Startroutine:
 bclr #TDUB_STOPPED,UNIT_FLAGS(a3) ; Stop-Flag löschen
 move.l MP_SIGTASK(a3),a1          ; Task nach a1
 clr.l d0                          ; d0=0
 move.b MP_SIGBIT(a3),d1           ; Signalbit nach d1
 bset d1,d0                        ; Bit setzen
 LINKSYS Signal,td_SysLib(a6)      ; Signal setzen
 rts                               ; Rücksprung

Flush:                             ; Alle Befehle als Abgebrochen zurück
 movem.l d2/a6,-(a7)               ; Register retten
 move.l td_SysLib(a6),a6           ; ExecBase nach a6
 FORBID
Flush_Loop:                        ; Schleife:
 move.l a3,a0                      ; Unit nach a0
 CALLSYS GetMsg                    ; Message holen
 tst.l  d0                         ; Message angekommen ?
 beq.s Flush_end                   ; Nein, dann Ende
 move.l d0,a1                      ; IOStdReq nach a1
 move.b #IOERR_ABORTED,IO_ERROR(a1); Fehler nach io_Error
 CALLSYS ReplyMsg                  ; Message zurückschicken
 bra.s Flush_Loop                  ; Zu Schleife springen
Flush_end: PERMIT
 movem.l (a7)+,d2/a6               ; Register freigeben
 move.l a2,a1                      ; IOStdReq nach a1
 bsr TermIO                        ; Zu TermIO springen
 rts                               ; Rücksprung

Seek:                              ; Schreib/Lesekopf positionieren
 move.l IO_OFFSET(a1),tdu_Position(a3) ; Position eintragen
AddChangeInt:                      ; Der Seek-Befehl endet wie die
RemChangeInt:                      ; folgenden Befehle.
Update:                            ; Nicht gültige Befehle, die jedoch
Clear:                             ; keinen Fehler zurückgeben dürfen, da
Remove:                            ; diese von DOS aufgerufen werden,
 clr.b IO_ERROR(a1)                ; weil sie bei einem Disketten-
 bsr TermIO                        ; laufwerk eine Bestimmte Funktion
 rts                               ; haben.

Motor:                             ; Motor ein- und Ausschalten
 move.l IO_LENGTH(a1),d0           ; Da Ram-Disks keine Motoren
 cmp.l #1,d0                       ; besitzen wird hier auch
 beq 1$                            ; nichts ausgeschaltet.
 move.l #1,d0                      ; DOS verlangt in Actual den
 bra 2$                            ; vorherigen Status des Motors.
1$:                                ; Es wird der Gegenteilige 
 move.l #0,d0                      ; Status aus io_Length in 
2$:                                ; io_Actual übergeben.
 move.l d0,IO_ACTUAL(a1)           ; Alter Status in Actual
 clr.b IO_ERROR(a1)                ; Kein Fehler
 bsr TermIO                        ; Zu TermIO springen
 rts                               ; Rücksprung

Status:                            ; Anzahl der Diskettenwechsel
 move.l #0,IO_ACTUAL(a1)           ; Diskette Schreibgeschützt ?
 move.b #0,IO_ERROR(a1)            ; Diskette eingelegt ?
 bsr TermIO                        ; Da alle Befehle in einer Ram-Disk 0
 rts                               ; ergeben, wird nur eine Routine benötigt.

 ; ==========================================================================

 ; Hier beginnen die Routinen für die Prozesse.
 ; Der Funktion CreateProc wird ein Zeiger auf TurboProc_SegList
 ; übergeben. Als erster Befehl in der SegList muß "dc.l 0" stehen,
 ; weil an dieser Stelle ein Zeiger auf das nächste Segment erwartet wird!

 CNOP 0,4                          ; Auf Langwortgröße erweitern
TurboProc_SegList:                 ; Anfang der SegList
 dc.l 0                            ; Zeiger auf nächstes Segment (=NULL)

Proc_Begin:                        ; Code der SegList
 move.l $4,a6                      ; ExecBase nach a6
 sub.l a1,a1                       ; a1=NULL
 CALLSYS FindTask                  ; Task suchen
 move.l d0,a0                      ; Task nach a0
 move.l d0,a4                      ; und a4
 lea pr_MsgPort(a0),a0             ; der MsgPort von a0 nach a0
 CALLSYS WaitPort                  ; Auf Nachricht warten
 move.l d0,a1                      ; Nachricht in a1
 move.l d0,a2                      ; und a2
 CALLSYS Remove                    ; Nachricht löschen
 move.l tdm_Device(a2),a5          ; Device nach a5
 move.l tdm_Unit(a2),a3            ; Unit nach a3
 moveq #-1,d0                      ; -1 nach d0
 CALLSYS AllocSignal               ; Freies Signal holen
 move.b d0,MP_SIGBIT(a3)           ; Signal in SigBit
 move.b #PA_SIGNAL,MP_FLAGS(a3)    ; Flags = PA_SIGNAL
 clr.l d7                          ; d7=0
 bset d0,d7                        ; Signal in d7 setzen
 bra.s Proc_CheckStatus            ; Zu Proc_CheckStatus springen

Proc_MainLoop:                     ; Hauptschleife
 move.l d7,d0                      ; Signal nach d7
 CALLSYS Wait                      ; Auf Signal warten
Proc_CheckStatus:                  ; Proc_CheckStatus:
 btst #TDUB_STOPPED,UNIT_FLAGS(a3) ; Ist Stop-Flag gesetzt ?
 bne.s Proc_MainLoop               ; Ja, dann zur Hauptschleife
 bset #UNITB_ACTIVE,UNIT_FLAGS(a3) ; Ist Task aktiv ?
 bne.s Proc_MainLoop               ; Ja, dann warten, bis Task frei wird

Proc_NextMsg:
 move.l a3,a0                      ; Zeiger auf Port in a0
 CALLSYS GetMsg                    ; Auf Message warten
 tst.l d0                          ; Message angekommen ?
 beq.s Proc_Unlock                 ; Nein, dann springe
 move.l d0,a1                      ; Message nach a1
 exg a5,a6                         ; a5 mit a6 vertauschen
 bsr PerformIO                     ; Befehl auswerten
 exg a5,a6                         ; a5 mit a6 vertauschen
 bra Proc_NextMsg                  ; Nächste Message bearbeiten

Proc_Unlock:
 bclr #UNITB_ACTIVE,UNIT_FLAGS(a3) ; Aktive-Bit löschen
 bclr #UNITB_INTASK,UNIT_FLAGS(a3) ; Intask-Bit löschen
 bra Proc_MainLoop                 ; Zur Hauptschleife springen

 END

