Immer auf dem Laufenden: Was belegt wie Ihre Festplatte

Bild 1: So meldet sich die Harddisk-Information

Festplatten als Massenspeicher erfreuen sich, besonders bei ST-Anwendern, wachsender Beliebtheit. Nachdem ein 20-Megabyte-Schuhkarton mit der Aufschrift SH204 sich viel Kritik über Lautstärke, Aussehen und kurze Kabel gefallen lassen mußte, hat Atari die SH205 gebaut, die sehr gut in das Design der Mega-ST-Reihe paßt. Bei solchen Speicherkapazitäten ist es wichtig, stets den noch freien Platz auf der Festplatte zu wissen. Das folgende Programm gibt Antworten auf folgende Fragen aus:

Dazu benutzt die 'HARDDISK-INFORMATION’ die Information im Bootsektor der Festplatte und die GEMDOS-Funktion Dfree, die den freien Platz auf einem Massenspeichermedium berechnet. Analog zum Bootsektor einer Diskette enthält der Bootsektor die vom Programm benötigte Information über Größe der Platte sowie Vorhandensein und Größen der einzelnen Partitionen. Der Bootsektor ist - wie bei den Disketten - der erste physikalische Sektor auf der Festplatte. Sein Aufbau ist im Listing in der Struktur BOOTBLOCK definiert.

Das Programm muß also den Boot-vektor lesen, um dessen Inhalt auswerten zu können. Ein Problem besteht in der Tatsache, daß der Bootsektor einer Festplatte keinesfalls identisch ist mit dem ersten Sektor der ersten Partition (vom Benutzer meist als Laufwerk C angemeldet). Da man unter TOS nicht in der Lage ist, die gesamte Platte anzusprechen, muß der Programmierer einen anderen Weg, nämlich den, die Platte direkt anzusteuern, gehen.

Anzusprechen ist eine Festplatte am Atari ST nur über den DMA-Chip. Er stellt die Hardware-Schnittstelle zu den externen Massenspeichern wie Diskettenlaufwerken und Festplatten dar und entlastet den Prozessor, indem er unabhängig von diesem arbeitet. Man erreicht den DMA über folgende Hardware-Register (Achtung: nur im Supervisor-Modus ansprechbar !!):

FF8604 (diskctl): Allgemeines Eingabe-/Ausgaberegister FF8606 (fifo): Das Modusregister. Hiermit wird der DMA-Chip gesteuert.

FF8609 (dmahigh): DMA-Adresse HI-Byte der zu schreibenden/lesen-den Daten.

FF860B (dmamid): DMA-Adresse MID-Byte

FF860D (dmalow): DMA-Adresse LO-Byte Die für den Harddisk-Betrieb wichtigen Bits des Steuerregisters des DMA- Chips sind diese:

Bit 1: Adreßleitung A0, wenn gesetzt, wird der Controller angesprochen

Bit 3: 0 = Zugriff auf den FDC (Flop-py-Disk-Controller)
1 = Zugriff auf den HDC (Harddisk-Controller)

Bit 4: 1 = Zugriff auf den DMA-Sek-tor-Zähler.

Bit 7: 0 = Zugriff auf den HDC 1 = Zugriff auf den FDC

Bit 8: 0 = DMA: in den Speicher einiesen
1 = DMA: aus dem Speicher schreiben

Wie muß das Programm Vorgehen, um den Bootsektor von der Festplatte lesen zu können ?

Zunächst wird über DrvmapO geprüft, ob überhaupt Partitionen der Festplatte als zusätzliche Stationen angemeldet sind. Dies ist eine reine Sicherheitsmaßnahme, denn wäre die Harddisk nicht in Betrieb, würde der Computer beim Zugriff auf den nicht vorhandenen HDC abstürzen.

Wie schon gesagt, haben alle Zugriffe auf den DMA-Chip im Supervisor-Modus zu erfolgen. Das Programm schaltet den Prozessor mittels super_on() um. Auch muß ein jetzt unerwünschter Eingriff des Betriebssystems in den Datenaustausch mit dem DMA-Chip und dem Festplatten-Controller unterbunden werden. Dazu wird die Systemvariable FLOCK auf einen Wert ungleich 0 gesetzt. Siehe dazu auch die Routine hd_read().

