/* Format a floppy disk (880k drive).
 * Author:		Mark R. Rinfret
 * Date:		06/28/87
 * Description:
 *		This set of routines may be incorporated into a program which
 *	has need of formatting a floppy disk.  I wrote it to support my
 *	hard disk backup utility.
 *
 * History:		(most recent change first)
 *
 * 12/08/87 -MRR- My floppy drive (at least) will occaisionally "hang"
 *                during formatting.  I don't know what the source of
 *                the problem is, but for now I've added a timer and
 *                switched to a SendIO/Wait combo to handle this.
 *
 * 08/26/87 -MRR- Modified FormatDisk to delay 5 seconds after
 *				  uninhibiting the drive.  This should give enough time
 *                for the validator to do its thing and prevent the
 *				  "Insert disk..." requester from rearing its ugly head
 *                if the application attempts to access the disk as
 *                soon as we return.
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/io.h>
#include <exec/devices.h>
#include <devices/trackdisk.h>
#include <libraries/dosextens.h>
#include ":src/lib/AmigaFunctions.h"

#include ":src/lib/Timer.h"

extern struct IOExtTD *CreateExtIO();

static int CkIOErr();

#define MAX_NAME	30L
#define TD_WRITE	CMD_WRITE
#define TRACKSIZE	NUMSECS * TD_SECTOR


/* Format a floppy disk - hardwired for the 3.5" 880k floppy drives.
 * Called with:
 *		drivename:	device name (DF0, etc.)
 *      name:		new volume name
 * Returns:
 *		Zero on success, 1 on failure
 * Note:
 *		This routine does not currently perform a verification, as
 *		recommended by the RKM.  Perhaps later...
 *		I also discovered that there's some erroneous crap in 
 *		"The Amiga Programmer's Workbook, Vol. II", by 
 *		Eugene P. Mortimore.  On page 339, he states that only 512
 *		bytes of track data are required for formatting.  The RKM
 *		correctly states that a "track's worth of data" is required.
 *		It took some playing with DiskEd to discover this error.
 */

