1990-Club80-07 Nr.31 S.17-22

Harddisk-Einbindung ins CP/M3-BIOS

hier am Beispiel des OMTI 5527 RLL Controllers und einer Platte mit 615 Zylindern und 4 Köpfen (32 MB im RLL Format)

Helmut Bernhardt, Peter Petersen

Die Hardware

Der Aufwand in Hardware, um einen PC-Harddisk-Controller wie die verschiedenen OMTI-Typen an den ECB-Bus oder auch an andere Z80-Bus-Systeme anzuschließen, ist minimal. Beim ECB-Bus bestehen die Probleme hauptsächlich in der ungeeigneten Geometrie des Controllers, der die Breite der Europakarten von 10 cm überschreitet. Die Montage auf eine ECB-Bus Trägerkarte, die auch die beiden zur Busanpassung nötigen ICs aufnimmt, muß für jedes Gerät individuell gelöst werden.

Die Busanpassung beschränkt sich auf die Umverlegung der ECB-Adressen an die Slotadressen des Controllers, damit bei einem Zugriff der CPU unter einer für den Controller vorgesehenen Portadresse am Controller die Portadresse 320H(-323H), unter der der Controller im PC decodiert ist, anliegt. Außerdem müssen aus den ECB-Signalen /RD, /WR, und /IORQ die PC-Bus-Signale /IOR und /IOW mit zwei OR-Gattern eines 74LS32 erzeugt werden. Auch das low aktive /RESET des ECB-Bus muß über einen 74LS04-Inverter in das high aktive RESET des PC-Bus umgesetzt werden.

Da 4 aufeinanderfolgende Portadressen benötigt werden, müssen A0 und A1 direkt an den Controller gelangen. Für eine Adreßlage bei D0H-D3H am ECB-Bus ist am Ende dieses Artikels eine Beschaltung gezeigt, die folgende Pegel-Übersetzung bewirkt:

Adresse        :  A9  A8  A7  A6  A5  A4  A3  A2  A1  A0
---------------------------------------------------------
ECB   Port D0H :   =   =   1   1   0   1   0   0   x   x
PC   Port 320H :   1   1   0   0   1   0   0   0   x   x

Außerdem ist an AEN und A10-A19 des Controllers GND und an /DACK3 und /MEMR +5V zu legen. Außer dem OMTI 5510 benötigen alle OMTI-Controller am Slot-Pin B9 +12V Versorgungsspannung. Selbstverständlich müssen die Daten D0-D7 an die gleichen Eingänge des Controllers gelegt werden.

Die Software

Wesentlich anspruchsvoller ist die Software-Einbindung der Festplatte in das BIOS des CP/M plus.

Mit den unter CP/M bestehenden Möglichkeiten zur Verwaltung der Files auf einer Platte (Unterteilung in User 0 bis User 15) lassen sich nur begrenzte Mengen an Files übersichtlich handhaben. Deshalb ist es bei größeren Platten sinnvoll, diese in mehrere logische Laufwerke zu partitionieren. Noch handhabbar sind dabei Größen von einigen Megabyte Kapazität. Danach könnte eine 32MB-Platte in 4 Partitionen aufgeteilt werden. Die sich anbietende Aufteilung, daß alle Spuren unter einem Kopf als ein Laufwerk behandelt werden, bedeutet, daß nach je 26 Sektoren der Kopf eine Spur weiter bewegt werden muß. Der Spurwechsel ist bei Festplattenzugriffen aber die zeitraubendste Aktion und sollte so wenig wie möglich vorkommen.

Deshalb wurde die Partitionierung hier auch anders durchgeführt: Eine logische Spur umfaßt alle 4 Spuren (Köpfe) eines Zylinders. Es werden nacheinander erst die 4 Spuren unter allen 4 Köpfen gelesen/geschrieben, bevor der Kopfschlitten um eine Position weiterbewegt wird. Dadurch können nacheinander 104 Sektoren verholt werden, ohne zwischendurch steppen zu müssen. Einer Partition wird dabei ein zusammenhängender Bereich von Zylindern zugeordnet: die Nummer des letzten absoluten Zylinders einer Partition ist die Anzahl Spuren und die Nummer des letzten absoluten Zylinders der vorherigen Partition ist die Anzahl reservierter Systemspuren.

