diff --git a/Modules/Storage/FDDv2/common.h b/Modules/Storage/FDDv2/common.h
index 2f35c7bccd65067d66971c6d264f5da364665a06..f426716f2e6630e4d920096d4d59ed5c34f19051 100644
--- a/Modules/Storage/FDDv2/common.h
+++ b/Modules/Storage/FDDv2/common.h
@@ -8,6 +8,8 @@
 #ifndef _FDC_COMMON_H_
 #define _FDC_COMMON_H_
 
+#include <mutex.h>
+
 // === CONSTANTS ===
 #define MAX_DISKS	8	// 4 per controller, 2 controllers
 #define TRACKS_PER_DISK	(1440*2/18)
@@ -19,15 +21,19 @@ typedef struct sFDD_Drive	tDrive;
 // === STRUCTURES ===
 struct sFDD_Drive
 {
+	 int	bValid;
 	 int	bInserted;
 	 int	MotorState;
 	 int	Timer;
+
+	tMutex	Mutex;
 	
 	void	*TrackData[TRACKS_PER_DISK];	// Whole tracks are read
 };
 
 // === FUNCTIONS ===
-extern void	FDD_int_IRQHandler(int IRQ, void *Ptr);
+extern int	FDD_SetupIO(void);
+extern int	FDD_int_ReadWriteTrack(int Disk, int Track, int bWrite, void *Buffer);
 
 // === GLOBALS ===
 extern tDrive	gaFDD_Disks[MAX_DISKS];
diff --git a/Modules/Storage/FDDv2/fdc.c b/Modules/Storage/FDDv2/fdc.c
index 81dc00cc861784103ea21531f20aa3f9b6bf5074..aeaebb55d59bf8552b646b007c5ee952fabb90a3 100644
--- a/Modules/Storage/FDDv2/fdc.c
+++ b/Modules/Storage/FDDv2/fdc.c
@@ -7,6 +7,7 @@
  */
 #include <acess.h>
 #include "common.h"
+#include <dma.h>
 
 // === CONSTANTS ===
 #define MOTOR_ON_DELAY	500
@@ -37,88 +38,216 @@ enum eFDC_Commands
 };
 
 // === PROTOTYPES ===
+ int	FDD_SetupIO(void);
  int	FDD_int_ReadWriteTrack(int Disk, int Track, int bWrite, void *Buffer);
-
+ int	FDD_int_SeekToTrack(int Disk, int Track);
+ int	FDD_int_Calibrate(int Disk);
+ int	FDD_int_Reset(int Disk);
+// --- FIFO
  int	FDD_int_WriteData(Uint16 Base, Uint8 Data);
  int	FDD_int_ReadData(Uint16 Base, Uint8 *Data);
 void	FDD_int_SenseInterrupt(Uint16 Base, Uint8 *ST0, Uint8 *Cyl);
- int	FDD_int_Calibrate(int Disk);
- int	FDD_int_Reset(Uint16 Base);
+// --- Motor Control
  int	FDD_int_StartMotor(int Disk);
  int	FDD_int_StopMotor(int Disk);
 void	FDD_int_StopMotorCallback(void *Ptr);
+// --- Helpers
+ int	FDD_int_HandleST0Error(const char *Fcn, int Disk, Uint8 ST0);
 Uint16	FDD_int_GetBase(int Disk, int *Drive);
+// --- Interrupt
 void	FDD_int_ClearIRQ(void);
  int	FDD_int_WaitIRQ(void);
 void	FDD_int_IRQHandler(int IRQ, void *Ptr);
 
 // === GLOBALS ===
+/**
+ * \brief Marker for IRQ6
+ * \todo Convert into a semaphore?
+ */
  int	gbFDD_IRQ6Fired;
+/**
+ * \brief Protector for DMA and IRQ6
+ */
+tMutex	gFDD_IOMutex;
 
 // === CODE ===
