*/
#include <acess.h>
#include "common.h"
+#include <dma.h>
// === CONSTANTS ===
#define MOTOR_ON_DELAY 500
};
// === 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
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 ;
}
* \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
*/
}
}
+/**
+ * \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
*/
#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;
+}