User:Omega

From OSDev.wiki
Jump to navigation Jump to search

Introduction


Welcome. So you think you are ready to start programming your floppy driver. Well, before we begin let's take a quick glance at the following few paragraphs to make sure that this is the type of driver that you actually can and want to support in your Operating System.

Prerequisites

  • 01. Have you enabled Protected Mode?
  • 02. Have you installed your new IDT?
  • 03. Have you installed your new GDT?
  • 04. Have you installed your new ISR?
  • 05. Have you installed your new IRQ?
  • 06. Have you installed your PIT yet?

Mandatory Tasks

  • 01. Create C Header FDC.H
  • 02. Create C Header FAT12.H
  • 03. Create C File FDC.C
  • 04. Create C File FAT12.C
  • 04. Create Custom OUTB Function
  • 04. Create Custom INB Function
  • 04. Create Custom PRINTF Function

Quick Summary

This project will produce by the end of this guide one 32 bit Protected Mode Floppy Driver which utilizes the DMA for disc IO functionality. This code assumes two main things: 01) You want to support a 32 bit Pmode Floppy Disk Driver. 02) You are compiling your C source using GCC or DJGPP.

Final Warning

The Floppy Drive is well on its way out and at some point soon no one will have one. Writing a Floppy Driver is quite obsolete to the rest of the world, so doing so must be done for your pure enjoyment. You should not design your OS to rely upon a floppy disk. However, the FAT12 and FAT32 is a lot a like, so it will be super easy to port this to FAT32. This program is distributed freely in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. PLEASE USE THIS CODE AT YOUR OWN RISK!!



HEADERS::FDC.H


Here we begin by defining our FAT12 Boot sector Structure and Helper Functions.

#ifndef FDC_H
#define FDC_H

struct floppyDisk
{
    short bytesPerSector;
    char sectorsPerCluster;
    short reservedSectors;
    char numOfFats;
    short maxRootEntries;
    short totalSectors;
    short sectorsPerFat;
    short sectorsPerTrack;
    short numberOfHeads;
    char bootSig;
    int volumeId;
    char volumeLabel[12];
};

void fdc_reset();
void get_floppy_type();
void init_floppy();
void floppy_ident();
void start_motor();
void stop_motor();
void wait_irq6();
void reset_floppy_controller();
void floppy_read_sectors(unsigned char *buf, int pos, int count);
void floppy_write_sector(unsigned char *buf, int pos, int read);
void fdc_handler();

#endif /* FDC_H */

HEADERS::FAT12.H


Here we define our FAT12/32 Structures and Helper Function.

#ifndef FAT12_H
#define FAT12_H

void get_bootsector();

typedef unsigned char	uint8_t;	/* or #include <stdint.h> */
typedef unsigned short	uint16_t;	/* Note: multi-byte values are little-endian */
typedef unsigned long	uint32_t;   /* 32 bytes total */

struct fat_bootsector			/* Warning: this struct must be packed */
{
	uint8_t		jump[3];               /* 16-bit JMP to boot code, or 8-bit JMP + NOP */
	uint8_t		oem_id[8];             /* e.g. 'MSWIN4.0' */
	uint16_t	bytes_per_sector;	/* usu. =512 */
	uint8_t		sectors_per_cluster;
	uint16_t	num_boot_sectors;	/* usu. =1 */
	uint8_t		num_fats;              /* usu. =2 */
	uint16_t	num_root_dir_ents;
	uint16_t	total_sectors;         /* 16-bit; 0 if num sectors > 65535 */
	uint8_t 	media_ID_byte;         /* usu. =0F0h */
	uint16_t	sectors_per_fat;
	uint16_t	sectors_per_track;
	uint16_t	heads;
	uint32_t	hidden_sectors;	/* =LBA partition start */
	uint32_t	total_sectors_large;   /* 32-bit; 0 if num sectors < 65536 */
	uint8_t		boot_code[474];
	uint8_t		magic[2];              /* 55h, 0AAh */
};                              /* 512 bytes total */