Mit set_buf() wird dem DMA-Chip die Adresse des Zielpuffers für den gelesenen Sektor mitgeteilt. Die Routine seek_block überträgt die Daten wie Sektornummer und Anzahl zum Plattencontroller. Er benötigt für den Lesezugriff eine Folge von fünf Bytes: (siehe Routine seek_block)

1. Byte: Befehl (Lesen = $08), in die obersten 3 Bits ist die Controllernummer der Platte einzusetzen.

2. Byte: Sektor-Adresse, High-Byte. In die oberen 3 Bits ist die Nummer des Laufwerkes einzusetzen.

3. Byte: Sektor-Adresse, Mid-Byte (ohne Laufwerksnummer).

4. Byte: Sektor-Adresse, Low-Byte.

5. Byte: Anzahl der zu lesenden Sektoren.

6.Byte: Wird hier nicht benutzt.

Dieses Protokoll entspricht übrigens dem des SCSI-Bus-Systems. Zusätzlich muß der DMA-Chip bei jedem zu übertragenden Byte auf $8A gesetzt werden, damit die Daten auch wirklich an den richtigen Controller gelangen und nicht aus Versehen beim FDC landen. Auch ist zu sagen, daß die Sektoren einer Festplatte fortlaufend durchnumeriert sind, also bei der Atari-Festplatte von #0 bis #41615. Zwischen den Übertragungen der einzelnen Bytes muß dem Controller etwas Zeit gelassen werden, um den Befehl zu verarbeiten. Wie auch der Floppycontroller meldet er, wenn er wieder empfangsbereit ist, indem er das Bit 5 im MFP-Register GPIP (Adresse FFFA01) auf Null setzt (siehe Routine Timer). Tut er das innerhalb von 1/20 Sekunden jedoch nicht, so darf man davon ausgehen, daß bei ihm irgendetwas schiefgelaufen ist. Die Routine seekjdock wird dann mit einer Fehlermeldung verlassen.

Hat der HDC jedoch alles geschluckt, was ihm der DMA-Chip hinübergeschickt hat, so wird er nun den erhaltenen Befehl ausführen, den Bootsektor von der Platte lesen und zum DMA-Chip senden. Zuerst muß aber auch der DMA-Chip noch darauf eingestellt werden, die Daten zu empfangen und an die gewünschte Adresse zu schreiben (siehe Routine dma_read()). Dazu muß einmal kurz mit der Schreib-Leseleitung in Bit 8 des Modus-Registers ‘geklappert’ werden, damit der Status des DMA-Chips gelöscht wird. Nun noch die gewünschte Anzahl der zu übertragenden Sektoren in FF8604 eintragen und die Übertragung kann beginnen. Nachdem der DMA-Chip die Daten, in diesem Falle den Bootsektor, in den Speicher geschrieben hat, überprüft die Routine status() noch, ob auch alles in Ordnung ist. Ist der Sektorzähler (erreichbar in *diskctl) nicht gleich Null, so konnten die Daten nicht korrekt übertragen werden und die Routine gibt Null als Fehlercode zurück.

Zu guter Letzt muß der DMA-Chip wieder in den Urzustand versetzt werden, indem man den FDC selektiert (Bit 7 setzen, alle anderen löschen). Außerdem sollte die Sperre für den Zugriff des TOS auf den DMA-Chip wieder aufgehoben werden, und zwar mit *flock = OFF. Da ab jetzt keine Zugriffe mehr auf geschützte Speicherbereiche erfolgen, kann der Supervisormodus mit super_off() verlassen werden.

Auf diese Weise kommt das Programm also an die Information im Bootsektor heran und kann diese nunmehr auswerten:

Es geht alle vier Partition-Einträge durch. Sollte eine der Partitionen den Id-Code ‘GEM’ haben, so handelt es sich hierbei um eine GEMDOS-Partition und man kann über Dfree() den freien Platz auf ihr erhalten (siehe Routine do_info()). Aus diesen Werten wird dann der noch freie Platz auf der gesamten Platte summiert.

