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, &sectorSize, &n);
378     if ((result != 3) || (result == EOF) || (cptr[n] != 0)) {
379         result = sscanf(cptr, "T:%d/N:%d/S:%d%n", &numberOfTracks, &numberOfSectors, &sectorSize, &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