+/**
+ * \brief Set up FDC IO
+ * \return Boolean failure
+ *
+ * Registers the IRQ handler and resets the controller
+ */
+int FDD_SetupIO(void)
+{
+	// Install IRQ6 Handler
+	IRQ_AddHandler(6, FDD_int_IRQHandler, NULL);
+	
+	// Reset controller
+	FDD_int_Reset(0);
+	// TODO: All controllers
+}
+
 /**
  * \brief Read/Write data from/to a disk
  * \param Disk Global disk number
- * \param Track Track number
+ * \param Track Track number (Cyl*2+Head)
  * \param bWrite Toggle write mode
  * \param Buffer Destination/Source buffer
  * \return Boolean failure
  */
 int FDD_int_ReadWriteTrack(int Disk, int Track, int bWrite, void *Buffer)
 {
-	return -1;
-}
+	Uint8	cmd;
+	 int	i, _disk;
+	Uint16	base = FDD_int_GetBase(Disk, &_disk);
+	 int	cyl = Track >> 1, head = Track & 1;
 
-/**
- * \brief Write a byte to the FIFO
- */
-int FDD_int_WriteData(Uint16 Base, Uint8 Data)
-{
-	for( int i = 0; i < 100; i ++ )
+	Mutex_Acquire( &gFDD_IOMutex );
+	
+	// Initialise DMA for read/write
+	// TODO: Support non 1.44MiB FDs
+	DMA_SetChannel(2, BYTES_PER_TRACK, !bWrite);
+	
+	// Select command
+	if( bWrite )
+		cmd = CMD_WRITE_DATA | 0xC0;
+	else
+		cmd = CMD_READ_DATA | 0xC0;
+	
+	// Seek
+	if( FDD_int_SeekToTrack(Disk, Track) ) {
+		Mutex_Release( &gFDD_IOMutex );
+		return -1;
+	}
+
+	for( i = 0; i < 20; i ++ )
 	{
-		if( inb(Base + FDC_MSR) & 0x80 )
-		{
-			outb(Base + FDC_FIFO, Data);
-			return 0;
+		FDD_int_StartMotor(Disk);
+
+		// Write data
+		if( bWrite )
+			DMA_WriteData(2, BYTES_PER_TRACK, Buffer);	
+	
+		FDD_int_WriteData(base, cmd);
+		FDD_int_WriteData(base, (head << 2) | _disk);
+		FDD_int_WriteData(base, cyl);
+		FDD_int_WriteData(base, head);
+		FDD_int_WriteData(base, 1);	// First Sector
+		FDD_int_WriteData(base, 2);	// Bytes per sector (128*2^n)
+		FDD_int_WriteData(base, 18);	// 18 tracks (full disk) - TODO: Non 1.44
+		FDD_int_WriteData(base, 0x1B);	// Gap length - TODO: again
+		FDD_int_WriteData(base, 0xFF);	// Data length - ?
+	
+		FDD_int_WaitIRQ();
+	
+		// No Sense Interrupt
+		
+		Uint8	st0=0, st1=0, st2=0, bps=0;
+		FDD_int_ReadData(base, &st0);
+		FDD_int_ReadData(base, &st1);	// st1
+		FDD_int_ReadData(base, &st2);	// st2
+		FDD_int_ReadData(base, NULL);	// rcy - Mutilated Cyl
+		FDD_int_ReadData(base, NULL);	// rhe - Mutilated Head
+		FDD_int_ReadData(base, NULL);	// rse - Mutilated sector
+		FDD_int_ReadData(base, &bps);	// bps - Should be the same as above
+
+		if( st0 & 0xc0 ) {
+			FDD_int_HandleST0Error(__func__, Disk, st0);
+			continue ;
 		}
-		Time_Delay(10);
+	
+		if( st2 & 0x02 ) {
+			Log_Debug("FDD", "Disk %i is not writable", Disk);
+			Mutex_Release( &gFDD_IOMutex );
+			return 2;
+		}
+		
+		if( st0 & 0x08 ) {
+			Log_Debug("FDD", "FDD_int_ReadWriteTrack: Drive not ready");
+			continue ;
+		}
+
+
+		if( st1 & 0x80 ) {
+			Log_Debug("FDD", "FDD_int_ReadWriteTrack: End of cylinder");
+			continue ;
+		}
+
+		if( st1 & (0x20|0x10|0x04|0x01) ) {
+			Log_Debug("FDD", "FDD_int_ReadWriteTrack: st1 = 0x%x", st1);
+			continue;
+		}
+		
+		if( st2 & (0x40|0x20|0x10|0x04|0x01) ) {
+			Log_Debug("FDD", "FDD_int_ReadWriteTrack: st2 = 0x%x", st2);
+			continue ;
+		}
+		
+		if( bps != 0x2 ) {
+			Log_Debug("FDD", "Wanted bps = 2 (512), got %i", bps);
+			continue ;
+		}
+
+		// Read back data
+		if( !bWrite )
+			DMA_ReadData(2, BYTES_PER_TRACK, Buffer);
+		
+		FDD_int_StopMotor(Disk);
+		Mutex_Release( &gFDD_IOMutex );
+		return 0;
 	}
-	Log_Error("FDD", "Write timeout");
+
+	Log_Debug("FDD", "%i retries exhausted", i);
+	FDD_int_StopMotor(Disk);
+	Mutex_Release( &gFDD_IOMutex );
 	return 1;
 }
 
 /**
- * \brief Read a byte from the FIFO
+ * \brief Seek to a specific track
+ * \param Disk Global disk number
+ * \param Track Track number (Cyl*2+Head)
+ * \return Boolean failure
  */
-int FDD_int_ReadData(Uint16 Base, Uint8 *Data)
+int FDD_int_SeekToTrack(int Disk, int Track)
 {
-	for( int i = 0; i < 100; i ++ )
+	Uint8	st0=0, res_cyl=0;
+	 int	cyl, head;
+	 int	_disk;
+	Uint16	base = FDD_int_GetBase(Disk, &_disk);;
+	
+	cyl = Track / 2;
+	head = Track % 1;
+	
+	FDD_int_StartMotor(Disk);
+	
+	for( int i = 0; i < 10; i ++ )
 	{
-		if( inb(Base + FDC_MSR) & 0x80 )
+		FDD_int_ClearIRQ();
+		FDD_int_WriteData(base, CMD_SEEK);
+		FDD_int_WriteData(base, (head << 2) + _disk);
+		FDD_int_WriteData(base, cyl);
+	
+		FDD_int_WaitIRQ();
+		FDD_int_SenseInterrupt(base, &st0, &res_cyl);
+	
+		if( st0 & 0xC0 )
 		{
-			Uint8 tmp = inb(Base + FDC_FIFO);
-			if(Data) *Data = tmp;
+			FDD_int_HandleST0Error(__func__, Disk, st0);
+			continue ;
+		}
+		
+		if( res_cyl == cyl ) {
+			FDD_int_StopMotor(Disk);
 			return 0;
 		}
-		Time_Delay(10);
 	}
-	Log_Error("FDD", "Read timeout");
+	
+	Log_Error("FDD", "FDD_int_SeekToTrack: 10 retries exhausted\n");
+	FDD_int_StopMotor(Disk);
 	return 1;
 }
 
-/**
- * \brief Acknowledge an interrupt
- * \param Base	Controller base address
- * \param ST0	Location to store the ST0 value
- * \param Cyl	Current cylinder
- */
-void FDD_int_SenseInterrupt(Uint16 Base, Uint8 *ST0, Uint8 *Cyl)
-{
-	FDD_int_WriteData(Base, CMD_SENSE_INTERRUPT);
-	FDD_int_ReadData(Base, ST0);
-	FDD_int_ReadData(Base, Cyl);
-}
-
 /**
  * \brief Calibrate a drive
  * \param Disk	Global disk number
@@ -142,12 +271,7 @@ int FDD_int_Calibrate(int Disk)
 		FDD_int_SenseInterrupt(base, &st0, NULL);
 		
 		if( st0 & 0xC0 ) {
-			static const char *status_type[] = {
-				0, "Error", "Invalid", "Drive Error"
-			};
-			Log_Debug("FDD", "FDD_int_Calibrate: st0 & 0xC0 = 0x%x, %s",
-				st0 & 0xC0, status_type[st0 >> 6]
-				);
+			FDD_int_HandleST0Error(__func__, Disk, st0);
 			continue ;
 		}
 		
@@ -167,28 +291,80 @@ int FDD_int_Calibrate(int Disk)
  * \brief Reset a controller
  * \param Base	Controller base address
  */
-int FDD_int_Reset(Uint16 Base)
+int FDD_int_Reset(int Disk)
 {
 	Uint8	tmp;
-	
-	tmp = inb(Base + FDC_DOR) & 0xF0;
-	outb( Base + FDC_DOR, 0x00 );
+	 int	_disk;
+	Uint16	base = FDD_int_Reset(Disk, &_disk);	
+
+	tmp = inb(base + FDC_DOR) & 0xF0;
+	outb( base + FDC_DOR, 0x00 );
 	Time_Delay(1);
-	outb( Base + FDC_DOR, tmp | 0x0C );
+	outb( base + FDC_DOR, tmp | 0x0C );
 
-	FDD_int_SenseInterrupt(Base, NULL, NULL);
+	FDD_int_SenseInterrupt(base, NULL, NULL);
 
-	outb(Base + FDC_CCR, 0x00);	// 500KB/s
+	outb(base + FDC_CCR, 0x00);	// 500KB/s
 
-	FDD_int_WriteData(Base, CMD_SPECIFY);	// Step and Head Load Times
-	FDD_int_WriteData(Base, 0xDF);	// Step Rate Time, Head Unload Time (Nibble each)
-	FDD_int_WriteData(Base, 0x02);	// Head Load Time >> 1
+	FDD_int_WriteData(base, CMD_SPECIFY);	// Step and Head Load Times
+	FDD_int_WriteData(base, 0xDF);	// Step Rate Time, Head Unload Time (Nibble each)
+	FDD_int_WriteData(base, 0x02);	// Head Load Time >> 1
 
 	// TODO: Recalibrate all present disks
-	FDD_int_Calibrate(0);
+	FDD_int_Calibrate(Disk);
 	return 0;
 }
 
+/**
+ * \brief Write a byte to the FIFO
+ */
+int FDD_int_WriteData(Uint16 Base, Uint8 Data)
+{
+	for( int i = 0; i < 100; i ++ )
+	{
+		if( inb(Base + FDC_MSR) & 0x80 )
+		{
+			outb(Base + FDC_FIFO, Data);
+			return 0;
+		}
+		Time_Delay(10);
+	}
+	Log_Error("FDD", "Write timeout");
+	return 1;
+}
+
+/**
+ * \brief Read a byte from the FIFO
+ */
+int FDD_int_ReadData(Uint16 Base, Uint8 *Data)
+{
+	for( int i = 0; i < 100; i ++ )
+	{
+		if( inb(Base + FDC_MSR) & 0x80 )
+		{
+			Uint8 tmp = inb(Base + FDC_FIFO);
+			if(Data) *Data = tmp;
+			return 0;
+		}
+		Time_Delay(10);
+	}
+	Log_Error("FDD", "Read timeout");
+	return 1;
+}
+
+/**
+ * \brief Acknowledge an interrupt
+ * \param Base	Controller base address
+ * \param ST0	Location to store the ST0 value
+ * \param Cyl	Current cylinder
+ */
+void FDD_int_SenseInterrupt(Uint16 Base, Uint8 *ST0, Uint8 *Cyl)
+{
+	FDD_int_WriteData(Base, CMD_SENSE_INTERRUPT);
+	FDD_int_ReadData(Base, ST0);
+	FDD_int_ReadData(Base, Cyl);
+}
+
 /**
  * \brief Start the motor on a disk
  */
@@ -266,6 +442,25 @@ Uint16 FDD_int_GetBase(int Disk, int *Drive)
 	}
 }
 