struct fat_dirent               	/* Warning: this struct must be packed */
{
	uint8_t  name[8];               /* ALL-CAPS, pad right with spaces */
	uint8_t  ext[3];                /* ALL-CAPS, pad right with spaces */
	uint8_t  attrib;                /* attribute byte */
	uint8_t  reserved;              /* =0 */
	uint8_t  ctime_ms;              /* file creation time, 10ms units */
	uint16_t ctime;              	/* file creation time, in DOS format */
	uint16_t cdate;              	/* file creation date, in DOS format */
	uint16_t adate;              	/* DOS date of last file access */
	uint16_t st_clust_msw;       	/* high 16 bits of starting cluster (FAT32) */
	uint16_t mtime;              	/* DOS time of last file modification */
	uint16_t mdate;              	/* DOS date of last file modification */
	uint16_t st_clust;           	/* starting cluster */
	uint32_t file_size;          	/* in bytes */
};

struct fat_direntry
{
   unsigned char name[11];
   unsigned char attr;
   char _reserved[10];
   unsigned short time;
   unsigned short date;
   unsigned short cluster;
   unsigned size;
} fat_direntry_t;

struct attrib                   /* Warning: this struct must be packed */
{
	int read_only : 1;      /* b0 */
	int hidden : 1;
	int system : 1;
	int volume_label : 1;
	int directory : 1;
	int archive : 1;
	int reserved : 2;       /* b6, b7 */
};                              /* 1 byte total */

struct dos_time                 /* Warning: this struct must be packed */
{
	unsigned two_secs : 5;  /* low 5 bits: 2-second increments */
	unsigned minutes : 6;   /* middle 6 bits: minutes */
	unsigned hours : 5;     /* high 5 bits: hours (0-23) */
};                              /* 2 bytes total */

struct dos_date                 /* Warning: this struct must be packed */
{
	unsigned date : 5;      /* low 5 bits: date (1-31) */
	unsigned month : 4;     /* middle 4 bits: month (1-12) */
	unsigned year : 7;      /* high 7 bits: year - 1980 */
};

struct FLOPPY_GEOMETRY
{
	int sectors;
	int heads;
	int track;
};

typedef struct fdisk
{
    char *name;
    char floppy_type;
    short base;
    int sr0;
    int motor_on;
    int track;
    int locked;
    struct FLOPPY_GEOMETRY * geometry;
} fdisk_t;

#endif /* FAT12_H */

You might notice at some point that none of the FAT32 structures are being used. This is because we have not yet completed that task. For now, I am going to leave this one up to you as the task of doing so at this point is relatively straightforward.

CFILE::FDC.C


Here is our brand new Floppy Drive 32 bit Pmode Driver!

#include <fdc.h>

#define DISK_PARAMETER_ADDRESS 0x000FEFC7
#define error 1

int first;
int second;
int current_track = -1;
short base_used;
volatile int irq6_state = 0;
int motor_on = 0;
int sr0 = 0;

char *floppy_types[] =
{
    "None.",
    "360kB 5.25: Unsupported.",
    "1.2MB 5.25: Unsupported.",
    "720KB 3.5: Unsupported.",
    "1.44MB 3.5.",
    "2.88MB 3.5: Unsupported."
};

// put in header later
#define LOW_BYTE(x)         (x & 0x00FF)
#define HI_BYTE(x)          ((x & 0xFF00) >> 8)

// the folowing is a simple look up table for channel and its ports
unsigned char maskport[8] = { 0x0A, 0x0A, 0x0A, 0x0A, 0xD4, 0xD4, 0xD4, 0xD4 };
unsigned char modeport[8] = { 0x0B, 0x0B, 0x0B, 0x0B, 0xD6, 0xD6, 0xD6, 0xD6 };
unsigned char clearport[8] = { 0x0C, 0x0C, 0x0C, 0x0C, 0xD8, 0xD8, 0xD8, 0xD8 };
unsigned char pageport[8] = { 0x87, 0x83, 0x81, 0x82, 0x8F, 0x8B, 0x89, 0x8A };
unsigned char addrport[8] = { 0x00, 0x02, 0x04, 0x06, 0xC0, 0xC4, 0xC8, 0xCC };
unsigned char countport[8] = { 0x01, 0x03, 0x05, 0x07, 0xC2, 0xC6, 0xCA, 0xCE };

