1 #include <u.h>
2
3 #include "lib.h"
4 #include "mem.h"
5 #include "dat.h"
6 #include "fns.h"
7 #include "io.h"
8 #include "ureg.h"
9
10 #include "fs.h"
11 #include "devfloppy.h"
12
13
14 /* Intel 82077A (8272A compatible) floppy controller */
15
16 /* This module expects the following functions to be defined
17 * elsewhere:
18 *
19 * inb()
20 * outb()
21 * floppyexec()
22 * floppyeject()
23 * floppysetup0()
24 * floppysetup1()
25 * dmainit()
26 * dmasetup()
27 * dmaend()
28 *
29 * On DMA systems, floppyexec() should be an empty function;
30 * on non-DMA systems, dmaend() should be an empty function;
31 * dmasetup() may enforce maximum transfer sizes.
32 */
33
34 enum {
35 /* file types */
36 Qdir= 0,
37 Qdata= (1<<2),
38 Qctl= (2<<2),
39 Qmask= (3<<2),
40
41 DMAchan= 2, /* floppy dma channel */
42 };
43
44 #define DPRINT if(0)print
45
46 FType floppytype[] =
47 {
48 { "3½HD", T1440kb, 512, 18, 2, 1, 80, 0x1B, 0x54, 0, },
49 { "3½DD", T1440kb, 512, 9, 2, 1, 80, 0x1B, 0x54, 2, },
50 { "3½DD", T720kb, 512, 9, 2, 1, 80, 0x1B, 0x54, 2, },
51 { "5¼HD", T1200kb, 512, 15, 2, 1, 80, 0x2A, 0x50, 0, },
52 { "5¼DD", T1200kb, 512, 9, 2, 2, 40, 0x2A, 0x50, 1, },
53 { "ATT3B1", T1200kb, 512, 8, 2, 2, 48, 0x2A, 0x50, 1, },
54 { "5¼DD", T360kb, 512, 9, 2, 1, 40, 0x2A, 0x50, 2, },
55 };
56
57 /*
58 * bytes per sector encoding for the controller.
59 * - index for b2c is is (bytes per sector/128).
60 * - index for c2b is code from b2c
61 */
62 static int b2c[] =
63 {
64 [1] 0,
65 [2] 1,
66 [4] 2,
67 [8] 3,
68 };
69 static int c2b[] =
70 {
71 128,
72 256,
73 512,
74 1024,
75 };
76
77 FController fl;
78
79 #define MOTORBIT(i) (1<<((i)+4))
80
81 /*
82 * predeclared
83 */
84 static int cmddone(void*);
85 static void floppyformat(FDrive*, char*);
86 static void floppykproc(void*);
87 static void floppypos(FDrive*,long);
88 static int floppyrecal(FDrive*);
89 static int floppyresult(void);
90 static void floppyrevive(void);
91 static vlong pcfloppyseek(FDrive*, vlong);
92 static int floppysense(void);
93 static void floppywait(int);
94 static long floppyxfer(FDrive*, int, void*, long, long);
95
96 static void
fldump(void)97 fldump(void)
98 {
99 DPRINT("sra %ux srb %ux dor %ux msr %ux dir %ux\n", inb(Psra), inb(Psrb),
100 inb(Pdor), inb(Pmsr), inb(Pdir));
101 }
102
103 static void
floppyalarm(Alarm * a)104 floppyalarm(Alarm* a)
105 {
106 FDrive *dp;
107
108 for(dp = fl.d; dp < &fl.d[fl.ndrive]; dp++){
109 if((fl.motor&MOTORBIT(dp->dev)) && TK2SEC(m->ticks - dp->lasttouched) > 5)
110 floppyoff(dp);
111 }
112
113 alarm(5*1000, floppyalarm, 0);
114 cancel(a);
115 }
116
117 /*
118 * set floppy drive to its default type
119 */
120 static void
floppysetdef(FDrive * dp)121 floppysetdef(FDrive *dp)
122 {
123 FType *t;
124
125 for(t = floppytype; t < &floppytype[nelem(floppytype)]; t++)
126 if(dp->dt == t->dt){
127 dp->t = t;
128 break;
129 }
130 }
131
132 static void
_floppydetach(void)133 _floppydetach(void)
134 {
135 /*
136 * stop the motors
137 */
138 fl.motor = 0;
139 delay(10);
140 outb(Pdor, fl.motor | Fintena | Fena);
141 delay(10);
142 }
143
144 int
floppyinit(void)145 floppyinit(void)
146 {
147 FDrive *dp;
148 FType *t;
149 ulong maxtsize;
150 int mask;
151
152 dmainit(DMAchan);
153
154 floppysetup0(&fl);
155
156 /*
157 * init dependent parameters
158 */
159 maxtsize = 0;
160 for(t = floppytype; t < &floppytype[nelem(floppytype)]; t++){
161 t->cap = t->bytes * t->heads * t->sectors * t->tracks;
162 t->bcode = b2c[t->bytes/128];
163 t->tsize = t->bytes * t->sectors;
164 if(maxtsize < t->tsize)
165 maxtsize = t->tsize;
166 }
167
168 fl.selected = fl.d;
169
170 floppydetach = _floppydetach;
171 floppydetach();
172
173 /*
174 * init drives
175 */
176 mask = 0;
177 for(dp = fl.d; dp < &fl.d[fl.ndrive]; dp++){
178 dp->dev = dp - fl.d;
179 if(dp->dt == Tnone)
180 continue;
181 mask |= 1<<dp->dev;
182 floppysetdef(dp);
183 dp->cyl = -1; /* because we don't know */
184 dp->cache = (uchar*)xspanalloc(maxtsize, BY2PG, 64*1024);
185 dp->ccyl = -1;
186 dp->vers = 0;
187 dp->maxtries = 5;
188 }
189
190 /*
191 * first operation will recalibrate
192 */
193 fl.confused = 1;
194
195 floppysetup1(&fl);
196
197 /* to turn the motor off when inactive */
198 alarm(5*1000, floppyalarm, 0);
199
200 return mask;
201 }
202
203 void
floppyinitdev(int i,char * name)204 floppyinitdev(int i, char *name)
205 {
206 if(i >= fl.ndrive)
207 panic("floppyinitdev");
208 sprint(name, "fd%d", i);
209 }
210
211 void
floppyprintdevs(int i)212 floppyprintdevs(int i)
213 {
214 if(i >= fl.ndrive)
215 panic("floppyprintdevs");
216 print(" fd%d", i);
217 }
218
219 int
floppyboot(int dev,char * file,Boot * b)220 floppyboot(int dev, char *file, Boot *b)
221 {
222 Fs *fs;
223
224 if(strncmp(file, "dos!", 4) == 0)
225 file += 4;
226 else if(strchr(file, '!') || strcmp(file, "")==0) {
227 print("syntax is fd0!file\n");
228 return -1;
229 }
230
231 fs = floppygetfspart(dev, "dos", 1);
232 if(fs == nil)
233 return -1;
234
235 return fsboot(fs, file, b);
236 }
237
238 void
floppyprintbootdevs(int dev)239 floppyprintbootdevs(int dev)
240 {
241 print(" fd%d", dev);
242 }
243
244 /*
245 * check if the floppy has been replaced under foot. cause
246 * an error if it has.
247 *
248 * a seek and a read clears the condition. this was determined
249 * experimentally, there has to be a better way.
250 *
251 * if the read fails, cycle through the possible floppy
252 * density till one works or we've cycled through all
253 * possibilities for this drive.
254 */
255 static int
changed(FDrive * dp)256 changed(FDrive *dp)
257 {
258 FType *start;
259
260 /*
261 * if floppy has changed or first time through
262 */
263 if((inb(Pdir)&Fchange) || dp->vers == 0){
264 DPRINT("changed\n");
265 fldump();
266 dp->vers++;
267 floppysetdef(dp);
268 dp->maxtries = 3;
269 start = dp->t;
270
271 /* flopppyon fails if there's no drive */
272 dp->confused = 1; /* make floppyon recal */
273 if(floppyon(dp) < 0)
274 return -1;
275
276 pcfloppyseek(dp, dp->t->heads*dp->t->tsize);
277
278 while(floppyxfer(dp, Fread, dp->cache, 0, dp->t->tsize) <= 0){
279
280 /*
281 * if the xfer attempt doesn't clear the changed bit,
282 * there's no floppy in the drive
283 */
284 if(inb(Pdir)&Fchange)
285 return -1;
286
287 while(++dp->t){
288 if(dp->t == &floppytype[nelem(floppytype)])
289 dp->t = floppytype;
290 if(dp->dt == dp->t->dt)
291 break;
292 }
293
294 /* flopppyon fails if there's no drive */
295 if(floppyon(dp) < 0)
296 return -1;
297
298 DPRINT("changed: trying %s\n", dp->t->name);
299 fldump();
300 if(dp->t == start)
301 return -1;
302 }
303 }
304
305 return 0;
306 }
307
308 static int
readtrack(FDrive * dp,int cyl,int head)309 readtrack(FDrive *dp, int cyl, int head)
310 {
311 int i, nn, sofar;
312 ulong pos;
313
314 nn = dp->t->tsize;
315 if(dp->ccyl==cyl && dp->chead==head)
316 return nn;
317 pos = (cyl*dp->t->heads+head) * nn;
318 for(sofar = 0; sofar < nn; sofar += i){
319 dp->ccyl = -1;
320 i = floppyxfer(dp, Fread, dp->cache + sofar, pos + sofar, nn - sofar);
321 if(i <= 0)
322 return -1;
323 }
324 dp->ccyl = cyl;
325 dp->chead = head;
326 return nn;
327 }
328
329 long
floppyread(Fs * fs,void * a,long n)330 floppyread(Fs *fs, void *a, long n)
331 {
332 FDrive *dp;
333 long rv, offset;
334 int sec, head, cyl;
335 long len;
336 uchar *aa;
337
338 aa = a;
339 dp = &fl.d[fs->dev];
340
341 offset = dp->offset;
342 floppyon(dp);
343 if(changed(dp))
344 return -1;
345
346 for(rv = 0; rv < n; rv += len){
347 /*
348 * all xfers come out of the track cache
349 */
350 dp->len = n - rv;
351 floppypos(dp, offset+rv);
352 cyl = dp->tcyl;
353 head = dp->thead;
354 len = dp->len;
355 sec = dp->tsec;
356 if(readtrack(dp, cyl, head) < 0)
357 break;
358 memmove(aa+rv, dp->cache + (sec-1)*dp->t->bytes, len);
359 }
360 dp->offset = offset+rv;
361
362 return rv;
363 }
364
365 void*
floppygetfspart(int i,char * name,int chatty)366 floppygetfspart(int i, char *name, int chatty)
367 {
368 static Fs fs;
369
370 if(strcmp(name, "dos") != 0){
371 if(chatty)
372 print("unknown partition fd%d!%s (use fd%d!dos)\n", i, name, i);
373 return nil;
374 }
375
376 fs.dev = i;
377 fs.diskread = floppyread;
378 fs.diskseek = floppyseek;
379
380 /* sometimes we get spurious errors and doing it again works */
381 if(dosinit(&fs) < 0 && dosinit(&fs) < 0){
382 if(chatty)
383 print("fd%d!%s does not contain a FAT file system\n", i, name);
384 return nil;
385 }
386 return &fs;
387 }
388
389 static int
return0(void *)390 return0(void*)
391 {
392 return 0;
393 }
394
395 static void
timedsleep(int (* f)(void *),void * arg,int ms)396 timedsleep(int (*f)(void*), void* arg, int ms)
397 {
398 int s;
399 ulong end;
400
401 end = m->ticks + 1 + MS2TK(ms);
402 while(m->ticks < end && !(*f)(arg)){
403 s = spllo();
404 delay(10);
405 splx(s);
406 }
407 }
408
409 /*
410 * start a floppy drive's motor.
411 */
412 static int
floppyon(FDrive * dp)413 floppyon(FDrive *dp)
414 {
415 int alreadyon;
416 int tries;
417
418 if(fl.confused)
419 floppyrevive();
420
421 /* start motor and select drive */
422 dp->lasttouched = m->ticks;
423 alreadyon = fl.motor & MOTORBIT(dp->dev);
424 if(!alreadyon){
425 fl.motor |= MOTORBIT(dp->dev);
426 outb(Pdor, fl.motor | Fintena | Fena | dp->dev);
427 /* wait for drive to spin up */
428 timedsleep(return0, 0, 750);
429
430 /* clear any pending interrupts */
431 floppysense();
432 }
433
434 /* set transfer rate */
435 if(fl.rate != dp->t->rate){
436 fl.rate = dp->t->rate;
437 outb(Pdsr, fl.rate);
438 }
439
440 /* get drive to a known cylinder */
441 if(dp->confused)
442 for(tries = 0; tries < 4; tries++)
443 if(floppyrecal(dp) >= 0)
444 break;
445 dp->lasttouched = m->ticks;
446 fl.selected = dp;
447 if(dp->confused)
448 return -1;
449 return 0;
450 }
451
452 /*
453 * stop the floppy if it hasn't been used in 5 seconds
454 */
455 static void
floppyoff(FDrive * dp)456 floppyoff(FDrive *dp)
457 {
458 fl.motor &= ~MOTORBIT(dp->dev);
459 outb(Pdor, fl.motor | Fintena | Fena | dp->dev);
460 }
461
462 /*
463 * send a command to the floppy
464 */
465 static int
floppycmd(void)466 floppycmd(void)
467 {
468 int i;
469 int tries;
470
471 fl.nstat = 0;
472 for(i = 0; i < fl.ncmd; i++){
473 for(tries = 0; ; tries++){
474 if((inb(Pmsr)&(Ffrom|Fready)) == Fready)
475 break;
476 if(tries > 1000){
477 DPRINT("cmd %ux can't be sent (%d)\n", fl.cmd[0], i);
478 fldump();
479
480 /* empty fifo, might have been a bad command */
481 floppyresult();
482 return -1;
483 }
484 microdelay(1);
485 }
486 outb(Pfdata, fl.cmd[i]);
487 }
488 return 0;
489 }
490
491 /*
492 * get a command result from the floppy
493 *
494 * when the controller goes ready waiting for a command
495 * (instead of sending results), we're done
496 *
497 */
498 static int
floppyresult(void)499 floppyresult(void)
500 {
501 int i, s;
502 int tries;
503
504 /* get the result of the operation */
505 for(i = 0; i < sizeof(fl.stat); i++){
506 /* wait for status byte */
507 for(tries = 0; ; tries++){
508 s = inb(Pmsr)&(Ffrom|Fready);
509 if(s == Fready){
510 fl.nstat = i;
511 return fl.nstat;
512 }
513 if(s == (Ffrom|Fready))
514 break;
515 if(tries > 1000){
516 DPRINT("floppyresult: %d stats\n", i);
517 fldump();
518 fl.confused = 1;
519 return -1;
520 }
521 microdelay(1);
522 }
523 fl.stat[i] = inb(Pfdata);
524 }
525 fl.nstat = sizeof(fl.stat);
526 return fl.nstat;
527 }
528
529 /*
530 * calculate physical address of a logical byte offset into the disk
531 *
532 * truncate dp->length if it crosses a track boundary
533 */
534 static void
floppypos(FDrive * dp,long off)535 floppypos(FDrive *dp, long off)
536 {
537 int lsec;
538 int ltrack;
539 int end;
540
541 lsec = off/dp->t->bytes;
542 ltrack = lsec/dp->t->sectors;
543 dp->tcyl = ltrack/dp->t->heads;
544 dp->tsec = (lsec % dp->t->sectors) + 1;
545 dp->thead = (lsec/dp->t->sectors) % dp->t->heads;
546
547 /*
548 * can't read across track boundaries.
549 * if so, decrement the bytes to be read.
550 */
551 end = (ltrack+1)*dp->t->sectors*dp->t->bytes;
552 if(off+dp->len > end)
553 dp->len = end - off;
554 }
555
556 /*
557 * get the interrupt cause from the floppy.
558 */
559 static int
floppysense(void)560 floppysense(void)
561 {
562 fl.ncmd = 0;
563 fl.cmd[fl.ncmd++] = Fsense;
564 if(floppycmd() < 0)
565 return -1;
566 if(floppyresult() < 2){
567 DPRINT("can't read sense response\n");
568 fldump();
569 fl.confused = 1;
570 return -1;
571 }
572 return 0;
573 }
574
575 static int
cmddone(void * a)576 cmddone(void *a)
577 {
578 USED(a);
579 return fl.ncmd == 0;
580 }
581
582 /*
583 * Wait for a floppy interrupt. If none occurs in 5 seconds, we
584 * may have missed one. This only happens on some portables which
585 * do power management behind our backs. Call the interrupt
586 * routine to try to clear any conditions.
587 */
588 static void
floppywait(int slow)589 floppywait(int slow)
590 {
591 timedsleep(cmddone, 0, slow ? 5000 : 1000);
592 if(!cmddone(0)){
593 floppyintr(0);
594 fl.confused = 1;
595 }
596 }
597
598 /*
599 * we've lost the floppy position, go to cylinder 0.
600 */
601 static int
floppyrecal(FDrive * dp)602 floppyrecal(FDrive *dp)
603 {
604 dp->ccyl = -1;
605 dp->cyl = -1;
606
607 fl.ncmd = 0;
608 fl.cmd[fl.ncmd++] = Frecal;
609 fl.cmd[fl.ncmd++] = dp->dev;
610 if(floppycmd() < 0)
611 return -1;
612 floppywait(1);
613 if(fl.nstat < 2){
614 DPRINT("recalibrate: confused %ux\n", inb(Pmsr));
615 fl.confused = 1;
616 return -1;
617 }
618 if((fl.stat[0] & (Codemask|Seekend)) != Seekend){
619 DPRINT("recalibrate: failed\n");
620 dp->confused = 1;
621 return -1;
622 }
623 dp->cyl = fl.stat[1];
624 if(dp->cyl != 0){
625 DPRINT("recalibrate: wrong cylinder %d\n", dp->cyl);
626 dp->cyl = -1;
627 dp->confused = 1;
628 return -1;
629 }
630
631 dp->confused = 0;
632 return 0;
633 }
634
635 /*
636 * if the controller or a specific drive is in a confused state,
637 * reset it and get back to a kown state
638 */
639 static void
floppyrevive(void)640 floppyrevive(void)
641 {
642 FDrive *dp;
643
644 /*
645 * reset the controller if it's confused
646 */
647 if(fl.confused){
648 DPRINT("floppyrevive in\n");
649 fldump();
650
651 /* reset controller and turn all motors off */
652 splhi();
653 fl.ncmd = 1;
654 fl.cmd[0] = 0;
655 outb(Pdor, 0);
656 delay(10);
657 outb(Pdor, Fintena|Fena);
658 delay(10);
659 spllo();
660 fl.motor = 0;
661 fl.confused = 0;
662 floppywait(0);
663
664 /* mark all drives in an unknown state */
665 for(dp = fl.d; dp < &fl.d[fl.ndrive]; dp++)
666 dp->confused = 1;
667
668 /* set rate to a known value */
669 outb(Pdsr, 0);
670 fl.rate = 0;
671
672 DPRINT("floppyrevive out\n");
673 fldump();
674 }
675 }
676
677 /*
678 * seek to the target cylinder
679 *
680 * interrupt, no results
681 */
682 static vlong
pcfloppyseek(FDrive * dp,vlong off)683 pcfloppyseek(FDrive *dp, vlong off)
684 {
685 floppypos(dp, off);
686 if(dp->cyl == dp->tcyl){
687 dp->offset = off;
688 return off;
689 }
690 dp->cyl = -1;
691
692 fl.ncmd = 0;
693 fl.cmd[fl.ncmd++] = Fseek;
694 fl.cmd[fl.ncmd++] = (dp->thead<<2) | dp->dev;
695 fl.cmd[fl.ncmd++] = dp->tcyl * dp->t->steps;
696 if(floppycmd() < 0)
697 return -1;
698 floppywait(1);
699 if(fl.nstat < 2){
700 DPRINT("seek: confused\n");
701 fl.confused = 1;
702 return -1;
703 }
704 if((fl.stat[0] & (Codemask|Seekend)) != Seekend){
705 DPRINT("seek: failed\n");
706 dp->confused = 1;
707 return -1;
708 }
709
710 dp->cyl = dp->tcyl;
711 dp->offset = off;
712 DPRINT("seek to %d succeeded\n", dp->offset);
713
714 return dp->offset;
715 }
716
717 /*
718 * read or write to floppy. try up to three times.
719 */
720 static long
floppyxfer(FDrive * dp,int cmd,void * a,long off,long n)721 floppyxfer(FDrive *dp, int cmd, void *a, long off, long n)
722 {
723 long offset;
724 int tries;
725
726 if(off >= dp->t->cap)
727 return 0;
728 if(off + n > dp->t->cap)
729 n = dp->t->cap - off;
730
731 /* retry on error (until it gets ridiculous) */
732 for(tries = 0; tries < dp->maxtries; tries++){
733
734 dp->len = n;
735 if(pcfloppyseek(dp, off) < 0){
736 DPRINT("xfer: seek failed\n");
737 dp->confused = 1;
738 continue;
739 }
740
741 /*
742 * set up the dma (dp->len may be trimmed)
743 */
744 dp->len = dmasetup(DMAchan, a, dp->len, cmd==Fread);
745 if(dp->len < 0){
746 buggery:
747 dmaend(DMAchan);
748 continue;
749 }
750
751 /*
752 * start operation
753 */
754 fl.ncmd = 0;
755 fl.cmd[fl.ncmd++] = cmd | (dp->t->heads > 1 ? Fmulti : 0);
756 fl.cmd[fl.ncmd++] = (dp->thead<<2) | dp->dev;
757 fl.cmd[fl.ncmd++] = dp->tcyl;
758 fl.cmd[fl.ncmd++] = dp->thead;
759 fl.cmd[fl.ncmd++] = dp->tsec;
760 fl.cmd[fl.ncmd++] = dp->t->bcode;
761 fl.cmd[fl.ncmd++] = dp->t->sectors;
762 fl.cmd[fl.ncmd++] = dp->t->gpl;
763 fl.cmd[fl.ncmd++] = 0xFF;
764 if(floppycmd() < 0)
765 goto buggery;
766
767 /*
768 * give bus to DMA, floppyintr() will read result
769 */
770 floppywait(0);
771 dmaend(DMAchan);
772
773 /*
774 * check for errors
775 */
776 if(fl.nstat < 7){
777 DPRINT("xfer: confused\n");
778 fl.confused = 1;
779 continue;
780 }
781 if((fl.stat[0] & Codemask)!=0 || fl.stat[1] || fl.stat[2]){
782 DPRINT("xfer: failed %ux %ux %ux\n", fl.stat[0],
783 fl.stat[1], fl.stat[2]);
784 DPRINT("offset %lud len %ld\n", off, dp->len);
785 if((fl.stat[0]&Codemask)==Cmdexec && fl.stat[1]==Overrun){
786 DPRINT("DMA overrun: retry\n");
787 } else
788 dp->confused = 1;
789 continue;
790 }
791
792 /*
793 * check for correct cylinder
794 */
795 offset = fl.stat[3] * dp->t->heads + fl.stat[4];
796 offset = offset*dp->t->sectors + fl.stat[5] - 1;
797 offset = offset * c2b[fl.stat[6]];
798 if(offset != off+dp->len){
799 DPRINT("xfer: ends on wrong cyl\n");
800 dp->confused = 1;
801 continue;
802 }
803
804 dp->lasttouched = m->ticks;
805 dp->maxtries = 20;
806 return dp->len;
807 }
808
809 return -1;
810 }
811
812 /*
813 void
814 floppymemwrite(void)
815 {
816 int i;
817 int n;
818 uchar *a;
819 FDrive *dp;
820
821 dp = &fl.d[0];
822 a = (uchar*)0x80000000;
823 n = 0;
824 while(n < 1440*1024){
825 i = floppyxfer(dp, Fwrite, a+n, n, 1440*1024-n);
826 if(i <= 0)
827 break;
828 n += i;
829 }
830 print("floppymemwrite wrote %d bytes\n", n);
831 splhi(); for(;;);
832 }
833 */
834
835 static void
floppyintr(Ureg * ur)836 floppyintr(Ureg *ur)
837 {
838 USED(ur);
839 switch(fl.cmd[0]&~Fmulti){
840 case Fread:
841 case Fwrite:
842 case Fformat:
843 case Fdumpreg:
844 floppyresult();
845 break;
846 case Fseek:
847 case Frecal:
848 default:
849 floppysense(); /* to clear interrupt */
850 break;
851 }
852 fl.ncmd = 0;
853 }
854