+/**
+ * \brief Convert a ST0 error value into a message
+ * \param Fcn	Calling function name
+ * \parma Disk	Global disk number
+ * \param ST0	ST0 Value
+ * \return Boolean failure
+ */
+int FDD_int_HandleST0Error(const char *Fcn, int Disk, Uint8 ST0)
+{
+	static const char *status_type[] = {
+		0, "Error", "Invalid", "Drive Error"
+	};
+
+	Log_Debug("FDD", "%s: Disk %i ST0 Status = %s (0x%x & 0xC0 = 0x%x)",
+		Fcn, Disk, status_type[ST0 >> 6], ST0, ST0 & 0xC0
+		);
+	return 0;
+}
+
 /**
  * \brief Clear the IRQ fired flag
  */
diff --git a/Modules/Storage/FDDv2/main.c b/Modules/Storage/FDDv2/main.c
index 191c50c5e187eb6a98428c135f359e4508ec17c8..6ffcfdb789a880e37a2d20689a986020e00ba34a 100644
--- a/Modules/Storage/FDDv2/main.c
+++ b/Modules/Storage/FDDv2/main.c
@@ -9,21 +9,206 @@
 #include <modules.h>
 #include <fs_devfs.h>
 #include "common.h"
+#include <api_drv_disk.h>
 
 // === CONSTANTS ===