int 
FormatDisk(drivename,name)
	char *drivename; char *name;
{
	long checkSum;
	char *dosID = "DOS";
	long dosWord = 0;
	short error;
	long diskBit, timerBit;
	struct MsgPort *diskPort = NULL;
	struct IOExtTD *diskRequest = NULL;
	ULONG diskChangeCount;
	USHORT retries, status = 0, i, timeout, track;
	long signals;						/* signal bits from Wait() */
	struct timerequest *timeRequest;		/* timeout timer request */
	int unit; 
	char *volname;

	char *diskBuffer;
	ULONG *diskBlock;			/* alias for diskBuffer, ULONG type */

	if (strlen(name) >= MAX_NAME) {
#ifdef DEBUG
		printf("Disk name is too long!\n");
#endif
		status = ERROR_INVALID_COMPONENT_NAME;	
		goto cleanup;
	}

	if ((unit = (drivename[2]-'0')) < 0 || unit >= NUMUNITS) {
#ifdef DEBUG
		printf("FormatDisk: invalid drive specification!\n");
#endif
		status = ERROR_INVALID_COMPONENT_NAME;
		goto cleanup;
	}

	if (!(diskBuffer = 
		AllocMem((long) TRACKSIZE, MEMF_PUBLIC | MEMF_CHIP))) {
		status = ERROR_NO_FREE_STORE;
		goto cleanup;
	}

	timeRequest = CreateTimer(0);	/* create uHERZ timer */
	if (timeRequest == NULL) {
		status = ERROR_NO_FREE_STORE;	/* would IoError be valid? */
		goto cleanup;
	}

	/* Store DOS "magic word" in disk block to be written during
	 * formatting. 
	 */
	diskBlock = (ULONG *) diskBuffer;/* we'll need this later */
	for (i = 0; i < 3; ++i)
		dosWord = (dosWord << 8) | dosID[i];
	dosWord = dosWord << 8;

#ifdef DEBUG
	printf("dosWord is %lx\n",dosWord);
#endif
	for (i = 0; i < TRACKSIZE / 4; ++i)
		diskBlock[i] = (dosWord | (long) (i & 0xff));

	if ((diskPort = CreatePort(0L, 0L)) == NULL) {
#ifdef DEBUG
		printf("FormatDisk can't create port!\n");
#endif
		status = 1;						/* is there a better error code? */
		goto cleanup;
	}

	if (!(diskRequest = (struct IOExtTD *) 
		CreateExtIO(diskPort, (long) sizeof(struct IOExtTD)))) {
		status = 1;
		goto cleanup;
	}

	if (status = OpenDevice(TD_NAME, (long) unit, diskRequest, 0L)) {
#ifdef DEBUG
		printf("FormatDisk: OpenDevice error: %d\n",error);
#endif
		goto cleanup;
	}

	if (status = Inhibit(drivename, 1)) {
#ifdef DEBUG
		printf("FormatDisk: unable to inhibit drive!\n");
#endif
		goto cleanup;
	}

/* Get the current disk change count.  This allows the trackdisk
 * driver to detect unwanted disk changes later on.
 */

	diskRequest->iotd_Req.io_Command = TD_CHANGENUM;
	DoIO(diskRequest);

/* Save a copy of the disk change count. */

	diskChangeCount = diskRequest->iotd_Req.io_Actual;

#ifdef DEBUG
	printf("Current disk change count is %ld\n", diskChangeCount);
#endif


	/* Format the disk, one track at a time.  This operation seems
	 * susceptible to timing out (infrequently) on my system, so we'll
	 * use a combination of SendIO and Wait, rather than DoIO.
	 */

	diskBit = 
		1L << diskRequest->iotd_Req.io_Message.mn_ReplyPort->mp_SigBit;
	timerBit = 	
		1L << timeRequest->tr_node.io_Message.mn_ReplyPort->mp_SigBit;

#ifdef DEBUG
	printf("diskBit => %08lx,  timerBit => %08lx\n",
			diskBit, timerBit);
#endif

	for (track = 0; track < NUMTRACKS; ++track) {
		retries = 0;
retry:
		diskRequest->iotd_Req.io_Command = TD_FORMAT;
		diskRequest->iotd_Req.io_Flags = 0;
		diskRequest->iotd_Req.io_Data = (APTR) diskBuffer;
		diskRequest->iotd_Count = diskChangeCount;
		diskRequest->iotd_Req.io_Length = NUMSECS * TD_SECTOR; 
		diskRequest->iotd_Req.io_Offset = track * NUMSECS * TD_SECTOR;
		SendIO(diskRequest);

		StartTimer(timeRequest, 5L, 0L); /* Start 5 second disk timer. */
#ifdef DEBUG
		if (timeRequest->tr_node.io_Error) {
			printf("Error on StartTimer: %d\n",
					timeRequest->tr_node.io_Error);
		}
#endif
		while (TRUE) {
			timeout = 0;
			signals = Wait(diskBit | timerBit);
			if (signals & timerBit) {
				if (CheckIO(timeRequest)) {	/* timer I/O complete? */
					WaitIO(timeRequest);	/* complete timer processing */
					AbortIO(diskRequest);	/* kill disk I/O */
					timeout = 1;
#ifdef DEBUG
					puts("--TIMEOUT--");
#endif
					break;
				}
			}

			if (signals & diskBit) {	/* Did disk complete? */
				if (CheckIO(diskRequest)) {
					WaitIO(diskRequest); /* complete disk processing */
					StopTimer(timeRequest);
					break;
				}
			}
		}							/* end while(TRUE) */

		/* Retry a timeout 3 times before giving up. */

		if (timeout && (++retries < 4) ) goto retry;

		if (status = CkIOErr(diskRequest,"Formatting error")) {
#ifdef DEBUG
			printf("  Track: %d\n",track);
#endif
			goto cleanup;
		}
	}

	/* Now comes some real KLUDGING.  Fill in the root block and the
	 * first hash block.  The information for this was gathered from
	 * the "AmigaDos Technical Reference Manual" and some sleuthing
	 * with DiskEd.
	 */

	for (i = 0; i < 128; ++i)
		diskBlock[i] = 0;

	diskBlock[0] = 2;			/* T.SHORT (type) */
	diskBlock[3] = 128 - 56;	/* hashtable size */
	diskBlock[78] = 0xffffffff; /* BMFLAG */
	diskBlock[79] = 881;		/* first bitmap block */
	DateStamp(&diskBlock[105]);	/* volume last altered date/time */
	DateStamp(&diskBlock[121]); /* volume creation date/time */
	volname = (char *) &diskBlock[108];
	/* convert input name to BSTR */
	*volname = strlen(name);
	for (i = 0; i < *volname; ++i)
		*(volname + 1 + i) = *(name + i);

	diskBlock[127] = 1;			/* ST.ROOT (secondary type) */

	checkSum = 0;
	for (i = 0; i < 128; ++i)
		checkSum += diskBlock[i];

	diskBlock[5] = - checkSum;

	/* Write the root block out to the disk. */

	diskRequest->iotd_Req.io_Command = TD_WRITE;
	diskRequest->iotd_Req.io_Length = TD_SECTOR;
	diskRequest->iotd_Req.io_Offset = TD_SECTOR * 880L;
	DoIO(diskRequest);
	if (status = CkIOErr(diskRequest, "Error writing root block")) {
		goto cleanup;
	}

	/* Write the first bitmap block. */

	for (i = 0; i < 56; ++i)
		diskBlock[i] = 0xffffffff;

	for (i = 56; i < 128; ++i)
		diskBlock[i] = 0;

	diskBlock[0] = 0xc000c037;	/* hint: x37 = 55 (last word of map?) */
	diskBlock[28] = 0xffff3fff; /* blocks 880, 881 used */
	diskBlock[55] = 0x3fffffff; /* blocks 1760, 1761 used? */

	diskRequest->iotd_Req.io_Length = TD_SECTOR;
	diskRequest->iotd_Req.io_Offset = 881L * TD_SECTOR;
	DoIO(diskRequest);					/* write out the bitmap */
	if (status = CkIOErr(diskRequest, "Error writing bitmap")) {
		goto cleanup;
	}

	diskRequest->iotd_Req.io_Command = ETD_UPDATE;
	diskRequest->iotd_Req.io_Flags = 0;
	DoIO(diskRequest);

	/* Turn the disk motor off. */

	diskRequest->iotd_Req.io_Command = TD_MOTOR;
	diskRequest->iotd_Req.io_Length = 0;
	DoIO(diskRequest);
	Inhibit(drivename, 0);				/* enable disk validator */

	Delay(3L * TICKS_PER_SECOND);		/* Give it a chance */

cleanup:
	CloseDevice(diskRequest);
	if (diskBuffer) FreeMem(diskBuffer, (long) TRACKSIZE);
	if (diskRequest) DeleteExtIO(diskRequest, (long) sizeof(*diskRequest));
	if (diskPort) DeletePort(diskPort);
	if (timeRequest) DeleteTimer(timeRequest);
	return status;
}

