thomasloven.com/pages/2014-04-16-Dito-Drives.md

3.6 KiB

layout: post title: "DITo - Drives" subtitle: "Exploring the MBR" tags: [osdev]

Let's write a few drive drivers.

Pure image

The simplest image we'd have to handle is a plain image file, such as a floppy disk. There's only a single partition and a one-to-one mapping from the image to the drive.

First of all, let's think of what data we'd need to store. The filename might be nice to have, and probably a pointer to the opened file. Would hate to lose that...

struct im_data
{
	char *filename;
	FILE *file;
};

The functions required for the driver would then make use of the c standard library:

int im_open(drive_t *d, int flags)
{
	struct im_data *data = d->data;
	data->file = fopen(data->filename, flags);
	return 1;
}
int im_close(drive_t *d, int flags)
{
	struct im_data *data = d->data;
	fclose(data->file);
	return 1;
}
int im_read(drive_t *d, void *buffer, size_t length, off_t offset)
{
	struct im_data *data = d->data;
	fseek(data->file, offset, SEEK_SET);
	return fread(buffer, length, 1, data->file);
}
int im_write(drive_t *d, void *buffer, size_t length, off_t offset)
{
	struct im_data *data = d->data;
	fseek(data->file, offset, SEEK_SET);
	return fwrite(buffer, length, 1, data->file);
}

The function for setting up the drive is equally simple:

drive_driver_t im_driver = 
{
	im_open,
	im_close,
	im_read,
	im_write,
};

drive_t *image_drive(const char *filename)
{
	drive_t *d = calloc(1, sizeof(drive_t));
	struct im_data *data = calloc(1, sizeof(struct im_data));
	
	d->driver = &im_driver;
	d->data = data;
	data->filename = strndup(filename, FILENAME_MAX_LENGTH);
	
	return d;
}

As always, I've stripped away all sanity checking and error handling.

###MBR formated disks

In order to use a harddrive for different things (like file storage or swap space), it can be split into several partitions. One standard for doing this is through a table in the MBR or Master Boot Record of the disk.

The MBR starts with 436 bytes of space for a bootloader, then there's a 10 byte disk ID followed by 4 partition table entries.

The entries of the table has the following structure:

struct MBR
{
	uint8_t boot_indicator;
	uint8_t start_chs[3];
	uint8_t system_id;
	uint8_t end_chs[3];
	uint32_t start_lba;
	uint32_t num_sectors;
}__attribute__((packed));

CHS and LBA are different ways of addressing sectors of the disk.

CHS - or Cylinder Sector Head - addresses the sectors by their physical location on the disk. The format in this table is first 8 bytes for the wanted head (two heads per harddrive platter), then 6 bits for the sector (angle on the platter) and finally 10 bits for the cylinder (distance from platter center). This method requires knowledge of the actual physical structure of the drive and maxes out at around 8 GB with 512 byte sectors.

LBA - or Logical Block Addressing - leaves all that to the drive and addresses the sectors by a single number. With 512 byte sectors, this is useful up to about 2 GB.

For our purposes, the fields start_lba and num_sectors are all we need (and system_id to check if the partition table entry is used).

###MBR driver

The driver for reading from a partition is pretty alike the plain image driver. Principally, the only difference is that 512 times start_lba has to be added to the offset for reading and writing, and that care must be taken not to allow reading or writing outside of the partition boundaries.

I even wrote the driver to piggyback on a different driver. I.e. the function for creating the drive object requires a drive object as argument (as in the example in the previous post).