+#define FDD_VERSION	VER2(1,10)
 
 // === STRUCTURES ===
 
 // === PROTOTYPES ===
  int	FDD_Install(char **Arguments);
+ int	FDD_RegisterFS(void);
+// --- VFS
+char	*FDD_ReadDir(tVFS_Node *Node, int pos);
+tVFS_Node	*FDD_FindDir(tVFS_Node *dirNode, const char *Name);
+ int	FDD_IOCtl(tVFS_Node *Node, int ID, void *Data);
+Uint64	FDD_ReadFS(tVFS_Node *node, Uint64 off, Uint64 len, void *buffer);
 
 // === GLOBALS ===
-MODULE_DEFINE(0, 0x110, Storage_FDDv2, FDD_Install, NULL, "x86_ISADMA", NULL);
+MODULE_DEFINE(0, FDD_VERSION, Storage_FDDv2, FDD_Install, NULL, "x86_ISADMA", NULL);
 tDrive	gaFDD_Disks[MAX_DISKS];
+tVFS_Node	gaFDD_DiskNodes[MAX_DISKS];
+tDevFS_Driver	gFDD_DriverInfo = {
+	NULL, "fdd",
+	{
+	.Size = -1,
+	.NumACLs = 1,
+	.ACLs = &gVFS_ACL_EveryoneRX,
+	.Flags = VFS_FFLAG_DIRECTORY,
+	.ReadDir = FDD_ReadDir,
+	.FindDir = FDD_FindDir,
+	.IOCtl = FDD_IOCtl
+	}
+	};
 
 // === CODE ===
 int FDD_Install(char **Arguments)
 {
+	// Query CMOS memory
+	{
+		Uint8	data;
+		outb(0x70, 0x10);
+		data = inb(0x71);
+		
+		// NOTE: CMOS only reports 2 disks
+		if( (data & 0xF0) == 0x40 )
+			gaFDD_Disks[0].bValid = gaFDD_Disks[0].bInserted = 1;
+		if( (data & 0x0F) == 0x04 )
+			gaFDD_Disks[1].bValid = gaFDD_Disks[1].bInserted = 1;
+		
+		if( gaFDD_Disks[0].bValid == 0 && gaFDD_Disks[1].bValid == 0 )
+			return MODULE_ERR_NOTNEEDED;
+	}
+	
+	// Initialise controller
+	FDD_SetupIO();
+
+	FDD_RegisterFS();
+
 	return 0;
 }
 
