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