Die Routine disk_info() gibt zuerst die Informationen über die Festplatte als Gesamtes aus und die Routine partitions() übernimmt die Ausgabe der Werte für die einzelnen Partitionen. Mit einem Tastendruck kehrt man schließlich ins Desktop zurück. Noch ein Wort zur Routine hd_read(): sie ist so programmiert, daß man auch beliebige andere Sektoren von der Harddisk lesen kann, auch mehrere. Da die SH205 die Möglichkeit bietet, mehrere Systeme hintereinanderzuschalten, kann hd_read() auch alle weiteren Controller und Laufwerke ansprechen, indem man deren Nummern beim Aufruf übergibt (siehe Listing). Zwei Beispiele hierzu stellen die Routinen hd_write() und hd_park() dar. Hd_write() in Verbindung mit dma_write() kann bis zu 255 Sektoren aus dem Speicher an beliebige Stellen auf der Festplatte schreiben. Hier wird der Lese-Befehl $0A an den Festplatten-Controller gesendet. Aber Vorsicht: diese Funktion ist in ihrer Wirkung in etwa vergleichbar mit einem Skalpell oder einer Motorsäge, besonders weil die Festplatte im Gegensatz zur 3.5"-Diskette kein Schreibschutzloch hat. Also gilt: absolute Vorsicht beim Einsatz. Genauso würde ich den Befehl $04 “Format Drive” nur dem HDX-Programm überlassen, wenn Sie ihre Daten noch einmal lebend Wiedersehen wollen! Ein weiteres, wesentlich harmloseres Beispiel, das eher zur Datensicherheit beiträgt, ist die Funktion hd_park(): Sie erfüllt die gleiche Aufgabe wie das SHIP-Programm von Atari - fährt also die Leseköpfe in die Parkposition - hat aber den Vorteil, daß sie auch in Accessories eingebaut werden kann. Nach dem Aufruf der Funktion sollte der Lesekopf nicht mehr angesprochen werden. Erst nach erneutem Hochfahren der Festplatte (also aus-und wieder einschalten) macht er wieder, was der Computer will.

Diese Routinen kann sich jeder nach eigenem Gutdünken in seine Programme einbauen, ich denke da z.B. .in einen Harddisk-Monitor.

Das Programm wurde in Megamax C geschrieben, sollte aber auch auf den Kollegen’ wie DRI C oder Mark Williams C laufen. Eventuell muß man dann die Datentypen an den Compiler anpassen.

Ich hoffe, daß dieses Programm dem geneigten Leser etwas Einsicht in den Betrieb mit der Atari-Festplatte gewährt hat. Sollten noch Fragen auftauchen, so stehe Ich gerne zur Verfügung. Ich übernehme aber keine Verantwortung für ‘abgeschossene’ Bootsektoren oder ähnliche Schwierigkeiten, die z.B. durch Fehler beim Abtippen entstehen können.


/**************************************************/
/*                                                */
/*    HARDDISK - INFORMATION   TOS-Version 2.0    */
/*                                                */
/* Programm zum	Ausgeben uon Informationen über  */
/* die Belegung	von SH204/205 und Festplatten mit */
/* kompatiblen Controllern (5T506/412/SCSI)       */
/**************************************************/
/*         Geschrieben in Megamax C von           */
/*                                                */
/*              Ulrich Meumann                    */
/*              Lehnerweg 2, 7900 Ulm             */
/**************************************************/

#include <osbind.h>

#define ON -1 
#define OFF 0

#define READ 0x08 /* Controller: Von Platte lesen */

#define LANG	400
#define KURZ	10

struct partition { /* Partition-Info im Bootblock */ 
				char p_flag; /* Flag, OB, wenn Part, gültig */ 
				char p_id1;	/* Drei ID-Bytes */
				char p_id2; 
				char p_id3;
				long p_start; /* Startsektor der Partition */ 
				long p_size; /* Länge in Sektoren */
};

struct bootblock {
				char nutzlos[0xlc0]:
				/* Je nach Autobooter versch. */ 
				char hi_spt;
				/* Sektoren pro Spur (SH205: 17)*/ 
				long hd_size;
				/* Länge der Platte in Sektoren */ 
				struct partition p_table[4];
				/* Vier Partition-Einträge */ 
				long bsl_start;
				/* Anfang der defekten Sektoren */ 
				long bsl_count;
				/* Anzahl der defekten Sektoren */ 
				int checksum;
				/* Checksumme für den Autoboot */
};

