1 /* $NetBSD: mfm.c,v 1.16 2019/10/28 21:11:23 christos Exp $ */
2 /*
3 * Copyright (c) 1996 Ludd, University of Lule}, Sweden.
4 * All rights reserved.
5 *
6 * This code is derived from software contributed to Ludd by
7 * Bertram Barth.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 /*
31 * ToDo:
32 *
33 * - insert appropriate delays for diskette-drive where needed
34 * - allow more than one sector per diskette-read
35 * - check for and handle bad sectors
36 * - ???
37 */
38
39 #include <sys/param.h>
40 #include <sys/reboot.h>
41 #include <sys/disklabel.h>
42
43 #include <lib/libsa/stand.h>
44 #include <lib/libsa/ufs.h>
45
46 #include <lib/libkern/libkern.h>
47
48 #include "../include/pte.h"
49 #include "../include/sid.h"
50 #include "../include/mtpr.h"
51 #include "../include/reg.h"
52 #include "../include/rpb.h"
53
54 #include "ka410.h"
55 #include "../vsa/hdc9224.h"
56
57 #include "data.h"
58 #include "vaxstand.h"
59
60 #define MAX_WAIT (1000*1000) /* # of loop-instructions in seconds */
61
62 struct mfm_softc {
63 int part;
64 int unit;
65 };
66
67 static struct disklabel mfmlabel;
68 static struct mfm_softc mfm_softc;
69 static char io_buf[DEV_BSIZE];
70
71 /*
72 * These should probably be somewhere else, but ka410 is the only
73 * one with mfm disks anyway...
74 */
75 volatile unsigned char *ka410_intreq = (void*)0x2008000f;
76 volatile unsigned char *ka410_intclr = (void*)0x2008000f;
77 volatile unsigned char *ka410_intmsk = (void*)0x2008000c;
78
79 static volatile struct hdc9224_DKCreg *dkc = (void *) 0x200c0000;
80 static volatile struct hdc9224_UDCreg sreg; /* input */
81 static volatile struct hdc9224_UDCreg creg; /* output */
82
83 static void sreg_read(void);
84 static void creg_write(void);
85 static int mfm_rxprepare(void);
86 static int mfm_command(int cmd);
87 static int mfm_rxselect(int unit);
88 static int mfm_rdselect(int unit);
89 static int mfm_rxstrategy(void *f, int func, daddr_t dblk, size_t size, void *buf, size_t *rsize);
90 static int mfm_rdstrategy(void *f, int func, daddr_t dblk, size_t size, void *buf, size_t *rsize);
91 /*
92 * we have to wait 0.7 usec between two accesses to any of the
93 * dkc-registers, on a VS2000 with 1 MIPS, this is roughly one
94 * instruction. Thus the loop-overhead will be enough...
95 */
96 static void
sreg_read(void)97 sreg_read(void)
98 {
99 int i;
100 char *p;
101
102 dkc->dkc_cmd = 0x40; /* set internal counter to zero */
103 p = (void *) &sreg;
104 for (i = 0; i < 10; i++)
105 *p++ = dkc->dkc_reg; /* dkc_reg auto-increments */
106 }
107
108 static void
creg_write(void)109 creg_write(void)
110 {
111 int i;
112 char *p;
113
114 dkc->dkc_cmd = 0x40; /* set internal counter to zero */
115 p = (void *) &creg;
116 for (i = 0; i < 10; i++)
117 dkc->dkc_reg = *p++; /* dkc_reg auto-increments */
118 }
119
120 /*
121 * floppies are handled in a quite strange way by this controller...
122 *
123 * before reading/writing a sector from/to floppy, we use the SEEK/READ_ID
124 * command to place the head at the desired location. Then we wait some
125 * time before issuing the real command in order to let the drive become
126 * ready...
127 */
128 int
mfm_rxprepare(void)129 mfm_rxprepare(void)
130 {
131 int error;
132
133 error = mfm_command(DKC_CMD_SEEKREADID | 0x04); /* step=1, verify=0 */
134 if (error) {
135 printf("error while stepping to position %d/%d/%x. Retry...\n",
136 creg.udc_dsect, creg.udc_dhead, creg.udc_dcyl);
137 error = mfm_command(DKC_CMD_SEEKREADID | 0x04);
138 }
139 return error;
140 }
141
142 static int
mfm_is_ready(int cnt,int error)143 mfm_is_ready(int cnt, int error)
144 {
145 if (error == 0 && (sreg.udc_dstat & UDC_DS_READY) == UDC_DS_READY)
146 return 1;
147 printf("diskette not ready(%d): %#x/%#x\n", cnt, error, sreg.udc_dstat);
148 return 0;
149 }
150
151 int
mfm_rxselect(int unit)152 mfm_rxselect(int unit)
153 {
154 int error;
155
156 /*
157 * bring "creg" in some known-to-work state and
158 * select the drive with the DRIVE SELECT command.
159 */
160 creg.udc_dma7 = 0;
161 creg.udc_dma15 = 0;
162 creg.udc_dma23 = 0;
163 creg.udc_dsect = 1; /* sectors are numbered 1..15 !!! */
164 creg.udc_dhead = 0;
165 creg.udc_dcyl = 0;
166 creg.udc_scnt = 0;
167
168 creg.udc_rtcnt = UDC_RC_RX33READ;
169 creg.udc_mode = UDC_MD_RX33;
170 creg.udc_term = UDC_TC_FDD;
171
172 /*
173 * this is ...
174 */
175 error = mfm_command(DKC_CMD_DRSEL_RX33 | unit);
176
177 if (mfm_is_ready(0, error))
178 return 0;
179
180 printf("\nfloppy-drive not ready (new floppy inserted?)\n\n");
181
182 /* clear INVRDY-flag and try again */
183 creg.udc_rtcnt &= ~UDC_RC_INVRDY;
184 error = mfm_command(DKC_CMD_DRSEL_RX33 | unit);
185
186 if (!mfm_is_ready(1, error)) {
187 printf("floppy-drive offline?\n");
188 return -1;
189
190 }
191 if (sreg.udc_dstat & UDC_DS_TRK00)
192 error = mfm_command(DKC_CMD_STEPIN_FDD);
193 else
194 error = mfm_command(DKC_CMD_STEPOUT_FDD);
195
196 /*
197 * now ready should be 0, cause INVRDY is not set
198 * (retrying a command makes this fail...)
199 */
200 mfm_is_ready(2, error);
201
202 creg.udc_rtcnt |= UDC_RC_INVRDY;
203 error = mfm_command(DKC_CMD_DRSEL_RX33 | unit);
204 if (!mfm_is_ready(3, error)) {
205 printf("no floppy inserted or floppy-door open\n");
206 return -1;
207 }
208
209 printf("floppy-drive reselected.\n");
210 return error;
211 }
212
213 int
mfm_rdselect(int unit)214 mfm_rdselect(int unit)
215 {
216 int error;
217
218 /*
219 * bring "creg" in some known-to-work state and
220 * select the drive with the DRIVE SELECT command.
221 */
222 creg.udc_dma7 = 0;
223 creg.udc_dma15 = 0;
224 creg.udc_dma23 = 0;
225 creg.udc_dsect = 0; /* sectors are numbered 0..16 */
226 creg.udc_dhead = 0;
227 creg.udc_dcyl = 0;
228 creg.udc_scnt = 0;
229
230 creg.udc_rtcnt = UDC_RC_HDD_READ;
231 creg.udc_mode = UDC_MD_HDD;
232 creg.udc_term = UDC_TC_HDD;
233
234 error = mfm_command(DKC_CMD_DRSEL_HDD | unit);
235
236 return (error);
237 }
238
239 static int mfm_retry = 0;
240
241 int
mfm_command(int cmd)242 mfm_command(int cmd)
243 {
244 int termcode, i;
245
246 creg_write(); /* write command-registers */
247 *ka410_intclr = INTR_DC;
248 dkc->dkc_cmd = cmd; /* issue command */
249 for (i = 0; i < MAX_WAIT; i++) {
250 if (*ka410_intreq & INTR_DC) /* wait for interrupt */
251 break;
252 }
253 if ((*ka410_intreq & INTR_DC) == 0)
254 printf("timeout in mfm_command...\n");
255
256 sreg_read(); /* read status-registers */
257
258 if (dkc->dkc_stat == (DKC_ST_DONE | DKC_TC_SUCCESS))
259 return (0);
260
261 if (sreg.udc_cstat & UDC_CS_ECCERR) {
262 printf(
263 "\nspurious(?) ECC/CRC error at s%d/t%d/c%d [s%d/t%d/c%d(%d)]\n",
264 sreg.udc_csect, sreg.udc_chead, sreg.udc_ccyl,
265 creg.udc_dsect, creg.udc_dhead, creg.udc_dcyl,creg.udc_scnt);
266 if (sreg.udc_csect != creg.udc_dsect + creg.udc_scnt - 1) {
267 printf("DMA: %x %x %x [%x]\n",
268 sreg.udc_dma23, sreg.udc_dma15,
269 sreg.udc_dma7, 512 * (sreg.udc_csect -
270 creg.udc_dsect));
271 creg.udc_scnt = creg.udc_scnt -
272 (sreg.udc_csect - creg.udc_dsect) - 1;
273 creg.udc_dsect = sreg.udc_csect + 1;
274 creg.udc_dma23 = sreg.udc_dma23;
275 creg.udc_dma15 = sreg.udc_dma15 + 2;
276 creg.udc_dma7 = 0;
277 printf("Retry starting from s%d/t%d/c%d (%d). ",
278 creg.udc_dsect, creg.udc_dhead, creg.udc_dcyl,
279 creg.udc_scnt);
280 }
281 goto retry;
282 }
283 termcode = (dkc->dkc_stat & DKC_ST_TERMCOD) >> 3;
284
285 printf("cmd:0x%x: termcode=0x%x, status=0x%x, cstat=0x%x, dstat=0x%x\n",
286 cmd, termcode, dkc->dkc_stat, sreg.udc_cstat, sreg.udc_dstat);
287
288 if (dkc->dkc_stat & DKC_ST_BADSECT)
289 printf("bad sector found: s%d/t%d/c%d\n", creg.udc_dsect,
290 creg.udc_dhead, creg.udc_dcyl);
291 retry:
292 if ((mfm_retry == 0) && (sreg.udc_cstat & UDC_CS_RETREQ)) {
293 mfm_retry = 1;
294 printf("Retrying... ");
295 mfm_command(cmd);
296 printf("Retry done.\n");
297 mfm_retry = 0;
298 }
299 return ((dkc->dkc_stat & DKC_ST_TERMCOD) >> 3);
300 }
301
302 /*
303 * on-disk geometry block
304 */
305 #define _aP __attribute__ ((packed)) /* force byte-alignment */
306
307 volatile struct mfm_xbn {
308 char mbz[10];/* 10 bytes of zero */
309 long xbn_count _aP; /* number of XBNs */
310 long dbn_count _aP; /* number of DBNs */
311 long lbn_count _aP; /* number of LBNs (Logical-Block-Numbers) */
312 long rbn_count _aP; /* number of RBNs (Replacement-Block-Numbers) */
313 short nspt; /* number of sectors per track */
314 short ntracks;/* number of tracks */
315 short ncylinders; /* number of cylinders */
316 short precomp;/* first cylinder for write precompensation */
317 short reduced;/* first cylinder for reduced write current */
318 short seek_rate; /* seek rate or zero for buffered
319 * seeks */
320 short crc_eec;/* 0 if CRC is being used or 1 if ECC is
321 * being used */
322 short rct; /* "replacement control table" (RCT) */
323 short rct_ncopies; /* number of copies of the RCT */
324 long media_id _aP; /* media identifier */
325 short interleave; /* sector-to-sector interleave */
326 short headskew; /* head-to-head skew */
327 short cylskew;/* cylinder-to-cylinder skew */
328 short gap0_size; /* size of GAP 0 in the MFM format */
329 short gap1_size; /* size of GAP 1 in the MFM format */
330 short gap2_size; /* size of GAP 2 in the MFM format */
331 short gap3_size; /* size of GAP 3 in the MFM format */
332 short sync_value; /* sync value used to start a track
333 * when formatting */
334 char reserved[32]; /* reserved for use by the RQDX1/2/3
335 * formatter */
336 short serial_number; /* serial number */
337 char fill[412]; /* Filler bytes to the end of the
338 * block */
339 short checksum; /* checksum over the XBN */
340 } mfm_xbn;
341
342 #ifdef verbose
display_xbn(struct mfm_xbn * p)343 display_xbn(struct mfm_xbn *p)
344 {
345 printf("**DiskData** XBNs: %d, DBNs: %d, LBNs: %d, RBNs: %d\n",
346 p->xbn_count, p->dbn_count, p->lbn_count, p->rbn_count);
347 printf("sect/track: %d, tracks: %d, cyl: %d, precomp/reduced: %d/%d\n",
348 p->nspt, p->ntracks, p->ncylinders, p->precomp, p->reduced);
349 printf("seek-rate: %d, crc/eec: %s, RCT: %d, RCT-copies: %d\n",
350 p->seek_rate, p->crc_eec ? "EEC" : "CRC", p->rct, p->rct_ncopies);
351 printf("media-ID: 0x%x, interleave: %d, headskew: %d, cylskew: %d\n",
352 &p->media_id, p->interleave, p->headskew, p->cylskew);
353 printf("gap0: %d, gap1: %d, gap2: %d, gap3: %d, sync-value: %d\n",
354 p->gap0_size, p->gap1_size, p->gap2_size, p->gap3_size,
355 p->sync_value);
356 printf("serial: %d, checksum: %d, size: %d, reserved: %32c\n",
357 p->serial_number, p->checksum, sizeof(*p), p->reserved);
358 }
359 #endif
360
361 int
mfmopen(struct open_file * f,int adapt,int ctlr,int unit,int part)362 mfmopen(struct open_file *f, int adapt, int ctlr, int unit, int part)
363 {
364 char *msg;
365 struct disklabel *lp = &mfmlabel;
366 struct mfm_softc *msc = &mfm_softc;
367 int err;
368 size_t i;
369
370 memset(lp, 0, sizeof(struct disklabel));
371 msc->unit = unit;
372 msc->part = part;
373
374 err = mfmstrategy(msc, F_READ, LABELSECTOR, DEV_BSIZE, io_buf, &i);
375 if (err) {
376 printf("reading disklabel: %s\n", strerror(err));
377 return 0;
378 }
379 msg = getdisklabel(io_buf + LABELOFFSET, lp);
380 if (msg)
381 printf("getdisklabel: %s\n", msg);
382
383 f->f_devdata = (void *) msc;
384
385 {
386 #ifdef verbose
387 int k;
388 unsigned char *ucp;
389 struct mfm_xbn *xp;
390 #endif
391
392 /* mfmstrategy(msc, F_READ, -16, 8192, io_buf, &i); */
393 mfmstrategy(msc, F_READ, -16, 512, io_buf, &i);
394 #ifdef verbose
395 printf("dumping raw disk-block #0:\n");
396 ucp = io_buf;
397 for (k = 0; k < 128; k++) {
398 if (ucp[k] < 0x10)
399 printf("0");
400 printf("%x ", ucp[k]);
401 if (k % 8 == 7)
402 printf(" ");
403 if (k % 16 == 15)
404 printf("\n");
405 }
406 printf("\n");
407
408 xp = (void *) io_buf;
409 display_xbn(xp);
410 printf("\n");
411 #endif
412 }
413
414 if (unit == 2) { /* floppy! */
415 if (lp->d_ntracks != 2) {
416 #ifdef verbose
417 printf("changing number of tracks from %d to %d.\n",
418 lp->d_ntracks, 2);
419 #endif
420 lp->d_ntracks = 2;
421 }
422 } else { /* hard-disk */
423 unsigned short *usp = (void *) io_buf;
424 #ifdef verbose
425 printf("label says: s/t/c = %d/%d/%d\n",
426 lp->d_nsectors, lp->d_ntracks, lp->d_ncylinders);
427 #endif
428 if (lp->d_nsectors != usp[13]) {
429 #ifdef verbose
430 printf("changing number of sectors from %d to %d.\n",
431 lp->d_nsectors, usp[13]);
432 #endif
433 lp->d_nsectors = usp[13];
434 }
435 if (lp->d_ntracks != usp[14]) {
436 #ifdef verbose
437 printf("changing number of heads/tracks from %d to %d.\n",
438 lp->d_ntracks, usp[14]);
439 #endif
440 lp->d_ntracks = usp[14];
441 }
442 if (lp->d_ncylinders != usp[15]) {
443 #ifdef verbose
444 printf("changing number of cylinders from %d to %d.\n",
445 lp->d_ncylinders, usp[15]);
446 #endif
447 lp->d_ncylinders = usp[15];
448 }
449 lp->d_secpercyl = lp->d_nsectors * lp->d_ntracks;
450 }
451
452 return (0);
453 }
454
455 int
mfm_rxstrategy(void * f,int func,daddr_t dblk,size_t size,void * buf,size_t * rsize)456 mfm_rxstrategy(void *f, int func, daddr_t dblk, size_t size, void *buf, size_t *rsize) {
457 struct mfm_softc *msc = f;
458 struct disklabel *lp;
459 int block, sect, head, cyl, scount;
460 char *cbuf;
461
462 cbuf = (char*)buf;
463
464 lp = &mfmlabel;
465 block = (dblk < 0 ? 0 : dblk + lp->d_partitions[msc->part].p_offset);
466
467 mfm_rxselect(msc->unit);
468
469 /*
470 * if label is empty, assume RX33
471 */
472 if (lp->d_nsectors == 0)
473 lp->d_nsectors = 15;
474 if (lp->d_ntracks == 0)
475 lp->d_ntracks = 2;
476 if (lp->d_secpercyl == 0)
477 lp->d_secpercyl = 30;
478
479 memset((void *) 0x200D0000, 0, size);
480 scount = size / 512;
481
482 while (scount) {
483 /*
484 * prepare drive/operation parameter
485 */
486 cyl = block / lp->d_secpercyl;
487 sect = block % lp->d_secpercyl;
488 head = sect / lp->d_nsectors;
489 sect = sect % lp->d_nsectors;
490
491 /*
492 * *rsize = 512; one sector after the other
493 * ...
494 */
495 *rsize = 512 * min(scount, lp->d_nsectors - sect);
496
497 /*
498 * now initialize the register values ...
499 */
500 creg.udc_dma7 = 0;
501 creg.udc_dma15 = 0;
502 creg.udc_dma23 = 0;
503
504 creg.udc_dsect = sect + 1; /* sectors are numbered 1..15
505 * !!! */
506 head |= (cyl >> 4) & 0x70;
507 creg.udc_dhead = head;
508 creg.udc_dcyl = cyl;
509
510 creg.udc_scnt = *rsize / 512;
511
512 if (func == F_WRITE) {
513 creg.udc_rtcnt = UDC_RC_RX33WRT;
514 creg.udc_mode = UDC_MD_RX33;
515 creg.udc_term = UDC_TC_FDD;
516
517 mfm_rxprepare();
518 /* copy from buf */
519 memcpy((void *) 0x200D0000, cbuf, *rsize);
520 (void)mfm_command(DKC_CMD_WRITE_RX33);
521 } else {
522 creg.udc_rtcnt = UDC_RC_RX33READ;
523 creg.udc_mode = UDC_MD_RX33;
524 creg.udc_term = UDC_TC_FDD;
525
526 mfm_rxprepare();
527 /* clear disk buffer */
528 memset((void *) 0x200D0000, 0, *rsize);
529 (void)mfm_command(DKC_CMD_READ_RX33);
530 /* copy to buf */
531 memcpy(cbuf, (void *) 0x200D0000, *rsize);
532 }
533
534 scount -= *rsize / 512;
535 block += *rsize / 512;
536 cbuf += *rsize;
537 }
538
539 *rsize = size;
540 return 0;
541 }
542
543 int
mfm_rdstrategy(void * f,int func,daddr_t dblk,size_t size,void * buf,size_t * rsize)544 mfm_rdstrategy(void *f, int func, daddr_t dblk, size_t size, void *buf, size_t *rsize) {
545 struct mfm_softc *msc = f;
546 struct disklabel *lp;
547 int block, sect, head, cyl, scount, cmd;
548 char *cbuf;
549
550 cbuf = (char *)buf;
551
552 lp = &mfmlabel;
553 block = (dblk < 0 ? 0 : dblk + lp->d_partitions[msc->part].p_offset);
554
555 /*
556 * if label is empty, assume RD32 (XXX this must go away!!!)
557 */
558 if (lp->d_nsectors == 0)
559 lp->d_nsectors = 17;
560 if (lp->d_ntracks == 0)
561 lp->d_ntracks = 6;
562 if (lp->d_secpercyl == 0)
563 lp->d_secpercyl = 102;
564
565 mfm_rdselect(msc->unit);
566
567 memset((void *) 0x200D0000, 0, size);
568 scount = size / 512;
569
570 while (scount) {
571 /*
572 * prepare drive/operation parameter
573 */
574 cyl = block / lp->d_secpercyl;
575 sect = block % lp->d_secpercyl;
576 head = sect / lp->d_nsectors;
577 sect = sect % lp->d_nsectors;
578
579 if (dblk < 0) {
580 #ifdef verbose
581 printf("using raw diskblock-data!\n");
582 printf("block %d, dblk %d ==> cyl %d, head %d, sect %d\n",
583 block, dblk, cyl, sect, head);
584 #endif
585 } else
586 cyl += 1; /* first cylinder is reserved for
587 * controller! */
588
589 *rsize = 512 * min(scount, lp->d_nsectors - sect);
590 /*
591 * now re-initialize the register values ...
592 */
593 creg.udc_dma7 = 0;
594 creg.udc_dma15 = 0;
595 creg.udc_dma23 = 0;
596
597 creg.udc_dsect = sect;
598 head |= (cyl >> 4) & 0x70;
599 creg.udc_dhead = head;
600 creg.udc_dcyl = cyl;
601
602 creg.udc_scnt = *rsize / 512;
603
604 if (func == F_WRITE) {
605 creg.udc_rtcnt = UDC_RC_HDD_WRT;
606 creg.udc_mode = UDC_MD_HDD;
607 creg.udc_term = UDC_TC_HDD;
608 cmd = DKC_CMD_WRITE_HDD;
609
610 memcpy((void *) 0x200D0000, cbuf, *rsize);
611 (void)mfm_command(cmd);
612 } else {
613 creg.udc_rtcnt = UDC_RC_HDD_READ;
614 creg.udc_mode = UDC_MD_HDD;
615 creg.udc_term = UDC_TC_HDD;
616 cmd = DKC_CMD_READ_HDD;
617
618 memset((void *) 0x200D0000, 0, *rsize);
619 (void)mfm_command(cmd);
620 memcpy(cbuf, (void *) 0x200D0000, *rsize);
621 }
622
623 scount -= *rsize / 512;
624 block += *rsize / 512;
625 cbuf += *rsize;
626 }
627
628 /*
629 * unselect the drive ...
630 */
631 mfm_command(DKC_CMD_DRDESELECT);
632
633 *rsize = size;
634 return 0;
635 }
636
637 int
mfmstrategy(void * f,int func,daddr_t dblk,size_t size,void * buf,size_t * rsize)638 mfmstrategy(void *f, int func, daddr_t dblk, size_t size, void *buf, size_t *rsize)
639 {
640 struct mfm_softc *msc = f;
641
642 switch (msc->unit) {
643 case 0:
644 case 1:
645 return mfm_rdstrategy(f, func, dblk, size, buf, rsize);
646 break;
647 case 2:
648 return mfm_rxstrategy(f, func, dblk, size, buf, rsize);
649 break;
650 default:
651 printf("invalid unit %d in mfmstrategy()\n", msc->unit);
652 return -1;
653 }
654 }
655