+/**
+ * \brief Register the FDD driver with DevFS
+ */
+int FDD_RegisterFS(void)
+{
+	gFDD_DriverInfo.RootNode.CTime = gFDD_DriverInfo.RootNode.MTime
+		= gFDD_DriverInfo.RootNode.ATime = now();
+
+	for( int i = 0; i < MAX_DISKS; i ++ )
+	{
+		if( !gaFDD_Disks[i].bValid )	continue ;
+	
+		// Initialise Child Nodes
+		gaFDD_DiskNodes[i].Inode = i;
+		gaFDD_DiskNodes[i].Flags = 0;
+		gaFDD_DiskNodes[i].NumACLs = 0;
+		gaFDD_DiskNodes[i].Read = FDD_ReadFS;
+		gaFDD_DiskNodes[i].Write = NULL;//FDD_WriteFS;
+		gaFDD_DiskNodes[i].Size = 1440*1024;	// TODO: Non 1.44 disks
+	}
+	
+	DevFS_AddDevice( &gFDD_DriverInfo );
+}
+
+/**
+ * \brief Get the name of the \a Pos th item in the driver root
+ * \param Node	Root node (unused)
+ * \param Pos	Position
+ * \return Heap string of node name
+ */
+char *FDD_ReadDir(tVFS_Node *Node, int Pos)
+{
+	char	ret_tpl[2];
+	if(Pos < 0 || Pos > MAX_DISKS )
+		return NULL;
+	if(gaFDD_Disks[Pos].bValid)
+		return VFS_SKIP;
+	
+	ret_tpl[0] = '0' + Pos;
+	ret_tpl[1] = '\0';
+	return strdup(ret_tpl);
+}
+
+/**
+ * \brief Get a node by name
+ * \param Node	Root node (unused)
+ * \param Name	Drive name
+ * \return Pointer to node structure
+ */
+tVFS_Node *FDD_FindDir(tVFS_Node *Node, const char *Name)
+{
+	 int	pos;
+	if( '0' > Name[0] || Name[0] > '9' )	return NULL;
+	if( Name[1] != '\0' )	return NULL;
+	
+	pos = Name[0] - '0';
+	
+	return &gaFDD_DiskNodes[pos];
+}
+
+static const char	*casIOCTLS[] = {DRV_IOCTLNAMES,DRV_DISK_IOCTLNAMES,NULL};
+/**
+ * \brief Driver root IOCtl Handler
+ * \param Node	Root node (unused)
+ * \param ID	IOCtl ID
+ * \param Data	IOCtl specific data pointer
+ */
+int FDD_IOCtl(tVFS_Node *Node, int ID, void *Data)
+{
+	switch(ID)
+	{
+	BASE_IOCTLS(DRV_TYPE_DISK, "FDDv2", FDD_VERSION, casIOCTLS);
+	
+	case DISK_IOCTL_GETBLOCKSIZE:	return 512;
+	
+	default:
+		return -1;
+	}
+}
+
+/**
+ * \brief Read from a disk
+ * \param Node	Disk node
+ * \param Offset	Byte offset in disk
+ * \param Length	Number of bytes to read
+ * \param Buffer	Destination buffer
+ * \return Number of bytes read
+ */
+Uint64 FDD_ReadFS(tVFS_Node *Node, Uint64 Offset, Uint64 Length, void *Buffer)
+{
+	 int	disk = Node->Inode;
+	 int	track;
+
+	track = Offset / BYTES_PER_TRACK;
+	
+	if( Offset % BYTES_PER_TRACK )
+	{
+		
+	}
+}
+
+/**
+ * \brief Read from a track
+ */
+int FDD_int_ReadWriteWithinTrack(int Disk, int Track, int bWrite, size_t Offset, size_t Length, void *Buffer)
+{
+	if( Offset > BYTES_PER_TRACK || Length > BYTES_PER_TRACK )
+		return 1;
+	if( Offset + Length > BYTES_PER_TRACK )
+		return 1;
+	
+	Mutex_Acquire( &gaFDD_Disks[Disk].Mutex );
+
+	// If the cache doesn't exist, create it
+	if( !gaFDD_Disks[Disk].TrackData[Track] )
+	{
+		gaFDD_Disks[Disk].TrackData[Track] = malloc( BYTES_PER_TRACK );
+		// Don't bother reading if this is a whole track write
+		if( !(bWrite && Offset == 0 && Length == BYTES_PER_TRACK) )
+		{
+			FDD_int_ReadWriteTrack(Disk, Track, 0, gaFDD_Disks[Disk].TrackData[Track]);
+		}
+	}
+	
+	// Read/Write
+	if( bWrite )
+	{
+		// Write to cache then commit cache to disk
+		char	*dest = gaFDD_Disks[Disk].TrackData[Track];
+		memcpy( dest + Offset, Buffer, Length );
+		FDD_int_ReadWriteTrack(Disk, Track, 1, gaFDD_Disks[Disk].TrackData[Track]);
+	}
+	else
+	{
+		// Read from cache
+		char	*src = gaFDD_Disks[Disk].TrackData[Track];
+		memcpy(Buffer, src + Offset, Length);
+	}
+	
+	Mutex_Release( &gaFDD_Disks[Disk].Mutex );
+	
+	return 0;
+}