Spur-Nr.    0 1         154          307          460          615
Partit. 0    =############---------------------------------------
Partit. 1    =============#############--------------------------
Partit. 2    ==========================#############-------------
Partit. 3    =======================================#############

       ===== reservierte Systemspuren
       ##### Datenspuren der Partition

Das BDOS kennt für Disk-I/O nur die Adressen einer Tabelle mit dem Namen @dtbl (drive table). In dieser Tabelle stehen die Adressen von 16 Disk-Parameter-Headern (DPHs) für 16 Laufwerke in der Reihenfolge ihrer logischen Namen (A, B, ...P). Nicht existierende Laufwerke erhalten hier den Eintrag 0. Wenn in einem Sourcecode-Modul nur die Drive Table und die DPHs und DPBs für RAM-Floppy und Harddisk enthalten sind, könnte das so aussehen:

	public	@dtbl			 ; Drive Table
	public	fdsd0,fdsd1,fdsd2,fdsd3	 ; dph's fuer 4 Floppies
	public	fdram			 ; dph RAM-Floppy
	public	hdsk0,hdsk1,hdsk2,hdsk3  ; dph's fuer Harddisk 0-3

	extrn	hdlogi,hdinit,hd1init	 ; Harddisk Routinen
	extrn	hdwrit,hdread

	maclib	xcpm3		; mit hdpb-Macro für große Drives

	cseg			; Drivetable und dpb's im Common