// dma xfer for floppy
void set_dma(unsigned char *buff, short size, int read, int channel)
{
    char page = 0;
    char mode = 0;
    short offset = 0;
    if(read)
		mode = 0x48 + (char)channel;
	else
		mode = 0x44 + (char)channel;
	page = (char)((int)buff >> 16);
	offset = (short)((int)buff & 0xFFFF);
	size--;
	cli();
	outb(maskport[channel], (0x04 | channel)); // mask channel
	outb(clearport[channel], 0x00);                // stop trans
	outb(modeport[channel], mode);                 // type of trans
	outb(addrport[channel], LOW_BYTE(offset));     // send address
    outb(addrport[channel], HI_BYTE(offset));      // send address
    outb(pageport[channel], page);                 // send page
    outb(countport[channel], LOW_BYTE(size));      // send size
    outb(countport[channel], HI_BYTE(size));       // send size
    outb(maskport[channel], channel);              // unmask channel
    sti();
}

void get_floppy_type()
{
    unsigned char data;
    outb(0x70, 0x10);
    data = inb(0x71);
    first = data >> 4;
    second = data & 0xF;
}

void fdc_handler()
{
    irq6_state = 1;

}

void wait_irq6()
{
    while(irq6_state == 0);
    irq6_state = 0;
}

void start_motor()
{
    outb((base_used + 0x2), 0x1C);
    //delay(1);
    motor_on = 1;
}

void stop_motor()
{
    outb((base_used + 0x2), 0x00);
    //delay(1);
    motor_on = 0;
}

/* from intel manual */
void sendbyte(char byte)
{
    volatile int msr;
    int tmo;
    for(tmo = 0; tmo < 128; tmo++)
    {
        msr = inb((base_used + 0x04));
        if ((msr & 0xC0) == 0x80)
        {
            outb((base_used + 0x05), byte);
            return;
        }
        inb(0x80);
    }
}

/* from intel manual */
int getbyte()
{
    volatile int msr;
    int tmo;
    for (tmo = 0; tmo < 128; tmo++)
    {
        msr = inb((base_used + 0x04));
        if ((msr & 0xd0) == 0xd0)
	        return inb((base_used + 0x05));
        inb(0x80);
    }
    return -1;
}

void waitfdc()
{

    wait_irq6();
		//printf(0x0F,1,1,"Now Recalibrate(1a)");
    sendbyte(0x08);
		//printf(0x0F,1,1,"Now Recalibrate(1b)");
    sr0 = getbyte();
		//printf(0x0F,1,1,"Now Recalibrate(1c)");
    (void)getbyte();
		//printf(0x0F,1,1,"Now Recalibrate(1d)");
}

int fdc_seek(char track)
{
    if(current_track == track)
        return 0;
    sendbyte(0x0F);
    sendbyte(0x00);
    sendbyte(track);
    //delay(1);
    waitfdc();
    if(sr0 != 0x20)
    {
        printf(0x0F,1,1,"'nTrack seek error");
        return error;
    }
    current_track = track;
    return 0;
}

void fdc_recalibrate()
{
    start_motor();
    sendbyte(0x07);
    sendbyte(0x00);
    waitfdc();
    stop_motor();
}

void fdc_reset()
{
   outb((base_used + 0x02), 0);    // stop everything
   motor_on = 0;
   outb((base_used + 0x04), 0);     // data rate (500K/s)
   outb((base_used + 0x02), 0x0C);  // restart ints
   //delay(1);
		//printf(0x0F,1,1,"Now Recalibrate(1)");
   waitfdc();
//   delay(500);
		//printf(0x0F,1,1,"Now Recalibrate(2)");
   sendbyte(0x03);                     // timing
   sendbyte(0xDF);                     // timing
   sendbyte(0x02);                     // timing
   //delay(1);
	//printf(0x0F,1,1,"Now Recalibrate()");
   while(fdc_seek(1) == error);        // set track
   fdc_recalibrate();
}

void init_floppy()
{
    base_used = 0x3F0;
    get_floppy_type();

}

void floppy_ident()
{
    printf(0x0F,1,1,"'tF0: %s",floppy_types[first]);
    printf(0x0F,1,2,"'tF1: %s",floppy_types[second]);
}

