Identifying installed floppy drives


If you’re implementing support for floppy drive controllers in your hobby OS kernel, then one of your first tasks is to identify what floppy drives are installed in the system. This is pretty easy to do, as we can interrogate the system configuration to find out.

The PC architecture has a little battery-backed memory chip called the CMOS (which stands for “Complimentary Metal Oxide Semiconductor” if you really want to know). This old bit of kit was originally only used as a bit of clock/calendar logic but had extra space which subsequently got used for storing system configuration information…and so it remains today. Part of the information retained in the CMOS are the floppy drive types of the first two drives (if they’re installed) and this is located at offset 0x10 of the CMOS memory space.

Digression:

In fact, there’s an “equipment” byte at offset 0x14 which includes a bit field for representing the existence (but not type) of up to 4 floppy drives. However, the equipment byte wasn’t universally used the same way by all manufacturers. Besides, we’d still need to know what type they are, so just knowing they are there is not sufficient. In reality, particularly these days, it’s extremely unlikely that there’ll be more than two so that’s all I’m going to bother supporting here.

To read the CMOS, we write the CMOS byte address into I/O port 0x70 and read the result byte back from I/O port 0x71. This means we can get the floppy drive type codes from the CMOS as follows…

uint8_t b;

outportb(0×70, 0×10);
b = inportb(0×71);

The resultant byte is encoded in two identical halves. The upper 4 bits identify the first drive (drive 0) and the lower 4 bits identify the second drive (drive 1). Here’s a diagram showing how these two 4-bit fields are encoded:

We can use the bit-encoding to help us determine important parameters that our floppy driver will use, for example the number of tracks, the time it takes to spin up and spin down the disk, and so on.

Example code

In my own kernel, I don’t even bother trying to support anything except a 3.5″ 1.44 MB floppy as the others are so rare or obsolete that I just don’t care. This keeps the identification code in my floppy driver simpler. Below is the corresponding code extract from my floppy driver. My kernel’s initialization routine calls probe_floppy() if the BIOS parameter block says there are one or more floppy drives in the system. This function then calls fd_identify() for each of the drives it finds in the CMOS.

/*
 * fill in this floppy drive instance's specification based
 * on the drive type obtained from CMOS
 */
static int
fd_identify(fdrive_t *fp, uint8_t type)
{
	memset(fp, 0, sizeof (fdrive_t));
	switch (type) {
	case 0:
		/* shouldn't happen - no drive! */
		error(E_WARN, "fd_identify called with type = 0\n");
		return (DEV_FAIL);

	case 1:
		/* 5.25" 360 KB floppy - obsolete */

	case 2:
		/* 5.25" 1.2 MB floppy - obsolete */
	case 3:
		/* 3.5" 720 KB floppy - obsolete */
	case 5:
		/* 3.5" 2.88 MB floppy - obsolete */
		error(E_WARN, "Obsolete %s floppy drive detected\n",
		    fdtypes[type]);
		return (DEV_FAIL);

	default:
		/* unknown type - assume it's the ubiquitous 3.5" 1.44MB */
	case 4:
		fp->ident = "3.5\" 1.44MB";
		fp->data_rate = 500;
		fp->hlt = 8;
		fp->spt = 18;
		fp->srt_hut = 0xcf;
		/*
		 * set the spin_up and spin_down times by clock ticks,
		 * which occur at 100 HZ.
		 */
		fp->spin_up = 40;	/* 0.4 seconds */
		fp->spin_down = 300;	/* 3 seconds */
		fp->tracks = 80;
		fp->heads = 2;
		fp->gap3 = 0x1b;
		fp->gap3fmt = 0x6c;
		break;
	}

	/*
	 * at the moment, we only support 2 floppy drives from a single
	 * (primary) floppy disk controller, so we set the base address
	 * directly here.
	 */
	fp->base = FDC_BASEADDR;
	fp->did = ndrives;

	/* create the floppy device node name */
	sprintf(fp->name, "fd%d", ndrives);

	if ((fp->devp = mem_alloc(sizeof (device_t))) == NULL) {
		error(E_WARN,
		    "cannot allocate memory for floppy device\n");
		return (DEV_FAIL);
	}

	memset(fp->devp, 0, sizeof (device_t));
	fp->devp->ops = &fd_devops;
	fp->devp->name = fp->name;
	fp->devp->data = fp;

	printf("fd%d  : %s floppy drive\n", ndrives, fp->ident);

	return (DEV_OK);
}

/*
 * we "probe" to see what floppy drives exist in the system. At the moment,
 * this is done by reading from the CMOS, which is pretty machine specific!
 * We also assume a maximum of two floppy drives, which is almost certainly
 * a reasonable assumption these days as most 80x86 don't have any!
 */
void
probe_floppy()
{
	uint8_t b, n;
	int attached = 0;

	/* find out what floppy drives exist from the CMOS */
	outportb(0x70, 0x10);
	b = inportb(0x71);

	/* check for drive 0 */
	if ((n = b >> 4) != 0) {
		if (fd_identify(&floppy[0], n) == 0) {
			ndrives = 1;
			attached++;
		}
	}

	if ((n = b & 0xf) != 0) {
		if (fd_identify(&floppy[1], n) == 0) {
			ndrives++;
			attached++;
		}
	}

	/* if we didn't find any drives, there's nothing more to do */
	if (ndrives == 0)
		return;

	if (attached && (fd_attach() != DEV_OK)) {
		error(E_WARN, "failed to attach floppy driver\n");
		return;
	}

	if (floppy[0].devp) {
		if (device_register(floppy[0].devp) != DEV_OK)
			error(E_WARN, "failed to register floppy "
			    "device 0\n");
		else
		if (sys_mkdev(NULL, "/dev/fd0", floppy[0].devp->id, 0) != 0)
			error(E_WARN, "cannot create /dev/fd0 node\n");
	}

	if (floppy[1].devp) {
		if (device_register(floppy[1].devp) != DEV_OK)
			error(E_WARN, "failed to register floppy "
			    "device 1\n");
		else
		if (sys_mkdev(NULL, "/dev/fd1", floppy[1].devp->id, 1) != 0)
			error(E_WARN, "cannot create /dev/fd1 node\n");
	}

	atom_init(&fdio_atom);
}