; Drive Table (zeigt auf die DPB's der verfuegbaren Laufwerke)

@dtbl	dw	fdsd0,fdsd1,fdsd2,fdsd3  ; Floppy Drives
	dw	fdram			 ; RAM-Floppy
	dw	hdsk0,hdsk1,hdsk2,hdsk3	 ; Harddisks
	dw	0,0,0,0,0,0,0		 ; nicht benutzt

;Die Einträge in @dtbl weisen auf Adressen der DPHs, wobei die Lables
;nicht am Anfang der xDPHs stehen. Der Aufbau der xDPHs sieht so aus:

	desg		; die DPHs koennen im gebankten RAM stehen

; extended disk parameter header (xDPH) der Harddisk-Partitionen

drva:	dw	hdwrit		; Routinen zum Schreiben
	dw	hdread		;   und Lesen eines Sektors
	dw	hdlogi		; Einloggen des Drives
	dw	hdinit		; Initialisieren des HDC
	dw	0		; relatives drive 0
	dw	0		; media flag :	not used
hdsk0	dph	0,hddpb0	; Skew-Tabelle und xDPB

drvb:	dw	hdwrit,hdread,hdlogi
	dw	hdinit		; kann auf RET zeigen
	db	1		; relative drive 1
	db	0		; media flag :	not used
hdsk2	dpb	0,hddpb1
;-------------------------------
drvd:	dw	hdwrit,hdread,hdlogi
	dw	hd1init		; kann auf RET zeigen
	db	3		; relative drive 3
	db	0		; media flag :	not used
hdsk3	dph	0,hddpb3

'dph' ist ein Macro-Aufruf, der in CPM3.LIB aufgelöst wird. Am Anfang des Moduls, in dem das xDPH steht, ist

'   maclib   cpm3'
anzugeben, damit RMAC den Macro bearbeiten kann (aus später benannten Gründen sollte lieber XCPM3.LIB benutzt werden).

Da der Skew der Festplatten bereits beim Formatieren durch physika- lischen Versatz der Sektoren auf der Spur vorgegeben wird, ist ein Soft-Skew nicht nötig: anstelle der Adresse einer Skew-Tabelle kann als erster Parameter für dph '0' angegeben werden.

Die Einträge hddpb0 bis hddpb3 geben die Adressen der xDPB's der vier Harddisk-Partitionen an. Diese können wieder mit einem Macro erzeugt werden:

	cseg		; die DPHs müssen im Common liegen
; hard disk parameter block macros

hddpb0	hdpb	512,104,154,4096,2048,1,8000h	; partition 0
hddpb1	hdpb	512,104,307,4096,2048,154,8000h	; partition 1
hddpb2	hdpb	512,104,460,4096,2048,307,8000h	; partition 2
hddpb3	hdpb	512,104,615,4096,2048,460,8000h	; partition 3

;		 |   |   |   |    |    |   |
;		 |   |   |   |    |    |   +----flag für nonre-
;		 |   |   |   |    |    |        movable media
;		 |   |   |   |    |    +----reserv. Systemspuren
;		 |   |   |   |    +----max. Anzahl DIR-Einträge
;		 |   |   |   +----Blockgröße (die durch einen
;		 |   |   |        DIR-Eintrag verwaltet wird)
;		 |   |   +----Anzahl Zylinder des Laufwerks
;		 |   +----Sektoren pro Zylinder (4 tracks mit 26 secs)
                 +----Bytes pro Sektor

Aufgrund des optionalen Eintrags 8000h wird das Laufwerk vom BDOS als 'nonremovable media' angesehen, woraufhin nicht bei jedem Zugriff ein neues Einloggen durchgeführt wird.

'hdpb' ist (ähnlich dem dpb in CPM3.LIB) ein in XCPM3.LIB zusätzlich vorhandener Macro, der auch für Laufwerke mit einer Kapazität von mehr als 8 MB einen richtigen Disk Parameter Block erzeugt.

Das Source-Modul (angenommen, es heißt MOVE.ASM) das diese Datenstrukturen enthält, wird mit 'rmac move $pz sz' assembliert (in einem SUBMIT-File sind 2 $-Zeichen einzutragen). M80 kann die Macrobibliotheken CPM3 und XCPM3 nicht benutzen. Da für die Datenstrukturen aber keinerlei Maschinenbefehle nötig sind, braucht man sich auch nicht unbedingt mit den von RMAC einzig verstandenen 8080-Mnemonics herumschlagen. Die eigentlichen Disk-I/O-Routinen können in einem anderen Modul formuliert werden, wobei man sich die Fähigkeit von M80 zunutzemachen kann, Z80 Mnemonics zu verstehen.

Im Kopf des Moduls mit diesen Datenstrukturen sind sämtliche Lables wie @dtbl auf die aus anderen Modulen zugegriffen wird mit 'public' bekanntzugeben, und alle Einträge in diesen Tabellen, die nicht in diesem Modul selbst als Lable auftauchen, mit 'extrn' als außerhalb stehend zu benennen.

Bei ausreichend bereigestelltem RAM-Speicher legt GENCPM Direktory Buffer in Bank 0 (Systembank) und Data-Buffer in anderen dafür angegebenen Banks an (siehe unten). Die Routinen 'hdwrit' und 'hdread' müssen also den Datentransfer zwischen Controller und verschiedenen Banks unterstüzen. Beim Aufruf bekommen sie in den in BIOSKRNL zugewiesenen Variablen entsprechende Parameter übergeben.

@dbnk	( 8Bit)	Source-/Ziel-Bank für Holen/Ablegen eines Sektors
		beim Schreiben/Lesen
@dma	(16Bit)	RAM-Adressen in der Bank, von wo ab mit aufsteigender
		Adresse die Daten des Sektors zu holen/abzulegen sind
@adrv	( 8Bit)	absolute Laufwerks-Nummer (entsprechend Position
		in qdtbl)
@rdrv	( 8Bit)	relative Laufwerks-Nummer (Zählung der Laufwerke
		für einen Controller)
@trk	(16Bit)	Spur des Laufwerks
@sect	(16Bit)	Sektor der Spur

Diese Daten stehen alle in der Systembank 0 und müssen ausgewertet werden, bevor die in @dbnk vorgegebene Bank eingestellt wird und diese Daten dann nicht mehr zur Verfügung stehen.

Die Übertragung von Daten erfogt direkt vom/zum Controller zur/von der Buffer-Bank. Da diese Bank dann eingeschaltet sein muß, kann die Übertragung nur von dieser Routine im Common durchgeführt werden. Das hat den Vorteil, daß der Common-Bereich keinen 512Byte-Zwischenspeicher enthalten muß, was auf Kosten der TPA geht, und daß nur ein Datentransfer nötig ist, was Geschwindigkeit bringt.

Befehle an den OMTI-Controller bestehen aus einer Folge von 6 Bytes, die nacheinander an die relative Portadresse 0 des Controllers zu übertragen sind. Das erste Byte ist das eigentliche Befehlsbyte und die folgenden 5 Bytes sind Parameter des Befehls. Diese Bytefolge wird zunächst im RAM erzeugt und dann an den Controller ausgegeben. Das Parameterfeld im RAM sieht so aus:

cfield:			; Befehlsparameter-Feld für HDC
head:	db	0	; D7 = Bit 10 der Zylinder-Nummer
			; D6 = 0. D5 = Nummer des zu bedienenden Lauf-
			; werks (0 oder 1; an dem OMTI lassen sich
			; 2 Festplatten anschließen) nicht mit der
			; Partition eines Laufwerks verwechsel!
			; D4-D0 : Head-Nummer
sect:	db	0	; D6,D7 : Bit 9 und 8 der Zylinder-Nummer
			; D5-D0 : Sector-Nummer
cylind:	db	0	; D7-D0 : Bit 7 - 0 der Zylinder-Nummer
bcount:	db	1	; Interleave-Count beim Formatieren
			; Blockcount bei MultIO
termin:	db	2	; D7-D5 : Command-Controll-Byte
			; D4-D0 : nicht benutzt

Für die Beschreibung der 'termin'-Bytes sei auf das Handbuch der OMTI-Controller verwiesen (das wollen wir uns hier sparen).

Vor der Übertragung dieser Parameter ist der eigentliche Befehl direkt an den Controller auszugeben. Der Inhalt dieses Feldes wird in der Routine hdrw_common berechnet und in die Tabelle eingetragen 'hdscmd' übergibt die Bytes an den Controller.

Die zwingend im Common anzusiedelnden Routinen 'hdrd' und 'hdwr' sowie der von beiden genutzte Ausgang 'termcmd1', der wieder die Bank 0 einschaltet, machen zusammen knapp 60 Bytes des kostbaren Common-Speichers aus, dem 512 Bytes eines Zwischenspeichers bei der anderen Variante gegenüberstehen.

Bei einem Prof mit 9,2 MHz Systemtakt ist damit ein Interleave 1:1 möglich. Wegen insgesamt bereitgestellter Data-Buffer von 64K RAM erscheint ein erster Zugriff auf die Festplatte recht langsam, weil dann immer gleich riesige Datenmengen bewegt werden. Nachfolgende Zugriffe können dann aber aus dem Buffer leben und erreichen dann die Geschwindigkeit einer RAM-Floppy.

Weiteres Tuneup am BIOS

Da die Speicheraufteilung des Prof 180 recht ungünstig ist und dadurch bedingt auch die RAM-Floppy-Routinen umständlich mit '?xmove' über einen 128 Byte Zwischenspeicher im common arbeiten, wurde auch die ganze Speicherkonfiguration nach Conitec's Vorstellung aufgegeben und durch eine sinnvollere ersetzt.

Speicherbereich		Adreßlage nach Conitec	      neue Adreßlage
----------------------------------------------------------------------
Systembank		    00000h-0efffh	      00000h-0ffffh
Common			    0f000h-0ffffh	      0f000h-0ffffh
TPA			    40000h-4efffh	      10000h-1efffh
CCP-Buffer		    4f000h-4ffffh	      1f000h-1ffffh
weitere Banks		     -----------	      20000h-2ffffh
RAM-Floppy		    10000h-3ffffh	      20000h-7ffffh
			  + 50000h-7ffffh
----------------------------------------------------------------------

Die ursprüngliche Einteilung von Conitec ermöglicht den Einsatz von 4164-RAMs, wobei die Selektion der beiden 64K-Blöcke durch den Pegel von A18 erfolgt (was bei 41256-RAMs auch sinnvoll ist). Entsprechend liegen diese beiden Blöcke auf den physikalischen Adressen 0xxxxh und 4xxxxh. Da bei den heutigen RAM-Preisen der Einsatz von 4164-RAMs unwahrscheinlich ist und hier 41256-RAMs stecken, wurde auf die Möglichkeit des Abrüstens auf 128K verzichtet, so daß der Bereich, der für Banks und/oder RAM-Floppy zur Verfügung steht, zusammenhängt. Das vereinfacht die RAM-Floppy-Routinen gewaltig.

Der Zugriff auf die RAM-Floppy erfolgt nur im DMA-Betrieb und zur Vereinfachung und Beschleunigung wurde die Sektorgröße von 128 auf 1024 Bytes erhöht und die Größe einer Spur auf 64K festgelegt. Die Track-Nr kann dann direkt (durch Addition des Offsets des RAM-Floppy-Anfanfs) in das Adreß-Zusatzbyte für den DMAC umgesetzt werden und aus der Sektor-Nummer kann durch Shiften um 2 Bits der Adreßanteil A15-A10 erhalten werden, A9-A0 sind immer 0 für die Anfangsadresse eines Sektors.

Die RAM-Floppy-Routinen sind in RFLO.MAC versammelt. DPH und DPB der RAM-Floppy sowie die Routinen und Tabellen für Bankumschaltung und DMA sind neben den DPHs und DPBs für die Festplatte in MOVE.ASM enthalten.

Durch die neue Speichereinteilung muß GENCPM bei der Erzeugung der Memory Segment Table auch mit anderen Parametern versorgt werden, als den in GENCPM.DAT bisher enthaltenen. Bei 64K Data-Buffer sind insgesamt 3 Segmente anzugeben (Common und TPA sowie CCP-Buffer werden hier nicht mitgezählt. Für jedes Segment sind dann je 3 Bytes anzugeben, die folgende Bedeutung haben.

  1) high Byte der logischen Anfangsadresse der Bank
  2) high Byte der Länge der Bank
  3) Bank-Nummer
