diskimage_scsicmd.cc Source File

Back to the index.

diskimage_scsicmd.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2003-2009 Anders Gavare. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * 1. Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  * 3. The name of the author may not be used to endorse or promote products
13  * derived from this software without specific prior written permission.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  *
27  *
28  * Disk image support: SCSI command emulation.
29  *
30  * TODO: There are LOTS of ugly magic values in this module. These should
31  * be replaced by proper defines.
32  *
33  * TODO: There's probably a bug in the tape support:
34  * Let's say there are 10240 bytes left in a file, and 10240
35  * bytes are read. Then feof() is not true yet (?), so the next
36  * read will also return 10240 bytes (but all zeroes), and then after
37  * that return feof (which results in a filemark). This is probably
38  * trivial to fix, but I don't feel like it right now.
39  */
40 
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 
46 #include "cpu.h"
47 #include "diskimage.h"
48 #include "machine.h"
49 #include "misc.h"
50 
51 
52 static const char *diskimage_types[] = DISKIMAGE_TYPES;
53 static struct scsi_transfer *first_free_scsi_transfer_alloc = NULL;
54 
55 
56 /*
57  * scsi_transfer_alloc():
58  *
59  * Allocates memory for a new scsi_transfer struct, and fills it with
60  * sane data (NULL pointers).
61  * The return value is a pointer to the new struct. If allocation
62  * failed, the program exits.
63  */
65 {
66  struct scsi_transfer *p;
67 
68  if (first_free_scsi_transfer_alloc != NULL) {
69  p = first_free_scsi_transfer_alloc;
70  first_free_scsi_transfer_alloc = p->next_free;
71  } else {
72  p = (struct scsi_transfer *) malloc(sizeof(struct scsi_transfer));
73  if (p == NULL) {
74  fprintf(stderr, "scsi_transfer_alloc(): out "
75  "of memory\n");
76  exit(1);
77  }
78  }
79 
80  memset(p, 0, sizeof(struct scsi_transfer));
81 
82  return p;
83 }
84 
85 
86 /*
87  * scsi_transfer_free():
88  *
89  * Frees the space used by a scsi_transfer struct. All buffers refered
90  * to by the scsi_transfer struct are freed.
91  */
93 {
94  if (p == NULL) {
95  fprintf(stderr, "scsi_transfer_free(): p == NULL\n");
96  exit(1);
97  }
98 
99  if (p->msg_out != NULL)
100  free(p->msg_out);
101  if (p->cmd != NULL)
102  free(p->cmd);
103  if (p->data_out != NULL)
104  free(p->data_out);
105 
106  if (p->data_in != NULL)
107  free(p->data_in);
108  if (p->msg_in != NULL)
109  free(p->msg_in);
110  if (p->status != NULL)
111  free(p->status);
112 
113  p->next_free = first_free_scsi_transfer_alloc;
114  first_free_scsi_transfer_alloc = p;
115 }
116 
117 
118 /*
119  * scsi_transfer_allocbuf():
120  *
121  * Helper function, used by diskimage_scsicommand(), and SCSI controller
122  * devices. Example of usage:
123  *
124  * scsi_transfer_allocbuf(&xferp->msg_in_len, &xferp->msg_in, 1);
125  */
126 void scsi_transfer_allocbuf(size_t *lenp, unsigned char **pp, size_t want_len,
127  int clearflag)
128 {
129  unsigned char *p = (*pp);
130 
131  if (p != NULL) {
132  printf("WARNING! scsi_transfer_allocbuf(): old pointer "
133  "was not NULL, freeing it now\n");
134  free(p);
135  }
136 
137  (*lenp) = want_len;
138  if ((p = (unsigned char *) malloc(want_len)) == NULL) {
139  fprintf(stderr, "scsi_transfer_allocbuf(): out of "
140  "memory trying to allocate %li bytes\n", (long)want_len);
141  exit(1);
142  }
143 
144  if (clearflag)
145  memset(p, 0, want_len);
146 
147  (*pp) = p;
148 }
149 
150 
151 /**************************************************************************/
152 
153 
154 /*
155  * diskimage__return_default_status_and_message():
156  *
157  * Set the status and msg_in parts of a scsi_transfer struct
158  * to default values (msg_in = 0x00, status = 0x00).
159  */
160 static void diskimage__return_default_status_and_message(
161  struct scsi_transfer *xferp)
162 {
163  scsi_transfer_allocbuf(&xferp->status_len, &xferp->status, 1, 0);
164  xferp->status[0] = 0x00;
165  scsi_transfer_allocbuf(&xferp->msg_in_len, &xferp->msg_in, 1, 0);
166  xferp->msg_in[0] = 0x00;
167 }
168 
169 
170 /*
171  * diskimage__switch_tape():
172  *
173  * Used by the SPACE command. (d is assumed to be non-NULL.)
174  */
175 static void diskimage__switch_tape(struct diskimage *d)
176 {
177  char tmpfname[1000];
178 
179  snprintf(tmpfname, sizeof(tmpfname), "%s.%i",
180  d->fname, d->tape_filenr);
181  tmpfname[sizeof(tmpfname)-1] = '\0';
182 
183  if (d->f != NULL)
184  fclose(d->f);
185 
186  d->f = fopen(tmpfname, d->writable? "r+" : "r");
187  if (d->f == NULL) {
188  fprintf(stderr, "[ diskimage__switch_tape(): could not "
189  "(re)open '%s' ]\n", tmpfname);
190  /* TODO: return error */
191  }
192  d->tape_offset = 0;
193 }
194 
195 
196 /**************************************************************************/
197 
198 
199 /*
200  * diskimage_scsicommand():
201  *
202  * Perform a SCSI command on a disk image.
203  *
204  * The xferp points to a scsi_transfer struct, containing msg_out, command,
205  * and data_out coming from the SCSI controller device. This function
206  * interprets the command, and (if necessary) creates responses in
207  * data_in, msg_in, and status.
208  *
209  * Returns:
210  * 2 if the command expects data from the DATA_OUT phase,
211  * 1 if otherwise ok,
212  * 0 on error.
213  */
214 int diskimage_scsicommand(struct cpu *cpu, int id, int type,
215  struct scsi_transfer *xferp)
216 {
217  char namebuf[16];
218  int retlen, i, q, result;
219  uint64_t size;
220  int64_t ofs;
221  int pagecode;
222  struct machine *machine = cpu->machine;
223  struct diskimage *d;
224 
225  if (machine == NULL) {
226  fatal("[ diskimage_scsicommand(): machine == NULL ]\n");
227  return 0;
228  }
229 
231  while (d != NULL) {
232  if (d->type == type && d->id == id)
233  break;
234  d = d->next;
235  }
236  if (d == NULL) {
237  fprintf(stderr, "[ diskimage_scsicommand(): %s "
238  " id %i not connected? ]\n", diskimage_types[type], id);
239  }
240 
241  if (xferp->cmd == NULL) {
242  fatal("[ diskimage_scsicommand(): cmd == NULL ]\n");
243  return 0;
244  }
245 
246  if (xferp->cmd_len < 1) {
247  fatal("[ diskimage_scsicommand(): cmd_len == %i ]\n",
248  xferp->cmd_len);
249  return 0;
250  }
251 
252  debug("[ diskimage_scsicommand(id=%i) cmd=0x%02x: ",
253  id, xferp->cmd[0]);
254 
255 #if 0
256  fatal("[ diskimage_scsicommand(id=%i) cmd=0x%02x len=%i:",
257  id, xferp->cmd[0], xferp->cmd_len);
258  for (i=0; i<xferp->cmd_len; i++)
259  fatal(" %02x", xferp->cmd[i]);
260  fatal("\n");
261 if (xferp->cmd_len > 7 && xferp->cmd[5] == 0x11)
263 #endif
264 
265 #if 0
266 {
267  static FILE *f = NULL;
268  if (f == NULL)
269  f = fopen("scsi_log.txt", "w");
270  if (f != NULL) {
271  int i;
272  fprintf(f, "id=%i cmd =", id);
273  for (i=0; i<xferp->cmd_len; i++)
274  fprintf(f, " %02x", xferp->cmd[i]);
275  fprintf(f, "\n");
276  fflush(f);
277  }
278 }
279 #endif
280 
281  switch (xferp->cmd[0]) {
282 
284  debug("TEST_UNIT_READY");
285  if (xferp->cmd_len != 6)
286  debug(" (weird len=%i)", xferp->cmd_len);
287 
288  /* TODO: bits 765 of buf[1] contains the LUN */
289  if (xferp->cmd[1] != 0x00)
290  fatal("WARNING: TEST_UNIT_READY with cmd[1]=0x%02x"
291  " not yet implemented\n", (int)xferp->cmd[1]);
292 
293  diskimage__return_default_status_and_message(xferp);
294  break;
295 
296  case SCSICMD_INQUIRY:
297  debug("INQUIRY");
298  if (xferp->cmd_len != 6)
299  debug(" (weird len=%i)", xferp->cmd_len);
300  if (xferp->cmd[1] != 0x00) {
301  debug("WARNING: INQUIRY with cmd[1]=0x%02x not yet "
302  "implemented\n", (int)xferp->cmd[1]);
303 
304  break;
305  }
306 
307  /* Return values: */
308  retlen = xferp->cmd[4];
309  if (retlen < 36) {
310  fatal("WARNING: SCSI inquiry len=%i, <36!\n", retlen);
311  retlen = 36;
312  }
313 
314  /* Return data: */
315  scsi_transfer_allocbuf(&xferp->data_in_len, &xferp->data_in,
316  retlen, 1);
317  xferp->data_in[0] = 0x00; /* 0x00 = Direct-access disk */
318  xferp->data_in[1] = 0x00; /* 0x00 = non-removable */
319  xferp->data_in[2] = 0x02; /* SCSI-2 */
320 #if 0
321 xferp->data_in[3] = 0x02; /* Response data format = SCSI-2 */
322 #endif
323  xferp->data_in[4] = retlen - 4; /* Additional length */
324 xferp->data_in[4] = 0x2c - 4; /* Additional length */
325  xferp->data_in[6] = 0x04; /* ACKREQQ */
326  xferp->data_in[7] = 0x60; /* WBus32, WBus16 */
327 
328  /* These are padded with spaces: */
329 
330  memcpy(xferp->data_in+8, "GXemul ", 8);
331  if (diskimage_getname(cpu->machine, id,
332  type, namebuf, sizeof(namebuf))) {
333  for (size_t j=0; j<sizeof(namebuf); j++) {
334  if (namebuf[j] == 0) {
335  for (; j<sizeof(namebuf); j++)
336  namebuf[j] = ' ';
337  break;
338  }
339  }
340 
341  memcpy(xferp->data_in+16, namebuf, 16);
342  } else
343  memcpy(xferp->data_in+16, "DISK ", 16);
344  memcpy(xferp->data_in+32, "0 ", 4);
345 
346  /*
347  * Some Ultrix kernels want specific responses from
348  * the drives.
349  */
350 
352  /* DEC, RZ25 (rev 0900) = 832527 sectors */
353  /* DEC, RZ58 (rev 2000) = 2698061 sectors */
354  memcpy(xferp->data_in+8, "DEC ", 8);
355  memcpy(xferp->data_in+16, "RZ58 (C) DEC", 16);
356  memcpy(xferp->data_in+32, "2000", 4);
357  }
358 
359  /* Some data is different for CD-ROM drives: */
360  if (d->is_a_cdrom) {
361  xferp->data_in[0] = 0x05; /* 0x05 = CD-ROM */
362  xferp->data_in[1] = 0x80; /* 0x80 = removable */
363  /* memcpy(xferp->data_in+16, "CD-ROM ", 16);*/
364 
366  /* SONY, CD-ROM: */
367  memcpy(xferp->data_in+8, "SONY ", 8);
368  memcpy(xferp->data_in+16,
369  "CD-ROM ", 16);
370 
371  /* ... or perhaps this: */
372  memcpy(xferp->data_in+8, "DEC ", 8);
373  memcpy(xferp->data_in+16,
374  "RRD42 (C) DEC ", 16);
375  memcpy(xferp->data_in+32, "4.5d", 4);
376  } else if (machine->machine_type == MACHINE_ARC) {
377  /* NEC, CD-ROM: */
378  memcpy(xferp->data_in+8, "NEC ", 8);
379  memcpy(xferp->data_in+16,
380  "CD-ROM CDR-210P ", 16);
381  memcpy(xferp->data_in+32, "1.0 ", 4);
382  }
383  }
384 
385  /* Data for tape devices: */
386  if (d->is_a_tape) {
387  xferp->data_in[0] = 0x01; /* 0x01 = tape */
388  xferp->data_in[1] = 0x80; /* 0x80 = removable */
389  memcpy(xferp->data_in+16, "TAPE ", 16);
390 
392  /*
393  * TODO: find out if these are correct.
394  *
395  * The name might be TZK10, TSZ07, or TLZ04,
396  * or something completely different.
397  */
398  memcpy(xferp->data_in+8, "DEC ", 8);
399  memcpy(xferp->data_in+16,
400  "TK50 (C) DEC", 16);
401  memcpy(xferp->data_in+32, "2000", 4);
402  }
403  }
404 
405  diskimage__return_default_status_and_message(xferp);
406  break;
407 
409  debug("READ_CAPACITY");
410 
411  if (xferp->cmd_len != 10)
412  fatal(" [ weird READ_CAPACITY len=%i, should be 10 ] ",
413  xferp->cmd_len);
414  else {
415  if (xferp->cmd[8] & 1) {
416  /* Partial Medium Indicator bit... TODO */
417  fatal("WARNING: READ_CAPACITY with PMI bit"
418  " set not yet implemented\n");
419  }
420  }
421 
422  /* Return data: */
423  scsi_transfer_allocbuf(&xferp->data_in_len, &xferp->data_in,
424  8, 1);
425 
427 
428  size = d->total_size / d->logical_block_size;
429  if (d->total_size & (d->logical_block_size-1))
430  size ++;
431 
432  xferp->data_in[0] = (size >> 24) & 255;
433  xferp->data_in[1] = (size >> 16) & 255;
434  xferp->data_in[2] = (size >> 8) & 255;
435  xferp->data_in[3] = size & 255;
436 
437  xferp->data_in[4] = (d->logical_block_size >> 24) & 255;
438  xferp->data_in[5] = (d->logical_block_size >> 16) & 255;
439  xferp->data_in[6] = (d->logical_block_size >> 8) & 255;
440  xferp->data_in[7] = d->logical_block_size & 255;
441 
442  diskimage__return_default_status_and_message(xferp);
443  break;
444 
445  case SCSICMD_MODE_SENSE:
446  case SCSICMD_MODE_SENSE10:
447  debug("MODE_SENSE");
448  q = 4; retlen = xferp->cmd[4];
449  switch (xferp->cmd_len) {
450  case 6: break;
451  case 10:q = 8;
452  retlen = xferp->cmd[7] * 256 + xferp->cmd[8];
453  break;
454  default:fatal(" (unimplemented mode_sense len=%i)",
455  xferp->cmd_len);
456  }
457 
458  /*
459  * NOTE/TODO: This code doesn't handle too short retlens
460  * very well. A quick hack around this is that I allocate
461  * a bit too much memory, so that nothing is actually
462  * written outside of xferp->data_in[].
463  */
464 
465  retlen += 100; /* Should be enough. (Ugly.) */
466 
467  if ((xferp->cmd[2] & 0xc0) != 0)
468  fatal("WARNING: mode sense, cmd[2] = 0x%02x\n",
469  xferp->cmd[2]);
470 
471  /* Return data: */
473  &xferp->data_in, retlen, 1);
474 
475  xferp->data_in_len -= 100; /* Restore size. */
476 
477  pagecode = xferp->cmd[2] & 0x3f;
478 
479  debug("[ MODE SENSE id %i, pagecode=%i ]\n", id, pagecode);
480 
481  /* 4 bytes of header for 6-byte command,
482  8 bytes of header for 10-byte command. */
483  xferp->data_in[0] = retlen; /* 0: mode data length */
484  xferp->data_in[1] = d->is_a_cdrom? 0x05 : 0x00;
485  /* 1: medium type */
486  xferp->data_in[2] = 0x00; /* device specific
487  parameter */
488  xferp->data_in[3] = 8 * 1; /* block descriptor
489  length: 1 page (?) */
490 
491  xferp->data_in[q+0] = 0x00; /* density code */
492  xferp->data_in[q+1] = 0; /* nr of blocks, high */
493  xferp->data_in[q+2] = 0; /* nr of blocks, mid */
494  xferp->data_in[q+3] = 0; /* nr of blocks, low */
495  xferp->data_in[q+4] = 0x00; /* reserved */
496  xferp->data_in[q+5] = (d->logical_block_size >> 16) & 255;
497  xferp->data_in[q+6] = (d->logical_block_size >> 8) & 255;
498  xferp->data_in[q+7] = d->logical_block_size & 255;
499  q += 8;
500 
501  diskimage__return_default_status_and_message(xferp);
502 
503  /* descriptors, 8 bytes (each) */
504 
505  /* page, n bytes (each) */
506  switch (pagecode) {
507  case 0:
508  /* TODO: Nothing here? */
509  break;
510  case 1: /* read-write error recovery page */
511  xferp->data_in[q + 0] = pagecode;
512  xferp->data_in[q + 1] = 10;
513  break;
514  case 3: /* format device page */
515  xferp->data_in[q + 0] = pagecode;
516  xferp->data_in[q + 1] = 22;
517 
518  /* 10,11 = sectors per track */
519  xferp->data_in[q + 10] = 0;
520  xferp->data_in[q + 11] = d->sectors_per_track;
521 
522  /* 12,13 = physical sector size */
523  xferp->data_in[q + 12] =
524  (d->logical_block_size >> 8) & 255;
525  xferp->data_in[q + 13] = d->logical_block_size & 255;
526  break;
527  case 4: /* rigid disk geometry page */
528  xferp->data_in[q + 0] = pagecode;
529  xferp->data_in[q + 1] = 22;
530  xferp->data_in[q + 2] = (d->ncyls >> 16) & 255;
531  xferp->data_in[q + 3] = (d->ncyls >> 8) & 255;
532  xferp->data_in[q + 4] = d->ncyls & 255;
533  xferp->data_in[q + 5] = d->heads;
534 
535  xferp->data_in[q + 20] = (d->rpms >> 8) & 255;
536  xferp->data_in[q + 21] = d->rpms & 255;
537  break;
538  case 5: /* flexible disk page */
539  xferp->data_in[q + 0] = pagecode;
540  xferp->data_in[q + 1] = 0x1e;
541 
542  /* 2,3 = transfer rate */
543  xferp->data_in[q + 2] = ((5000) >> 8) & 255;
544  xferp->data_in[q + 3] = (5000) & 255;
545 
546  xferp->data_in[q + 4] = d->heads;
547  xferp->data_in[q + 5] = d->sectors_per_track;
548 
549  /* 6,7 = data bytes per sector */
550  xferp->data_in[q + 6] = (d->logical_block_size >> 8)
551  & 255;
552  xferp->data_in[q + 7] = d->logical_block_size & 255;
553 
554  xferp->data_in[q + 8] = (d->ncyls >> 8) & 255;
555  xferp->data_in[q + 9] = d->ncyls & 255;
556 
557  xferp->data_in[q + 28] = (d->rpms >> 8) & 255;
558  xferp->data_in[q + 29] = d->rpms & 255;
559  break;
560  default:
561  fatal("[ MODE_SENSE for page %i is not yet "
562  "implemented! ]\n", pagecode);
563  }
564 
565  break;
566 
567  case SCSICMD_READ:
568  case SCSICMD_READ_10:
569  debug("READ");
570 
571  /*
572  * For tape devices, read data at the current position.
573  * For disk and CDROM devices, the command bytes contain
574  * an offset telling us where to read from the device.
575  */
576 
577  if (d->is_a_tape) {
578  /* bits 7..5 of cmd[1] are the LUN bits... TODO */
579 
580  size = (xferp->cmd[2] << 16) +
581  (xferp->cmd[3] << 8) +
582  xferp->cmd[4];
583 
584  /* Bit 1 of cmd[1] is the SILI bit (TODO), and
585  bit 0 is the "use fixed length" bit. */
586 
587  if (xferp->cmd[1] & 0x01) {
588  /* Fixed block length: */
589  size *= d->logical_block_size;
590  }
591 
592  if (d->filemark) {
593  /* At end of file, switch to the next
594  automagically: */
595  d->tape_filenr ++;
596  diskimage__switch_tape(d);
597 
598  d->filemark = 0;
599  }
600 
601  ofs = d->tape_offset;
602 
603  fatal("[ READ tape, id=%i file=%i, cmd[1]=%02x size=%i"
604  ", ofs=%lli ]\n", id, d->tape_filenr,
605  xferp->cmd[1], (int)size, (long long)ofs);
606  } else {
607  if (xferp->cmd[0] == SCSICMD_READ) {
608  if (xferp->cmd_len != 6)
609  debug(" (weird len=%i)",
610  xferp->cmd_len);
611 
612  /*
613  * bits 4..0 of cmd[1], and cmd[2] and cmd[3]
614  * hold the logical block address.
615  *
616  * cmd[4] holds the number of logical blocks
617  * to transfer. (Special case if the value is
618  * 0, actually means 256.)
619  */
620  ofs = ((xferp->cmd[1] & 0x1f) << 16) +
621  (xferp->cmd[2] << 8) + xferp->cmd[3];
622  retlen = xferp->cmd[4];
623  if (retlen == 0)
624  retlen = 256;
625  } else {
626  if (xferp->cmd_len != 10)
627  debug(" (weird len=%i)",
628  xferp->cmd_len);
629 
630  /*
631  * cmd[2..5] hold the logical block address.
632  * cmd[7..8] holds the number of logical
633  * blocks to transfer. (NOTE: If the value is
634  * 0, this means 0, not 65536. :-)
635  */
636  ofs = ((uint64_t)xferp->cmd[2] << 24) +
637  (xferp->cmd[3] << 16) + (xferp->cmd[4] << 8)
638  + xferp->cmd[5];
639  retlen = (xferp->cmd[7] << 8) + xferp->cmd[8];
640  }
641 
642  size = retlen * d->logical_block_size;
643  ofs *= d->logical_block_size;
644  }
645 
646  /* Return data: */
647  scsi_transfer_allocbuf(&xferp->data_in_len, &xferp->data_in,
648  size, 0);
649 
650  debug(" READ ofs=%lli size=%i\n", (long long)ofs, (int)size);
651 
652  diskimage__return_default_status_and_message(xferp);
653 
654  d->filemark = 0;
655 
656  /*
657  * Failure? Then set check condition.
658  * For tapes, error should only occur at the end of a file.
659  *
660  * "If the logical unit encounters a filemark during
661  * a READ command, CHECK CONDITION status shall be
662  * returned and the filemark and valid bits shall be
663  * set to one in the sense data. The sense key shall
664  * be set to NO SENSE"..
665  */
666  if (d->is_a_tape && d->f != NULL && feof(d->f)) {
667  debug(" feof id=%i\n", id);
668  xferp->status[0] = 0x02; /* CHECK CONDITION */
669 
670  d->filemark = 1;
671  } else {
672  result = diskimage__internal_access(d, 0, ofs,
673  xferp->data_in, size);
674  }
675 
676  if (d->is_a_tape && d->f != NULL)
677  d->tape_offset = ftello(d->f);
678 
679  /* TODO: other errors? */
680  break;
681 
682  case SCSICMD_WRITE:
683  case SCSICMD_WRITE_10:
684  debug("WRITE");
685 
686  /* TODO: tape */
687 
688  if (xferp->cmd[0] == SCSICMD_WRITE) {
689  if (xferp->cmd_len != 6)
690  debug(" (weird len=%i)", xferp->cmd_len);
691 
692  /*
693  * bits 4..0 of cmd[1], and cmd[2] and cmd[3] hold the
694  * logical block address.
695  *
696  * cmd[4] holds the number of logical blocks to
697  * transfer. (Special case if the value is 0, actually
698  * means 256.)
699  */
700  ofs = ((xferp->cmd[1] & 0x1f) << 16) +
701  (xferp->cmd[2] << 8) + xferp->cmd[3];
702  retlen = xferp->cmd[4];
703  if (retlen == 0)
704  retlen = 256;
705  } else {
706  if (xferp->cmd_len != 10)
707  debug(" (weird len=%i)", xferp->cmd_len);
708 
709  /*
710  * cmd[2..5] hold the logical block address.
711  * cmd[7..8] holds the number of logical blocks to
712  * transfer. (NOTE: If the value is 0 this means 0,
713  * not 65536.)
714  */
715  ofs = ((uint64_t)xferp->cmd[2] << 24) +
716  (xferp->cmd[3] << 16) + (xferp->cmd[4] << 8) +
717  xferp->cmd[5];
718  retlen = (xferp->cmd[7] << 8) + xferp->cmd[8];
719  }
720 
721  size = retlen * d->logical_block_size;
722  ofs *= d->logical_block_size;
723 
724  if (xferp->data_out_offset != size) {
725  debug(", data_out == NULL, wanting %i bytes, \n\n",
726  (int)size);
727  xferp->data_out_len = size;
728  return 2;
729  }
730 
731  debug(", data_out != NULL, OK :-)");
732 
733  debug("WRITE ofs=%i size=%i offset=%i\n", (int)ofs,
734  (int)size, (int)xferp->data_out_offset);
735 
736  result = diskimage__internal_access(d, 1, ofs,
737  xferp->data_out, size);
738 
739  /* TODO: how about return code? */
740 
741  diskimage__return_default_status_and_message(xferp);
742  break;
743 
745  debug("SYNCHRONIZE_CACHE");
746 
747  if (xferp->cmd_len != 10)
748  debug(" (weird len=%i)", xferp->cmd_len);
749 
750  /* TODO: actualy care about cmd[] */
751  /* TODO: Move to diskimage.cc and make sure that both
752  d->f is fsynced AND any overlays and overlay bitmap
753  files are fsynced too! */
754  fsync(fileno(d->f));
755 
756  diskimage__return_default_status_and_message(xferp);
757  break;
758 
760  debug("START_STOP_UNIT");
761 
762  if (xferp->cmd_len != 6)
763  debug(" (weird len=%i)", xferp->cmd_len);
764 
765  for (i=0; i<(ssize_t)xferp->cmd_len; i++)
766  debug(" %02x", xferp->cmd[i]);
767 
768  /* TODO: actualy care about cmd[] */
769 
770  diskimage__return_default_status_and_message(xferp);
771  break;
772 
774  debug("REQUEST_SENSE");
775 
776  retlen = xferp->cmd[4];
777 
778  /* TODO: bits 765 of buf[1] contains the LUN */
779  if (xferp->cmd[1] != 0x00)
780  fatal("WARNING: REQUEST_SENSE with cmd[1]=0x%02x not"
781  " yet implemented\n", (int)xferp->cmd[1]);
782 
783  if (retlen < 18) {
784  fatal("WARNING: SCSI request sense len=%i, <18!\n",
785  (int)retlen);
786  retlen = 18;
787  }
788 
789  /* Return data: */
790  scsi_transfer_allocbuf(&xferp->data_in_len, &xferp->data_in,
791  retlen, 1);
792 
793  xferp->data_in[0] = 0x80 + 0x70;/* 0x80 = valid,
794  0x70 = "current errors" */
795  xferp->data_in[2] = 0x00; /* SENSE KEY! */
796 
797  if (d->filemark) {
798  xferp->data_in[2] = 0x80;
799  }
800  debug(": [2]=0x%02x ", xferp->data_in[2]);
801 
802  printf(" XXX(!) \n");
803 
804  /* TODO */
805  xferp->data_in[7] = retlen - 7; /* additional sense length */
806  /* TODO */
807 
808  diskimage__return_default_status_and_message(xferp);
809  break;
810 
812  debug("READ_BLOCK_LIMITS");
813 
814  retlen = 6;
815 
816  /* TODO: bits 765 of buf[1] contains the LUN */
817  if (xferp->cmd[1] != 0x00)
818  fatal("WARNING: READ_BLOCK_LIMITS with cmd[1]="
819  "0x%02x not yet implemented\n", (int)xferp->cmd[1]);
820 
821  /* Return data: */
822  scsi_transfer_allocbuf(&xferp->data_in_len, &xferp->data_in,
823  retlen, 1);
824 
825  /*
826  * data[0] is reserved, data[1..3] contain the maximum block
827  * length limit, data[4..5] contain the minimum limit.
828  */
829 
830  {
831  int max_limit = 32768;
832  int min_limit = 128;
833 
834  xferp->data_in[1] = (max_limit >> 16) & 255;
835  xferp->data_in[2] = (max_limit >> 8) & 255;
836  xferp->data_in[3] = max_limit & 255;
837  xferp->data_in[4] = (min_limit >> 8) & 255;
838  xferp->data_in[5] = min_limit & 255;
839  }
840 
841  diskimage__return_default_status_and_message(xferp);
842  break;
843 
844  case SCSICMD_REWIND:
845  debug("REWIND");
846 
847  /* TODO: bits 765 of buf[1] contains the LUN */
848  if ((xferp->cmd[1] & 0xe0) != 0x00)
849  fatal("WARNING: REWIND with cmd[1]=0x%02x not yet "
850  "implemented\n", (int)xferp->cmd[1]);
851 
852  /* Close and reopen. */
853 
854  if (d->f != NULL)
855  fclose(d->f);
856 
857  d->f = fopen(d->fname, d->writable? "r+" : "r");
858  if (d->f == NULL) {
859  fprintf(stderr, "[ diskimage: could not (re)open "
860  "'%s' ]\n", d->fname);
861  /* TODO: return error */
862  }
863 
864  d->tape_offset = 0;
865  d->tape_filenr = 0;
866  d->filemark = 0;
867 
868  diskimage__return_default_status_and_message(xferp);
869  break;
870 
871  case SCSICMD_SPACE:
872  debug("SPACE");
873 
874  /* TODO: bits 765 of buf[1] contains the LUN */
875  if ((xferp->cmd[1] & 0xe0) != 0x00)
876  fatal("WARNING: SPACE with cmd[1]=0x%02x not yet "
877  "implemented\n", (int)xferp->cmd[1]);
878 
879  /*
880  * Bits 2..0 of buf[1] contain the 'code' which describes how
881  * spacing should be done, and buf[2..4] contain the number of
882  * operations.
883  */
884  debug("[ SPACE: buf[] = %02x %02x %02x %02x %02x %02x ]\n",
885  xferp->cmd[0],
886  xferp->cmd[1],
887  xferp->cmd[2],
888  xferp->cmd[3],
889  xferp->cmd[4],
890  xferp->cmd[5]);
891 
892  switch (xferp->cmd[1] & 7) {
893  case 1: /* Seek to a different file nr: */
894  {
895  int diff = (xferp->cmd[2] << 16) +
896  (xferp->cmd[3] << 8) + xferp->cmd[4];
897 
898  /* Negative seek offset: */
899  if (diff & (1 << 23))
900  diff = - (16777216 - diff);
901 
902  d->tape_filenr += diff;
903  }
904 
905  /* At end of file, switch to the next tape file: */
906  if (d->filemark) {
907  d->tape_filenr ++;
908  d->filemark = 0;
909  }
910 
911  debug("{ switching to tape file %i }", d->tape_filenr);
912  diskimage__switch_tape(d);
913  d->filemark = 0;
914  break;
915  default:
916  fatal("[ diskimage.c: unimplemented SPACE type %i ]\n",
917  xferp->cmd[1] & 7);
918  }
919 
920  diskimage__return_default_status_and_message(xferp);
921  break;
922 
924  /*
925  * According to
926  * http://mail-index.netbsd.org/port-i386/1997/03/03/0010.html:
927  *
928  * "The READ_CD_CAPACITY, READ_SUBCHANNEL, and MODE_SELECT
929  * commands have the same opcode in SCSI or ATAPI, but don't
930  * have the same command structure"...
931  *
932  * TODO: This still doesn't work. Hm.
933  */
934  retlen = 48;
935 
936  debug("CDROM_READ_SUBCHANNEL/READ_CD_CAPACITY, cmd[1]=0x%02x",
937  xferp->cmd[1]);
938 
939  /* Return data: */
941  &xferp->data_in, retlen, 1);
942 
944 
945  size = d->total_size / d->logical_block_size;
946  if (d->total_size & (d->logical_block_size-1))
947  size ++;
948 
949  xferp->data_in[0] = (size >> 24) & 255;
950  xferp->data_in[1] = (size >> 16) & 255;
951  xferp->data_in[2] = (size >> 8) & 255;
952  xferp->data_in[3] = size & 255;
953 
954  xferp->data_in[4] = (d->logical_block_size >> 24) & 255;
955  xferp->data_in[5] = (d->logical_block_size >> 16) & 255;
956  xferp->data_in[6] = (d->logical_block_size >> 8) & 255;
957  xferp->data_in[7] = d->logical_block_size & 255;
958 
959  diskimage__return_default_status_and_message(xferp);
960  break;
961 
962  case SCSICDROM_READ_TOC:
963  debug("(CDROM_READ_TOC: ");
964  debug("lun=%i msf=%i ",
965  xferp->cmd[1] >> 5, (xferp->cmd[1] >> 1) & 1);
966  debug("starting_track=%i ", xferp->cmd[6]);
967  retlen = xferp->cmd[7] * 256 + xferp->cmd[8];
968  debug("allocation_len=%i)\n", retlen);
969 
970  /* Return data: */
972  &xferp->data_in, retlen, 1);
973 
974  xferp->data_in[0] = 0;
975  xferp->data_in[1] = 10;
976  xferp->data_in[2] = 0; /* First track. */
977  xferp->data_in[3] = 0; /* Last track. */
978 
979  /* Track 0 data: */
980  xferp->data_in[4] = 0x00; /* Reserved. */
981  xferp->data_in[5] = 0x04; /* ADR + CTRL:
982  Data, not audio */
983  xferp->data_in[6] = 0x00; /* Track nr */
984  xferp->data_in[7] = 0x00; /* Reserved */
985  /* 8..11 = absolute CDROM address */
986 
987  diskimage__return_default_status_and_message(xferp);
988  break;
989 
991  /* (Patch from Håvard Eidnes.) */
992  debug("CDROM_READ_DISCINFO, cmd[1]=0x%02x", xferp->cmd[1]);
993  retlen = 34;
995  &xferp->data_in, retlen, 1);
996 
997  /* TODO: Make this more generic! */
998  xferp->data_in[0] = retlen-2; /* length of info, excl len */
999  xferp->data_in[1] = 0; /* length of info-(len field), msb */
1000  xferp->data_in[2] = 0xE; /* 11=complete ses., 10=fin disc */
1001  xferp->data_in[3] = 0; /* First track on disc */
1002  xferp->data_in[4] = 1; /* Number of sessions, lsb */
1003  xferp->data_in[5] = 0; /* first_track_last_session_lsb */
1004  xferp->data_in[6] = 0; /* last_track_last_session_lsb */
1005  xferp->data_in[7] = 0x20;/* various flags */
1006  xferp->data_in[8] = 0; /* CD-ROM disc */
1007  xferp->data_in[9] = 1; /* num sessions, msb */
1008  xferp->data_in[10] = 0; /* first_track_last_session_msb */
1009  xferp->data_in[11] = 0; /* last_track_last_session_msb */
1010 
1011  /* Lead-in data, for completed cd-rom: */
1012  for (size_t j=16; j<=23; j++)
1013  xferp->data_in[j] = 0xff;
1014 
1015  diskimage__return_default_status_and_message(xferp);
1016  break;
1017 
1019  /* (Patch from Håvard Eidnes.) */
1020  debug("CDROM_READ_TRACKINFO");
1021  retlen = 36;
1023  &xferp->data_in, retlen, 1);
1024 
1026 
1027  size = d->total_size / d->logical_block_size;
1028  if (d->total_size & (d->logical_block_size-1))
1029  size ++;
1030 
1031  /* TODO: Make more generic? */
1032  /* TODO: Don't use magic values. */
1033  xferp->data_in[0] = retlen-2; /* length of info, excl len */
1034  xferp->data_in[1] = 0; /* length of info, msb */
1035  xferp->data_in[2] = 1; /* track#, lsb */
1036  xferp->data_in[3] = 1; /* session#, lsb */
1037  xferp->data_in[4] = 0; /* reserved */
1038  xferp->data_in[5] = 0x6; /* trk mode: unintr. data,
1039  copyable */
1040  xferp->data_in[6] = 0x81; /* trk info: RT + trk mode */
1041  xferp->data_in[7] = 0x2; /* last rec=valid, next w=not
1042  valid */
1043  /*
1044  * track start, next writable, free blcks,
1045  * blocking factor
1046  */
1047  for(size_t j=8; j<=23; j++)
1048  xferp->data_in[j] = 0;
1049 
1050  /* Track size: */
1051  xferp->data_in[24] = (size >> 24) & 0xff;
1052  xferp->data_in[25] = (size >> 16) & 0xff;
1053  xferp->data_in[26] = (size >> 8) & 0xff;
1054  xferp->data_in[27] = size & 0xff;
1055 
1056  /* Last recorded address, only for dvd; zero out the rest: */
1057  for (size_t k=28; k<=35; k++)
1058  xferp->data_in[k] = 0;
1059 
1060  diskimage__return_default_status_and_message(xferp);
1061  break;
1062 
1063  case SCSICMD_MODE_SELECT:
1064  debug("[ SCSI MODE_SELECT: ");
1065 
1066  /*
1067  * TODO:
1068  *
1069  * This is super-hardcoded for NetBSD's usage of mode_select
1070  * to set the size of CDROM sectors to 2048.
1071  */
1072 
1073  if (xferp->data_out_offset == 0) {
1074  xferp->data_out_len = 12; /* TODO */
1075  debug("data_out == NULL, wanting %i bytes ]\n",
1076  (int)xferp->data_out_len);
1077  return 2;
1078  }
1079 
1080  debug("data_out!=NULL (OK), ");
1081 
1082  /* TODO: Care about cmd? */
1083 
1084  /* Set sector size to 2048: */
1085  /* 00 05 00 08 00 03 ca 40 00 00 08 00 */
1086  if (xferp->data_out[0] == 0x00 &&
1087  xferp->data_out[1] == 0x05 &&
1088  xferp->data_out[2] == 0x00 &&
1089  xferp->data_out[3] == 0x08) {
1090  d->logical_block_size =
1091  (xferp->data_out[9] << 16) +
1092  (xferp->data_out[10] << 8) +
1093  xferp->data_out[11];
1094  debug("[ setting logical_block_size to %i ]\n",
1095  d->logical_block_size);
1096  } else {
1097  int j;
1098  fatal("[ unknown MODE_SELECT: cmd =");
1099  for (j=0; j<(ssize_t)xferp->cmd_len; j++)
1100  fatal(" %02x", xferp->cmd[j]);
1101  fatal(", data_out =");
1102  for (j=0; j<(ssize_t)xferp->data_out_len; j++)
1103  fatal(" %02x", xferp->data_out[j]);
1104  fatal(" ]");
1105  }
1106 
1107  debug(" ]\n");
1108  diskimage__return_default_status_and_message(xferp);
1109  break;
1110 
1112  debug("[ SCSI 0x%02x Prevent/allow medium removal: "
1113  "TODO ]\n", xferp->cmd[0]);
1114 
1115  diskimage__return_default_status_and_message(xferp);
1116  break;
1117 
1118  case 0xbd:
1119  fatal("[ SCSI 0x%02x (len %i), TODO: ", xferp->cmd[0],
1120  xferp->cmd_len);
1121  for (i=0; i<(ssize_t)xferp->cmd_len; i++)
1122  fatal(" %02x", xferp->cmd[i]);
1123  fatal(" ]\n");
1124 
1125  /*
1126  * Used by Windows NT?
1127  *
1128  * Not documented in http://www.danbbs.dk/~dino/
1129  * SCSI/SCSI2-D.html.
1130  * Google gave the answer "MECHANISM_STATUS" for ATAPI. Hm.
1131  */
1132 
1133  if (xferp->cmd_len < 12) {
1134  fatal("WEIRD LEN?\n");
1135  retlen = 8;
1136  } else {
1137  retlen = xferp->cmd[8] * 256 + xferp->cmd[9];
1138  }
1139 
1140  /* Return data: */
1142  &xferp->data_in, retlen, 1);
1143 
1144  diskimage__return_default_status_and_message(xferp);
1145 
1146  break;
1147 
1148  default:
1149  debug("[ UNIMPLEMENTED SCSI command 0x%02x, disk id=%i",
1150  xferp->cmd[0], id);
1151  // exit(1);
1152  }
1153 
1154  debug(" ]\n");
1155 
1156  return 1;
1157 }
1158 
1159 
scsi_transfer::msg_out
unsigned char * msg_out
Definition: diskimage.h:97
SCSICMD_REQUEST_SENSE
#define SCSICMD_REQUEST_SENSE
Definition: diskimage.h:156
f
void f(int s, int func, int only_name)
Definition: generate_arm_r.c:45
diskimage::logical_block_size
int logical_block_size
Definition: diskimage.h:76
diskimage::fname
char * fname
Definition: diskimage.h:62
SCSIBLOCKCMD_READ_CAPACITY
#define SCSIBLOCKCMD_READ_CAPACITY
Definition: diskimage.h:172
SCSICMD_MODE_SENSE
#define SCSICMD_MODE_SENSE
Definition: diskimage.h:164
diskimage.h
SCSICMD_SYNCHRONIZE_CACHE
#define SCSICMD_SYNCHRONIZE_CACHE
Definition: diskimage.h:169
diskimage_scsicommand
int diskimage_scsicommand(struct cpu *cpu, int id, int type, struct scsi_transfer *xferp)
Definition: diskimage_scsicmd.cc:214
SCSICMD_MODE_SELECT
#define SCSICMD_MODE_SELECT
Definition: diskimage.h:163
debug
#define debug
Definition: dev_adb.cc:57
scsi_transfer::data_out_len
size_t data_out_len
Definition: diskimage.h:105
diskimage__internal_access
int diskimage__internal_access(struct diskimage *d, int writeflag, off_t offset, unsigned char *buf, size_t len)
Definition: diskimage.cc:558
diskimage::is_a_tape
int is_a_tape
Definition: diskimage.h:82
if
addr & if(addr >=0x24 &&page !=NULL)
Definition: tmp_arm_multi.cc:56
scsi_transfer_free
void scsi_transfer_free(struct scsi_transfer *p)
Definition: diskimage_scsicmd.cc:92
diskimage::id
int id
Definition: diskimage.h:59
DISKIMAGE_TYPES
#define DISKIMAGE_TYPES
Definition: diskimage.h:44
SCSICMD_INQUIRY
#define SCSICMD_INQUIRY
Definition: diskimage.h:157
SCSICMD_WRITE_10
#define SCSICMD_WRITE_10
Definition: diskimage.h:162
machine::first_diskimage
struct diskimage * first_diskimage
Definition: machine.h:142
SCSICDROM_READ_TOC
#define SCSICDROM_READ_TOC
Definition: diskimage.h:176
scsi_transfer
Definition: diskimage.h:93
scsi_transfer::status_len
size_t status_len
Definition: diskimage.h:114
single_step
volatile int single_step
Definition: debugger.cc:68
scsi_transfer::cmd_len
size_t cmd_len
Definition: diskimage.h:100
scsi_transfer::data_out
unsigned char * data_out
Definition: diskimage.h:104
SCSICMD_START_STOP_UNIT
#define SCSICMD_START_STOP_UNIT
Definition: diskimage.h:165
ENTER_SINGLE_STEPPING
#define ENTER_SINGLE_STEPPING
Definition: debugger.h:48
fatal
void fatal(const char *fmt,...)
Definition: main.cc:152
misc.h
diskimage::heads
int heads
Definition: diskimage.h:71
machine.h
machine
Definition: machine.h:97
SCSICDROM_READ_SUBCHANNEL
#define SCSICDROM_READ_SUBCHANNEL
Definition: diskimage.h:175
SCSICMD_MODE_SENSE10
#define SCSICMD_MODE_SENSE10
Definition: diskimage.h:167
SCSICMD_READ_10
#define SCSICMD_READ_10
Definition: diskimage.h:160
cpu.h
SCSICMD_REWIND
#define SCSICMD_REWIND
Definition: diskimage.h:181
SCSICMD_TEST_UNIT_READY
#define SCSICMD_TEST_UNIT_READY
Definition: diskimage.h:155
scsi_transfer::status
unsigned char * status
Definition: diskimage.h:113
diskimage::writable
int writable
Definition: diskimage.h:78
cpu::machine
struct machine * machine
Definition: cpu.h:328
diskimage::next
struct diskimage * next
Definition: diskimage.h:57
SCSICDROM_READ_DISCINFO
#define SCSICDROM_READ_DISCINFO
Definition: diskimage.h:177
SCSICDROM_READ_TRACKINFO
#define SCSICDROM_READ_TRACKINFO
Definition: diskimage.h:178
diskimage::total_size
off_t total_size
Definition: diskimage.h:74
diskimage::tape_filenr
int tape_filenr
Definition: diskimage.h:84
diskimage::f
FILE * f
Definition: diskimage.h:63
scsi_transfer_allocbuf
void scsi_transfer_allocbuf(size_t *lenp, unsigned char **pp, size_t want_len, int clearflag)
Definition: diskimage_scsicmd.cc:126
SCSICMD_READ
#define SCSICMD_READ
Definition: diskimage.h:159
scsi_transfer_alloc
struct scsi_transfer * scsi_transfer_alloc(void)
Definition: diskimage_scsicmd.cc:64
scsi_transfer::next_free
struct scsi_transfer * next_free
Definition: diskimage.h:94
diskimage::type
int type
Definition: diskimage.h:58
SCSICMD_PREVENT_ALLOW_REMOVE
#define SCSICMD_PREVENT_ALLOW_REMOVE
Definition: diskimage.h:166
MACHINE_PMAX
#define MACHINE_PMAX
Definition: machine.h:213
diskimage
Definition: diskimage.h:56
SCSICMD_WRITE
#define SCSICMD_WRITE
Definition: diskimage.h:161
machine::machine_type
int machine_type
Definition: machine.h:111
scsi_transfer::data_in_len
size_t data_in_len
Definition: diskimage.h:110
diskimage::tape_offset
uint64_t tape_offset
Definition: diskimage.h:83
scsi_transfer::data_out_offset
size_t data_out_offset
Definition: diskimage.h:106
diskimage::ncyls
int ncyls
Definition: diskimage.h:88
diskimage_recalc_size
void diskimage_recalc_size(struct diskimage *d)
Definition: diskimage.cc:165
SCSICMD_SPACE
#define SCSICMD_SPACE
Definition: diskimage.h:183
scsi_transfer::msg_in_len
size_t msg_in_len
Definition: diskimage.h:112
cpu
Definition: cpu.h:326
scsi_transfer::msg_in
unsigned char * msg_in
Definition: diskimage.h:111
diskimage::is_a_cdrom
int is_a_cdrom
Definition: diskimage.h:79
diskimage_getname
int diskimage_getname(struct machine *machine, int id, int type, char *buf, size_t bufsize)
Definition: diskimage.cc:1031
scsi_transfer::cmd
unsigned char * cmd
Definition: diskimage.h:99
scsi_transfer::data_in
unsigned char * data_in
Definition: diskimage.h:109
MACHINE_ARC
#define MACHINE_ARC
Definition: machine.h:218
diskimage::sectors_per_track
int sectors_per_track
Definition: diskimage.h:72
SCSICMD_READ_BLOCK_LIMITS
#define SCSICMD_READ_BLOCK_LIMITS
Definition: diskimage.h:182
diskimage::rpms
int rpms
Definition: diskimage.h:87
diskimage::filemark
int filemark
Definition: diskimage.h:85

Generated on Tue Aug 25 2020 19:25:06 for GXemul by doxygen 1.8.18