1 /*************************************************************************
3 * $Id: s100_disk2.c 1771 2008-01-09 07:10:46Z hharte $ *
5 * Copyright (c) 2007-2008 Howard M. Harte. *
6 * http://www.hartetec.com *
8 * Permission is hereby granted, free of charge, to any person obtaining *
9 * a copy of this software and associated documentation files (the *
10 * "Software"), to deal in the Software without restriction, including *
11 * without limitation the rights to use, copy, modify, merge, publish, *
12 * distribute, sublicense, and/or sell copies of the Software, and to *
13 * permit persons to whom the Software is furnished to do so, subject to *
14 * the following conditions: *
16 * The above copyright notice and this permission notice shall be *
17 * included in all copies or substantial portions of the Software. *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, *
20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND *
22 * NONINFRINGEMENT. IN NO EVENT SHALL HOWARD M. HARTE BE LIABLE FOR ANY *
23 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, *
24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE *
25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
27 * Except as contained in this notice, the name of Howard M. Harte shall *
28 * not be used in advertising or otherwise to promote the sale, use or *
29 * other dealings in this Software without prior written authorization *
32 * SIMH Interface based on altairz80_hdsk.c, by Peter Schorn. *
34 * Module Description: *
35 * CompuPro DISK2 Hard Disk Controller module for SIMH. *
36 * This module must be used in conjunction with the CompuPro Selector *
37 * Channel Module for proper operation. *
42 *************************************************************************/
44 #include "altairz80_defs.h"
55 #define RD_DATA_MSG 0x08
56 #define WR_DATA_MSG 0x10
57 #define STATUS_MSG 0x20
58 #define VERBOSE_MSG 0x80
60 #define DISK2_MAX_DRIVES 4
70 static SECTOR_FORMAT sdata
;
75 uint16 ntracks
; /* number of tracks */
76 uint8 nheads
; /* number of heads */
77 uint8 nsectors
; /* number of sectors/track */
78 uint32 sectsize
; /* sector size, not including pre/postamble */
79 uint16 track
; /* Current Track */
80 uint8 ready
; /* Is drive ready? */
84 PNP_INFO pnp
; /* Plug and Play */
85 uint8 sel_drive
; /* Currently selected drive */
86 uint8 head_sel
; /* Head select (signals to drive itself) */
87 uint8 head
; /* Head set by write to the HEAD register */
88 uint8 cyl
; /* Cyl that the current operation is targetting */
89 uint8 sector
; /* Sector the current READ/WRITE operation is targetting */
90 uint8 hdr_sector
; /* Current sector for WRITE_HEADER */
101 DISK2_DRIVE_INFO drive
[DISK2_MAX_DRIVES
];
104 static DISK2_INFO disk2_info_data
= { { 0x0, 0, 0xC8, 2 } };
105 static DISK2_INFO
*disk2_info
= &disk2_info_data
;
109 extern t_stat
set_iobase(UNIT
*uptr
, int32 val
, char *cptr
, void *desc
);
110 extern t_stat
show_iobase(FILE *st
, UNIT
*uptr
, int32 val
, void *desc
);
111 extern uint32
sim_map_resource(uint32 baseaddr
, uint32 size
, uint32 resource_type
,
112 int32 (*routine
)(const int32
, const int32
, const int32
), uint8 unmap
);
113 extern int32
selchan_dma(uint8
*buf
, uint32 len
);
114 extern int32
find_unit_index(UNIT
*uptr
);
116 /* These are needed for DMA. PIO Mode has not been implemented yet. */
117 extern void PutBYTEWrapper(const uint32 Addr
, const uint32 Value
);
118 extern uint8
GetBYTEWrapper(const uint32 Addr
);
120 #define UNIT_V_DISK2_WLK (UNIT_V_UF + 0) /* write locked */
121 #define UNIT_DISK2_WLK (1 << UNIT_V_DISK2_WLK)
122 #define UNIT_V_DISK2_VERBOSE (UNIT_V_UF + 1) /* verbose mode, i.e. show error messages */
123 #define UNIT_DISK2_VERBOSE (1 << UNIT_V_DISK2_VERBOSE)
124 #define DISK2_CAPACITY (77*2*16*256) /* Default Micropolis Disk Capacity */
125 #define IMAGE_TYPE_DSK 1 /* Flat binary "DSK" image file. */
126 #define IMAGE_TYPE_IMD 2 /* ImageDisk "IMD" image file. */
127 #define IMAGE_TYPE_CPT 3 /* CP/M Transfer "CPT" image file. */
129 static t_stat
disk2_reset(DEVICE
*disk2_dev
);
130 static t_stat
disk2_attach(UNIT
*uptr
, char *cptr
);
131 static t_stat
disk2_detach(UNIT
*uptr
);
133 static int32
disk2dev(const int32 port
, const int32 io
, const int32 data
);
135 static uint8
DISK2_Read(const uint32 Addr
);
136 static uint8
DISK2_Write(const uint32 Addr
, uint8 cData
);
138 static int32 trace_level
= 0; /* Disable all tracing by default */
140 static UNIT disk2_unit
[] = {
141 { UDATA (NULL
, UNIT_FIX
+ UNIT_ATTABLE
+ UNIT_DISABLE
+ UNIT_ROABLE
, DISK2_CAPACITY
) },
142 { UDATA (NULL
, UNIT_FIX
+ UNIT_ATTABLE
+ UNIT_DISABLE
+ UNIT_ROABLE
, DISK2_CAPACITY
) },
143 { UDATA (NULL
, UNIT_FIX
+ UNIT_ATTABLE
+ UNIT_DISABLE
+ UNIT_ROABLE
, DISK2_CAPACITY
) },
144 { UDATA (NULL
, UNIT_FIX
+ UNIT_ATTABLE
+ UNIT_DISABLE
+ UNIT_ROABLE
, DISK2_CAPACITY
) }
147 static REG disk2_reg
[] = {
148 { HRDATA (TRACELEVEL
, trace_level
, 16), },
152 static MTAB disk2_mod
[] = {
153 { MTAB_XTD
|MTAB_VDV
, 0, "IOBASE", "IOBASE", &set_iobase
, &show_iobase
, NULL
},
154 { UNIT_DISK2_WLK
, 0, "WRTENB", "WRTENB", NULL
},
155 { UNIT_DISK2_WLK
, UNIT_DISK2_WLK
, "WRTLCK", "WRTLCK", NULL
},
156 /* quiet, no warning messages */
157 { UNIT_DISK2_VERBOSE
, 0, "QUIET", "QUIET", NULL
},
158 /* verbose, show warning messages */
159 { UNIT_DISK2_VERBOSE
, UNIT_DISK2_VERBOSE
, "VERBOSE", "VERBOSE", NULL
},
164 "DISK2", disk2_unit
, disk2_reg
, disk2_mod
,
165 DISK2_MAX_DRIVES
, 10, 31, 1, DISK2_MAX_DRIVES
, DISK2_MAX_DRIVES
,
166 NULL
, NULL
, &disk2_reset
,
167 NULL
, &disk2_attach
, &disk2_detach
,
168 &disk2_info_data
, (DEV_DISABLE
| DEV_DIS
), 0,
173 static t_stat
disk2_reset(DEVICE
*dptr
)
175 PNP_INFO
*pnp
= (PNP_INFO
*)dptr
->ctxt
;
177 if(dptr
->flags
& DEV_DIS
) { /* Disconnect I/O Ports */
178 sim_map_resource(pnp
->io_base
, pnp
->io_size
, RESOURCE_TYPE_IO
, &disk2dev
, TRUE
);
180 /* Connect DISK2 at base address */
181 if(sim_map_resource(pnp
->io_base
, pnp
->io_size
, RESOURCE_TYPE_IO
, &disk2dev
, FALSE
) != 0) {
182 printf("%s: error mapping I/O resource at 0x%04x\n", __FUNCTION__
, pnp
->io_base
);
191 static t_stat
disk2_attach(UNIT
*uptr
, char *cptr
)
194 DISK2_DRIVE_INFO
*pDrive
;
198 i
= find_unit_index(uptr
);
202 pDrive
= &disk2_info
->drive
[i
];
205 disk2_info
->write_fault
= 1;
207 pDrive
->ntracks
= 243;
209 pDrive
->nsectors
= 11;
210 pDrive
->sectsize
= 1024;
212 r
= attach_unit(uptr
, cptr
); /* attach unit */
213 if ( r
!= SCPE_OK
) /* error? */
216 /* Determine length of this disk */
217 if(sim_fsize(uptr
->fileref
) != 0) {
218 uptr
->capac
= sim_fsize(uptr
->fileref
);
220 uptr
->capac
= (pDrive
->ntracks
* pDrive
->nsectors
* pDrive
->nheads
* pDrive
->sectsize
);
225 /* Default for new file is DSK */
226 uptr
->u3
= IMAGE_TYPE_DSK
;
228 if(uptr
->capac
> 0) {
229 fgets(header
, 4, uptr
->fileref
);
230 if(!strcmp(header
, "IMD")) {
231 uptr
->u3
= IMAGE_TYPE_IMD
;
232 } else if(!strcmp(header
, "CPT")) {
233 printf("CPT images not yet supported\n");
234 uptr
->u3
= IMAGE_TYPE_CPT
;
238 uptr
->u3
= IMAGE_TYPE_DSK
;
242 if (uptr
->flags
& UNIT_DISK2_VERBOSE
)
243 printf("DISK2%d, attached to '%s', type=%s, len=%d\n", i
, cptr
,
244 uptr
->u3
== IMAGE_TYPE_IMD
? "IMD" : uptr
->u3
== IMAGE_TYPE_CPT
? "CPT" : "DSK",
247 if(uptr
->u3
== IMAGE_TYPE_IMD
) {
248 if(uptr
->capac
< 318000) {
249 printf("Cannot create IMD files with SIMH.\nCopy an existing file and format it with CP/M.\n");
254 if (uptr
->flags
& UNIT_DISK2_VERBOSE
)
255 printf("--------------------------------------------------------\n");
256 disk2_info
->drive
[i
].imd
= diskOpen((uptr
->fileref
), (uptr
->flags
& UNIT_DISK2_VERBOSE
));
257 if (uptr
->flags
& UNIT_DISK2_VERBOSE
) printf("\n");
259 disk2_info
->drive
[i
].imd
= NULL
;
267 t_stat
disk2_detach(UNIT
*uptr
)
272 i
= find_unit_index(uptr
);
278 if (uptr
->flags
& UNIT_DISK2_VERBOSE
)
279 printf("Detach DISK2%d\n", i
);
281 r
= detach_unit(uptr
); /* detach unit */
289 static int32
disk2dev(const int32 port
, const int32 io
, const int32 data
)
291 /* TRACE_PRINT(VERBOSE_MSG, ("DISK2: " ADDRESS_FORMAT " IO %s, Port %02x" NLP, PCX, io ? "WR" : "RD", port)); */
293 DISK2_Write(port
, data
);
296 return(DISK2_Read(port
));
300 #define DISK2_CSR 0 /* R=DISK2 Status / W=DISK2 Control Register */
301 #define DISK2_DATA 1 /* R=Step Pulse / W=Write Data Register */
303 static uint8
DISK2_Read(const uint32 Addr
)
306 DISK2_DRIVE_INFO
*pDrive
;
308 pDrive
= &disk2_info
->drive
[disk2_info
->sel_drive
];
313 cData
= (disk2_info
->ctl_attn
) << 7;
314 cData
|= (disk2_info
->timeout
) << 6;
315 cData
|= (disk2_info
->crc_error
) << 5;
316 cData
|= (disk2_info
->overrun
) << 4;
317 cData
|= (pDrive
->ready
== 0) ? 0x08 : 0x00;
318 cData
|= (disk2_info
->seek_complete
== 0) ? 0x04 : 0x00;
319 cData
|= (disk2_info
->write_fault
) << 1;
320 cData
|= ((pDrive
->track
!= 0) || (disk2_info
->seek_complete
== 0)) ? 0x01 : 0x00;
321 TRACE_PRINT(STATUS_MSG
, ("DISK2: " ADDRESS_FORMAT
" RD STATUS = 0x%02x" NLP
, PCX
, cData
));
323 disk2_info
->seek_complete
= 1;
326 if(disk2_info
->ctl_op
& 0x04) {
327 if(pDrive
->track
< pDrive
->ntracks
) {
331 if(pDrive
->track
> 0) {
335 TRACE_PRINT(SEEK_MSG
, ("DISK2: " ADDRESS_FORMAT
" Step %s, Track=%d" NLP
,
336 PCX
, disk2_info
->ctl_op
& 0x04 ? "IN" : "OUT", pDrive
->track
));
337 disk2_info
->seek_complete
= 0;
338 cData
= 0xFF; /* Return High-Z data */
345 #define DISK2_OP_DRIVE 0x00
346 #define DISK2_OP_CYL 0x01
347 #define DISK2_OP_HEAD 0x02
348 #define DISK2_OP_SECTOR 0x03
350 #define DISK2_CMD_NULL 0x00
351 #define DISK2_CMD_READ_DATA 0x01
352 #define DISK2_CMD_WRITE_DATA 0x02
353 #define DISK2_CMD_WRITE_HEADER 0x03
354 #define DISK2_CMD_READ_HEADER 0x04
356 static uint8
DISK2_Write(const uint32 Addr
, uint8 cData
)
362 DISK2_DRIVE_INFO
*pDrive
;
364 pDrive
= &disk2_info
->drive
[disk2_info
->sel_drive
];
367 case DISK2_CSR
: /* Write CTL register */
368 disk2_info
->ctl_attn
= (cData
& 0x80) >> 7;
369 disk2_info
->ctl_run
= (cData
& 0x40) >> 6;
370 disk2_info
->ctl_op
= (cData
& 0x38) >> 3;
371 disk2_info
->ctl_fault_clr
= (cData
& 0x04) >> 2;
372 if(disk2_info
->ctl_fault_clr
== 1) {
373 disk2_info
->timeout
= 0;
375 disk2_info
->ctl_us
= (cData
& 0x03);
376 TRACE_PRINT(VERBOSE_MSG
, ("DISK2: " ADDRESS_FORMAT
" ATTN*=%d, RUN=%d, OP=%d, FAULT_CLR=%d, US=%d" NLP
,
378 disk2_info
->ctl_attn
,
381 disk2_info
->ctl_fault_clr
,
382 disk2_info
->ctl_us
));
384 /* FIXME: seek_complete = 1 is needed by CP/M, but why? Also, maybe related,
385 * there appears to be a bug in the seeking logic. For some reason, the
386 * pDrive->track does not equal the disk2_info->cyl, when doing READ_DATA and
387 * WRITE_DATA commands. For this reason, disk2_info->cyl is used instead of
388 * pDrive->track for these commands. For READ_HEADER and WRITE_HEADER,
389 * pDrive->track is used, because the DISK2 format program (DISK2.COM) does not
390 * issue DISK2_OP_CYL. The root cause of this anomaly needs to be determined,
391 * because it is surely a bug in the logic somewhere.
393 /* pDrive->track may be different from disk2_info->cyl when a program such as DISK2.COM
394 moves the position of the track without informing the CP/M BIOS which stores the
395 current track for each drive. This appears to be an application program bug.
397 disk2_info
->seek_complete
= 1;
399 if(disk2_info
->ctl_run
== 1) {
400 disk2_info
->timeout
= 0;
401 track_offset
= disk2_info
->cyl
* pDrive
->nheads
* pDrive
->nsectors
* (pDrive
->sectsize
+ 3);
403 switch(disk2_info
->ctl_op
) {
405 TRACE_PRINT(CMD_MSG
, ("DISK2: " ADDRESS_FORMAT
" NULL Command" NLP
, PCX
));
407 case DISK2_CMD_READ_DATA
:
408 TRACE_PRINT(RD_DATA_MSG
, ("DISK2: " ADDRESS_FORMAT
" READ_DATA: (C:%d/H:%d/S:%d)" NLP
,
412 disk2_info
->sector
));
413 if(disk2_info
->head_sel
!= disk2_info
->head
) {
414 printf("DISK2: " ADDRESS_FORMAT
" READ_DATA: head_sel != head" NLP
, PCX
);
416 /* See FIXME above... that might be why this does not work properly... */
417 if(disk2_info
->cyl
!= pDrive
->track
) { /* problem, should not happen, see above */
418 TRACE_PRINT(BUG_MSG
, ("DISK2: " ADDRESS_FORMAT
" READ_DATA: cyl=%d, track=%d" NLP
,
419 PCX
, disk2_info
->cyl
, pDrive
->track
));
420 pDrive
->track
= disk2_info
->cyl
; /* update track */
422 fseek((pDrive
->uptr
)->fileref
, track_offset
+ (disk2_info
->head_sel
* pDrive
->nsectors
* (pDrive
->sectsize
+ 3)), SEEK_SET
);
423 for(i
=0;i
<pDrive
->nsectors
;i
++) {
425 fread(sdata
.raw
, (pDrive
->sectsize
+ 3), 1, (pDrive
->uptr
)->fileref
);
426 if(sdata
.u
.header
[2] == disk2_info
->sector
) {
427 if(sdata
.u
.header
[0] != disk2_info
->cyl
) { /*pDrive->track) { */
428 printf("DISK2: " ADDRESS_FORMAT
" READ_DATA Incorrect header: track" NLP
, PCX
);
429 disk2_info
->timeout
= 1;
431 if(sdata
.u
.header
[1] != disk2_info
->head
) {
432 printf("DISK2: " ADDRESS_FORMAT
" READ_DATA Incorrect header: head" NLP
, PCX
);
433 disk2_info
->timeout
= 1;
436 selchan_dma(sdata
.u
.data
, pDrive
->sectsize
);
439 if(i
== pDrive
->nsectors
) {
440 printf("DISK2: " ADDRESS_FORMAT
" Sector not found" NLP
, PCX
);
441 disk2_info
->timeout
= 1;
446 case DISK2_CMD_WRITE_DATA
:
447 TRACE_PRINT(WR_DATA_MSG
, ("DISK2: " ADDRESS_FORMAT
" WRITE_DATA: (C:%d/H:%d/S:%d)" NLP
,
451 disk2_info
->sector
));
452 if(disk2_info
->head_sel
!= disk2_info
->head
) {
453 printf("DISK2: " ADDRESS_FORMAT
" WRITE_DATA: head_sel != head" NLP
, PCX
);
455 if(disk2_info
->cyl
!= pDrive
->track
) { /* problem, should not happen, see above */
456 TRACE_PRINT(BUG_MSG
, ("DISK2: " ADDRESS_FORMAT
" WRITE_DATA = 0x%02x, cyl=%d, track=%d" NLP
,
457 PCX
, cData
, disk2_info
->cyl
, pDrive
->track
));
458 pDrive
->track
= disk2_info
->cyl
; /* update track */
461 fseek((pDrive
->uptr
)->fileref
, track_offset
+ (disk2_info
->head_sel
* pDrive
->nsectors
* (pDrive
->sectsize
+ 3)), SEEK_SET
);
462 for(i
=0;i
<pDrive
->nsectors
;i
++) {
464 file_offset
= ftell((pDrive
->uptr
)->fileref
);
465 fread(sdata
.raw
, 3, 1, (pDrive
->uptr
)->fileref
);
466 if(sdata
.u
.header
[2] == disk2_info
->sector
) {
467 if(sdata
.u
.header
[0] != disk2_info
->cyl
) {
468 printf("DISK2: " ADDRESS_FORMAT
" WRITE_DATA Incorrect header: track" NLP
, PCX
);
469 disk2_info
->timeout
= 1;
471 if(sdata
.u
.header
[1] != disk2_info
->head
) {
472 printf("DISK2: " ADDRESS_FORMAT
" WRITE_DATA Incorrect header: head" NLP
, PCX
);
473 disk2_info
->timeout
= 1;
476 selchan_dma(sdata
.u
.data
, pDrive
->sectsize
);
477 fseek((pDrive
->uptr
)->fileref
, file_offset
+3, SEEK_SET
);
478 fwrite(sdata
.u
.data
, (pDrive
->sectsize
), 1, (pDrive
->uptr
)->fileref
);
481 fread(sdata
.raw
, pDrive
->sectsize
, 1, (pDrive
->uptr
)->fileref
);
482 if(i
== pDrive
->nsectors
) {
483 printf("DISK2: " ADDRESS_FORMAT
" Sector not found" NLP
, PCX
);
484 disk2_info
->timeout
= 1;
488 case DISK2_CMD_WRITE_HEADER
:
489 track_offset
= pDrive
->track
* pDrive
->nheads
* pDrive
->nsectors
* (pDrive
->sectsize
+ 3);
490 TRACE_PRINT(CMD_MSG
, ("DISK2: " ADDRESS_FORMAT
" WRITE_HEADER Command: track=%d (%d), Head=%d, Sector=%d" NLP
,
494 disk2_info
->head_sel
,
495 disk2_info
->hdr_sector
));
497 i
= disk2_info
->hdr_sector
;
498 selchan_dma(sdata
.raw
, 3);
499 fseek((pDrive
->uptr
)->fileref
, track_offset
+ (disk2_info
->head_sel
* (pDrive
->sectsize
+ 3) * pDrive
->nsectors
) + (i
* (pDrive
->sectsize
+ 3)), SEEK_SET
);
500 fwrite(sdata
.raw
, 3, 1, (pDrive
->uptr
)->fileref
);
502 disk2_info
->hdr_sector
++;
503 if(disk2_info
->hdr_sector
>= pDrive
->nsectors
) {
504 disk2_info
->hdr_sector
= 0;
505 disk2_info
->timeout
= 1;
508 case DISK2_CMD_READ_HEADER
:
509 track_offset
= pDrive
->track
* pDrive
->nheads
* pDrive
->nsectors
* (pDrive
->sectsize
+ 3);
510 TRACE_PRINT(CMD_MSG
, ("DISK2: " ADDRESS_FORMAT
" READ_HEADER Command" NLP
, PCX
));
511 fseek((pDrive
->uptr
)->fileref
, track_offset
+ (disk2_info
->head_sel
* pDrive
->nsectors
* (pDrive
->sectsize
+ 3)), SEEK_SET
);
512 fread(sdata
.raw
, 3, 1, (pDrive
->uptr
)->fileref
);
513 selchan_dma(sdata
.raw
, 3);
516 printf("DISK2: " ADDRESS_FORMAT
" Unknown CMD=%d" NLP
, PCX
, disk2_info
->ctl_op
);
520 disk2_info
->ctl_attn
= 0;
525 switch(disk2_info
->ctl_op
) {
529 disk2_info
->sel_drive
= 0;
532 disk2_info
->sel_drive
= 1;
535 disk2_info
->sel_drive
= 2;
538 disk2_info
->sel_drive
= 3;
541 printf("DISK2: " ADDRESS_FORMAT
" Error, invalid drive select=0x%x" NLP
, PCX
, cData
>> 4);
545 disk2_info
->head_sel
= cData
& 0x0F;
547 TRACE_PRINT(VERBOSE_MSG
, ("DISK2: " ADDRESS_FORMAT
" Write DATA [DRIVE]=%d, Head=%d" NLP
,
548 PCX
, disk2_info
->sel_drive
, disk2_info
->head
));
551 disk2_info
->cyl
= cData
;
552 TRACE_PRINT(VERBOSE_MSG
, ("DISK2: " ADDRESS_FORMAT
" Write DATA [CYL] = %02x" NLP
,
556 disk2_info
->head
= cData
;
557 TRACE_PRINT(VERBOSE_MSG
, ("DISK2: " ADDRESS_FORMAT
" Write DATA [HEAD] = %02x" NLP
,
560 case DISK2_OP_SECTOR
:
561 disk2_info
->sector
= cData
;
562 TRACE_PRINT(VERBOSE_MSG
, ("DISK2: " ADDRESS_FORMAT
" Write Register [SECTOR] = %02x" NLP
,
566 TRACE_PRINT(VERBOSE_MSG
, ("DISK2: " ADDRESS_FORMAT
" Write Register unknown op [%d] = %02x" NLP
,
567 PCX
, disk2_info
->ctl_op
, cData
));