Eine Angabe '00.80.02' bedeutet, daß die Bank 2 von 0000h bis 7fffh reicht (logische Adressen). Wo diese Bank physikalisch im Speicher liegt, spielt keine Rolle.

Für die Bank 0 sind diese Angaben (speziel beim Prof 180) etwas komplizierter. Der Prof 180 benutzt 4 KByte des Boot-ROMs (bzw. des auf gleiche Adressen ins RAM kopierten ROM-Inhalts) vom BIOS des CP/M aus. Deshalb muß für die Systembank als high Byte der Anfangsadresse 10 an- gegeben werden, damit diese ROM-Routinen nicht überschrieben werden. Als Länge ist die Differenz des von GENCPM vorher ausgegebenen Anfangs des gebanten BDOS abzüglich dieser Adresse anzugeben. Die Bank-Nr ist natürlich 00.

    UM unseren CP/M-Diskothekar nicht mit Format-Konvertier-Problemen
    zu überlasten, bitte ich darum, bei Interesse an den BIOS-Sources
    für die Festplatte und die neue Variante der RAM-Floppy mir eine
    formatierte Diskette mit den gerade verfügbaren Angaben zum Format
    und möglichst einigen Text-Files darauf (damit ich prüfen kann, ob
    ich das Format richtig eingestellt habe) zu schicken. Beiliegen-
    des Rückporto wird auch gerne gesehen.		Helmut