char puffer[512];	/* Hier kommt der Bootblock rein*/

struct bootblock *boot = puffer:

char *gpip = 0xFFFA01L) /* I/O-Register im MFP */

int	*diskctl_w = 0xFF8604L; /* HDC-Register in der DMA */
long	*diskctl_l = 0xFF8604L; /* Das Gleiche in long */
int	*fifo = 0xFF8606L; /* Steuerregister der DMA */
char	*dmahigh = 0xFF8609L; /* DMA-Adresse, High-Byte */
char	*dmamid	=	0xFF860BL;	/* Mid-Byte */
char	*dmalow	=	0xFF860DL;	/* Low-Byte */
int	*hz_200	=	0x4BAL;	/* Der 200Hz-Systemzähler */ 
int	Xflock	=	0x43EL;	/* Sperren der Floppy-VBL's */

long save_ssp; /* Supervisor-Stackpointer */ 

main()
{
	if (get_boot()) 
		do_info(); 
	else
		Cconws('Leider kann keine Information ausgegeben werden !!\n\r"); 
	Cconin();
}

/*********************************/
/* Bootsektor von Harddisk lesen */ 
/*********************************/

int get_boot()
{
	if(Drvmap() & 60) /* Sind über Stationen angemeldet ?*/ 
		return(hd_read(0L,1,puffer,0,0)); 
	else
		return(0);
}

/**************************************************/ 
/* >anzahl< 5ektoren ab >sektor< vom Laufw. >drv< */ 
/*         mit dem Controller >crtl< in           */
/*            den Puffer >buf< laden              */
/**************************************************/ 

int hd_read(sektor, anzahl, buf, drv, crtl)
long sektor, buf:
int anzahl, drv, crtl;
{

	int ok;
	super_on(); /* Supervisor-Modus einschalten */ 
	*flock = ON; /* Floppy-VBL sperren */ 
	set_buf(buf); /* Adresse des Puffers eintragen */ 
	ok = seek_block(READ, sektor, anzahl, drv, crtl);
				/* Sektoren suchen */
	if (ok)
		dma_read(anzahl); /* DMA auf Lesen programmieren */ 
	ok = status(); /* Hat's auch geklappt ??? */
	*fifo = 0x80; /* DMA wieder in Normalzustand */ 
	*flock = OFF; /* Floppy-VBl wieder freigeben */ 
	super_off(); /* Zurück in den User-Modus */ 
	return(ok); /* Das war's! */
}

set_buf(buf) /* Pufferadresse in DMA eintragen */ 
long buf;
{
	*dmalow = (char)(buf & 0xFF); 
	*dmamid = (char)((buf >> 8) & 0xFF);
	*dmahigh= (char)((buf >> 16) & 0xFF);
}

/****************************************/
/*  Sektor(en) auf der Platte anfahren  */ 
/****************************************/

int seek_block(befehl, sektor, anzahl, drive, controller); 
int befehl, anzahl, drive, controller; 
long sektor;
{
	long kommando; 
	int fehler;

	*fifo = 0x88;

	kommando = ((long)controller << 21) | ((long)befehl << 16) | 0x8a; 
	*diskctl_l = kommando; /* 1.Byte an den Controller schicken*/ 
	if ((fehler = timer(KURZ)) == 0) /* 1/20 Sekunde warten, bis */
		return(fehler); /* der Controller bereit ist */
	*diskctl_l = (long)drive << 21) | (sektor & 0xFF0000) | 0x8A;

	if ((fehler = timer(KURZ)) == 0) /* 2.Byte senden und warten */
		return(fehler))

	*diskctl_l * (sektor & 0xFF00) << 8 | 8x8A;	/* 3.Byte senden */ 
	if ((fehler = timer(KURZ)) == 0) 
		return(fehler);

	*diskctl_l = (sektor & 0xFF) << 16 | 0x8A;	/* 4.Byte senden */ 
	if ((fehler = timer(KURZ)) == 0) 
		return(fehler);

	*diskctl_l = ((long)anzahl & 0xFF) << 16 | 0x8A;	/* Anzahl senden */

	return(timer(KURZ));
}