void floppy_read_sectors(unsigned char *buf, int pos, int count)
{
    int sec, cyl, head, max_pos = pos + count;
    start_motor();
    for(; pos < max_pos; pos++)
    {
        sec = (pos % 18) + 1;
        cyl = pos / 36;
        head = (pos / 18) % 2;
        outb((base_used + 0x7), 0);
        while(fdc_seek((char)cyl) == error);
        set_dma(0x00000000, 512, 0, 2);
        //delay(1);
//        if(inb(base_used + 0x4) != 0x80)
//        {
//            puts("The DMA bit wasn't set'n");
////            stop_motor();
//            return;
//        }
        sendbyte(0xE6);
        sendbyte((unsigned char)head << 2);
        sendbyte((unsigned char)cyl);
        sendbyte((unsigned char)head);
        sendbyte((unsigned char)sec);
        sendbyte(0x02);
        sendbyte(0x12);
        sendbyte(0x1B);
        sendbyte(0xFF);
        wait_irq6();
        (void)getbyte();
        (void)getbyte();
        (void)getbyte();
        (void)getbyte();
        (void)getbyte();
        (void)getbyte();
        (void)getbyte();
        memcpy((unsigned int *)buf, (unsigned int *)0x00000000, 128);
        buf += 0x200;
    }
    stop_motor();
}

void floppy_write_sector(unsigned char *buf, int pos, int read)
{
    int sec, cyl, head, stat = error;
    sec = (pos % 18) + 1;
    cyl = (pos / 18) / 2;
    head = cyl % 2;
    start_motor();
    outb((base_used + 0x7), 0);
    while(stat == error)
        stat = fdc_seek((char)cyl);
    set_dma(buf, 512, 1, 2);
    if(inb(base_used + 0x4) != 0x80)
    {
        printf(0x0F,1,1,"'nThe DMA bit wasn't set");
        return;
    }
    sendbyte(0xC5);
    sendbyte((unsigned char)head << 2);
    sendbyte((unsigned char)cyl);
    sendbyte((unsigned char)head);
    sendbyte((unsigned char)sec);
    sendbyte(0x02);
    sendbyte(0x12);
    sendbyte(0x1B);
    sendbyte(0xFF);
    wait_irq6();
    (void)getbyte();
    (void)getbyte();
    (void)getbyte();
    (void)getbyte();
    (void)getbyte();
    (void)getbyte();
    (void)getbyte();
    stop_motor();
}

void fdc_install(void)
{
	outb(0x20, 0x20);
	irq_install_handler(6,fdc_handler);
}

CFILE::FAT12.C


Here is our gateway to FDD interaction. We can use this portion to determine specifics about the drive in order to swap out different drivers for different FAT types or we could also load an EXE/BIN file right off our disk and execute it! For now, we only print the values to the screen.

#include <fdc.h>

struct floppyDisk floppy1;  //defined in FDC.H

