Modules/FDDv2 - VFS interface almost done, Caching added
[tpg/acess2.git] / Modules / Storage / FDDv2 / fdc.c
1 /*
2  * Acess2 82077AA FDC
3  * - By John Hodge (thePowersGang)
4  *
5  * fdc.c
6  * - FDC IO Functions
7  */
8 #include <acess.h>
9 #include "common.h"
10 #include <dma.h>
11
12 // === CONSTANTS ===
13 #define MOTOR_ON_DELAY  500
14 #define MOTOR_OFF_DELAY 2000
15
16 enum eMotorState
17 {
18         MOTOR_OFF,
19         MOTOR_ATSPEED,
20 };
21
22 enum eFDC_Registers
23 {
24         FDC_DOR  = 0x02,        // Digital Output Register
25         FDC_MSR  = 0x04,        // Master Status Register (Read Only)
26         FDC_FIFO = 0x05,        // FIFO port
27         FDC_CCR  = 0x07         // Configuration Control Register (write only)
28 };
29
30 enum eFDC_Commands
31 {
32         CMD_SPECIFY = 3,            // Specify parameters
33         CMD_WRITE_DATA = 5,         // Write Data
34         CMD_READ_DATA = 6,          // Read Data
35         CMD_RECALIBRATE = 7,        // Recalibrate a drive
36         CMD_SENSE_INTERRUPT = 8,    // Sense (Ack) an interrupt
37         CMD_SEEK = 15,              // Seek to a track
38 };
39
40 // === PROTOTYPES ===
41  int    FDD_SetupIO(void);
42  int    FDD_int_ReadWriteTrack(int Disk, int Track, int bWrite, void *Buffer);
43  int    FDD_int_SeekToTrack(int Disk, int Track);
44  int    FDD_int_Calibrate(int Disk);
45  int    FDD_int_Reset(int Disk);
46 // --- FIFO
47  int    FDD_int_WriteData(Uint16 Base, Uint8 Data);
48  int    FDD_int_ReadData(Uint16 Base, Uint8 *Data);
49 void    FDD_int_SenseInterrupt(Uint16 Base, Uint8 *ST0, Uint8 *Cyl);
50 // --- Motor Control
51  int    FDD_int_StartMotor(int Disk);
52  int    FDD_int_StopMotor(int Disk);
53 void    FDD_int_StopMotorCallback(void *Ptr);
54 // --- Helpers
55  int    FDD_int_HandleST0Error(const char *Fcn, int Disk, Uint8 ST0);
56 Uint16  FDD_int_GetBase(int Disk, int *Drive);
57 // --- Interrupt
58 void    FDD_int_ClearIRQ(void);
59  int    FDD_int_WaitIRQ(void);
60 void    FDD_int_IRQHandler(int IRQ, void *Ptr);
61
62 // === GLOBALS ===
63 /**
64  * \brief Marker for IRQ6
65  * \todo Convert into a semaphore?
66  */
67  int    gbFDD_IRQ6Fired;
68 /**
69  * \brief Protector for DMA and IRQ6
70  */
71 tMutex  gFDD_IOMutex;
72
73 // === CODE ===
74 /**
75  * \brief Set up FDC IO
76  * \return Boolean failure
77  *
78  * Registers the IRQ handler and resets the controller
79  */
80 int FDD_SetupIO(void)
81 {
82         // Install IRQ6 Handler
83         IRQ_AddHandler(6, FDD_int_IRQHandler, NULL);
84         
85         // Reset controller
86         FDD_int_Reset(0);
87         // TODO: All controllers
88 }
89
90 /**
91  * \brief Read/Write data from/to a disk
92  * \param Disk Global disk number
93  * \param Track Track number (Cyl*2+Head)
94  * \param bWrite Toggle write mode
95  * \param Buffer Destination/Source buffer
96  * \return Boolean failure
97  */
98 int FDD_int_ReadWriteTrack(int Disk, int Track, int bWrite, void *Buffer)
99 {
100         Uint8   cmd;
101          int    i, _disk;
102         Uint16  base = FDD_int_GetBase(Disk, &_disk);
103          int    cyl = Track >> 1, head = Track & 1;
104
105         Mutex_Acquire( &gFDD_IOMutex );
106         
107         // Initialise DMA for read/write
108         // TODO: Support non 1.44MiB FDs
109         DMA_SetChannel(2, BYTES_PER_TRACK, !bWrite);
110         
111         // Select command
112         if( bWrite )
113                 cmd = CMD_WRITE_DATA | 0xC0;
114         else
115                 cmd = CMD_READ_DATA | 0xC0;
116         
117         // Seek
118         if( FDD_int_SeekToTrack(Disk, Track) ) {
119                 Mutex_Release( &gFDD_IOMutex );
120                 return -1;
121         }
122
123         for( i = 0; i < 20; i ++ )
124         {
125                 FDD_int_StartMotor(Disk);
126
127                 // Write data
128                 if( bWrite )
129                         DMA_WriteData(2, BYTES_PER_TRACK, Buffer);      
130         
131                 FDD_int_WriteData(base, cmd);
132                 FDD_int_WriteData(base, (head << 2) | _disk);
133                 FDD_int_WriteData(base, cyl);
134                 FDD_int_WriteData(base, head);
135                 FDD_int_WriteData(base, 1);     // First Sector
136                 FDD_int_WriteData(base, 2);     // Bytes per sector (128*2^n)
137                 FDD_int_WriteData(base, 18);    // 18 tracks (full disk) - TODO: Non 1.44
138                 FDD_int_WriteData(base, 0x1B);  // Gap length - TODO: again
139                 FDD_int_WriteData(base, 0xFF);  // Data length - ?
140         
141                 FDD_int_WaitIRQ();
142         
143                 // No Sense Interrupt
144                 
145                 Uint8   st0=0, st1=0, st2=0, bps=0;
146                 FDD_int_ReadData(base, &st0);
147                 FDD_int_ReadData(base, &st1);   // st1
148                 FDD_int_ReadData(base, &st2);   // st2
149                 FDD_int_ReadData(base, NULL);   // rcy - Mutilated Cyl
150                 FDD_int_ReadData(base, NULL);   // rhe - Mutilated Head
151                 FDD_int_ReadData(base, NULL);   // rse - Mutilated sector
152                 FDD_int_ReadData(base, &bps);   // bps - Should be the same as above
153
154                 if( st0 & 0xc0 ) {
155                         FDD_int_HandleST0Error(__func__, Disk, st0);
156                         continue ;
157                 }
158         
159                 if( st2 & 0x02 ) {
160                         Log_Debug("FDD", "Disk %i is not writable", Disk);
161                         Mutex_Release( &gFDD_IOMutex );
162                         return 2;
163                 }
164                 
165                 if( st0 & 0x08 ) {
166                         Log_Debug("FDD", "FDD_int_ReadWriteTrack: Drive not ready");
167                         continue ;
168                 }
169
170
171                 if( st1 & 0x80 ) {
172                         Log_Debug("FDD", "FDD_int_ReadWriteTrack: End of cylinder");
173                         continue ;
174                 }
175
176                 if( st1 & (0x20|0x10|0x04|0x01) ) {
177                         Log_Debug("FDD", "FDD_int_ReadWriteTrack: st1 = 0x%x", st1);
178                         continue;
179                 }
180                 
181                 if( st2 & (0x40|0x20|0x10|0x04|0x01) ) {
182                         Log_Debug("FDD", "FDD_int_ReadWriteTrack: st2 = 0x%x", st2);
183                         continue ;
184                 }
185                 
186                 if( bps != 0x2 ) {
187                         Log_Debug("FDD", "Wanted bps = 2 (512), got %i", bps);
188                         continue ;
189                 }
190
191                 // Read back data
192                 if( !bWrite )
193                         DMA_ReadData(2, BYTES_PER_TRACK, Buffer);
194                 
195                 FDD_int_StopMotor(Disk);
196                 Mutex_Release( &gFDD_IOMutex );
197                 return 0;
198         }
199
200         Log_Debug("FDD", "%i retries exhausted", i);
201         FDD_int_StopMotor(Disk);
202         Mutex_Release( &gFDD_IOMutex );
203         return 1;
204 }
205
206 /**
207  * \brief Seek to a specific track
208  * \param Disk Global disk number
209  * \param Track Track number (Cyl*2+Head)
210  * \return Boolean failure
211  */
212 int FDD_int_SeekToTrack(int Disk, int Track)
213 {
214         Uint8   st0=0, res_cyl=0;
215          int    cyl, head;
216          int    _disk;
217         Uint16  base = FDD_int_GetBase(Disk, &_disk);;
218         
219         cyl = Track / 2;
220         head = Track % 1;
221         
222         FDD_int_StartMotor(Disk);
223         
224         for( int i = 0; i < 10; i ++ )
225         {
226                 FDD_int_ClearIRQ();
227                 FDD_int_WriteData(base, CMD_SEEK);
228                 FDD_int_WriteData(base, (head << 2) + _disk);
229                 FDD_int_WriteData(base, cyl);
230         
231                 FDD_int_WaitIRQ();
232                 FDD_int_SenseInterrupt(base, &st0, &res_cyl);
233         
234                 if( st0 & 0xC0 )
235                 {
236                         FDD_int_HandleST0Error(__func__, Disk, st0);
237                         continue ;
238                 }
239                 
240                 if( res_cyl == cyl ) {
241                         FDD_int_StopMotor(Disk);
242                         return 0;
243                 }
244         }
245         
246         Log_Error("FDD", "FDD_int_SeekToTrack: 10 retries exhausted\n");
247         FDD_int_StopMotor(Disk);
248         return 1;
249 }
250
251 /**
252  * \brief Calibrate a drive
253  * \param Disk  Global disk number
254  */
255 int FDD_int_Calibrate(int Disk)
256 {
257          int    _disk;
258         Uint16  base = FDD_int_GetBase(Disk, &_disk);
259         FDD_int_StartMotor(Disk);
260         
261         for( int i = 0; i < 10; i ++ )
262         {
263                 Uint8   st0=0, cyl = -1;
264         
265                 FDD_int_ClearIRQ();     
266                 FDD_int_WriteData(base, CMD_RECALIBRATE);
267                 FDD_int_WriteData(base, _disk);
268                 
269                 FDD_int_WaitIRQ();
270         
271                 FDD_int_SenseInterrupt(base, &st0, NULL);
272                 
273                 if( st0 & 0xC0 ) {
274                         FDD_int_HandleST0Error(__func__, Disk, st0);
275                         continue ;
276                 }
277                 
278                 if( cyl == 0 )
279                 {
280                         FDD_int_StopMotor(Disk);
281                         return 0;
282                 }
283         }
284         
285         Log_Error("FDD", "FDD_int_Calibrate: Retries exhausted");
286         
287         return 1;
288 }
289
290 /**
291  * \brief Reset a controller
292  * \param Base  Controller base address
293  */
294 int FDD_int_Reset(int Disk)
295 {
296         Uint8   tmp;
297          int    _disk;
298         Uint16  base = FDD_int_Reset(Disk, &_disk);     
299
300         tmp = inb(base + FDC_DOR) & 0xF0;
301         outb( base + FDC_DOR, 0x00 );
302         Time_Delay(1);
303         outb( base + FDC_DOR, tmp | 0x0C );
304
305         FDD_int_SenseInterrupt(base, NULL, NULL);
306
307         outb(base + FDC_CCR, 0x00);     // 500KB/s
308
309         FDD_int_WriteData(base, CMD_SPECIFY);   // Step and Head Load Times
310         FDD_int_WriteData(base, 0xDF);  // Step Rate Time, Head Unload Time (Nibble each)
311         FDD_int_WriteData(base, 0x02);  // Head Load Time >> 1
312
313         // TODO: Recalibrate all present disks
314         FDD_int_Calibrate(Disk);
315         return 0;
316 }
317
318 /**
319  * \brief Write a byte to the FIFO
320  */
321 int FDD_int_WriteData(Uint16 Base, Uint8 Data)
322 {
323         for( int i = 0; i < 100; i ++ )
324         {
325                 if( inb(Base + FDC_MSR) & 0x80 )
326                 {
327                         outb(Base + FDC_FIFO, Data);
328                         return 0;
329                 }
330                 Time_Delay(10);
331         }
332         Log_Error("FDD", "Write timeout");
333         return 1;
334 }
335
336 /**
337  * \brief Read a byte from the FIFO
338  */
339 int FDD_int_ReadData(Uint16 Base, Uint8 *Data)
340 {
341         for( int i = 0; i < 100; i ++ )
342         {
343                 if( inb(Base + FDC_MSR) & 0x80 )
344                 {
345                         Uint8 tmp = inb(Base + FDC_FIFO);
346                         if(Data) *Data = tmp;
347                         return 0;
348                 }
349                 Time_Delay(10);
350         }
351         Log_Error("FDD", "Read timeout");
352         return 1;
353 }
354
355 /**
356  * \brief Acknowledge an interrupt
357  * \param Base  Controller base address
358  * \param ST0   Location to store the ST0 value
359  * \param Cyl   Current cylinder
360  */
361 void FDD_int_SenseInterrupt(Uint16 Base, Uint8 *ST0, Uint8 *Cyl)
362 {
363         FDD_int_WriteData(Base, CMD_SENSE_INTERRUPT);
364         FDD_int_ReadData(Base, ST0);
365         FDD_int_ReadData(Base, Cyl);
366 }
367
368 /**
369  * \brief Start the motor on a disk
370  */
371 int FDD_int_StartMotor(int Disk)
372 {
373          int    _disk;
374         Uint16  base = FDD_int_GetBase(Disk, &_disk);
375         
376         if( gaFDD_Disks[Disk].MotorState == MOTOR_ATSPEED )
377                 return 0;
378
379         // Clear the motor off timer    
380         Time_RemoveTimer(gaFDD_Disks[Disk].Timer);
381         gaFDD_Disks[Disk].Timer = -1;
382
383         // Turn motor on
384         outb(base + FDC_DOR, inb(base+FDC_DOR) | (1 << (_disk + 4)));
385
386         // Wait for it to reach speed
387         Time_Delay(MOTOR_ON_DELAY);
388
389         gaFDD_Disks[Disk].MotorState = MOTOR_ATSPEED;
390
391         return 0;
392 }
393
394 /**
395  * \brief Schedule the motor to stop
396  */
397 int FDD_int_StopMotor(int Disk)
398 {
399         if( gaFDD_Disks[Disk].MotorState != MOTOR_ATSPEED )
400                 return 0;
401         if( gaFDD_Disks[Disk].Timer != -1 )
402                 return 0;
403
404         gaFDD_Disks[Disk].Timer = Time_CreateTimer(MOTOR_OFF_DELAY, FDD_int_StopMotorCallback, (void*)(tVAddr)Disk);
405
406         return 0;
407 }
408
409 /**
410  * \brief Actually stop the motor
411  * \param Ptr   Actaully the global disk number
412  */
413 void FDD_int_StopMotorCallback(void *Ptr)
414 {
415          int    Disk = (tVAddr)Ptr;
416          int    _disk;
417         Uint16  base = FDD_int_GetBase(Disk, &_disk);
418
419         gaFDD_Disks[Disk].Timer = -1;
420         gaFDD_Disks[Disk].MotorState = MOTOR_OFF;
421         
422         outb(base + FDC_DOR, inb(base+FDC_DOR) & ~(1 << (_disk + 4)));
423
424         return ;
425 }
426
427 /**
428  * \brief Converts a global disk number into a controller and drive
429  * \param Disk  Global disk number
430  * \param Drive Destination for controller disk number
431  * \return Controller base address
432  */
433 Uint16 FDD_int_GetBase(int Disk, int *Drive)
434 {
435         if(Drive)       *Drive = Disk & 3;
436         switch(Disk >> 2)
437         {
438         case 0: return 0x3F0;
439         case 1: return 0x370;
440         default:
441                 return 0;
442         }
443 }
444
445 /**
446  * \brief Convert a ST0 error value into a message
447  * \param Fcn   Calling function name
448  * \parma Disk  Global disk number
449  * \param ST0   ST0 Value
450  * \return Boolean failure
451  */
452 int FDD_int_HandleST0Error(const char *Fcn, int Disk, Uint8 ST0)
453 {
454         static const char *status_type[] = {
455                 0, "Error", "Invalid", "Drive Error"
456         };
457
458         Log_Debug("FDD", "%s: Disk %i ST0 Status = %s (0x%x & 0xC0 = 0x%x)",
459                 Fcn, Disk, status_type[ST0 >> 6], ST0, ST0 & 0xC0
460                 );
461         return 0;
462 }
463
464 /**
465  * \brief Clear the IRQ fired flag
466  */
467 void FDD_int_ClearIRQ(void)
468 {
469         gbFDD_IRQ6Fired = 0;
470 }
471
472 /**
473  * \brief Wait for an IRQ to fire
474  */
475 int FDD_int_WaitIRQ(void)
476 {
477         while(gbFDD_IRQ6Fired == 0)
478                 Threads_Yield();
479         return 0;
480 }
481
482 /**
483  * \brief IRQ Handler
484  * \param IRQ   IRQ Number (unused)
485  * \param Ptr   Data Pointer (unused)
486  */
487 void FDD_int_IRQHandler(int IRQ, void *Ptr)
488 {
489         gbFDD_IRQ6Fired = 1;
490 }
491

UCC git Repository :: git.ucc.asn.au