/****************************************************/ 
/*  Auf die Bereit-Meldung des Controllers warten,  */ 
/*  aber höchstens zeit/20 Sek.                     */
/****************************************************/ 
timer(zeit) 
int zeit;
{
	int wert;

	wert = *hz_200 + zeit; 
	while (*hz_200 != wert)
		if ((*gpip & 0x20) == 0) /* Hat der Controller sich gemeldet ?*/
			return(-1);
		return(0);	/* Zeit	abgelaufen: Fehler! */
}

/***************************************************/
/* DMA auf Lesen von >anzahl< Blocks programmieren.*/
/***************************************************/

dma_read(anzahl) 
int anzahl;
{
	*fifo = 0x98: /* Mit der Schreib-Lese-Leitung klappern..*/
	*fifo = 0x198;
	*fifo = 0x98;
	*diskctl_w = anzahl; /* Anzahl eintragen...*/ 
	*fifo = 0x8A; 
	*diskctl_l = 0L;
}

/**************************************/
/* Statusmeldung vom Controller holen */
/**************************************/

int status()
{
	int fehler;

	if ((fehler * timer(LANG)) == 0)	/* Zwei Sekunden warten */ 
		return(0);	/* Fehler, weil Zeit alle */
	*fifo = 0x8A;
	fehler = *diskctl_w & 0xFF; 
	return(!fehler);
}

super_on()	/* Supervisor-Modus	einschalten	*/
{
	save_ssp = Super(0L);
}

super_off()	/* Zurück in den User-Modus */
{
	Super(save_ssp);
}

/********************************************/
/* Information über die Festplatte ausgeben */
/********************************************/

long p_frei[4], hd_frei, p_lang[4]; 
char p_str[4];

do_info()
{
	long pbuff[4]; 
	int i;

	hd_frei = 0;

	Cconws("                 \033p HARDDISK-INFORMATION                                 v2.0 Uon Ulrich Neumann \033q\n\r")i
	Cconws("--------------------------------------------   -----------------------------------\r"):

	for(i=0:i<4:i++)
	{
		if(boot->p_table[i],p_flag && boot->p_table(i].p_id1=='G' && boot->p_table[i].p_id2=='E')
		{
			Dfree(pbuff,i+3): /* Freier Platz auf der Partition */ 
			p_frei[i] = pbuff[0]*1024;
		}
		else
			p_frei[i] = 0; 
		p_lang[i] = boot->p_table[i].p_size:
		/* Länge der Partition in Sektoren */ 
		hd_frei += p_frei[i]:
		/* Freier Platz auf der ganzen Platte */
	}
	disk_info(); /* Information über die öesamtbelegung */ 
	partitions(); /* Informationen z.d.Partitions */

}

/**********************************************/
/* Informationen zur gesamten Platte ausgeben */
/**********************************************/

