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

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