void get_bootsector()  //defined in FAT12.H
{
    char bootsector[512];
    floppy_read_sectors(bootsector, 0, 1);
    floppy1.bytesPerSector = (short)bootsector[11];
    floppy1.bytesPerSector |= (short)bootsector[12] << 8;
    floppy1.sectorsPerCluster = bootsector[13];
    floppy1.reservedSectors = (short)bootsector[14];
    floppy1.reservedSectors |= (short)bootsector[15] << 8;
    floppy1.numOfFats = bootsector[16];
    floppy1.maxRootEntries = (short)bootsector[17];
    floppy1.maxRootEntries |= (short)bootsector[18] << 8;
    floppy1.totalSectors = (short)bootsector[19];
    floppy1.totalSectors |= (short)bootsector[20] << 8;
    floppy1.sectorsPerFat = (short)bootsector[22];
    floppy1.sectorsPerFat |= (short)bootsector[23] << 8;
    floppy1.sectorsPerTrack = (short)bootsector[24];
    floppy1.sectorsPerTrack |= (short)bootsector[25] << 8;
    floppy1.numberOfHeads = (short)bootsector[26];
    floppy1.numberOfHeads |= (short)bootsector[27] << 8;
    floppy1.bootSig = bootsector[38];
    floppy1.volumeId = (int)bootsector[39];
    floppy1.volumeId |= (int)bootsector[40] << 8;
    floppy1.volumeId |= (int)bootsector[41] << 16;
    floppy1.volumeId |= (int)bootsector[42] << 24;
    floppy1.volumeLabel[0] = bootsector[43];
    floppy1.volumeLabel[1] = bootsector[44];
    floppy1.volumeLabel[2] = bootsector[45];
    floppy1.volumeLabel[3] = bootsector[46];
    floppy1.volumeLabel[4] = bootsector[47];
    floppy1.volumeLabel[5] = bootsector[48];
    floppy1.volumeLabel[6] = bootsector[49];
    floppy1.volumeLabel[7] = bootsector[50];
    floppy1.volumeLabel[8] = bootsector[51];
    floppy1.volumeLabel[9] = bootsector[52];
    floppy1.volumeLabel[10] = bootsector[53];
    floppy1.volumeLabel[11] = '\0';

    //print this stuff for now
    printf(0x0F,1,1,"FDD Info");
    printf(0x0F,1,2,"Bytes per Sector: %d",floppy1.bytesPerSector);
    printf(0x0F,1,3,"Sectors per Cluster: %d",floppy1.sectorsPerCluster);
    printf(0x0F,1,4,"Reserved Sectors: %d",floppy1.reservedSectors);
    printf(0x0F,1,5,"Num of Fats: %d",floppy1.numOfFats);
    printf(0x0F,1,6,"Max Root Entries: %d",floppy1.maxRootEntries);
    printf(0x0F,1,7,"Total Sectors: %d",floppy1.totalSectors);
    printf(0x0F,1,8,"Sectors per FAT: %d",floppy1.sectorsPerFat);
    printf(0x0F,1,9,"Sectors per Track: %d",floppy1.sectorsPerTrack);
    printf(0x0F,1,10,"Number of Heads: %d",floppy1.numberOfHeads);
    printf(0x0F,1,11,"Boot Signature: %d",floppy1.bootSig);
    printf(0x0F,1,12,"Volume ID: %d",floppy1.volumeId);
    printf(0x0F,1,13,"Volume Label: %s",floppy1.volumeLabel);
}

void readRoot() 
{
// develop this to find the Root Directory
}

Kernel Initialization Code


Alright, as long as you have successfully created all of these files and have fulfilled the prerequisites, then you are officially ready to get the ball rolling in your kernel. Place this code below in your main kernel function, for example.

#include <fdc.h>
#include <fat12.h>

int main() 
{
  sti();                          //Turn on interrupts
  pit();                          //Start your timer (not included)
  fdc_install();                  //Install IRQ Handler
  init_floppy();                  //Initialize Floppy
  fdc_reset();                    //Reset Motor/Recalibrate
  //floppy_ident();               //Could be used to load drivers for different formats, I comment it out for now
  char mbuff[512];
  floppy_read_sectors(mbuff,0,1); //read the floppy boot sector (512 bytes)
  get_bootsector();               //fill our structure with the boot sector values
  for(;;);                        //idle
  return 0;
}

PIT Wait Function


This code is located in my PIT.C source file which is not included here. You will need this code to perform your wait functions.

void timer_wait(int ticks)
{
    int eticks;
    eticks = timer_ticks + ticks;

	while (timer_ticks < eticks);
	//schedule();
}

The schedule() function simply handles tasks like a good driver should, but this isn't a good driver and I have that commented out for now; it will be good soon.

Quick Run Down


Real quick this code works like this:

  • 01. We just got back from our loader and interrupts are turned on.
  • 02. The timer (PIT) is enabled by mapping it to the IRQ 0 ISR.
  • 03. The floppy driver is enabled by mapping it to the IRQ 6 ISR.
  • 04. We then reset the motor and recalibrate the drive.
  • 05. Then we issue a floppy_read_sectors command to read only the first 512 bytes (bootsector) of floppy disk.
  • 06. As that is being done we are also filling our struct and printing the contents to screen.
  • 07. Done and Halt.

In Summation


At this point you should be well on your way to making this Floppy Driver happen for your OS. The next item on your list could be building a Virtual File System, Paging/Heap, Multi-Tasking, Scheduling/Tasking, FAT32 or NTFS support, maybe take some time off to work on your console interface, terminal, etc or just take a break from learning for a while because you have certainly come a long way if you are here now. Whatever you choose, make sure that you are taking the time to truly understand what you are learning and are truly capable of putting that knowledge to practical use before you move on to your next project. Most of all, have fun, good luck, and happy coding.