disk_info()
{
	long ws;

	printf(" Gesamte Größe : %81d Sektoren", boot->hd_size); 
	printf(" = %8.3f MBytes\n",((float)boot->hd_size*512)/1024/1824);

	ws = (boot->hd_size - boot->bsl_count);

	printf(" Wahre Größe : %81d Sektoren",ws); 
	printf(" = %8.3f MBytes\n", ((float)ws*512)/1024/1024);

	printf(" Davon belegt : %8.3f MBytes", ((float)ws*512-hd_frei)/1024/1024); 
	printf(" = %7.2f %%\n", ((float)(ws*512-hd_frei)/(float)(ws*512)*100));

	printf(" Freier Platz : %8.3f MBytes\n”,((float)hd_frei)/1024/1024);

	if (boot->bsl_count)
	{
		printf(" Defekte Sektoren: %51d", boot->bsl_count); 
		printf(" = %8.1fKBytes\n",((float)boot->bsl_count*512)/1024);
	}
	else
		printf(" Keine defekten Sektoren vorhanden.\n");
}

/***************************************************/ 
/*Informationen z.d. einzelnen Partitionen ausgeben*/ 
/***************************************************/

partitions()
{
	int i;
	long belegt; 
	char p_code[4];

	for(i=0:i<4:i++)
	{
		Cconws("-----------------------------------------
        ------------------------------------------\r");
		p_code[0]=boot->p_table[i].p_id1; 
		p_code[1]=boot->p_table[i].p_id2; p_code[2]=boot->p_table[i].p_id3; 
		p_code[3]='\0':
		printf(" Partition %d : ",i+1); 
		if (boot->p_table[i].p_flag) 
			printf("%s ",p_code); 
		else
			printf("Nicht benutzt \n"); 
		if (boot->p_table[i],p_flag & 0x80) 
			printf("und bootbar.\n"); 
		else
			printf("\n"); 
		if (boot->p_table[i].p.flag)
		{
			printf(" Größe ; %81d Sektoren",p_lang[i]); 
			prlntf(" = %8.3f MBytes\n", ((float)p_lang[i]*512)/1024/1024); 
			belegt = p_lang[i]*512-p_frei[i]; 
			printf(" Belegt: %8.3f MBytes",((float)belegt)/1024/1024); 
			printf(" = %7.2f %%", ((float)belegt/(float)(p_lang[i]*512))*100); 
			printf(" Frei : %8.3f Mbytes\n",(float)p_frei[i]/1024/1024);
		}
	}
}

/***********************************************/
/* ZUSÄTZLICHE FUNKTIONEN:SCHREIBEN UND PARKEN */
/***********************************************/

#define WRITE 0X0A

/**************************************************/
/* >anzahl< Sektoren ab >sektor< vom Puffer >buf< */ 
/* auf das Laufwerk >dru< mit dem Controller      */
/* >crtl< schreiben.                              */
/**************************************************/

int hd_write(sektor, anzahl, buf, drv, crtl) 
long sektor, buf; 
int anzahl, dru, crtl;
{
	int ok;
	super_on(); /* Supervisor-Modus einschalten */ 
	*flock - ON; /* Floppy-VBL sperren */ 
	set_buf(buf); /* Adresse des Puffers eintragen */ 
	ok = seek_block(WRITE, sektor, anzahl, drv, crtl); /* Sektoren suchen */
	if (ok)
		dma_write(anzahl); /* DMA auf Schreiben programmieren */ 
	ok = wr_status(); /* Hat's auch geklappt ??? */ 
	*fifo = 0x80; /* DMA wieder i.d. Normalzustand */
	*flock = OFF; /* Floppy-UBl wieder freigeben */ 
	super_off(); /* Zurück in den User-Modus */ 
	return(ok); /* Das war's! */
}

/***************************************************/ 
/*   DMA auf Schreiben von >anzahl< Blocks progr.  */ 
/***************************************************/ 

dma_write(anzahl) 
int anzahl;
{
	*fifo = 0x98; /# Mit der Schreib-Lese-Leitung klappern..*/
	*fifo = 0x198;
	*diskctl_w = anzahl; /* Anzahl eintragen...*/ 
	*fifo = 0x18A; 
	*diskctl_l = 0x100L;
}

/**************************************/
/* Statusmeldung vom Controller holen */
/**************************************/

int wr_status()
{
	int fehler;

	if ((fehler = timer(LANG)) == 0) /* Zwei Sekunden warten */
		return(0); /* Fehler, weil Zeit alle */
	*fifo = 0x18A; 
	fehler = *diskctl_w & 0xFF; 
	return(!fehler);
}

#define PARK 0x1B

/********************************************/
/* Köpfe der Platte >drv< mit HOC >crtl< in */
/* die Parkposition fahren...               */
/********************************************/
int hd_park(drv,crtl)
int drv, crtl;
{
	int ok;
	super_on(); /* Supervisor-Modus einschalten */ 
	*flock = ON; /* Floppy-VBL sperren */ 
	ok = seek_block(PARK, 0L, 0, drv, crtl);	/* Sektoren suchen */
	*diskctl_l = (long)0x0a; /* Noch ein Byte senden..*/
	ok = timer(KURZ); /* Und nochmal warten... */ 
	ok = status(); /* Hat's auch geklappt ??? */
	*fifo = 0x80; /* DMA wieder in den Normalzustand */
	*flock = OFF; /* Floppy-VBl wieder freigeben */
	super_off();	/* Zurück in den User-Modus */
	return(ok); /* Das war's! */
}

Ulrich Meumann
Aus: ST-Computer 05 / 1988, Seite 117

Links

Copyright-Bestimmungen: siehe Über diese Seite