Zum Thema Harddisk unter CP/M geben folgende Artikel was her:

Andreas Zippel 'Hart, schnell und sicher'. c't 8/86, S.52ff; c't 9/86, S.105; c't 10(6 S.144ff

Martin Rost, Detlef Grell 'Harddisk-Controller einmal anders'. c't 4/87, S.138ff; c't 9/87, S.138ff

Christian Persson 'OMTI-Alternative'. c't 5/89, S. 244


Anschluß des OMTI-Controllers 5527 an den ECB-Bus

   ECB-Bus          PC-Slot              ECB-Bus          PC-Slot
Pin-Nr Signal    Signal Pin-Nr        Pin-Nr Signal    Signal Pin-Nr
------------------------------        ------------------------------
   c9  A7-----------A9  A22              c2  D0-----------D0  A9
   a9  A6-----------A8  A23             c14  D1-----------D1  A8
   a8  A5--------A7,A6  A24,A25          c4  D2-----------D2  A7
   a7  A4-----------A5  A26              a4  D3-----------D3  A6
   c6  A3-----------A4  A27              a5  D4-----------D4  A5
   a6  A2--------A3,A2  A28,A29          a2  D5-----------D5  A4
   c7  A1-----------A1  A30              a3  D6-----------D6  A3
   c5  A0-----------A0  A31              c3  D7-----------D7  A2