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

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