First Commit of my working state
[simh.git] / AltairZ80 / altairz80_hdsk.c
1 /* altairz80_hdsk.c: simulated hard disk device to increase capacity
2
3 Copyright (c) 2002-2008, Peter Schorn
4
5 Permission is hereby granted, free of charge, to any person obtaining a
6 copy of this software and associated documentation files (the "Software"),
7 to deal in the Software without restriction, including without limitation
8 the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 and/or sell copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 PETER SCHORN BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 Except as contained in this notice, the name of Peter Schorn shall not
23 be used in advertising or otherwise to promote the sale, use or other dealings
24 in this Software without prior written authorization from Peter Schorn.
25
26 Contains code from Howard M. Harte for defining and changing disk geometry.
27 */
28
29 #include "altairz80_defs.h"
30 #include <assert.h>
31
32 /* The following routines are based on work from Howard M. Harte */
33 static t_stat set_geom(UNIT *uptr, int32 val, char *cptr, void *desc);
34 static t_stat show_geom(FILE *st, UNIT *uptr, int32 val, void *desc);
35 static t_stat set_format(UNIT *uptr, int32 val, char *cptr, void *desc);
36 static t_stat show_format(FILE *st, UNIT *uptr, int32 val, void *desc);
37
38 static t_stat hdsk_reset(DEVICE *dptr);
39 static t_stat hdsk_attach(UNIT *uptr, char *cptr);
40
41 #define UNIT_V_HDSK_WLK (UNIT_V_UF + 0) /* write locked */
42 #define UNIT_HDSK_WLK (1 << UNIT_V_HDSK_WLK)
43 #define UNIT_V_HDSK_VERBOSE (UNIT_V_UF + 1) /* verbose mode, i.e. show error messages */
44 #define UNIT_HDSK_VERBOSE (1 << UNIT_V_HDSK_VERBOSE)
45 #define HDSK_MAX_SECTOR_SIZE 1024 /* maximum size of a sector */
46 #define HDSK_SECTOR_SIZE u5 /* size of sector */
47 #define HDSK_SECTORS_PER_TRACK u4 /* sectors per track */
48 #define HDSK_NUMBER_OF_TRACKS u3 /* number of tracks */
49 #define HDSK_FORMAT_TYPE u6 /* Disk Format Type */
50 #define HDSK_CAPACITY (2048*32*128) /* Default Altair HDSK Capacity */
51 #define HDSK_NUMBER 8 /* number of hard disks */
52 #define CPM_OK 0 /* indicates to CP/M everything ok */
53 #define CPM_ERROR 1 /* indicates to CP/M an error condition */
54 #define CPM_EMPTY 0xe5 /* default value for non-existing bytes */
55 #define HDSK_NONE 0
56 #define HDSK_RESET 1
57 #define HDSK_READ 2
58 #define HDSK_WRITE 3
59 #define HDSK_PARAM 4
60 #define HDSK_BOOT_ADDRESS 0x5c00
61 #define DPB_NAME_LENGTH 15
62 #define BOOTROM_SIZE_HDSK 256
63
64 extern char messageBuffer[];
65 extern uint32 PCX;
66 extern REG *sim_PC;
67 extern UNIT cpu_unit;
68
69 extern void install_ALTAIRbootROM(void);
70 extern void printMessage(void);
71 extern void PutBYTEWrapper(const uint32 Addr, const uint32 Value);
72 extern uint8 GetBYTEWrapper(const uint32 Addr);
73 extern t_stat install_bootrom(int32 bootrom[], int32 size, int32 addr, int32 makeROM);
74 extern int32 bootrom_dsk[];
75 extern t_stat set_iobase(UNIT *uptr, int32 val, char *cptr, void *desc);
76 extern t_stat show_iobase(FILE *st, UNIT *uptr, int32 val, void *desc);
77 extern uint32 sim_map_resource(uint32 baseaddr, uint32 size, uint32 resource_type,
78 int32 (*routine)(const int32, const int32, const int32), uint8 unmap);
79
80 static t_stat hdsk_boot(int32 unitno, DEVICE *dptr);
81 int32 hdsk_io(const int32 port, const int32 io, const int32 data);
82
83 static int32 hdskLastCommand = HDSK_NONE;
84 static int32 hdskCommandPosition = 0;
85 static int32 paramcount = 0;
86 static int32 selectedDisk;
87 static int32 selectedSector;
88 static int32 selectedTrack;
89 static int32 selectedDMA;
90 static int32 trace_level = 0;
91
92 typedef struct {
93 char name[DPB_NAME_LENGTH + 1]; /* name of CP/M disk parameter block */
94 t_addr capac; /* capacity */
95 uint16 spt; /* sectors per track */
96 uint8 bsh; /* data allocation block shift factor */
97 uint8 blm; /* data allocation block mask */
98 uint8 exm; /* extent mask */
99 uint16 dsm; /* maximum data block number */
100 uint16 drm; /* total number of directory entries */
101 uint8 al0; /* determine reserved directory blocks */
102 uint8 al1; /* determine reserved directory blocks */
103 uint16 cks; /* size of directory check vector */
104 uint16 off; /* number of reserved tracks */
105 uint8 psh; /* physical record shift factor, CP/M 3 */
106 uint8 phm; /* physical record mask, CP/M 3 */
107 } DPB;
108
109 typedef struct {
110 PNP_INFO pnp; /* Plug and Play */
111 } HDSK_INFO;
112
113 static HDSK_INFO hdsk_info_data = { { 0x0000, 0, 0xFD, 1 } };
114 /* static HDSK_INFO *hdsk_info = &hdsk_info_data; */
115
116 /* Note in the following CKS = 0 for fixed media which are not supposed to be changed while CP/M is executing */
117 static DPB dpb[] = {
118 /* name capac spt bsh blm exm dsm drm al0 al1 cks off psh phm */
119 { "HDSK", HDSK_CAPACITY, 32, 0x05, 0x1F, 0x01, 0x07f9, 0x03FF, 0xFF, 0x00, 0x0000, 0x0006, 0x00, 0x00 }, /* AZ80 HDSK */
120 { "EZ80FL", 131072, 32, 0x03, 0x07, 0x00, 127, 0x003E, 0xC0, 0x00, 0x0000, 0x0000, 0x02, 0x03 }, /* 128K FLASH */
121 { "P112", 1474560, 72, 0x04, 0x0F, 0x00, 710, 0x00FE, 0xF0, 0x00, 0x0000, 0x0002, 0x02, 0x03 }, /* 1.44M P112 */
122 { "SU720", 737280, 36, 0x04, 0x0F, 0x00, 354, 0x007E, 0xC0, 0x00, 0x0020, 0x0002, 0x02, 0x03 }, /* 720K Super I/O */
123 { "OSB1", 102400, 20, 0x04, 0x0F, 0x01, 45, 0x003F, 0x80, 0x00, 0x0000, 0x0003, 0x02, 0x03 }, /* Osborne1 5.25" SS SD */
124 { "OSB2", 204800, 40, 0x03, 0x07, 0x00, 184, 0x003F, 0xC0, 0x00, 0x0000, 0x0003, 0x02, 0x03 }, /* Osborne1 5.25" SS DD */
125 { "NSSS1", 179200, 40, 0x03, 0x07, 0x00, 0xA4, 0x003F, 0xC0, 0x00, 0x0010, 0x0002, 0x02, 0x03 }, /* Northstar SSDD Format 1 */
126 { "NSSS2", 179200, 40, 0x04, 0x0F, 0x01, 0x51, 0x003F, 0x80, 0x00, 0x0010, 0x0002, 0x02, 0x03 }, /* Northstar SSDD Format 2 */
127 { "NSDS2", 358400, 40, 0x04, 0x0F, 0x01, 0xA9, 0x003F, 0x80, 0x00, 0x0010, 0x0002, 0x02, 0x03 }, /* Northstar DSDD Format 2 */
128 { "VGSS", 315392, 32, 0x04, 0x0F, 0x00, 149, 0x007F, 0xC0, 0x00, 0x0020, 0x0002, 0x02, 0x03 }, /* Vector SS SD */
129 { "VGDS", 632784, 32, 0x04, 0x0F, 0x00, 299, 0x007F, 0xC0, 0x00, 0x0020, 0x0004, 0x02, 0x03 }, /* Vector DS SD */
130 /* Note on DISK1A Images: this is a bit of a mess. The first track on the disk is 128x26 bytes (SD) and to make this work
131 I had to "move" the data from 0x2d00 in the DSK image file down to 0x4000 (2-tracks in). I used WinHex to do it. */
132 { "DISK1A", 630784, 64, 0x04, 0x0F, 0x00, 299, 0x007F, 0xC0, 0x00, 0x0020, 0x0002, 0x02, 0x03 }, /* CompuPro Disk1A 8" SS SD */
133 { "SSSD8", 256256, 26, 0x03, 0x07, 0x00, 242, 0x003F, 0xC0, 0x00, 0x0000, 0x0002, 0x00, 0x00 }, /* Standard 8" SS SD */
134 { "", 0 }
135 };
136
137 static UNIT hdsk_unit[] = {
138 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
139 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
140 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
141 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
142 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
143 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
144 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
145 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }
146 };
147
148 static REG hdsk_reg[] = {
149 { DRDATA (HDCMD, hdskLastCommand, 32), REG_RO },
150 { DRDATA (HDPOS, hdskCommandPosition, 32), REG_RO },
151 { DRDATA (HDDSK, selectedDisk, 32), REG_RO },
152 { DRDATA (HDSEC, selectedSector, 32), REG_RO },
153 { DRDATA (HDTRK, selectedTrack, 32), REG_RO },
154 { DRDATA (HDDMA, selectedDMA, 32), REG_RO },
155 { HRDATA (TRACELEVEL, trace_level, 16), },
156 { NULL }
157 };
158
159 static MTAB hdsk_mod[] = {
160 { MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE", &set_iobase, &show_iobase, NULL },
161 { MTAB_XTD|MTAB_VUN|MTAB_VAL, 0, "FORMAT", "FORMAT", &set_format, &show_format, NULL },
162 { UNIT_HDSK_WLK, 0, "WRTENB", "WRTENB", NULL },
163 { UNIT_HDSK_WLK, UNIT_HDSK_WLK, "WRTLCK", "WRTLCK", NULL },
164 /* quiet, no warning messages */
165 { UNIT_HDSK_VERBOSE, 0, "QUIET", "QUIET", NULL },
166 /* verbose, show warning messages */
167 { UNIT_HDSK_VERBOSE, UNIT_HDSK_VERBOSE, "VERBOSE", "VERBOSE", NULL },
168 { MTAB_XTD|MTAB_VUN|MTAB_VAL, 0, "GEOM", "GEOM", &set_geom, &show_geom, NULL },
169 { 0 }
170 };
171
172 DEVICE hdsk_dev = {
173 "HDSK", hdsk_unit, hdsk_reg, hdsk_mod,
174 8, 10, 31, 1, 8, 8,
175 NULL, NULL, &hdsk_reset,
176 &hdsk_boot, &hdsk_attach, NULL,
177 &hdsk_info_data, (DEV_DISABLE), 0,
178 NULL, NULL, NULL
179 };
180
181 /* Reset routine */
182 static t_stat hdsk_reset(DEVICE *dptr) {
183 PNP_INFO *pnp = (PNP_INFO *)dptr->ctxt;
184 if (dptr->flags & DEV_DIS) {
185 sim_map_resource(pnp->io_base, pnp->io_size, RESOURCE_TYPE_IO, &hdsk_io, TRUE);
186 } else {
187 /* Connect HDSK at base address */
188 if (sim_map_resource(pnp->io_base, pnp->io_size, RESOURCE_TYPE_IO, &hdsk_io, FALSE) != 0) {
189 printf("%s: error mapping I/O resource at 0x%04x\n", __FUNCTION__, pnp->mem_base);
190 dptr->flags |= DEV_DIS;
191 return SCPE_ARG;
192 }
193 }
194 return SCPE_OK;
195 }
196
197 /* Attach routine */
198 static t_stat hdsk_attach(UNIT *uptr, char *cptr) {
199 t_stat r;
200 uint32 i;
201 char unitChar;
202
203 r = attach_unit(uptr, cptr); /* attach unit */
204 if ( r != SCPE_OK) /* error? */
205 return r;
206
207 /* Step 1: Determine capacity of this disk */
208 uptr -> capac = sim_fsize(uptr -> fileref); /* the file length is a good candidate */
209 if (uptr -> capac == 0) { /* file does not exist or has length 0 */
210 uptr -> capac = uptr -> HDSK_NUMBER_OF_TRACKS *
211 uptr -> HDSK_SECTORS_PER_TRACK * uptr -> HDSK_SECTOR_SIZE;
212 if (uptr -> capac == 0)
213 uptr -> capac = HDSK_CAPACITY;
214 } /* post condition: uptr -> capac > 0 */
215 assert(uptr -> capac);
216
217 /* Step 2: Determine format based on disk capacity */
218 uptr -> HDSK_FORMAT_TYPE = -1; /* default to unknown format type */
219 for (i = 0; dpb[i].capac != 0; i++) { /* find disk parameter block */
220 if (dpb[i].capac == uptr -> capac) { /* found if correct capacity */
221 uptr -> HDSK_FORMAT_TYPE = i;
222 break;
223 }
224 }
225
226 /* Step 3: Set number of sectors per track and sector size */
227 if (uptr -> HDSK_FORMAT_TYPE == -1) { /* Case 1: no disk parameter block found*/
228 for (i = 0; i < hdsk_dev.numunits; i++) /* find affected unit number */
229 if (&hdsk_unit[i] == uptr)
230 break; /* found */
231 unitChar = '0' + i;
232 uptr -> HDSK_FORMAT_TYPE = 0;
233 printf("HDSK%c: WARNING: Unsupported disk capacity, assuming HDSK type with capacity %iKB.\n",
234 unitChar, uptr -> capac / 1000);
235 uptr -> flags |= UNIT_HDSK_WLK;
236 printf("HDSK%c: WARNING: Forcing WRTLCK.\n", unitChar);
237 /* check whether capacity corresponds to setting of tracks, sectors per track and sector size */
238 if (uptr -> capac != (uptr -> HDSK_NUMBER_OF_TRACKS *
239 uptr -> HDSK_SECTORS_PER_TRACK * uptr -> HDSK_SECTOR_SIZE)) {
240 printf("HDSK%c: WARNING: Fixing geometry.\n", unitChar);
241 if (uptr -> HDSK_SECTORS_PER_TRACK == 0) uptr -> HDSK_SECTORS_PER_TRACK = 32;
242 if (uptr -> HDSK_SECTOR_SIZE == 0) uptr -> HDSK_SECTOR_SIZE = 128;
243 }
244 }
245 else { /* Case 2: disk parameter block found */
246 uptr -> HDSK_SECTORS_PER_TRACK = dpb[uptr -> HDSK_FORMAT_TYPE].spt >> dpb[uptr -> HDSK_FORMAT_TYPE].psh;
247 uptr -> HDSK_SECTOR_SIZE = (128 << dpb[uptr -> HDSK_FORMAT_TYPE].psh);
248 }
249 assert(uptr -> HDSK_SECTORS_PER_TRACK && uptr -> HDSK_SECTOR_SIZE);
250
251 /* Step 4: Number of tracks is smallest number to accomodate capacity */
252 uptr -> HDSK_NUMBER_OF_TRACKS = (uptr -> capac + uptr -> HDSK_SECTORS_PER_TRACK *
253 uptr -> HDSK_SECTOR_SIZE - 1) / (uptr -> HDSK_SECTORS_PER_TRACK * uptr -> HDSK_SECTOR_SIZE);
254 assert( ( (t_addr) ((uptr -> HDSK_NUMBER_OF_TRACKS - 1) * uptr -> HDSK_SECTORS_PER_TRACK *
255 uptr -> HDSK_SECTOR_SIZE) < uptr -> capac) &&
256 (uptr -> capac <= (t_addr) (uptr -> HDSK_NUMBER_OF_TRACKS *
257 uptr -> HDSK_SECTORS_PER_TRACK * uptr -> HDSK_SECTOR_SIZE) ) );
258 return SCPE_OK;
259 }
260
261 /* Set disk geometry routine */
262 static t_stat set_geom(UNIT *uptr, int32 val, char *cptr, void *desc) {
263 uint32 numberOfTracks, numberOfSectors, sectorSize;
264 int result, n;
265
266 if (cptr == NULL) return SCPE_ARG;
267 if (uptr == NULL) return SCPE_IERR;
268 result = sscanf(cptr, "%d/%d/%d%n", &numberOfTracks, &numberOfSectors, &sectorSize, &n);
269 if ((result != 3) || (result == EOF) || (cptr[n] != 0)) {
270 result = sscanf(cptr, "T:%d/N:%d/S:%d%n", &numberOfTracks, &numberOfSectors, &sectorSize, &n);
271 if ((result != 3) || (result == EOF) || (cptr[n] != 0)) return SCPE_ARG;
272 }
273 uptr -> HDSK_NUMBER_OF_TRACKS = numberOfTracks;
274 uptr -> HDSK_SECTORS_PER_TRACK = numberOfSectors;
275 uptr -> HDSK_SECTOR_SIZE = sectorSize;
276 uptr -> capac = numberOfTracks * numberOfSectors * sectorSize;
277 return SCPE_OK;
278 }
279
280 /* Show disk geometry routine */
281 static t_stat show_geom(FILE *st, UNIT *uptr, int32 val, void *desc) {
282 if (uptr == NULL) return SCPE_IERR;
283 fprintf(st, "T:%d/N:%d/S:%d", uptr -> HDSK_NUMBER_OF_TRACKS,
284 uptr -> HDSK_SECTORS_PER_TRACK, uptr -> HDSK_SECTOR_SIZE);
285 return SCPE_OK;
286 }
287
288 #define QUOTE1(text) #text
289 #define QUOTE2(text) QUOTE1(text)
290 /* Set disk format routine */
291 static t_stat set_format(UNIT *uptr, int32 val, char *cptr, void *desc) {
292 char fmtname[DPB_NAME_LENGTH + 1];
293 int32 i;
294
295 if (cptr == NULL) return SCPE_ARG;
296 if (uptr == NULL) return SCPE_IERR;
297 if (sscanf(cptr, "%" QUOTE2(DPB_NAME_LENGTH) "s", fmtname) == 0) return SCPE_ARG;
298 for (i = 0; dpb[i].capac != 0; i++) {
299 if (strncmp(fmtname, dpb[i].name, strlen(fmtname)) == 0) {
300 uptr -> HDSK_FORMAT_TYPE = i;
301 uptr -> capac = dpb[i].capac; /* Set capacity */
302
303 /* Configure physical disk geometry */
304 uptr -> HDSK_SECTOR_SIZE = (128 << dpb[uptr -> HDSK_FORMAT_TYPE].psh);
305 uptr -> HDSK_SECTORS_PER_TRACK = dpb[uptr -> HDSK_FORMAT_TYPE].spt >> dpb[uptr -> HDSK_FORMAT_TYPE].psh;
306 uptr -> HDSK_NUMBER_OF_TRACKS = (uptr -> capac +
307 uptr -> HDSK_SECTORS_PER_TRACK * uptr -> HDSK_SECTOR_SIZE - 1) /
308 (uptr -> HDSK_SECTORS_PER_TRACK * uptr -> HDSK_SECTOR_SIZE);
309
310 return SCPE_OK;
311 }
312 }
313 return SCPE_ARG;
314 }
315
316 /* Show disk format routine */
317 static t_stat show_format(FILE *st, UNIT *uptr, int32 val, void *desc) {
318 if (uptr == NULL) return SCPE_IERR;
319 fprintf(st, "%s", dpb[uptr -> HDSK_FORMAT_TYPE].name);
320 return SCPE_OK;
321 }
322
323 static int32 bootrom_hdsk[BOOTROM_SIZE_HDSK] = {
324 0xf3, 0x06, 0x80, 0x3e, 0x0e, 0xd3, 0xfe, 0x05, /* 5c00-5c07 */
325 0xc2, 0x05, 0x5c, 0x3e, 0x16, 0xd3, 0xfe, 0x3e, /* 5c08-5c0f */
326 0x12, 0xd3, 0xfe, 0xdb, 0xfe, 0xb7, 0xca, 0x20, /* 5c10-5c17 */
327 0x5c, 0x3e, 0x0c, 0xd3, 0xfe, 0xaf, 0xd3, 0xfe, /* 5c18-5c1f */
328 0x06, 0x20, 0x3e, 0x01, 0xd3, 0xfd, 0x05, 0xc2, /* 5c20-5c27 */
329 0x24, 0x5c, 0x11, 0x08, 0x00, 0x21, 0x00, 0x00, /* 5c28-5c2f */
330 0x0e, 0xb8, 0x3e, 0x02, 0xd3, 0xfd, 0x3a, 0x37, /* 5c30-5c37 */
331 0xff, 0xd6, 0x08, 0xd3, 0xfd, 0x7b, 0xd3, 0xfd, /* 5c38-5c3f */
332 0x7a, 0xd3, 0xfd, 0xaf, 0xd3, 0xfd, 0x7d, 0xd3, /* 5c40-5c47 */
333 0xfd, 0x7c, 0xd3, 0xfd, 0xdb, 0xfd, 0xb7, 0xca, /* 5c48-5c4f */
334 0x53, 0x5c, 0x76, 0x79, 0x0e, 0x80, 0x09, 0x4f, /* 5c50-5c57 */
335 0x0d, 0xc2, 0x60, 0x5c, 0xfb, 0xc3, 0x00, 0x00, /* 5c58-5c5f */
336 0x1c, 0x1c, 0x7b, 0xfe, 0x20, 0xca, 0x73, 0x5c, /* 5c60-5c67 */
337 0xfe, 0x21, 0xc2, 0x32, 0x5c, 0x1e, 0x00, 0x14, /* 5c68-5c6f */
338 0xc3, 0x32, 0x5c, 0x1e, 0x01, 0xc3, 0x32, 0x5c, /* 5c70-5c77 */
339 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c78-5c7f */
340 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c80-5c87 */
341 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c88-5c8f */
342 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c90-5c97 */
343 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c98-5c9f */
344 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ca0-5ca7 */
345 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ca8-5caf */
346 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cb0-5cb7 */
347 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cb8-5cbf */
348 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cc0-5cc7 */
349 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cc8-5ccf */
350 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cd0-5cd7 */
351 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cd8-5cdf */
352 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ce0-5ce7 */
353 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ce8-5cef */
354 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cf0-5cf7 */
355 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cf8-5cff */
356 };
357
358 static t_stat hdsk_boot(int32 unitno, DEVICE *dptr) {
359 if (MEMORYSIZE < 24*KB) {
360 printf("Need at least 24KB RAM to boot from hard disk.\n");
361 return SCPE_ARG;
362 }
363 if (cpu_unit.flags & (UNIT_CPU_ALTAIRROM | UNIT_CPU_BANKED)) {
364 /* check whether we are really modifying an LD A,<> instruction */
365 if (bootrom_dsk[UNIT_NO_OFFSET_1 - 1] == LDA_INSTRUCTION)
366 bootrom_dsk[UNIT_NO_OFFSET_1] = (unitno + NUM_OF_DSK) & 0xff; /* LD A,<unitno> */
367 else { /* Attempt to modify non LD A,<> instructions is refused. */
368 printf("Incorrect boot ROM offset detected.\n");
369 return SCPE_IERR;
370 }
371 install_ALTAIRbootROM(); /* install modified ROM */
372 }
373 assert(install_bootrom(bootrom_hdsk, BOOTROM_SIZE_HDSK, HDSK_BOOT_ADDRESS, FALSE) == SCPE_OK);
374 *((int32 *) sim_PC->loc) = HDSK_BOOT_ADDRESS;
375 return SCPE_OK;
376 }
377
378 /* returns TRUE iff there exists a disk with VERBOSE */
379 static int32 hdsk_hasVerbose(void) {
380 int32 i;
381 for (i = 0; i < HDSK_NUMBER; i++)
382 if (hdsk_dev.units[i].flags & UNIT_HDSK_VERBOSE) return TRUE;
383 return FALSE;
384 }
385
386 /* The hard disk port is 0xfd. It understands the following commands.
387
388 1. Reset
389 ld b,32
390 ld a,HDSK_RESET
391 l: out (0fdh),a
392 dec b
393 jp nz,l
394
395 2. Read / write
396 ; parameter block
397 cmd: db HDSK_READ or HDSK_WRITE
398 hd: db 0 ; 0 .. 7, defines hard disk to be used
399 sector: db 0 ; 0 .. 31, defines sector
400 track: dw 0 ; 0 .. 2047, defines track
401 dma: dw 0 ; defines where result is placed in memory
402
403 ; routine to execute
404 ld b,7 ; size of parameter block
405 ld hl,cmd ; start address of parameter block
406 l: ld a,(hl) ; get byte of parameter block
407 out (0fdh),a ; send it to port
408 inc hl ; point to next byte
409 dec b ; decrement counter
410 jp nz,l ; again, if not done
411 in a,(0fdh) ; get result code
412
413 3. Retrieve Disk Parameters from controller (Howard M. Harte)
414 Reads a 19-byte parameter block from the disk controller.
415 This parameter block is in CP/M DPB format for the first 17 bytes,
416 and the last two bytes are the lsb/msb of the disk's physical
417 sector size.
418
419 ; routine to execute
420 ld a,hdskParam ; hdskParam = 4
421 out (hdskPort),a ; Send 'get parameters' command, hdskPort = 0fdh
422 ld a,(diskno)
423 out (hdskPort),a ; Send selected HDSK number
424 ld b,17
425 1: in a,(hdskPort) ; Read 17-bytes of DPB
426 ld (hl), a
427 inc hl
428 djnz 1
429 in a,(hdskPort) ; Read LSB of disk's physical sector size.
430 ld (hsecsiz), a
431 in a,(hdskPort) ; Read MSB of disk's physical sector size.
432 ld (hsecsiz+1), a
433
434 */
435
436 /* check the parameters and return TRUE iff parameters are correct or have been repaired */
437 static int32 checkParameters(void) {
438 UNIT *uptr = &hdsk_dev.units[selectedDisk];
439 int32 currentFlag;
440 if ((selectedDisk < 0) || (selectedDisk >= HDSK_NUMBER)) {
441 if (hdsk_hasVerbose()) {
442 MESSAGE_2("HDSK%d does not exist, will use HDSK0 instead.", selectedDisk);
443 }
444 selectedDisk = 0;
445 }
446 currentFlag = hdsk_dev.units[selectedDisk].flags;
447 if ((currentFlag & UNIT_ATT) == 0) {
448 if (currentFlag & UNIT_HDSK_VERBOSE) {
449 MESSAGE_2("HDSK%d is not attached.", selectedDisk);
450 }
451 return FALSE; /* cannot read or write */
452 }
453 if ((selectedSector < 0) || (selectedSector >= uptr -> HDSK_SECTORS_PER_TRACK)) {
454 if (currentFlag & UNIT_HDSK_VERBOSE) {
455 MESSAGE_4("HDSK%d: 0 <= Sector=%02d < %d violated, will use 0 instead.",
456 selectedDisk, selectedSector, uptr -> HDSK_SECTORS_PER_TRACK);
457 }
458 selectedSector = 0;
459 }
460 if ((selectedTrack < 0) || (selectedTrack >= uptr -> HDSK_NUMBER_OF_TRACKS)) {
461 if (currentFlag & UNIT_HDSK_VERBOSE) {
462 MESSAGE_4("HDSK%d: 0 <= Track=%04d < %04d violated, will use 0 instead.",
463 selectedDisk, selectedTrack, uptr -> HDSK_NUMBER_OF_TRACKS);
464 }
465 selectedTrack = 0;
466 }
467 selectedDMA &= ADDRMASK;
468 if (trace_level) {
469 MESSAGE_7("%s HDSK%d Track=%04d Sector=%02d Len=%04d DMA=%04x",
470 (hdskLastCommand == HDSK_READ) ? "Read" : "Write",
471 selectedDisk, selectedTrack, selectedSector, uptr -> HDSK_SECTOR_SIZE, selectedDMA);
472 }
473 return TRUE;
474 }
475
476 static int32 doSeek(void) {
477 UNIT *uptr = &hdsk_dev.units[selectedDisk];
478 if (fseek(uptr -> fileref,
479 (uptr -> HDSK_SECTORS_PER_TRACK * uptr -> HDSK_SECTOR_SIZE) * selectedTrack +
480 (uptr -> HDSK_SECTOR_SIZE * selectedSector), SEEK_SET)) {
481 if ((uptr -> flags) & UNIT_HDSK_VERBOSE) {
482 MESSAGE_4("Could not access HDSK%d Sector=%02d Track=%04d.",
483 selectedDisk, selectedSector, selectedTrack);
484 }
485 return CPM_ERROR;
486 }
487 else return CPM_OK;
488 }
489
490 uint8 hdskbuf[HDSK_MAX_SECTOR_SIZE] = { 0 }; /* data buffer */
491
492 static int32 doRead(void) {
493 int32 i;
494 UNIT *uptr = &hdsk_dev.units[selectedDisk];
495 if (doSeek()) return CPM_ERROR;
496
497 if (fread(hdskbuf, uptr -> HDSK_SECTOR_SIZE, 1, uptr -> fileref) != 1) {
498 for (i = 0; i < uptr -> HDSK_SECTOR_SIZE; i++) hdskbuf[i] = CPM_EMPTY;
499 if ((uptr -> flags) & UNIT_HDSK_VERBOSE) {
500 MESSAGE_4("Could not read HDSK%d Sector=%02d Track=%04d.",
501 selectedDisk, selectedSector, selectedTrack);
502 }
503 return CPM_OK; /* allows the creation of empty hard disks */
504 }
505 for (i = 0; i < uptr -> HDSK_SECTOR_SIZE; i++) PutBYTEWrapper(selectedDMA + i, hdskbuf[i]);
506 return CPM_OK;
507 }
508
509 static int32 doWrite(void) {
510 int32 i;
511
512 UNIT *uptr = &hdsk_dev.units[selectedDisk];
513 if (((uptr -> flags) & UNIT_HDSK_WLK) == 0) { /* write enabled */
514 if (doSeek()) return CPM_ERROR;
515 for (i = 0; i < uptr -> HDSK_SECTOR_SIZE; i++) hdskbuf[i] = GetBYTEWrapper(selectedDMA + i);
516 if (fwrite(hdskbuf, uptr -> HDSK_SECTOR_SIZE, 1, uptr -> fileref) != 1) {
517 if ((uptr -> flags) & UNIT_HDSK_VERBOSE) {
518 MESSAGE_4("Could not write HDSK%d Sector=%02d Track=%04d.",
519 selectedDisk, selectedSector, selectedTrack);
520 }
521 return CPM_ERROR;
522 }
523 }
524 else {
525 if ((uptr -> flags) & UNIT_HDSK_VERBOSE) {
526 MESSAGE_4("Could not write to locked HDSK%d Sector=%02d Track=%04d.",
527 selectedDisk, selectedSector, selectedTrack);
528 }
529 return CPM_ERROR;
530 }
531 return CPM_OK;
532 }
533
534 static int32 hdsk_in(const int32 port) {
535 UNIT *uptr = &hdsk_dev.units[selectedDisk];
536
537 int32 result;
538 if ((hdskCommandPosition == 6) && ((hdskLastCommand == HDSK_READ) || (hdskLastCommand == HDSK_WRITE))) {
539 result = checkParameters() ? ((hdskLastCommand == HDSK_READ) ? doRead() : doWrite()) : CPM_ERROR;
540 hdskLastCommand = HDSK_NONE;
541 hdskCommandPosition = 0;
542 return result;
543 } else if (hdskLastCommand == HDSK_PARAM) {
544 DPB current = dpb[uptr -> HDSK_FORMAT_TYPE];
545 uint8 params[17];
546 params[ 0] = current.spt & 0xff; params[ 1] = (current.spt >> 8) & 0xff;
547 params[ 2] = current.bsh;
548 params[ 3] = current.blm;
549 params[ 4] = current.exm;
550 params[ 5] = current.dsm & 0xff; params[ 6] = (current.dsm >> 8) & 0xff;
551 params[ 7] = current.drm & 0xff; params[ 8] = (current.drm >> 8) & 0xff;
552 params[ 9] = current.al0;
553 params[10] = current.al1;
554 params[11] = current.cks & 0xff; params[12] = (current.cks >> 8) & 0xff;
555 params[13] = current.off & 0xff; params[14] = (current.off >> 8) & 0xff;
556 params[15] = current.psh;
557 params[16] = current.phm;
558 if (++paramcount >= 19) hdskLastCommand = HDSK_NONE;
559 if (paramcount <= 17)
560 return params[paramcount - 1];
561 else if (paramcount == 18)
562 return (uptr -> HDSK_SECTOR_SIZE & 0xff);
563 else if (paramcount == 19)
564 return (uptr -> HDSK_SECTOR_SIZE >> 8);
565 else {
566 MESSAGE_2("HDSK%d Get parameter error.", selectedDisk);
567 }
568
569 }
570 else if (hdsk_hasVerbose()) {
571 MESSAGE_4("Illegal IN command detected (port=%03xh, cmd=%d, pos=%d).",
572 port, hdskLastCommand, hdskCommandPosition);
573 }
574 return CPM_OK;
575 }
576
577 static int32 hdsk_out(const int32 port, const int32 data) {
578 switch(hdskLastCommand) {
579
580 case HDSK_PARAM:
581 paramcount = 0;
582 selectedDisk = data;
583 break;
584
585 case HDSK_READ:
586
587 case HDSK_WRITE:
588 switch(hdskCommandPosition) {
589
590 case 0:
591 selectedDisk = data;
592 hdskCommandPosition++;
593 break;
594
595 case 1:
596 selectedSector = data;
597 hdskCommandPosition++;
598 break;
599
600 case 2:
601 selectedTrack = data;
602 hdskCommandPosition++;
603 break;
604
605 case 3:
606 selectedTrack += (data << 8);
607 hdskCommandPosition++;
608 break;
609
610 case 4:
611 selectedDMA = data;
612 hdskCommandPosition++;
613 break;
614
615 case 5:
616 selectedDMA += (data << 8);
617 hdskCommandPosition++;
618 break;
619
620 default:
621 hdskLastCommand = HDSK_NONE;
622 hdskCommandPosition = 0;
623 }
624 break;
625
626 default:
627 if ((HDSK_RESET <= data) && (data <= HDSK_PARAM))
628 hdskLastCommand = data;
629 else {
630 if (hdsk_hasVerbose()) {
631 MESSAGE_3("Illegal OUT command detected (port=%03xh, cmd=%d).",
632 port, data);
633 }
634 hdskLastCommand = HDSK_RESET;
635 }
636 hdskCommandPosition = 0;
637 }
638 return 0; /* ignored, since OUT */
639 }
640
641 int32 hdsk_io(const int32 port, const int32 io, const int32 data) {
642 return io == 0 ? hdsk_in(port) : hdsk_out(port, data);
643 }