1 /* altairz80_hdsk.c: simulated hard disk device to increase capacity
2
3 Copyright (c) 2002-2011, 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 /* Debug flags */
33 #define READ_MSG (1 << 0)
34 #define WRITE_MSG (1 << 1)
35 #define VERBOSE_MSG (1 << 2)
36
37 /* The following routines are based on work from Howard M. Harte */
38 static t_stat set_geom(UNIT *uptr, int32 val, char *cptr, void *desc);
39 static t_stat show_geom(FILE *st, UNIT *uptr, int32 val, void *desc);
40 static t_stat set_format(UNIT *uptr, int32 val, char *cptr, void *desc);
41 static t_stat show_format(FILE *st, UNIT *uptr, int32 val, void *desc);
42
43 static t_stat hdsk_reset(DEVICE *dptr);
44 static t_stat hdsk_attach(UNIT *uptr, char *cptr);
45 static t_stat hdsk_detach(UNIT *uptr);
46
47 #define UNIT_V_HDSK_WLK (UNIT_V_UF + 0) /* write locked */
48 #define UNIT_HDSK_WLK (1 << UNIT_V_HDSK_WLK)
49 #define HDSK_MAX_SECTOR_SIZE 1024 /* maximum size of a sector */
50 #define HDSK_SECTOR_SIZE u5 /* size of sector */
51 #define HDSK_SECTORS_PER_TRACK u4 /* sectors per track */
52 #define HDSK_NUMBER_OF_TRACKS u3 /* number of tracks */
53 #define HDSK_FORMAT_TYPE u6 /* Disk Format Type */
54 #define HDSK_CAPACITY (2048*32*128) /* Default Altair HDSK Capacity */
55 #define HDSK_NUMBER 8 /* number of hard disks */
56 #define CPM_OK 0 /* indicates to CP/M everything ok */
57 #define CPM_ERROR 1 /* indicates to CP/M an error condition */
58 #define CPM_EMPTY 0xe5 /* default value for non-existing bytes */
59 #define HDSK_NONE 0
60 #define HDSK_RESET 1
61 #define HDSK_READ 2
62 #define HDSK_WRITE 3
63 #define HDSK_PARAM 4
64 #define HDSK_BOOT_ADDRESS 0x5c00
65 #define DPB_NAME_LENGTH 15
66 #define BOOTROM_SIZE_HDSK 256
67
68 extern uint32 PCX;
69 extern REG *sim_PC;
70 extern UNIT cpu_unit;
71
72 extern void install_ALTAIRbootROM(void);
73 extern void PutBYTEWrapper(const uint32 Addr, const uint32 Value);
74 extern uint8 GetBYTEWrapper(const uint32 Addr);
75 extern t_stat install_bootrom(int32 bootrom[], int32 size, int32 addr, int32 makeROM);
76 extern int32 bootrom_dsk[];
77 extern t_stat set_iobase(UNIT *uptr, int32 val, char *cptr, void *desc);
78 extern t_stat show_iobase(FILE *st, UNIT *uptr, int32 val, void *desc);
79 extern uint32 sim_map_resource(uint32 baseaddr, uint32 size, uint32 resource_type,
80 int32 (*routine)(const int32, const int32, const int32), uint8 unmap);
81 extern int32 find_unit_index(UNIT *uptr);
82
83 static t_stat hdsk_boot(int32 unitno, DEVICE *dptr);
84 int32 hdsk_io(const int32 port, const int32 io, const int32 data);
85
86 static int32 hdskLastCommand = HDSK_NONE;
87 static int32 hdskCommandPosition = 0;
88 static int32 parameterCount = 0;
89 static int32 selectedDisk;
90 static int32 selectedSector;
91 static int32 selectedTrack;
92 static int32 selectedDMA;
93
94 typedef struct {
95 char name[DPB_NAME_LENGTH + 1]; /* name of CP/M disk parameter block */
96 t_addr capac; /* capacity */
97 uint16 spt; /* sectors per track */
98 uint8 bsh; /* data allocation block shift factor */
99 uint8 blm; /* data allocation block mask */
100 uint8 exm; /* extent mask */
101 uint16 dsm; /* maximum data block number */
102 uint16 drm; /* total number of directory entries */
103 uint8 al0; /* determine reserved directory blocks */
104 uint8 al1; /* determine reserved directory blocks */
105 uint16 cks; /* size of directory check vector */
106 uint16 off; /* number of reserved tracks */
107 uint8 psh; /* physical record shift factor, CP/M 3 */
108 uint8 phm; /* physical record mask, CP/M 3 */
109 int32 physicalSectorSize; /* 0 for 128 << psh, > 0 for special */
110 int32 offset; /* offset in physical sector where logical sector starts */
111 int32 *skew; /* pointer to skew table or NULL */
112 } DPB;
113
114 typedef struct {
115 PNP_INFO pnp; /* Plug and Play */
116 } HDSK_INFO;
117
118 #define SPT16 16
119 #define SPT32 32
120 #define SPT26 26
121
122 static HDSK_INFO hdsk_info_data = { { 0x0000, 0, 0xFD, 1 } };
123
124 static int32 standard8[SPT26] = { 0, 6, 12, 18, 24, 4, 10, 16,
125 22, 2, 8, 14, 20, 1, 7, 13,
126 19, 25, 5, 11, 17, 23, 3, 9,
127 15, 21 };
128
129 static int32 appple_ii_DOS[SPT16] = { 0, 6, 12, 3, 9, 15, 14, 5,
130 11, 2, 8, 7, 13, 4, 10, 1 };
131
132 static int32 appple_ii_DOS2[SPT32] = { 0, 1, 12, 13, 24, 25, 6, 7,
133 18, 19, 30, 31, 28, 29, 10, 11,
134 22, 23, 4, 5, 16, 17, 14, 15,
135 26, 27, 8, 9, 20, 21, 2, 3 };
136
137 static int32 appple_ii_PRODOS[SPT16] = { 0, 9, 3, 12, 6, 15, 1, 10,
138 4, 13, 7, 8, 2, 11, 5, 14 };
139
140 static int32 appple_ii_PRODOS2[SPT32] = { 0, 1, 18, 19, 6, 7, 24, 25,
141 12, 13, 30, 31, 2, 3, 20, 21,
142 8, 9, 26, 27, 14, 15, 16, 17,
143 4, 5, 22, 23, 10, 11, 28, 29 };
144
145 static int32 mits[SPT32] = { 0, 17, 2, 19, 4, 21, 6, 23,
146 8, 25, 10, 27, 12, 29, 14, 31,
147 16, 1, 18, 3, 20, 5, 22, 7,
148 24, 9, 26, 11, 28, 13, 30, 15 };
149
150 /* Note in the following CKS = 0 for fixed media which are not supposed to be
151 changed while CP/M is executing. Also note that spt (sectors per track) is
152 measured in CP/M sectors of size 128 bytes. Standard format "HDSK" must be
153 first as index 0 is used as default in some cases.
154 */
155 static DPB dpb[] = {
156 /* name capac spt bsh blm exm dsm drm
157 al0 al1 cks off psh phm ss off skew */
158 { "HDSK", HDSK_CAPACITY, 32, 0x05, 0x1F, 0x01, 0x07f9, 0x03FF,
159 0xFF, 0x00, 0x0000, 0x0006, 0x00, 0x00, 0, 0, NULL }, /* AZ80 HDSK */
160
161 { "EZ80FL", 131072, 32, 0x03, 0x07, 0x00, 127, 0x003E,
162 0xC0, 0x00, 0x0000, 0x0000, 0x02, 0x03, 0, 0, NULL }, /* 128K FLASH */
163
164 { "P112", 1474560, 72, 0x04, 0x0F, 0x00, 710, 0x00FE,
165 0xF0, 0x00, 0x0000, 0x0002, 0x02, 0x03, 0, 0, NULL }, /* 1.44M P112 */
166
167 { "SU720", 737280, 36, 0x04, 0x0F, 0x00, 354, 0x007E,
168 0xC0, 0x00, 0x0020, 0x0002, 0x02, 0x03, 0, 0, NULL }, /* 720K Super I/O */
169
170 { "OSB1", 102400, 20, 0x04, 0x0F, 0x01, 45, 0x003F,
171 0x80, 0x00, 0x0000, 0x0003, 0x02, 0x03, 0, 0, NULL }, /* Osborne1 5.25" SS SD */
172
173 { "OSB2", 204800, 40, 0x03, 0x07, 0x00, 184, 0x003F,
174 0xC0, 0x00, 0x0000, 0x0003, 0x02, 0x03, 0, 0, NULL }, /* Osborne1 5.25" SS DD */
175
176 { "NSSS1", 179200, 40, 0x03, 0x07, 0x00, 0xA4, 0x003F,
177 0xC0, 0x00, 0x0010, 0x0002, 0x02, 0x03, 0, 0, NULL }, /* Northstar SSDD Format 1 */
178
179 { "NSSS2", 179200, 40, 0x04, 0x0F, 0x01, 0x51, 0x003F,
180 0x80, 0x00, 0x0010, 0x0002, 0x02, 0x03, 0, 0, NULL }, /* Northstar SSDD Format 2 */
181
182 { "NSDS2", 358400, 40, 0x04, 0x0F, 0x01, 0xA9, 0x003F,
183 0x80, 0x00, 0x0010, 0x0002, 0x02, 0x03, 0, 0, NULL }, /* Northstar DSDD Format 2 */
184
185 { "VGSS", 315392, 32, 0x04, 0x0F, 0x00, 149, 0x007F,
186 0xC0, 0x00, 0x0020, 0x0002, 0x02, 0x03, 0, 0, NULL }, /* Vector SS SD */
187
188 { "VGDS", 630784, 32, 0x04, 0x0F, 0x00, 299, 0x007F,
189 0xC0, 0x00, 0x0020, 0x0004, 0x02, 0x03, 0, 0, NULL }, /* Vector DS SD */
190
191 { "DISK1A", 630784, 64, 0x04, 0x0F, 0x00, 299, 0x007F,
192 0xC0, 0x00, 0x0020, 0x0002, 0x02, 0x03, 0, 0, NULL }, /* CompuPro Disk1A 8" SS SD */
193
194 { "SSSD8", 256256, SPT26, 0x03, 0x07, 0x00, 242, 0x003F,
195 0xC0, 0x00, 0x0000, 0x0002, 0x00, 0x00, 0, 0, NULL }, /* Standard 8" SS SD */
196
197 { "SSSD8S", 256256, SPT26, 0x03, 0x07, 0x00, 242, 0x003F,
198 0xC0, 0x00, 0x0000, 0x0002, 0x00, 0x00, 0, 0, standard8 }, /* Standard 8" SS SD with skew */
199
200 { "APPLE-DO",143360, SPT32, 0x03, 0x07, 0x00, 127, 0x003F,
201 0xC0, 0x00, 0x0000, 0x0003, 0x01, 0x01, 0, 0, appple_ii_DOS }, /* Apple II DOS 3.3 */
202
203 { "APPLE-PO",143360, SPT32, 0x03, 0x07, 0x00, 127, 0x003F,
204 0xC0, 0x00, 0x0000, 0x0003, 0x01, 0x01, 0, 0, appple_ii_PRODOS }, /* Apple II PRODOS */
205
206 { "APPLE-D2",143360, SPT32, 0x03, 0x07, 0x00, 127, 0x003F,
207 0xC0, 0x00, 0x0000, 0x0003, 0x00, 0x00, 0, 0, appple_ii_DOS2 }, /* Apple II DOS 3.3, deblocked */
208
209 { "APPLE-P2",143360, SPT32, 0x03, 0x07, 0x00, 127, 0x003F,
210 0xC0, 0x00, 0x0000, 0x0003, 0x00, 0x00, 0, 0, appple_ii_PRODOS2 }, /* Apple II PRODOS, deblocked */
211
212 { "MITS", 337568, SPT32, 0x03, 0x07, 0x00, 254, 0x00FF,
213 0xFF, 0x00, 0x0000, 0x0006, 0x00, 0x00, 137, 3, mits }, /* MITS Altair original */
214
215 { "MITS2", 1113536, SPT32, 0x04, 0x0F, 0x00, 0x1EF, 0x00FF,
216 0xF0, 0x00, 0x0000, 0x0006, 0x00, 0x00, 137, 3, mits }, /* MITS Altair original, extra */
217
218 { "", 0 }
219 };
220
221 static UNIT hdsk_unit[] = {
222 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
223 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
224 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
225 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
226 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
227 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
228 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) },
229 { UDATA (NULL, UNIT_FIX + UNIT_ATTABLE + UNIT_DISABLE + UNIT_ROABLE, HDSK_CAPACITY) }
230 };
231
232 static REG hdsk_reg[] = {
233 { DRDATA (HDCMD, hdskLastCommand, 32), REG_RO },
234 { DRDATA (HDPOS, hdskCommandPosition, 32), REG_RO },
235 { DRDATA (HDDSK, selectedDisk, 32), REG_RO },
236 { DRDATA (HDSEC, selectedSector, 32), REG_RO },
237 { DRDATA (HDTRK, selectedTrack, 32), REG_RO },
238 { DRDATA (HDDMA, selectedDMA, 32), REG_RO },
239 { NULL }
240 };
241
242 static MTAB hdsk_mod[] = {
243 { MTAB_XTD|MTAB_VDV, 0, "IOBASE", "IOBASE", &set_iobase, &show_iobase, NULL },
244 { MTAB_XTD|MTAB_VUN, 0, "FORMAT", "FORMAT", &set_format, &show_format, NULL },
245 { UNIT_HDSK_WLK, 0, "WRTENB", "WRTENB", NULL },
246 { UNIT_HDSK_WLK, UNIT_HDSK_WLK, "WRTLCK", "WRTLCK", NULL },
247 { MTAB_XTD|MTAB_VUN, 0, "GEOM", "GEOM", &set_geom, &show_geom, NULL },
248 { 0 }
249 };
250
251 /* Debug Flags */
252 static DEBTAB hdsk_dt[] = {
253 { "READ", READ_MSG },
254 { "WRITE", WRITE_MSG },
255 { "VERBOSE", VERBOSE_MSG },
256 { NULL, 0 }
257 };
258
259 DEVICE hdsk_dev = {
260 "HDSK", hdsk_unit, hdsk_reg, hdsk_mod,
261 8, 10, 31, 1, 8, 8,
262 NULL, NULL, &hdsk_reset,
263 &hdsk_boot, &hdsk_attach, &hdsk_detach,
264 &hdsk_info_data, (DEV_DISABLE | DEV_DEBUG), 0,
265 hdsk_dt, NULL, "Hard Disk HDSK"
266 };
267
268 /* Reset routine */
hdsk_reset(DEVICE * dptr)269 static t_stat hdsk_reset(DEVICE *dptr) {
270 PNP_INFO *pnp = (PNP_INFO *)dptr->ctxt;
271 if (dptr->flags & DEV_DIS) {
272 sim_map_resource(pnp->io_base, pnp->io_size, RESOURCE_TYPE_IO, &hdsk_io, TRUE);
273 } else {
274 /* Connect HDSK at base address */
275 if (sim_map_resource(pnp->io_base, pnp->io_size, RESOURCE_TYPE_IO, &hdsk_io, FALSE) != 0) {
276 printf("%s: error mapping I/O resource at 0x%04x\n", __FUNCTION__, pnp->mem_base);
277 dptr->flags |= DEV_DIS;
278 return SCPE_ARG;
279 }
280 }
281 return SCPE_OK;
282 }
283
284 /* Attach routine */
hdsk_attach(UNIT * uptr,char * cptr)285 static t_stat hdsk_attach(UNIT *uptr, char *cptr) {
286 t_stat r;
287 uint32 i;
288 char unitChar;
289
290 r = attach_unit(uptr, cptr); /* attach unit */
291 if ( r != SCPE_OK) /* error? */
292 return r;
293
294 /* Step 1: Determine capacity of this disk */
295 uptr -> capac = sim_fsize(uptr -> fileref); /* the file length is a good indication */
296 if (uptr -> capac == 0) { /* file does not exist or has length 0 */
297 uptr -> capac = uptr -> HDSK_NUMBER_OF_TRACKS *
298 uptr -> HDSK_SECTORS_PER_TRACK * uptr -> HDSK_SECTOR_SIZE;
299 if (uptr -> capac == 0)
300 uptr -> capac = HDSK_CAPACITY;
301 } /* post condition: uptr -> capac > 0 */
302 assert(uptr -> capac);
303
304 /* Step 2: Determine format based on disk capacity */
305 uptr -> HDSK_FORMAT_TYPE = -1; /* default to unknown format type */
306 for (i = 0; dpb[i].capac != 0; i++) { /* find disk parameter block */
307 if (dpb[i].capac == uptr -> capac) { /* found if correct capacity */
308 uptr -> HDSK_FORMAT_TYPE = i;
309 break;
310 }
311 }
312
313 /* Step 3: Set number of sectors per track and sector size */
314 if (uptr -> HDSK_FORMAT_TYPE == -1) { /* Case 1: no disk parameter block found*/
315 for (i = 0; i < hdsk_dev.numunits; i++) /* find affected unit number */
316 if (&hdsk_unit[i] == uptr)
317 break; /* found */
318 unitChar = '0' + i;
319 uptr -> HDSK_FORMAT_TYPE = 0;
320 printf("HDSK%c: WARNING: Unsupported disk capacity, assuming HDSK type with capacity %iKB.\n",
321 unitChar, uptr -> capac / 1000);
322 uptr -> flags |= UNIT_HDSK_WLK;
323 printf("HDSK%c: WARNING: Forcing WRTLCK.\n", unitChar);
324 /* check whether capacity corresponds to setting of tracks, sectors per track and sector size */
325 if (uptr -> capac != (uint32)(uptr -> HDSK_NUMBER_OF_TRACKS *
326 uptr -> HDSK_SECTORS_PER_TRACK * uptr -> HDSK_SECTOR_SIZE)) {
327 printf("HDSK%c: WARNING: Fixing geometry.\n", unitChar);
328 if (uptr -> HDSK_SECTORS_PER_TRACK == 0)
329 uptr -> HDSK_SECTORS_PER_TRACK = 32;
330 if (uptr -> HDSK_SECTOR_SIZE == 0)
331 uptr -> HDSK_SECTOR_SIZE = 128;
332 }
333 }
334 else { /* Case 2: disk parameter block found */
335 uptr -> HDSK_SECTORS_PER_TRACK = dpb[uptr -> HDSK_FORMAT_TYPE].spt >> dpb[uptr -> HDSK_FORMAT_TYPE].psh;
336 uptr -> HDSK_SECTOR_SIZE = (128 << dpb[uptr -> HDSK_FORMAT_TYPE].psh);
337 }
338 assert((uptr -> HDSK_SECTORS_PER_TRACK) && (uptr -> HDSK_SECTOR_SIZE) && (uptr -> HDSK_FORMAT_TYPE >= 0));
339
340 /* Step 4: Number of tracks is smallest number to accomodate capacity */
341 uptr -> HDSK_NUMBER_OF_TRACKS = (uptr -> capac + uptr -> HDSK_SECTORS_PER_TRACK *
342 uptr -> HDSK_SECTOR_SIZE - 1) / (uptr -> HDSK_SECTORS_PER_TRACK * uptr -> HDSK_SECTOR_SIZE);
343 assert( ( (t_addr) ((uptr -> HDSK_NUMBER_OF_TRACKS - 1) * uptr -> HDSK_SECTORS_PER_TRACK *
344 uptr -> HDSK_SECTOR_SIZE) < uptr -> capac) &&
345 (uptr -> capac <= (t_addr) (uptr -> HDSK_NUMBER_OF_TRACKS *
346 uptr -> HDSK_SECTORS_PER_TRACK * uptr -> HDSK_SECTOR_SIZE) ) );
347
348 return SCPE_OK;
349 }
350
hdsk_detach(UNIT * uptr)351 static t_stat hdsk_detach(UNIT *uptr) {
352 t_stat result;
353 if (uptr == NULL)
354 return SCPE_IERR;
355 result = detach_unit(uptr);
356 uptr -> capac = HDSK_CAPACITY;
357 uptr -> HDSK_FORMAT_TYPE = 0;
358 uptr -> HDSK_SECTOR_SIZE = 0;
359 uptr -> HDSK_SECTORS_PER_TRACK = 0;
360 uptr -> HDSK_NUMBER_OF_TRACKS = 0;
361 return result;
362 }
363
364 /* Set disk geometry routine */
set_geom(UNIT * uptr,int32 val,char * cptr,void * desc)365 static t_stat set_geom(UNIT *uptr, int32 val, char *cptr, void *desc) {
366 uint32 numberOfTracks, numberOfSectors, sectorSize;
367 int result, n;
368
369 if (cptr == NULL)
370 return SCPE_ARG;
371 if (uptr == NULL)
372 return SCPE_IERR;
373 if (((uptr -> flags) & UNIT_ATT) == 0) {
374 printf("Cannot set geometry for not attached unit %i.\n", find_unit_index(uptr));
375 return SCPE_ARG;
376 }
377 result = sscanf(cptr, "%d/%d/%d%n", &numberOfTracks, &numberOfSectors, §orSize, &n);
378 if ((result != 3) || (result == EOF) || (cptr[n] != 0)) {
379 result = sscanf(cptr, "T:%d/N:%d/S:%d%n", &numberOfTracks, &numberOfSectors, §orSize, &n);
380 if ((result != 3) || (result == EOF) || (cptr[n] != 0))
381 return SCPE_ARG;
382 }
383 uptr -> HDSK_NUMBER_OF_TRACKS = numberOfTracks;
384 uptr -> HDSK_SECTORS_PER_TRACK = numberOfSectors;
385 uptr -> HDSK_SECTOR_SIZE = sectorSize;
386 uptr -> capac = numberOfTracks * numberOfSectors * sectorSize;
387 return SCPE_OK;
388 }
389
390 /* Show disk geometry routine */
show_geom(FILE * st,UNIT * uptr,int32 val,void * desc)391 static t_stat show_geom(FILE *st, UNIT *uptr, int32 val, void *desc) {
392 if (uptr == NULL)
393 return SCPE_IERR;
394 fprintf(st, "T:%d/N:%d/S:%d", uptr -> HDSK_NUMBER_OF_TRACKS,
395 uptr -> HDSK_SECTORS_PER_TRACK, uptr -> HDSK_SECTOR_SIZE);
396 return SCPE_OK;
397 }
398
399 #define QUOTE1(text) #text
400 #define QUOTE2(text) QUOTE1(text)
401 /* Set disk format routine */
set_format(UNIT * uptr,int32 val,char * cptr,void * desc)402 static t_stat set_format(UNIT *uptr, int32 val, char *cptr, void *desc) {
403 char fmtname[DPB_NAME_LENGTH + 1];
404 int32 i;
405
406 if (cptr == NULL)
407 return SCPE_ARG;
408 if (uptr == NULL)
409 return SCPE_IERR;
410 if (sscanf(cptr, "%" QUOTE2(DPB_NAME_LENGTH) "s", fmtname) == 0)
411 return SCPE_ARG;
412 if (((uptr -> flags) & UNIT_ATT) == 0) {
413 printf("Cannot set format for not attached unit %i.\n", find_unit_index(uptr));
414 return SCPE_ARG;
415 }
416 for (i = 0; dpb[i].capac != 0; i++) {
417 if (strncmp(fmtname, dpb[i].name, strlen(fmtname)) == 0) {
418 uptr -> HDSK_FORMAT_TYPE = i;
419 uptr -> capac = dpb[i].capac; /* Set capacity */
420
421 /* Configure physical disk geometry */
422 uptr -> HDSK_SECTOR_SIZE = (128 << dpb[uptr -> HDSK_FORMAT_TYPE].psh);
423 uptr -> HDSK_SECTORS_PER_TRACK = dpb[uptr -> HDSK_FORMAT_TYPE].spt >> dpb[uptr -> HDSK_FORMAT_TYPE].psh;
424 uptr -> HDSK_NUMBER_OF_TRACKS = (uptr -> capac +
425 uptr -> HDSK_SECTORS_PER_TRACK * uptr -> HDSK_SECTOR_SIZE - 1) /
426 (uptr -> HDSK_SECTORS_PER_TRACK * uptr -> HDSK_SECTOR_SIZE);
427
428 return SCPE_OK;
429 }
430 }
431 return SCPE_ARG;
432 }
433
434 /* Show disk format routine */
show_format(FILE * st,UNIT * uptr,int32 val,void * desc)435 static t_stat show_format(FILE *st, UNIT *uptr, int32 val, void *desc) {
436 if (uptr == NULL)
437 return SCPE_IERR;
438 fprintf(st, "%s", dpb[uptr -> HDSK_FORMAT_TYPE].name);
439 return SCPE_OK;
440 }
441
442 static int32 bootrom_hdsk[BOOTROM_SIZE_HDSK] = {
443 0xf3, 0x06, 0x80, 0x3e, 0x0e, 0xd3, 0xfe, 0x05, /* 5c00-5c07 */
444 0xc2, 0x05, 0x5c, 0x3e, 0x16, 0xd3, 0xfe, 0x3e, /* 5c08-5c0f */
445 0x12, 0xd3, 0xfe, 0xdb, 0xfe, 0xb7, 0xca, 0x20, /* 5c10-5c17 */
446 0x5c, 0x3e, 0x0c, 0xd3, 0xfe, 0xaf, 0xd3, 0xfe, /* 5c18-5c1f */
447 0x06, 0x20, 0x3e, 0x01, 0xd3, 0xfd, 0x05, 0xc2, /* 5c20-5c27 */
448 0x24, 0x5c, 0x11, 0x08, 0x00, 0x21, 0x00, 0x00, /* 5c28-5c2f */
449 0x0e, 0xb8, 0x3e, 0x02, 0xd3, 0xfd, 0x3a, 0x37, /* 5c30-5c37 */
450 0xff, 0xd6, 0x08, 0xd3, 0xfd, 0x7b, 0xd3, 0xfd, /* 5c38-5c3f */
451 0x7a, 0xd3, 0xfd, 0xaf, 0xd3, 0xfd, 0x7d, 0xd3, /* 5c40-5c47 */
452 0xfd, 0x7c, 0xd3, 0xfd, 0xdb, 0xfd, 0xb7, 0xca, /* 5c48-5c4f */
453 0x53, 0x5c, 0x76, 0x79, 0x0e, 0x80, 0x09, 0x4f, /* 5c50-5c57 */
454 0x0d, 0xc2, 0x60, 0x5c, 0xfb, 0xc3, 0x00, 0x00, /* 5c58-5c5f */
455 0x1c, 0x1c, 0x7b, 0xfe, 0x20, 0xca, 0x73, 0x5c, /* 5c60-5c67 */
456 0xfe, 0x21, 0xc2, 0x32, 0x5c, 0x1e, 0x00, 0x14, /* 5c68-5c6f */
457 0xc3, 0x32, 0x5c, 0x1e, 0x01, 0xc3, 0x32, 0x5c, /* 5c70-5c77 */
458 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c78-5c7f */
459 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c80-5c87 */
460 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c88-5c8f */
461 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c90-5c97 */
462 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5c98-5c9f */
463 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ca0-5ca7 */
464 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ca8-5caf */
465 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cb0-5cb7 */
466 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cb8-5cbf */
467 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cc0-5cc7 */
468 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cc8-5ccf */
469 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cd0-5cd7 */
470 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cd8-5cdf */
471 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ce0-5ce7 */
472 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5ce8-5cef */
473 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cf0-5cf7 */
474 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 5cf8-5cff */
475 };
476
hdsk_boot(int32 unitno,DEVICE * dptr)477 static t_stat hdsk_boot(int32 unitno, DEVICE *dptr) {
478 if (MEMORYSIZE < 24*KB) {
479 printf("Need at least 24KB RAM to boot from hard disk.\n");
480 return SCPE_ARG;
481 }
482 if (cpu_unit.flags & (UNIT_CPU_ALTAIRROM | UNIT_CPU_BANKED)) {
483 /* check whether we are really modifying an LD A,<> instruction */
484 if (bootrom_dsk[UNIT_NO_OFFSET_1 - 1] == LDA_INSTRUCTION)
485 bootrom_dsk[UNIT_NO_OFFSET_1] = (unitno + NUM_OF_DSK) & 0xff; /* LD A,<unitno> */
486 else { /* Attempt to modify non LD A,<> instructions is refused. */
487 printf("Incorrect boot ROM offset detected.\n");
488 return SCPE_IERR;
489 }
490 install_ALTAIRbootROM(); /* install modified ROM */
491 }
492 assert(install_bootrom(bootrom_hdsk, BOOTROM_SIZE_HDSK, HDSK_BOOT_ADDRESS, FALSE) == SCPE_OK);
493 *((int32 *) sim_PC->loc) = HDSK_BOOT_ADDRESS;
494 return SCPE_OK;
495 }
496
497 /* The hard disk port is 0xfd. It understands the following commands.
498
499 1. Reset
500 ld b,32
501 ld a,HDSK_RESET
502 l: out (0fdh),a
503 dec b
504 jp nz,l
505
506 2. Read / write
507 ; parameter block
508 cmd: db HDSK_READ or HDSK_WRITE
509 hd: db 0 ; 0 .. 7, defines hard disk to be used
510 sector: db 0 ; 0 .. 31, defines sector
511 track: dw 0 ; 0 .. 2047, defines track
512 dma: dw 0 ; defines where result is placed in memory
513
514 ; routine to execute
515 ld b,7 ; size of parameter block
516 ld hl,cmd ; start address of parameter block
517 l: ld a,(hl) ; get byte of parameter block
518 out (0fdh),a ; send it to port
519 inc hl ; point to next byte
520 dec b ; decrement counter
521 jp nz,l ; again, if not done
522 in a,(0fdh) ; get result code
523
524 3. Retrieve Disk Parameters from controller (Howard M. Harte)
525 Reads a 19-byte parameter block from the disk controller.
526 This parameter block is in CP/M DPB format for the first 17 bytes,
527 and the last two bytes are the lsb/msb of the disk's physical
528 sector size.
529
530 ; routine to execute
531 ld a,hdskParam ; hdskParam = 4
532 out (hdskPort),a ; Send 'get parameters' command, hdskPort = 0fdh
533 ld a,(diskno)
534 out (hdskPort),a ; Send selected HDSK number
535 ld b,17
536 1: in a,(hdskPort) ; Read 17-bytes of DPB
537 ld (hl), a
538 inc hl
539 djnz 1
540 in a,(hdskPort) ; Read LSB of disk's physical sector size.
541 ld (hsecsiz), a
542 in a,(hdskPort) ; Read MSB of disk's physical sector size.
543 ld (hsecsiz+1), a
544
545 */
546
547 /* check the parameters and return TRUE iff parameters are correct or have been repaired */
checkParameters(void)548 static int32 checkParameters(void) {
549 UNIT *uptr;
550 if ((selectedDisk < 0) || (selectedDisk >= HDSK_NUMBER)) {
551 sim_debug(VERBOSE_MSG, &hdsk_dev, "HDSK%d: " ADDRESS_FORMAT
552 " Disk %i does not exist, will use HDSK0 instead.\n",
553 selectedDisk, PCX, selectedDisk);
554 selectedDisk = 0;
555 }
556 uptr = &hdsk_dev.units[selectedDisk];
557 if ((hdsk_dev.units[selectedDisk].flags & UNIT_ATT) == 0) {
558 sim_debug(VERBOSE_MSG, &hdsk_dev, "HDSK%d: " ADDRESS_FORMAT
559 " Disk %i is not attached.\n", selectedDisk, PCX, selectedDisk);
560 return FALSE; /* cannot read or write */
561 }
562 if ((selectedSector < 0) || (selectedSector >= uptr -> HDSK_SECTORS_PER_TRACK)) {
563 sim_debug(VERBOSE_MSG, &hdsk_dev, "HDSK%d: " ADDRESS_FORMAT
564 " Constraint violation 0 <= Sector=%02d < %d, will use sector 0 instead.\n",
565 selectedDisk, PCX, selectedSector, uptr -> HDSK_SECTORS_PER_TRACK);
566 selectedSector = 0;
567 }
568 if ((selectedTrack < 0) || (selectedTrack >= uptr -> HDSK_NUMBER_OF_TRACKS)) {
569 sim_debug(VERBOSE_MSG, &hdsk_dev, "HDSK%d: " ADDRESS_FORMAT
570 " Constraint violation 0 <= Track=%04d < %04d, will use track 0 instead.\n",
571 selectedDisk, PCX, selectedTrack, uptr -> HDSK_NUMBER_OF_TRACKS);
572 selectedTrack = 0;
573 }
574 selectedDMA &= ADDRMASK;
575 if (hdskLastCommand == HDSK_READ)
576 sim_debug(READ_MSG, &hdsk_dev, "HDSK%d " ADDRESS_FORMAT
577 " Read Track=%04d Sector=%02d Len=%04d DMA=%04x\n",
578 selectedDisk, PCX, selectedTrack, selectedSector, uptr -> HDSK_SECTOR_SIZE, selectedDMA);
579 if (hdskLastCommand == HDSK_WRITE)
580 sim_debug(WRITE_MSG, &hdsk_dev, "HDSK%d " ADDRESS_FORMAT
581 " Write Track=%04d Sector=%02d Len=%04d DMA=%04x\n",
582 selectedDisk, PCX, selectedTrack, selectedSector, uptr -> HDSK_SECTOR_SIZE, selectedDMA);
583 return TRUE;
584 }
585
586 /* pre-condition: checkParameters has been executed to repair any faulty parameters */
doSeek(void)587 static int32 doSeek(void) {
588 UNIT *uptr = &hdsk_dev.units[selectedDisk];
589 int32 hostSector = (dpb[uptr -> HDSK_FORMAT_TYPE].skew == NULL) ?
590 selectedSector : dpb[uptr -> HDSK_FORMAT_TYPE].skew[selectedSector];
591 int32 sectorSize = (dpb[uptr -> HDSK_FORMAT_TYPE].physicalSectorSize == 0) ?
592 uptr -> HDSK_SECTOR_SIZE : dpb[uptr -> HDSK_FORMAT_TYPE].physicalSectorSize;
593 if (sim_fseek(uptr -> fileref,
594 sectorSize * (uptr -> HDSK_SECTORS_PER_TRACK * selectedTrack + hostSector) +
595 dpb[uptr -> HDSK_FORMAT_TYPE].offset, SEEK_SET)) {
596 sim_debug(VERBOSE_MSG, &hdsk_dev, "HDSK%d: " ADDRESS_FORMAT
597 " Could not access Sector=%02d[=%02d] Track=%04d.\n",
598 selectedDisk, PCX, selectedSector, hostSector, selectedTrack);
599 return CPM_ERROR;
600 }
601 return CPM_OK;
602 }
603
604 uint8 hdskbuf[HDSK_MAX_SECTOR_SIZE] = { 0 }; /* data buffer */
605
606 /* pre-condition: checkParameters has been executed to repair any faulty parameters */
doRead(void)607 static int32 doRead(void) {
608 int32 i;
609 UNIT *uptr = &hdsk_dev.units[selectedDisk];
610 if (doSeek())
611 return CPM_ERROR;
612
613 if (sim_fread(hdskbuf, 1, uptr -> HDSK_SECTOR_SIZE, uptr -> fileref) != (size_t)(uptr -> HDSK_SECTOR_SIZE)) {
614 for (i = 0; i < uptr -> HDSK_SECTOR_SIZE; i++)
615 hdskbuf[i] = CPM_EMPTY;
616 sim_debug(VERBOSE_MSG, &hdsk_dev, "HDSK%d: " ADDRESS_FORMAT
617 " Could not read Sector=%02d Track=%04d.\n",
618 selectedDisk, PCX, selectedSector, selectedTrack);
619 return CPM_OK; /* allows the creation of empty hard disks */
620 }
621 for (i = 0; i < uptr -> HDSK_SECTOR_SIZE; i++)
622 PutBYTEWrapper(selectedDMA + i, hdskbuf[i]);
623 return CPM_OK;
624 }
625
626 /* pre-condition: checkParameters has been executed to repair any faulty parameters */
doWrite(void)627 static int32 doWrite(void) {
628 int32 i;
629 size_t rtn;
630 UNIT *uptr = &hdsk_dev.units[selectedDisk];
631 if (((uptr -> flags) & UNIT_HDSK_WLK) == 0) { /* write enabled */
632 if (doSeek())
633 return CPM_ERROR;
634 for (i = 0; i < uptr -> HDSK_SECTOR_SIZE; i++)
635 hdskbuf[i] = GetBYTEWrapper(selectedDMA + i);
636 rtn = sim_fwrite(hdskbuf, 1, uptr -> HDSK_SECTOR_SIZE, uptr -> fileref);
637 if (rtn != (size_t)(uptr -> HDSK_SECTOR_SIZE)) {
638 sim_debug(VERBOSE_MSG, &hdsk_dev, "HDSK%d: " ADDRESS_FORMAT
639 " Could not write Sector=%02d Track=%04d Result=%zd.\n",
640 selectedDisk, PCX, selectedSector, selectedTrack, rtn);
641 return CPM_ERROR;
642 }
643 }
644 else {
645 sim_debug(VERBOSE_MSG, &hdsk_dev, "HDSK%d: " ADDRESS_FORMAT
646 " Could not write to locked disk Sector=%02d Track=%04d.\n",
647 selectedDisk, PCX, selectedSector, selectedTrack);
648 return CPM_ERROR;
649 }
650 return CPM_OK;
651 }
652
653 #define PARAMETER_BLOCK_SIZE 19
654 static uint8 parameterBlock[PARAMETER_BLOCK_SIZE];
655
hdsk_in(const int32 port)656 static int32 hdsk_in(const int32 port) {
657 if ((hdskCommandPosition == 6) && ((hdskLastCommand == HDSK_READ) || (hdskLastCommand == HDSK_WRITE))) {
658 int32 result = checkParameters() ? ((hdskLastCommand == HDSK_READ) ? doRead() : doWrite()) : CPM_ERROR;
659 hdskLastCommand = HDSK_NONE;
660 hdskCommandPosition = 0;
661 return result;
662 }
663 if (hdskLastCommand == HDSK_PARAM) {
664 if (++parameterCount >= PARAMETER_BLOCK_SIZE)
665 hdskLastCommand = HDSK_NONE;
666 return parameterBlock[parameterCount - 1];
667 }
668 sim_debug(VERBOSE_MSG, &hdsk_dev, "HDSK%d: " ADDRESS_FORMAT
669 " Illegal IN command detected (port=%03xh, cmd=%d, pos=%d).\n",
670 selectedDisk, PCX, port, hdskLastCommand, hdskCommandPosition);
671 return CPM_OK;
672 }
673
hdsk_out(const int32 port,const int32 data)674 static int32 hdsk_out(const int32 port, const int32 data) {
675 int32 thisDisk;
676 UNIT *uptr;
677 DPB current;
678
679 switch(hdskLastCommand) {
680
681 case HDSK_PARAM:
682 parameterCount = 0;
683 thisDisk = (0 <= data) && (data < HDSK_NUMBER) ? data : 0;
684 uptr = &hdsk_dev.units[thisDisk];
685 if ((uptr -> flags) & UNIT_ATT) {
686 current = dpb[uptr -> HDSK_FORMAT_TYPE];
687 parameterBlock[17] = uptr -> HDSK_SECTOR_SIZE & 0xff;
688 parameterBlock[18] = (uptr -> HDSK_SECTOR_SIZE >> 8) & 0xff;
689 }
690 else {
691 current = dpb[0];
692 parameterBlock[17] = 128;
693 parameterBlock[18] = 0;
694 }
695 parameterBlock[ 0] = current.spt & 0xff; parameterBlock[ 1] = (current.spt >> 8) & 0xff;
696 parameterBlock[ 2] = current.bsh;
697 parameterBlock[ 3] = current.blm;
698 parameterBlock[ 4] = current.exm;
699 parameterBlock[ 5] = current.dsm & 0xff; parameterBlock[ 6] = (current.dsm >> 8) & 0xff;
700 parameterBlock[ 7] = current.drm & 0xff; parameterBlock[ 8] = (current.drm >> 8) & 0xff;
701 parameterBlock[ 9] = current.al0;
702 parameterBlock[10] = current.al1;
703 parameterBlock[11] = current.cks & 0xff; parameterBlock[12] = (current.cks >> 8) & 0xff;
704 parameterBlock[13] = current.off & 0xff; parameterBlock[14] = (current.off >> 8) & 0xff;
705 parameterBlock[15] = current.psh;
706 parameterBlock[16] = current.phm;
707 break;
708
709 case HDSK_READ:
710
711 case HDSK_WRITE:
712 switch(hdskCommandPosition) {
713
714 case 0:
715 selectedDisk = data;
716 hdskCommandPosition++;
717 break;
718
719 case 1:
720 selectedSector = data;
721 hdskCommandPosition++;
722 break;
723
724 case 2:
725 selectedTrack = data;
726 hdskCommandPosition++;
727 break;
728
729 case 3:
730 selectedTrack += (data << 8);
731 hdskCommandPosition++;
732 break;
733
734 case 4:
735 selectedDMA = data;
736 hdskCommandPosition++;
737 break;
738
739 case 5:
740 selectedDMA += (data << 8);
741 hdskCommandPosition++;
742 break;
743
744 default:
745 hdskLastCommand = HDSK_NONE;
746 hdskCommandPosition = 0;
747 }
748 break;
749
750 default:
751 if ((HDSK_RESET <= data) && (data <= HDSK_PARAM))
752 hdskLastCommand = data;
753 else {
754 sim_debug(VERBOSE_MSG, &hdsk_dev, "HDSK%d: " ADDRESS_FORMAT
755 " Illegal OUT command detected (port=%03xh, cmd=%d).\n",
756 selectedDisk, PCX, port, data);
757 hdskLastCommand = HDSK_RESET;
758 }
759 hdskCommandPosition = 0;
760 }
761 return 0; /* ignored, since OUT */
762 }
763
hdsk_io(const int32 port,const int32 io,const int32 data)764 int32 hdsk_io(const int32 port, const int32 io, const int32 data) {
765 return io == 0 ? hdsk_in(port) : hdsk_out(port, data);
766 }
767