/* Check the disk request block for an error code.  If an error
 * occurred, print the argument string.
 * Called with:
 *		req:	pointer to I/O request structure
 *		msg:	error message string
 * Returns:
 *		error code from request structure
 */
static int CkIOErr(req, msg)
	struct IOStdReq *req; char *msg;
{
	register int code;

	if (code = req->io_Error) {
#ifdef DEBUG
		printf("%s, code: %d\n",msg,code);
#endif
	}
	return code;
}

#ifdef DEBUG
main(argc, argv)
	int argc; char *argv[];
{
	char *diskname;
	char *volname;

	int unit;

	if (argc < 3)
		volname = "GoodJob!";
	else
		volname = argv[2];

	if (argc < 2)
		diskname = "DF1:";
	else {
		diskname = argv[1];
		if (strlen(diskname) != 4 || 
			(strncmp(diskname,"df",2) && strncmp(diskname,"DF",2))) {
bad_drive:
			printf("Drive name may only be df0: through df3:!\n");
			exit(1);
		}
		if ((unit = (diskname[2] - '0')) < 0 || unit > 3)
			goto bad_drive;

	}
	printf("Insert disk in %s, then hit return\n",diskname);
	while (getchar() != '\n');
	if (FormatDisk(diskname,volname))
		printf("FormatDisk failed\n");
	else {
		printf("FormatDisk succeeded\n");
	}
}
#endif
