/[gxemul]/trunk/src/diskimage.c
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /trunk/src/diskimage.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 34 - (show annotations)
Mon Oct 8 16:21:17 2007 UTC (13 years, 1 month ago) by dpavlin
File MIME type: text/plain
File size: 47583 byte(s)
++ trunk/HISTORY	(local)
$Id: HISTORY,v 1.1480 2007/02/19 01:34:42 debug Exp $
20061029	Changing usleep(1) calls in the debugger to usleep(10000)
20061107	Adding a new disk image option (-d o...) which sets the ISO9660
		filesystem base offset; also making some other hacks to allow
		NetBSD/dreamcast and homebrew demos/games to boot directly
		from a filesystem image.
		Moving Dreamcast-specific stuff in the documentation to its
		own page (dreamcast.html).
		Adding a border to the Dreamcast PVR framebuffer.
20061108	Adding a -T command line option (again?), for halting the
		emulator on unimplemented memory accesses.
20061109	Continuing on various SH4 and Dreamcast related things.
		The emulator should now halt on more unimplemented device
		accesses, instead of just printing a warning, forcing me to
		actually implement missing stuff :)
20061111	Continuing on SH4 and Dreamcast stuff.
		Adding a bogus Landisk (SH4) machine mode.
20061112	Implementing some parts of the Dreamcast GDROM device. With
		some ugly hacks, NetBSD can (barely) mount an ISO image.
20061113	NetBSD/dreamcast now starts booting from the Live CD image,
		but crashes randomly quite early on in the boot process.
20061122	Beginning on a skeleton interrupt.h and interrupt.c for the
		new interrupt subsystem.
20061124	Continuing on the new interrupt system; taking the first steps
		to attempt to connect CPUs (SuperH and MIPS) and devices
		(dev_cons and SH4 timer interrupts) to it. Many things will
		probably break from now on.
20061125	Converting dev_ns16550, dev_8253 to the new interrupt system.
		Attempting to begin to convert the ISA bus.
20061130	Incorporating a patch from Brian Foley for the configure
		script, which checks for X11 libs in /usr/X11R6/lib64 (which
		is used on some Linux systems).
20061227	Adding a note in the man page about booting from Dreamcast
		CDROM images (i.e. that no external kernel is needed).
20061229	Continuing on the interrupt system rewrite: beginning to
		convert more devices, adding abort() calls for legacy interrupt
		system calls so that everything now _has_ to be rewritten!
		Almost all machine modes are now completely broken.
20061230	More progress on removing old interrupt code, mostly related
		to the ISA bus + devices, the LCA bus (on AlphaBook1), and
		the Footbridge bus (for CATS). And some minor PCI stuff.
		Connecting the ARM cpu to the new interrupt system.
		The CATS, NetWinder, and QEMU_MIPS machine modes now work with
		the new interrupt system :)
20061231	Connecting PowerPC CPUs to the new interrupt system.
		Making PReP machines (IBM 6050) work again.
		Beginning to convert the GT PCI controller (for e.g. Malta
		and Cobalt emulation). Some things work, but not everything.
		Updating Copyright notices for 2007.
20070101	Converting dev_kn02 from legacy style to devinit; the 3max
		machine mode now works with the new interrupt system :-]
20070105	Beginning to convert the SGI O2 machine to the new interrupt
		system; finally converting O2 (IP32) devices to devinit, etc.
20070106	Continuing on the interrupt system redesign/rewrite; KN01
		(PMAX), KN230, and Dreamcast ASIC interrupts should work again,
		moving out stuff from machine.h and devices.h into the
		corresponding devices, beginning the rewrite of i80321
		interrupts, etc.
20070107	Beginning on the rewrite of Eagle interrupt stuff (PReP, etc).
20070117	Beginning the rewrite of Algor (V3) interrupts (finally
		changing dev_v3 into devinit style).
20070118	Removing the "bus" registry concept from machine.h, because
		it was practically meaningless.
		Continuing on the rewrite of Algor V3 ISA interrupts.
20070121	More work on Algor interrupts; they are now working again,
		well enough to run NetBSD/algor. :-)
20070122	Converting VR41xx (HPCmips) interrupts. NetBSD/hpcmips
		can be installed using the new interrupt system :-)
20070123	Making the testmips mode work with the new interrupt system.
20070127	Beginning to convert DEC5800 devices to devinit, and to the
		new interrupt system.
		Converting Playstation 2 devices to devinit, and converting
		the interrupt system. Also fixing a severe bug: the interrupt
		mask register on Playstation 2 is bitwise _toggled_ on writes.
20070128	Removing the dummy NetGear machine mode and the 8250 device
		(which was only used by the NetGear machine).
		Beginning to convert the MacPPC GC (Grand Central) interrupt
		controller to the new interrupt system.
		Converting Jazz interrupts (PICA61 etc.) to the new interrupt
		system. NetBSD/arc can be installed again :-)
		Fixing the JAZZ timer (hardcoding it at 100 Hz, works with
		NetBSD and it is better than a completely dummy timer as it
		was before).
		Converting dev_mp to the new interrupt system, although I
		haven't had time to actually test it yet.
		Completely removing src/machines/interrupts.c, cpu_interrupt
		and cpu_interrupt_ack in src/cpu.c, and
		src/include/machine_interrupts.h! Adding fatal error messages
		+ abort() in the few places that are left to fix.
		Converting dev_z8530 to the new interrupt system.
		FINALLY removing the md_int struct completely from the
		machine struct.
		SH4 fixes (adding a PADDR invalidation in the ITLB replacement
		code in memory_sh.c); the NetBSD/dreamcast LiveCD now runs
		all the way to the login prompt, and can be interacted with :-)
		Converting the CPC700 controller (PCI and interrupt controller
		for PM/PPC) to the new interrupt system.
20070129	Fixing MACE ISA interrupts (SGI IP32 emulation). Both NetBSD/
		sgimips' and OpenBSD/sgi's ramdisk kernels can now be
		interacted with again.
20070130	Moving out the MIPS multi_lw and _sw instruction combinations
		so that they are auto-generated at compile time instead.
20070131	Adding detection of amd64/x86_64 hosts in the configure script,
		for doing initial experiments (again :-) with native code
		generation.
		Adding a -k command line option to set the size of the dyntrans
		cache, and a -B command line option to disable native code
		generation, even if GXemul was compiled with support for
		native code generation for the specific host CPU architecture.
20070201	Experimenting with a skeleton for native code generation.
		Changing the default behaviour, so that native code generation
		is now disabled by default, and has to be enabled by using
		-b on the command line.
20070202	Continuing the native code generation experiments.
		Making PCI interrupts work for Footbridge again.
20070203	More native code generation experiments.
		Removing most of the native code generation experimental code,
		it does not make sense to include any quick hacks like this.
		Minor cleanup/removal of some more legacy MIPS interrupt code.
20070204	Making i80321 interrupts work again (for NetBSD/evbarm etc.),
		and fixing the timer at 100 Hz.
20070206	Experimenting with removing the wdc interrupt slowness hack.
20070207	Lowering the number of dyntrans TLB entries for MIPS from
		192 to 128, resulting in a minor speed improvement.
		Minor optimization to the code invalidation routine in
		cpu_dyntrans.c.
20070208	Increasing (experimentally) the nr of dyntrans instructions per
		loop from 60 to 120.
20070210	Commenting out (experimentally) the dyntrans_device_danger
		detection in memory_rw.c.
		Changing the testmips and baremips machines to use a revision 2
		MIPS64 CPU by default, instead of revision 1.
		Removing the dummy i960, IA64, x86, AVR32, and HP PA-RISC
		files, the PC bios emulation, and the Olivetti M700 (ARC) and
		db64360 emulation modes.
20070211	Adding an "mp" demo to the demos directory, which tests the
		SMP functionality of the testmips machine.
		Fixing PReP interrupts some more. NetBSD/prep now boots again.
20070216	Adding a "nop workaround" for booting Mach/PMAX to the
		documentation; thanks to Artur Bujdoso for the values.
		Converting more of the MacPPC interrupt stuff to the new
		system.
		Beginning to convert BeBox interrupts to the new system.
		PPC603e should NOT have the PPC_NO_DEC flag! Removing it.
		Correcting BeBox clock speed (it was set to 100 in the NetBSD
		bootinfo block, but should be 33000000/4), allowing NetBSD
		to start without using the (incorrect) PPC_NO_DEC hack.
20070217	Implementing (slow) AltiVec vector loads and stores, allowing
		NetBSD/macppc to finally boot using the GENERIC kernel :-)
		Updating the documentation with install instructions for
		NetBSD/macppc.
20070218-19	Regression testing for the release.

==============  RELEASE 0.4.4  ==============


1 /*
2 * Copyright (C) 2003-2007 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 * $Id: diskimage.c,v 1.116 2006/12/30 13:30:51 debug Exp $
29 *
30 * Disk image support.
31 *
32 * TODO: There's probably a bug in the tape support:
33 * Let's say there are 10240 bytes left in a file, and 10240
34 * bytes are read. Then feof() is not true yet (?), so the next
35 * read will also return 10240 bytes (but all zeroes), and then after
36 * that return feof (which results in a filemark). This is probably
37 * trivial to fix, but I don't feel like it right now.
38 *
39 * TODO: diskimage_remove()? This would be useful for floppies in PC-style
40 * machines, where disks may need to be swapped during boot etc.
41 */
42
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 #include <sys/types.h>
48 #include <sys/stat.h>
49
50 #include "cpu.h"
51 #include "diskimage.h"
52 #include "machine.h"
53 #include "misc.h"
54
55
56 /* #define debug fatal */
57
58 extern int single_step;
59
60 static char *diskimage_types[] = DISKIMAGE_TYPES;
61
62 static struct scsi_transfer *first_free_scsi_transfer_alloc = NULL;
63
64
65 /**************************************************************************/
66
67 /*
68 * my_fseek():
69 *
70 * A helper function, like fseek() but takes off_t. If the system has
71 * fseeko, then that is used. Otherwise I try to fake off_t offsets here.
72 *
73 * The correct position is reached by seeking 2 billion bytes at a time
74 * (or less). Note: This method is only used for SEEK_SET, for SEEK_CUR
75 * and SEEK_END, normal fseek() is used!
76 *
77 * TODO: It seemed to work on Linux/i386, but not on Solaris/sparc (?).
78 * Anyway, most modern systems have fseeko(), so it shouldn't be a problem.
79 */
80 static int my_fseek(FILE *f, off_t offset, int whence)
81 {
82 #ifdef HACK_FSEEKO
83 if (whence == SEEK_SET) {
84 int res = 0;
85 off_t curoff = 0;
86 off_t cur_step;
87
88 fseek(f, 0, SEEK_SET);
89 while (curoff < offset) {
90 /* How far to seek? */
91 cur_step = offset - curoff;
92 if (cur_step > 2000000000)
93 cur_step = 2000000000;
94 res = fseek(f, cur_step, SEEK_CUR);
95 if (res)
96 return res;
97 curoff += cur_step;
98 }
99 return 0;
100 } else
101 return fseek(f, offset, whence);
102 #else
103 return fseeko(f, offset, whence);
104 #endif
105 }
106
107
108 /**************************************************************************/
109
110
111 /*
112 * scsi_transfer_alloc():
113 *
114 * Allocates memory for a new scsi_transfer struct, and fills it with
115 * sane data (NULL pointers).
116 * The return value is a pointer to the new struct. If allocation
117 * failed, the program exits.
118 */
119 struct scsi_transfer *scsi_transfer_alloc(void)
120 {
121 struct scsi_transfer *p;
122
123 if (first_free_scsi_transfer_alloc != NULL) {
124 p = first_free_scsi_transfer_alloc;
125 first_free_scsi_transfer_alloc = p->next_free;
126 } else {
127 p = malloc(sizeof(struct scsi_transfer));
128 if (p == NULL) {
129 fprintf(stderr, "scsi_transfer_alloc(): out "
130 "of memory\n");
131 exit(1);
132 }
133 }
134
135 memset(p, 0, sizeof(struct scsi_transfer));
136
137 return p;
138 }
139
140
141 /*
142 * scsi_transfer_free():
143 *
144 * Frees the space used by a scsi_transfer struct. All buffers refered
145 * to by the scsi_transfer struct are freed.
146 */
147 void scsi_transfer_free(struct scsi_transfer *p)
148 {
149 if (p == NULL) {
150 fprintf(stderr, "scsi_transfer_free(): p == NULL\n");
151 exit(1);
152 }
153
154 if (p->msg_out != NULL)
155 free(p->msg_out);
156 if (p->cmd != NULL)
157 free(p->cmd);
158 if (p->data_out != NULL)
159 free(p->data_out);
160
161 if (p->data_in != NULL)
162 free(p->data_in);
163 if (p->msg_in != NULL)
164 free(p->msg_in);
165 if (p->status != NULL)
166 free(p->status);
167
168 p->next_free = first_free_scsi_transfer_alloc;
169 first_free_scsi_transfer_alloc = p;
170 }
171
172
173 /*
174 * scsi_transfer_allocbuf():
175 *
176 * Helper function, used by diskimage_scsicommand(), and SCSI controller
177 * devices. Example of usage:
178 *
179 * scsi_transfer_allocbuf(&xferp->msg_in_len, &xferp->msg_in, 1);
180 */
181 void scsi_transfer_allocbuf(size_t *lenp, unsigned char **pp, size_t want_len,
182 int clearflag)
183 {
184 unsigned char *p = (*pp);
185
186 if (p != NULL) {
187 printf("WARNING! scsi_transfer_allocbuf(): old pointer "
188 "was not NULL, freeing it now\n");
189 free(p);
190 }
191
192 (*lenp) = want_len;
193 if ((p = malloc(want_len)) == NULL) {
194 fprintf(stderr, "scsi_transfer_allocbuf(): out of "
195 "memory trying to allocate %li bytes\n", (long)want_len);
196 exit(1);
197 }
198
199 if (clearflag)
200 memset(p, 0, want_len);
201
202 (*pp) = p;
203 }
204
205
206 /**************************************************************************/
207
208
209 /*
210 * diskimage_exist():
211 *
212 * Returns 1 if the specified disk id (for a specific type) exists, 0
213 * otherwise.
214 */
215 int diskimage_exist(struct machine *machine, int id, int type)
216 {
217 struct diskimage *d = machine->first_diskimage;
218
219 while (d != NULL) {
220 if (d->type == type && d->id == id)
221 return 1;
222 d = d->next;
223 }
224 return 0;
225 }
226
227
228 /*
229 * diskimage_recalc_size():
230 *
231 * Recalculate a disk's size by stat()-ing it.
232 * d is assumed to be non-NULL.
233 */
234 static void diskimage_recalc_size(struct diskimage *d)
235 {
236 struct stat st;
237 int res;
238 off_t size = 0;
239
240 res = stat(d->fname, &st);
241 if (res) {
242 fprintf(stderr, "[ diskimage_recalc_size(): could not stat "
243 "'%s' ]\n", d->fname);
244 return;
245 }
246
247 size = st.st_size;
248
249 /*
250 * TODO: CD-ROM devices, such as /dev/cd0c, how can one
251 * check how much data is on that cd-rom without reading it?
252 * For now, assume some large number, hopefully it will be
253 * enough to hold any cd-rom image.
254 */
255 if (d->is_a_cdrom && size == 0)
256 size = 762048000;
257
258 d->total_size = size;
259 d->ncyls = d->total_size / 1048576;
260
261 /* TODO: There is a mismatch between d->ncyls and d->cylinders,
262 SCSI-based stuff usually doesn't care. TODO: Fix this. */
263 }
264
265
266 /*
267 * diskimage_getsize():
268 *
269 * Returns -1 if the specified disk id/type does not exists, otherwise
270 * the size of the disk image is returned.
271 */
272 int64_t diskimage_getsize(struct machine *machine, int id, int type)
273 {
274 struct diskimage *d = machine->first_diskimage;
275
276 while (d != NULL) {
277 if (d->type == type && d->id == id)
278 return d->total_size;
279 d = d->next;
280 }
281 return -1;
282 }
283
284
285 /*
286 * diskimage_get_baseoffset():
287 *
288 * Returns -1 if the specified disk id/type does not exists, otherwise
289 * the base offset of the disk image is returned.
290 */
291 int64_t diskimage_get_baseoffset(struct machine *machine, int id, int type)
292 {
293 struct diskimage *d = machine->first_diskimage;
294
295 while (d != NULL) {
296 if (d->type == type && d->id == id)
297 return d->override_base_offset;
298 d = d->next;
299 }
300 return -1;
301 }
302
303
304 /*
305 * diskimage_getchs():
306 *
307 * Returns the current CHS values of a disk image.
308 */
309 void diskimage_getchs(struct machine *machine, int id, int type,
310 int *c, int *h, int *s)
311 {
312 struct diskimage *d = machine->first_diskimage;
313
314 while (d != NULL) {
315 if (d->type == type && d->id == id) {
316 *c = d->cylinders;
317 *h = d->heads;
318 *s = d->sectors_per_track;
319 return;
320 }
321 d = d->next;
322 }
323 fatal("diskimage_getchs(): disk id %i (type %i) not found?\n",
324 id, diskimage_types[type]);
325 exit(1);
326 }
327
328
329 /*
330 * diskimage__return_default_status_and_message():
331 *
332 * Set the status and msg_in parts of a scsi_transfer struct
333 * to default values (msg_in = 0x00, status = 0x00).
334 */
335 static void diskimage__return_default_status_and_message(
336 struct scsi_transfer *xferp)
337 {
338 scsi_transfer_allocbuf(&xferp->status_len, &xferp->status, 1, 0);
339 xferp->status[0] = 0x00;
340 scsi_transfer_allocbuf(&xferp->msg_in_len, &xferp->msg_in, 1, 0);
341 xferp->msg_in[0] = 0x00;
342 }
343
344
345 /*
346 * diskimage__switch_tape():
347 *
348 * Used by the SPACE command. (d is assumed to be non-NULL.)
349 */
350 static void diskimage__switch_tape(struct diskimage *d)
351 {
352 char tmpfname[1000];
353
354 snprintf(tmpfname, sizeof(tmpfname), "%s.%i",
355 d->fname, d->tape_filenr);
356 tmpfname[sizeof(tmpfname)-1] = '\0';
357
358 if (d->f != NULL)
359 fclose(d->f);
360
361 d->f = fopen(tmpfname, d->writable? "r+" : "r");
362 if (d->f == NULL) {
363 fprintf(stderr, "[ diskimage__switch_tape(): could not "
364 "(re)open '%s' ]\n", tmpfname);
365 /* TODO: return error */
366 }
367 d->tape_offset = 0;
368 }
369
370
371 /*
372 * diskimage_access__cdrom():
373 *
374 * This is a special-case function, called from diskimage__internal_access().
375 * On my FreeBSD 4.9 system, the cdrom device /dev/cd0c seems to not be able
376 * to handle something like "fseek(512); fread(512);" but it handles
377 * "fseek(2048); fread(512);" just fine. So, if diskimage__internal_access()
378 * fails in reading a block of data, this function is called as an attempt to
379 * align reads at 2048-byte sectors instead.
380 *
381 * (Ugly hack. TODO: how to solve this cleanly?)
382 *
383 * NOTE: Returns the number of bytes read, 0 if nothing was successfully
384 * read. (These are not the same as diskimage_access()).
385 */
386 #define CDROM_SECTOR_SIZE 2048
387 static size_t diskimage_access__cdrom(struct diskimage *d, off_t offset,
388 unsigned char *buf, size_t len)
389 {
390 off_t aligned_offset;
391 size_t bytes_read, total_copied = 0;
392 unsigned char cdrom_buf[CDROM_SECTOR_SIZE];
393 off_t buf_ofs, i = 0;
394
395 /* printf("diskimage_access__cdrom(): offset=0x%llx size=%lli\n",
396 (long long)offset, (long long)len); */
397
398 aligned_offset = (offset / CDROM_SECTOR_SIZE) * CDROM_SECTOR_SIZE;
399 my_fseek(d->f, aligned_offset, SEEK_SET);
400
401 while (len != 0) {
402 bytes_read = fread(cdrom_buf, 1, CDROM_SECTOR_SIZE, d->f);
403 if (bytes_read != CDROM_SECTOR_SIZE)
404 return 0;
405
406 /* Copy (part of) cdrom_buf into buf: */
407 buf_ofs = offset - aligned_offset;
408 while (buf_ofs < CDROM_SECTOR_SIZE && len != 0) {
409 buf[i ++] = cdrom_buf[buf_ofs ++];
410 total_copied ++;
411 len --;
412 }
413
414 aligned_offset += CDROM_SECTOR_SIZE;
415 offset = aligned_offset;
416 }
417
418 return total_copied;
419 }
420
421
422 /*
423 * diskimage__internal_access():
424 *
425 * Read from or write to a struct diskimage.
426 *
427 * Returns 1 if the access completed successfully, 0 otherwise.
428 */
429 static int diskimage__internal_access(struct diskimage *d, int writeflag,
430 off_t offset, unsigned char *buf, size_t len)
431 {
432 ssize_t lendone;
433 int res;
434
435 if (buf == NULL) {
436 fprintf(stderr, "diskimage__internal_access(): buf = NULL\n");
437 exit(1);
438 }
439 if (len == 0)
440 return 1;
441 if (d->f == NULL)
442 return 0;
443
444 res = my_fseek(d->f, offset, SEEK_SET);
445 if (res != 0) {
446 fatal("[ diskimage__internal_access(): fseek() failed on "
447 "disk id %i \n", d->id);
448 return 0;
449 }
450
451 if (writeflag) {
452 if (!d->writable)
453 return 0;
454
455 lendone = fwrite(buf, 1, len, d->f);
456 } else {
457 /*
458 * Special case for CD-ROMs. Actually, this is not needed
459 * for .iso images, only for physical CDROMS on some OSes,
460 * such as FreeBSD.
461 */
462 if (d->is_a_cdrom)
463 lendone = diskimage_access__cdrom(d, offset, buf, len);
464 else
465 lendone = fread(buf, 1, len, d->f);
466
467 if (lendone < (ssize_t)len)
468 memset(buf + lendone, 0, len - lendone);
469 }
470
471 /* Warn about non-complete data transfers: */
472 if (lendone != (ssize_t)len) {
473 #ifdef UNSTABLE_DEVEL
474 fatal("[ diskimage__internal_access(): disk_id %i, offset %lli"
475 ", transfer not completed. len=%i, len_done=%i ]\n",
476 d->id, (long long)offset, (int)len, (int)lendone);
477 #endif
478 return 0;
479 }
480
481 return 1;
482 }
483
484
485 /*
486 * diskimage_scsicommand():
487 *
488 * Perform a SCSI command on a disk image.
489 *
490 * The xferp points to a scsi_transfer struct, containing msg_out, command,
491 * and data_out coming from the SCSI controller device. This function
492 * interprets the command, and (if necessary) creates responses in
493 * data_in, msg_in, and status.
494 *
495 * Returns:
496 * 2 if the command expects data from the DATA_OUT phase,
497 * 1 if otherwise ok,
498 * 0 on error.
499 */
500 int diskimage_scsicommand(struct cpu *cpu, int id, int type,
501 struct scsi_transfer *xferp)
502 {
503 char namebuf[16];
504 int retlen, i, q;
505 uint64_t size;
506 int64_t ofs;
507 int pagecode;
508 struct machine *machine = cpu->machine;
509 struct diskimage *d;
510
511 if (machine == NULL) {
512 fatal("[ diskimage_scsicommand(): machine == NULL ]\n");
513 return 0;
514 }
515
516 d = machine->first_diskimage;
517 while (d != NULL) {
518 if (d->type == type && d->id == id)
519 break;
520 d = d->next;
521 }
522 if (d == NULL) {
523 fprintf(stderr, "[ diskimage_scsicommand(): %s "
524 " id %i not connected? ]\n", diskimage_types[type], id);
525 }
526
527 if (xferp->cmd == NULL) {
528 fatal("[ diskimage_scsicommand(): cmd == NULL ]\n");
529 return 0;
530 }
531
532 if (xferp->cmd_len < 1) {
533 fatal("[ diskimage_scsicommand(): cmd_len == %i ]\n",
534 xferp->cmd_len);
535 return 0;
536 }
537
538 debug("[ diskimage_scsicommand(id=%i) cmd=0x%02x: ",
539 id, xferp->cmd[0]);
540
541 #if 0
542 fatal("[ diskimage_scsicommand(id=%i) cmd=0x%02x len=%i:",
543 id, xferp->cmd[0], xferp->cmd_len);
544 for (i=0; i<xferp->cmd_len; i++)
545 fatal(" %02x", xferp->cmd[i]);
546 fatal("\n");
547 if (xferp->cmd_len > 7 && xferp->cmd[5] == 0x11)
548 single_step = ENTER_SINGLE_STEPPING;
549 #endif
550
551 #if 0
552 {
553 static FILE *f = NULL;
554 if (f == NULL)
555 f = fopen("scsi_log.txt", "w");
556 if (f != NULL) {
557 int i;
558 fprintf(f, "id=%i cmd =", id);
559 for (i=0; i<xferp->cmd_len; i++)
560 fprintf(f, " %02x", xferp->cmd[i]);
561 fprintf(f, "\n");
562 fflush(f);
563 }
564 }
565 #endif
566
567 switch (xferp->cmd[0]) {
568
569 case SCSICMD_TEST_UNIT_READY:
570 debug("TEST_UNIT_READY");
571 if (xferp->cmd_len != 6)
572 debug(" (weird len=%i)", xferp->cmd_len);
573
574 /* TODO: bits 765 of buf[1] contains the LUN */
575 if (xferp->cmd[1] != 0x00)
576 fatal("WARNING: TEST_UNIT_READY with cmd[1]=0x%02x"
577 " not yet implemented\n", (int)xferp->cmd[1]);
578
579 diskimage__return_default_status_and_message(xferp);
580 break;
581
582 case SCSICMD_INQUIRY:
583 debug("INQUIRY");
584 if (xferp->cmd_len != 6)
585 debug(" (weird len=%i)", xferp->cmd_len);
586 if (xferp->cmd[1] != 0x00) {
587 debug("WARNING: INQUIRY with cmd[1]=0x%02x not yet "
588 "implemented\n", (int)xferp->cmd[1]);
589
590 break;
591 }
592
593 /* Return values: */
594 retlen = xferp->cmd[4];
595 if (retlen < 36) {
596 fatal("WARNING: SCSI inquiry len=%i, <36!\n", retlen);
597 retlen = 36;
598 }
599
600 /* Return data: */
601 scsi_transfer_allocbuf(&xferp->data_in_len, &xferp->data_in,
602 retlen, 1);
603 xferp->data_in[0] = 0x00; /* 0x00 = Direct-access disk */
604 xferp->data_in[1] = 0x00; /* 0x00 = non-removable */
605 xferp->data_in[2] = 0x02; /* SCSI-2 */
606 #if 0
607 xferp->data_in[3] = 0x02; /* Response data format = SCSI-2 */
608 #endif
609 xferp->data_in[4] = retlen - 4; /* Additional length */
610 xferp->data_in[4] = 0x2c - 4; /* Additional length */
611 xferp->data_in[6] = 0x04; /* ACKREQQ */
612 xferp->data_in[7] = 0x60; /* WBus32, WBus16 */
613
614 /* These are padded with spaces: */
615
616 memcpy(xferp->data_in+8, "GXemul ", 8);
617 if (diskimage_getname(cpu->machine, id,
618 type, namebuf, sizeof(namebuf))) {
619 size_t i;
620 for (i=0; i<sizeof(namebuf); i++)
621 if (namebuf[i] == 0) {
622 for (; i<sizeof(namebuf); i++)
623 namebuf[i] = ' ';
624 break;
625 }
626 memcpy(xferp->data_in+16, namebuf, 16);
627 } else
628 memcpy(xferp->data_in+16, "DISK ", 16);
629 memcpy(xferp->data_in+32, "0 ", 4);
630
631 /*
632 * Some Ultrix kernels want specific responses from
633 * the drives.
634 */
635
636 if (machine->machine_type == MACHINE_PMAX) {
637 /* DEC, RZ25 (rev 0900) = 832527 sectors */
638 /* DEC, RZ58 (rev 2000) = 2698061 sectors */
639 memcpy(xferp->data_in+8, "DEC ", 8);
640 memcpy(xferp->data_in+16, "RZ58 (C) DEC", 16);
641 memcpy(xferp->data_in+32, "2000", 4);
642 }
643
644 /* Some data is different for CD-ROM drives: */
645 if (d->is_a_cdrom) {
646 xferp->data_in[0] = 0x05; /* 0x05 = CD-ROM */
647 xferp->data_in[1] = 0x80; /* 0x80 = removable */
648 /* memcpy(xferp->data_in+16, "CD-ROM ", 16);*/
649
650 if (machine->machine_type == MACHINE_PMAX) {
651 /* SONY, CD-ROM: */
652 memcpy(xferp->data_in+8, "SONY ", 8);
653 memcpy(xferp->data_in+16,
654 "CD-ROM ", 16);
655
656 /* ... or perhaps this: */
657 memcpy(xferp->data_in+8, "DEC ", 8);
658 memcpy(xferp->data_in+16,
659 "RRD42 (C) DEC ", 16);
660 memcpy(xferp->data_in+32, "4.5d", 4);
661 } else if (machine->machine_type == MACHINE_ARC) {
662 /* NEC, CD-ROM: */
663 memcpy(xferp->data_in+8, "NEC ", 8);
664 memcpy(xferp->data_in+16,
665 "CD-ROM CDR-210P ", 16);
666 memcpy(xferp->data_in+32, "1.0 ", 4);
667 }
668 }
669
670 /* Data for tape devices: */
671 if (d->is_a_tape) {
672 xferp->data_in[0] = 0x01; /* 0x01 = tape */
673 xferp->data_in[1] = 0x80; /* 0x80 = removable */
674 memcpy(xferp->data_in+16, "TAPE ", 16);
675
676 if (machine->machine_type == MACHINE_PMAX) {
677 /*
678 * TODO: find out if these are correct.
679 *
680 * The name might be TZK10, TSZ07, or TLZ04,
681 * or something completely different.
682 */
683 memcpy(xferp->data_in+8, "DEC ", 8);
684 memcpy(xferp->data_in+16,
685 "TK50 (C) DEC", 16);
686 memcpy(xferp->data_in+32, "2000", 4);
687 }
688 }
689
690 diskimage__return_default_status_and_message(xferp);
691 break;
692
693 case SCSIBLOCKCMD_READ_CAPACITY:
694 debug("READ_CAPACITY");
695
696 if (xferp->cmd_len != 10)
697 fatal(" [ weird READ_CAPACITY len=%i, should be 10 ] ",
698 xferp->cmd_len);
699 else {
700 if (xferp->cmd[8] & 1) {
701 /* Partial Medium Indicator bit... TODO */
702 fatal("WARNING: READ_CAPACITY with PMI bit"
703 " set not yet implemented\n");
704 }
705 }
706
707 /* Return data: */
708 scsi_transfer_allocbuf(&xferp->data_in_len, &xferp->data_in,
709 8, 1);
710
711 diskimage_recalc_size(d);
712
713 size = d->total_size / d->logical_block_size;
714 if (d->total_size & (d->logical_block_size-1))
715 size ++;
716
717 xferp->data_in[0] = (size >> 24) & 255;
718 xferp->data_in[1] = (size >> 16) & 255;
719 xferp->data_in[2] = (size >> 8) & 255;
720 xferp->data_in[3] = size & 255;
721
722 xferp->data_in[4] = (d->logical_block_size >> 24) & 255;
723 xferp->data_in[5] = (d->logical_block_size >> 16) & 255;
724 xferp->data_in[6] = (d->logical_block_size >> 8) & 255;
725 xferp->data_in[7] = d->logical_block_size & 255;
726
727 diskimage__return_default_status_and_message(xferp);
728 break;
729
730 case SCSICMD_MODE_SENSE:
731 case SCSICMD_MODE_SENSE10:
732 debug("MODE_SENSE");
733 q = 4; retlen = xferp->cmd[4];
734 switch (xferp->cmd_len) {
735 case 6: break;
736 case 10:q = 8;
737 retlen = xferp->cmd[7] * 256 + xferp->cmd[8];
738 break;
739 default:fatal(" (unimplemented mode_sense len=%i)",
740 xferp->cmd_len);
741 }
742
743 /*
744 * NOTE/TODO: This code doesn't handle too short retlens
745 * very well. A quick hack around this is that I allocate
746 * a bit too much memory, so that nothing is actually
747 * written outside of xferp->data_in[].
748 */
749
750 retlen += 100; /* Should be enough. (Ugly.) */
751
752 if ((xferp->cmd[2] & 0xc0) != 0)
753 fatal("WARNING: mode sense, cmd[2] = 0x%02x\n",
754 xferp->cmd[2]);
755
756 /* Return data: */
757 scsi_transfer_allocbuf(&xferp->data_in_len,
758 &xferp->data_in, retlen, 1);
759
760 xferp->data_in_len -= 100; /* Restore size. */
761
762 pagecode = xferp->cmd[2] & 0x3f;
763
764 debug("[ MODE SENSE id %i, pagecode=%i ]\n", id, pagecode);
765
766 /* 4 bytes of header for 6-byte command,
767 8 bytes of header for 10-byte command. */
768 xferp->data_in[0] = retlen; /* 0: mode data length */
769 xferp->data_in[1] = d->is_a_cdrom? 0x05 : 0x00;
770 /* 1: medium type */
771 xferp->data_in[2] = 0x00; /* device specific
772 parameter */
773 xferp->data_in[3] = 8 * 1; /* block descriptor
774 length: 1 page (?) */
775
776 xferp->data_in[q+0] = 0x00; /* density code */
777 xferp->data_in[q+1] = 0; /* nr of blocks, high */
778 xferp->data_in[q+2] = 0; /* nr of blocks, mid */
779 xferp->data_in[q+3] = 0; /* nr of blocks, low */
780 xferp->data_in[q+4] = 0x00; /* reserved */
781 xferp->data_in[q+5] = (d->logical_block_size >> 16) & 255;
782 xferp->data_in[q+6] = (d->logical_block_size >> 8) & 255;
783 xferp->data_in[q+7] = d->logical_block_size & 255;
784 q += 8;
785
786 diskimage__return_default_status_and_message(xferp);
787
788 /* descriptors, 8 bytes (each) */
789
790 /* page, n bytes (each) */
791 switch (pagecode) {
792 case 0:
793 /* TODO: Nothing here? */
794 break;
795 case 1: /* read-write error recovery page */
796 xferp->data_in[q + 0] = pagecode;
797 xferp->data_in[q + 1] = 10;
798 break;
799 case 3: /* format device page */
800 xferp->data_in[q + 0] = pagecode;
801 xferp->data_in[q + 1] = 22;
802
803 /* 10,11 = sectors per track */
804 xferp->data_in[q + 10] = 0;
805 xferp->data_in[q + 11] = d->sectors_per_track;
806
807 /* 12,13 = physical sector size */
808 xferp->data_in[q + 12] =
809 (d->logical_block_size >> 8) & 255;
810 xferp->data_in[q + 13] = d->logical_block_size & 255;
811 break;
812 case 4: /* rigid disk geometry page */
813 xferp->data_in[q + 0] = pagecode;
814 xferp->data_in[q + 1] = 22;
815 xferp->data_in[q + 2] = (d->ncyls >> 16) & 255;
816 xferp->data_in[q + 3] = (d->ncyls >> 8) & 255;
817 xferp->data_in[q + 4] = d->ncyls & 255;
818 xferp->data_in[q + 5] = d->heads;
819
820 xferp->data_in[q + 20] = (d->rpms >> 8) & 255;
821 xferp->data_in[q + 21] = d->rpms & 255;
822 break;
823 case 5: /* flexible disk page */
824 xferp->data_in[q + 0] = pagecode;
825 xferp->data_in[q + 1] = 0x1e;
826
827 /* 2,3 = transfer rate */
828 xferp->data_in[q + 2] = ((5000) >> 8) & 255;
829 xferp->data_in[q + 3] = (5000) & 255;
830
831 xferp->data_in[q + 4] = d->heads;
832 xferp->data_in[q + 5] = d->sectors_per_track;
833
834 /* 6,7 = data bytes per sector */
835 xferp->data_in[q + 6] = (d->logical_block_size >> 8)
836 & 255;
837 xferp->data_in[q + 7] = d->logical_block_size & 255;
838
839 xferp->data_in[q + 8] = (d->ncyls >> 8) & 255;
840 xferp->data_in[q + 9] = d->ncyls & 255;
841
842 xferp->data_in[q + 28] = (d->rpms >> 8) & 255;
843 xferp->data_in[q + 29] = d->rpms & 255;
844 break;
845 default:
846 fatal("[ MODE_SENSE for page %i is not yet "
847 "implemented! ]\n", pagecode);
848 }
849
850 break;
851
852 case SCSICMD_READ:
853 case SCSICMD_READ_10:
854 debug("READ");
855
856 /*
857 * For tape devices, read data at the current position.
858 * For disk and CDROM devices, the command bytes contain
859 * an offset telling us where to read from the device.
860 */
861
862 if (d->is_a_tape) {
863 /* bits 7..5 of cmd[1] are the LUN bits... TODO */
864
865 size = (xferp->cmd[2] << 16) +
866 (xferp->cmd[3] << 8) +
867 xferp->cmd[4];
868
869 /* Bit 1 of cmd[1] is the SILI bit (TODO), and
870 bit 0 is the "use fixed length" bit. */
871
872 if (xferp->cmd[1] & 0x01) {
873 /* Fixed block length: */
874 size *= d->logical_block_size;
875 }
876
877 if (d->filemark) {
878 /* At end of file, switch to the next
879 automagically: */
880 d->tape_filenr ++;
881 diskimage__switch_tape(d);
882
883 d->filemark = 0;
884 }
885
886 ofs = d->tape_offset;
887
888 fatal("[ READ tape, id=%i file=%i, cmd[1]=%02x size=%i"
889 ", ofs=%lli ]\n", id, d->tape_filenr,
890 xferp->cmd[1], (int)size, (long long)ofs);
891 } else {
892 if (xferp->cmd[0] == SCSICMD_READ) {
893 if (xferp->cmd_len != 6)
894 debug(" (weird len=%i)",
895 xferp->cmd_len);
896
897 /*
898 * bits 4..0 of cmd[1], and cmd[2] and cmd[3]
899 * hold the logical block address.
900 *
901 * cmd[4] holds the number of logical blocks
902 * to transfer. (Special case if the value is
903 * 0, actually means 256.)
904 */
905 ofs = ((xferp->cmd[1] & 0x1f) << 16) +
906 (xferp->cmd[2] << 8) + xferp->cmd[3];
907 retlen = xferp->cmd[4];
908 if (retlen == 0)
909 retlen = 256;
910 } else {
911 if (xferp->cmd_len != 10)
912 debug(" (weird len=%i)",
913 xferp->cmd_len);
914
915 /*
916 * cmd[2..5] hold the logical block address.
917 * cmd[7..8] holds the number of logical
918 * blocks to transfer. (NOTE: If the value is
919 * 0, this means 0, not 65536. :-)
920 */
921 ofs = ((uint64_t)xferp->cmd[2] << 24) +
922 (xferp->cmd[3] << 16) + (xferp->cmd[4] << 8)
923 + xferp->cmd[5];
924 retlen = (xferp->cmd[7] << 8) + xferp->cmd[8];
925 }
926
927 size = retlen * d->logical_block_size;
928 ofs *= d->logical_block_size;
929 }
930
931 /* Return data: */
932 scsi_transfer_allocbuf(&xferp->data_in_len, &xferp->data_in,
933 size, 0);
934
935 debug(" READ ofs=%lli size=%i\n", (long long)ofs, (int)size);
936
937 diskimage__return_default_status_and_message(xferp);
938
939 d->filemark = 0;
940
941 /*
942 * Failure? Then set check condition.
943 * For tapes, error should only occur at the end of a file.
944 *
945 * "If the logical unit encounters a filemark during
946 * a READ command, CHECK CONDITION status shall be
947 * returned and the filemark and valid bits shall be
948 * set to one in the sense data. The sense key shall
949 * be set to NO SENSE"..
950 */
951 if (d->is_a_tape && d->f != NULL && feof(d->f)) {
952 debug(" feof id=%i\n", id);
953 xferp->status[0] = 0x02; /* CHECK CONDITION */
954
955 d->filemark = 1;
956 } else
957 diskimage__internal_access(d, 0, ofs,
958 xferp->data_in, size);
959
960 if (d->is_a_tape && d->f != NULL)
961 d->tape_offset = ftello(d->f);
962
963 /* TODO: other errors? */
964 break;
965
966 case SCSICMD_WRITE:
967 case SCSICMD_WRITE_10:
968 debug("WRITE");
969
970 /* TODO: tape */
971
972 if (xferp->cmd[0] == SCSICMD_WRITE) {
973 if (xferp->cmd_len != 6)
974 debug(" (weird len=%i)", xferp->cmd_len);
975
976 /*
977 * bits 4..0 of cmd[1], and cmd[2] and cmd[3] hold the
978 * logical block address.
979 *
980 * cmd[4] holds the number of logical blocks to
981 * transfer. (Special case if the value is 0, actually
982 * means 256.)
983 */
984 ofs = ((xferp->cmd[1] & 0x1f) << 16) +
985 (xferp->cmd[2] << 8) + xferp->cmd[3];
986 retlen = xferp->cmd[4];
987 if (retlen == 0)
988 retlen = 256;
989 } else {
990 if (xferp->cmd_len != 10)
991 debug(" (weird len=%i)", xferp->cmd_len);
992
993 /*
994 * cmd[2..5] hold the logical block address.
995 * cmd[7..8] holds the number of logical blocks to
996 * transfer. (NOTE: If the value is 0 this means 0,
997 * not 65536.)
998 */
999 ofs = ((uint64_t)xferp->cmd[2] << 24) +
1000 (xferp->cmd[3] << 16) + (xferp->cmd[4] << 8) +
1001 xferp->cmd[5];
1002 retlen = (xferp->cmd[7] << 8) + xferp->cmd[8];
1003 }
1004
1005 size = retlen * d->logical_block_size;
1006 ofs *= d->logical_block_size;
1007
1008 if (xferp->data_out_offset != size) {
1009 debug(", data_out == NULL, wanting %i bytes, \n\n",
1010 (int)size);
1011 xferp->data_out_len = size;
1012 return 2;
1013 }
1014
1015 debug(", data_out != NULL, OK :-)");
1016
1017 debug("WRITE ofs=%i size=%i offset=%i\n", (int)ofs,
1018 (int)size, (int)xferp->data_out_offset);
1019
1020 diskimage__internal_access(d, 1, ofs,
1021 xferp->data_out, size);
1022
1023 /* TODO: how about return code? */
1024
1025 /* Is this really necessary? */
1026 /* fsync(fileno(d->f)); */
1027
1028 diskimage__return_default_status_and_message(xferp);
1029 break;
1030
1031 case SCSICMD_SYNCHRONIZE_CACHE:
1032 debug("SYNCHRONIZE_CACHE");
1033
1034 if (xferp->cmd_len != 10)
1035 debug(" (weird len=%i)", xferp->cmd_len);
1036
1037 /* TODO: actualy care about cmd[] */
1038 fsync(fileno(d->f));
1039
1040 diskimage__return_default_status_and_message(xferp);
1041 break;
1042
1043 case SCSICMD_START_STOP_UNIT:
1044 debug("START_STOP_UNIT");
1045
1046 if (xferp->cmd_len != 6)
1047 debug(" (weird len=%i)", xferp->cmd_len);
1048
1049 for (i=0; i<(ssize_t)xferp->cmd_len; i++)
1050 debug(" %02x", xferp->cmd[i]);
1051
1052 /* TODO: actualy care about cmd[] */
1053
1054 diskimage__return_default_status_and_message(xferp);
1055 break;
1056
1057 case SCSICMD_REQUEST_SENSE:
1058 debug("REQUEST_SENSE");
1059
1060 retlen = xferp->cmd[4];
1061
1062 /* TODO: bits 765 of buf[1] contains the LUN */
1063 if (xferp->cmd[1] != 0x00)
1064 fatal("WARNING: REQUEST_SENSE with cmd[1]=0x%02x not"
1065 " yet implemented\n", (int)xferp->cmd[1]);
1066
1067 if (retlen < 18) {
1068 fatal("WARNING: SCSI request sense len=%i, <18!\n",
1069 (int)retlen);
1070 retlen = 18;
1071 }
1072
1073 /* Return data: */
1074 scsi_transfer_allocbuf(&xferp->data_in_len, &xferp->data_in,
1075 retlen, 1);
1076
1077 xferp->data_in[0] = 0x80 + 0x70;/* 0x80 = valid,
1078 0x70 = "current errors" */
1079 xferp->data_in[2] = 0x00; /* SENSE KEY! */
1080
1081 if (d->filemark) {
1082 xferp->data_in[2] = 0x80;
1083 }
1084 debug(": [2]=0x%02x ", xferp->data_in[2]);
1085
1086 printf(" XXX(!) \n");
1087
1088 /* TODO */
1089 xferp->data_in[7] = retlen - 7; /* additional sense length */
1090 /* TODO */
1091
1092 diskimage__return_default_status_and_message(xferp);
1093 break;
1094
1095 case SCSICMD_READ_BLOCK_LIMITS:
1096 debug("READ_BLOCK_LIMITS");
1097
1098 retlen = 6;
1099
1100 /* TODO: bits 765 of buf[1] contains the LUN */
1101 if (xferp->cmd[1] != 0x00)
1102 fatal("WARNING: READ_BLOCK_LIMITS with cmd[1]="
1103 "0x%02x not yet implemented\n", (int)xferp->cmd[1]);
1104
1105 /* Return data: */
1106 scsi_transfer_allocbuf(&xferp->data_in_len, &xferp->data_in,
1107 retlen, 1);
1108
1109 /*
1110 * data[0] is reserved, data[1..3] contain the maximum block
1111 * length limit, data[4..5] contain the minimum limit.
1112 */
1113
1114 {
1115 int max_limit = 32768;
1116 int min_limit = 128;
1117
1118 xferp->data_in[1] = (max_limit >> 16) & 255;
1119 xferp->data_in[2] = (max_limit >> 8) & 255;
1120 xferp->data_in[3] = max_limit & 255;
1121 xferp->data_in[4] = (min_limit >> 8) & 255;
1122 xferp->data_in[5] = min_limit & 255;
1123 }
1124
1125 diskimage__return_default_status_and_message(xferp);
1126 break;
1127
1128 case SCSICMD_REWIND:
1129 debug("REWIND");
1130
1131 /* TODO: bits 765 of buf[1] contains the LUN */
1132 if ((xferp->cmd[1] & 0xe0) != 0x00)
1133 fatal("WARNING: REWIND with cmd[1]=0x%02x not yet "
1134 "implemented\n", (int)xferp->cmd[1]);
1135
1136 /* Close and reopen. */
1137
1138 if (d->f != NULL)
1139 fclose(d->f);
1140
1141 d->f = fopen(d->fname, d->writable? "r+" : "r");
1142 if (d->f == NULL) {
1143 fprintf(stderr, "[ diskimage: could not (re)open "
1144 "'%s' ]\n", d->fname);
1145 /* TODO: return error */
1146 }
1147
1148 d->tape_offset = 0;
1149 d->tape_filenr = 0;
1150 d->filemark = 0;
1151
1152 diskimage__return_default_status_and_message(xferp);
1153 break;
1154
1155 case SCSICMD_SPACE:
1156 debug("SPACE");
1157
1158 /* TODO: bits 765 of buf[1] contains the LUN */
1159 if ((xferp->cmd[1] & 0xe0) != 0x00)
1160 fatal("WARNING: SPACE with cmd[1]=0x%02x not yet "
1161 "implemented\n", (int)xferp->cmd[1]);
1162
1163 /*
1164 * Bits 2..0 of buf[1] contain the 'code' which describes how
1165 * spacing should be done, and buf[2..4] contain the number of
1166 * operations.
1167 */
1168 debug("[ SPACE: buf[] = %02x %02x %02x %02x %02x %02x ]\n",
1169 xferp->cmd[0],
1170 xferp->cmd[1],
1171 xferp->cmd[2],
1172 xferp->cmd[3],
1173 xferp->cmd[4],
1174 xferp->cmd[5]);
1175
1176 switch (xferp->cmd[1] & 7) {
1177 case 1: /* Seek to a different file nr: */
1178 {
1179 int diff = (xferp->cmd[2] << 16) +
1180 (xferp->cmd[3] << 8) + xferp->cmd[4];
1181
1182 /* Negative seek offset: */
1183 if (diff & (1 << 23))
1184 diff = - (16777216 - diff);
1185
1186 d->tape_filenr += diff;
1187 }
1188
1189 /* At end of file, switch to the next tape file: */
1190 if (d->filemark) {
1191 d->tape_filenr ++;
1192 d->filemark = 0;
1193 }
1194
1195 debug("{ switching to tape file %i }", d->tape_filenr);
1196 diskimage__switch_tape(d);
1197 d->filemark = 0;
1198 break;
1199 default:
1200 fatal("[ diskimage.c: unimplemented SPACE type %i ]\n",
1201 xferp->cmd[1] & 7);
1202 }
1203
1204 diskimage__return_default_status_and_message(xferp);
1205 break;
1206
1207 case SCSICDROM_READ_SUBCHANNEL:
1208 /*
1209 * According to
1210 * http://mail-index.netbsd.org/port-i386/1997/03/03/0010.html:
1211 *
1212 * "The READ_CD_CAPACITY, READ_SUBCHANNEL, and MODE_SELECT
1213 * commands have the same opcode in SCSI or ATAPI, but don't
1214 * have the same command structure"...
1215 *
1216 * TODO: This still doesn't work. Hm.
1217 */
1218 retlen = 48;
1219
1220 debug("CDROM_READ_SUBCHANNEL/READ_CD_CAPACITY, cmd[1]=0x%02x",
1221 xferp->cmd[1]);
1222
1223 /* Return data: */
1224 scsi_transfer_allocbuf(&xferp->data_in_len,
1225 &xferp->data_in, retlen, 1);
1226
1227 diskimage_recalc_size(d);
1228
1229 size = d->total_size / d->logical_block_size;
1230 if (d->total_size & (d->logical_block_size-1))
1231 size ++;
1232
1233 xferp->data_in[0] = (size >> 24) & 255;
1234 xferp->data_in[1] = (size >> 16) & 255;
1235 xferp->data_in[2] = (size >> 8) & 255;
1236 xferp->data_in[3] = size & 255;
1237
1238 xferp->data_in[4] = (d->logical_block_size >> 24) & 255;
1239 xferp->data_in[5] = (d->logical_block_size >> 16) & 255;
1240 xferp->data_in[6] = (d->logical_block_size >> 8) & 255;
1241 xferp->data_in[7] = d->logical_block_size & 255;
1242
1243 diskimage__return_default_status_and_message(xferp);
1244 break;
1245
1246 case SCSICDROM_READ_TOC:
1247 debug("(CDROM_READ_TOC: ");
1248 debug("lun=%i msf=%i ",
1249 xferp->cmd[1] >> 5, (xferp->cmd[1] >> 1) & 1);
1250 debug("starting_track=%i ", xferp->cmd[6]);
1251 retlen = xferp->cmd[7] * 256 + xferp->cmd[8];
1252 debug("allocation_len=%i)\n", retlen);
1253
1254 /* Return data: */
1255 scsi_transfer_allocbuf(&xferp->data_in_len,
1256 &xferp->data_in, retlen, 1);
1257
1258 xferp->data_in[0] = 0;
1259 xferp->data_in[1] = 10;
1260 xferp->data_in[2] = 0; /* First track. */
1261 xferp->data_in[3] = 0; /* Last track. */
1262
1263 /* Track 0 data: */
1264 xferp->data_in[4] = 0x00; /* Reserved. */
1265 xferp->data_in[5] = 0x04; /* ADR + CTRL:
1266 Data, not audio */
1267 xferp->data_in[6] = 0x00; /* Track nr */
1268 xferp->data_in[7] = 0x00; /* Reserved */
1269 /* 8..11 = absolute CDROM address */
1270
1271 diskimage__return_default_status_and_message(xferp);
1272 break;
1273
1274 case SCSICDROM_READ_DISCINFO:
1275 debug("(SCSICDROM_READ_DISCINFO: ");
1276 debug("TODO");
1277 retlen = 0;
1278
1279 /* Return data: */
1280 scsi_transfer_allocbuf(&xferp->data_in_len,
1281 &xferp->data_in, retlen, 1);
1282
1283 /* TODO */
1284
1285 diskimage__return_default_status_and_message(xferp);
1286 break;
1287
1288 case SCSICDROM_READ_TRACKINFO:
1289 debug("(SCSICDROM_READ_TRACKINFO: ");
1290 debug("TODO");
1291 retlen = 0;
1292
1293 /* Return data: */
1294 scsi_transfer_allocbuf(&xferp->data_in_len,
1295 &xferp->data_in, retlen, 1);
1296
1297 /* TODO */
1298
1299 diskimage__return_default_status_and_message(xferp);
1300 break;
1301
1302 case SCSICMD_MODE_SELECT:
1303 debug("[ SCSI MODE_SELECT: ");
1304
1305 /*
1306 * TODO:
1307 *
1308 * This is super-hardcoded for NetBSD's usage of mode_select
1309 * to set the size of CDROM sectors to 2048.
1310 */
1311
1312 if (xferp->data_out_offset == 0) {
1313 xferp->data_out_len = 12; /* TODO */
1314 debug("data_out == NULL, wanting %i bytes ]\n",
1315 (int)xferp->data_out_len);
1316 return 2;
1317 }
1318
1319 debug("data_out!=NULL (OK), ");
1320
1321 /* TODO: Care about cmd? */
1322
1323 /* Set sector size to 2048: */
1324 /* 00 05 00 08 00 03 ca 40 00 00 08 00 */
1325 if (xferp->data_out[0] == 0x00 &&
1326 xferp->data_out[1] == 0x05 &&
1327 xferp->data_out[2] == 0x00 &&
1328 xferp->data_out[3] == 0x08) {
1329 d->logical_block_size =
1330 (xferp->data_out[9] << 16) +
1331 (xferp->data_out[10] << 8) +
1332 xferp->data_out[11];
1333 debug("[ setting logical_block_size to %i ]\n",
1334 d->logical_block_size);
1335 } else {
1336 int i;
1337 fatal("[ unknown MODE_SELECT: cmd =");
1338 for (i=0; i<(ssize_t)xferp->cmd_len; i++)
1339 fatal(" %02x", xferp->cmd[i]);
1340 fatal(", data_out =");
1341 for (i=0; i<(ssize_t)xferp->data_out_len; i++)
1342 fatal(" %02x", xferp->data_out[i]);
1343 fatal(" ]");
1344 }
1345
1346 debug(" ]\n");
1347 diskimage__return_default_status_and_message(xferp);
1348 break;
1349
1350 case SCSICMD_PREVENT_ALLOW_REMOVE:
1351 debug("[ SCSI 0x%02x Prevent/allow medium removal: "
1352 "TODO ]\n", xferp->cmd[0]);
1353
1354 diskimage__return_default_status_and_message(xferp);
1355 break;
1356
1357 case 0xbd:
1358 fatal("[ SCSI 0x%02x (len %i), TODO: ", xferp->cmd[0],
1359 xferp->cmd_len);
1360 for (i=0; i<(ssize_t)xferp->cmd_len; i++)
1361 fatal(" %02x", xferp->cmd[i]);
1362 fatal(" ]\n");
1363
1364 /*
1365 * Used by Windows NT?
1366 *
1367 * Not documented in http://www.danbbs.dk/~dino/
1368 * SCSI/SCSI2-D.html.
1369 * Google gave the answer "MECHANISM_STATUS" for ATAPI. Hm.
1370 */
1371
1372 if (xferp->cmd_len < 12) {
1373 fatal("WEIRD LEN?\n");
1374 retlen = 8;
1375 } else {
1376 retlen = xferp->cmd[8] * 256 + xferp->cmd[9];
1377 }
1378
1379 /* Return data: */
1380 scsi_transfer_allocbuf(&xferp->data_in_len,
1381 &xferp->data_in, retlen, 1);
1382
1383 diskimage__return_default_status_and_message(xferp);
1384
1385 break;
1386
1387 default:
1388 fatal("[ UNIMPLEMENTED SCSI command 0x%02x, disk id=%i ]\n",
1389 xferp->cmd[0], id);
1390 exit(1);
1391 }
1392 debug(" ]\n");
1393
1394 return 1;
1395 }
1396
1397
1398 /*
1399 * diskimage_access():
1400 *
1401 * Read from or write to a disk image on a machine.
1402 *
1403 * Returns 1 if the access completed successfully, 0 otherwise.
1404 */
1405 int diskimage_access(struct machine *machine, int id, int type, int writeflag,
1406 off_t offset, unsigned char *buf, size_t len)
1407 {
1408 struct diskimage *d = machine->first_diskimage;
1409
1410 while (d != NULL) {
1411 if (d->type == type && d->id == id)
1412 break;
1413 d = d->next;
1414 }
1415
1416 if (d == NULL) {
1417 fatal("[ diskimage_access(): ERROR: trying to access a "
1418 "non-existant %s disk image (id %i)\n",
1419 diskimage_types[type], id);
1420 return 0;
1421 }
1422
1423 offset -= d->override_base_offset;
1424 if (offset < 0 && offset + d->override_base_offset >= 0) {
1425 debug("[ reading before start of disk image ]\n");
1426 /* Returning zeros. */
1427 memset(buf, 0, len);
1428 return 1;
1429 }
1430
1431 return diskimage__internal_access(d, writeflag, offset, buf, len);
1432 }
1433
1434
1435 /*
1436 * diskimage_add():
1437 *
1438 * Add a disk image. fname is the filename of the disk image.
1439 * The filename may be prefixed with one or more modifiers, followed
1440 * by a colon.
1441 *
1442 * b specifies that this is a bootable device
1443 * c CD-ROM (instead of a normal DISK)
1444 * d DISK (this is the default)
1445 * f FLOPPY (instead of SCSI)
1446 * gH;S; set geometry (H=heads, S=sectors per track, cylinders are
1447 * automatically calculated). (This is ignored for floppies.)
1448 * i IDE (instead of SCSI)
1449 * oOFS; set base offset in bytes, when booting from an ISO9660 fs
1450 * r read-only (don't allow changes to the file)
1451 * s SCSI (this is the default)
1452 * t tape
1453 * 0-7 force a specific SCSI ID number
1454 *
1455 * machine is assumed to be non-NULL.
1456 * Returns an integer >= 0 identifying the disk image.
1457 */
1458 int diskimage_add(struct machine *machine, char *fname)
1459 {
1460 struct diskimage *d, *d2;
1461 int id = 0, override_heads=0, override_spt=0;
1462 int64_t bytespercyl, override_base_offset=0;
1463 char *cp;
1464 int prefix_b=0, prefix_c=0, prefix_d=0, prefix_f=0, prefix_g=0;
1465 int prefix_i=0, prefix_r=0, prefix_s=0, prefix_t=0, prefix_id=-1;
1466 int prefix_o=0;
1467
1468 if (fname == NULL) {
1469 fprintf(stderr, "diskimage_add(): NULL ptr\n");
1470 return 0;
1471 }
1472
1473 /* Get prefix from fname: */
1474 cp = strchr(fname, ':');
1475 if (cp != NULL) {
1476 while (fname <= cp) {
1477 char c = *fname++;
1478 switch (c) {
1479 case '0':
1480 case '1':
1481 case '2':
1482 case '3':
1483 case '4':
1484 case '5':
1485 case '6':
1486 case '7':
1487 prefix_id = c - '0';
1488 break;
1489 case 'b':
1490 prefix_b = 1;
1491 break;
1492 case 'c':
1493 prefix_c = 1;
1494 break;
1495 case 'd':
1496 prefix_d = 1;
1497 break;
1498 case 'f':
1499 prefix_f = 1;
1500 break;
1501 case 'g':
1502 prefix_g = 1;
1503 override_heads = atoi(fname);
1504 while (*fname != '\0' && *fname != ';')
1505 fname ++;
1506 if (*fname == ';')
1507 fname ++;
1508 override_spt = atoi(fname);
1509 while (*fname != '\0' && *fname != ';' &&
1510 *fname != ':')
1511 fname ++;
1512 if (*fname == ';')
1513 fname ++;
1514 if (override_heads < 1 ||
1515 override_spt < 1) {
1516 fatal("Bad geometry: heads=%i "
1517 "spt=%i\n", override_heads,
1518 override_spt);
1519 exit(1);
1520 }
1521 break;
1522 case 'i':
1523 prefix_i = 1;
1524 break;
1525 case 'o':
1526 prefix_o = 1;
1527 override_base_offset = atoi(fname);
1528 while (*fname != '\0' && *fname != ':'
1529 && *fname != ';')
1530 fname ++;
1531 if (*fname == ':' || *fname == ';')
1532 fname ++;
1533 if (override_base_offset < 0) {
1534 fatal("Bad base offset: %"PRIi64
1535 "\n", override_base_offset);
1536 exit(1);
1537 }
1538 break;
1539 case 'r':
1540 prefix_r = 1;
1541 break;
1542 case 's':
1543 prefix_s = 1;
1544 break;
1545 case 't':
1546 prefix_t = 1;
1547 break;
1548 case ':':
1549 break;
1550 default:
1551 fprintf(stderr, "diskimage_add(): invalid "
1552 "prefix char '%c'\n", c);
1553 exit(1);
1554 }
1555 }
1556 }
1557
1558 /* Allocate a new diskimage struct: */
1559 d = malloc(sizeof(struct diskimage));
1560 if (d == NULL) {
1561 fprintf(stderr, "out of memory in diskimage_add()\n");
1562 exit(1);
1563 }
1564 memset(d, 0, sizeof(struct diskimage));
1565
1566 d2 = machine->first_diskimage;
1567 if (d2 == NULL) {
1568 machine->first_diskimage = d;
1569 } else {
1570 while (d2->next != NULL)
1571 d2 = d2->next;
1572 d2->next = d;
1573 }
1574
1575 /* Default to IDE disks... */
1576 d->type = DISKIMAGE_IDE;
1577
1578 /* ... but some machines use SCSI by default: */
1579 if (machine->machine_type == MACHINE_PMAX ||
1580 machine->machine_type == MACHINE_ARC)
1581 d->type = DISKIMAGE_SCSI;
1582
1583 if (prefix_i + prefix_f + prefix_s > 1) {
1584 fprintf(stderr, "Invalid disk image prefix(es). You can"
1585 "only use one of i, f, and s\nfor each disk image.\n");
1586 exit(1);
1587 }
1588
1589 if (prefix_i)
1590 d->type = DISKIMAGE_IDE;
1591 if (prefix_f)
1592 d->type = DISKIMAGE_FLOPPY;
1593 if (prefix_s)
1594 d->type = DISKIMAGE_SCSI;
1595
1596 if (prefix_o)
1597 d->override_base_offset = override_base_offset;
1598
1599 d->fname = strdup(fname);
1600 if (d->fname == NULL) {
1601 fprintf(stderr, "out of memory\n");
1602 exit(1);
1603 }
1604
1605 d->logical_block_size = 512;
1606
1607 /*
1608 * Is this a tape, CD-ROM or a normal disk?
1609 *
1610 * An intelligent guess, if no prefixes are used, would be that
1611 * filenames ending with .iso or .cdr are CD-ROM images.
1612 */
1613 if (prefix_t) {
1614 d->is_a_tape = 1;
1615 } else {
1616 if (prefix_c ||
1617 ((strlen(d->fname) > 4 &&
1618 (strcasecmp(d->fname + strlen(d->fname) - 4, ".cdr") == 0 ||
1619 strcasecmp(d->fname + strlen(d->fname) - 4, ".iso") == 0))
1620 && !prefix_d)
1621 ) {
1622 d->is_a_cdrom = 1;
1623
1624 /*
1625 * This is tricky. Should I use 512 or 2048 here?
1626 * NetBSD/pmax 1.6.2 and Ultrix likes 512 bytes
1627 * per sector, but NetBSD 2.0_BETA suddenly ignores
1628 * this value and uses 2048 instead.
1629 *
1630 * OpenBSD/arc doesn't like 2048, it requires 512
1631 * to work correctly.
1632 *
1633 * TODO
1634 */
1635
1636 #if 0
1637 if (machine->machine_type == MACHINE_PMAX)
1638 d->logical_block_size = 512;
1639 else
1640 d->logical_block_size = 2048;
1641 #endif
1642 d->logical_block_size = 512;
1643 }
1644 }
1645
1646 diskimage_recalc_size(d);
1647
1648 if ((d->total_size == 720*1024 || d->total_size == 1474560
1649 || d->total_size == 2949120 || d->total_size == 1228800)
1650 && !prefix_i && !prefix_s)
1651 d->type = DISKIMAGE_FLOPPY;
1652
1653 switch (d->type) {
1654 case DISKIMAGE_FLOPPY:
1655 if (d->total_size < 737280) {
1656 fatal("\nTODO: small (non-80-cylinder) floppies?\n\n");
1657 exit(1);
1658 }
1659 d->cylinders = 80;
1660 d->heads = 2;
1661 d->sectors_per_track = d->total_size / (d->cylinders *
1662 d->heads * 512);
1663 break;
1664 default:/* Non-floppies: */
1665 d->heads = 16;
1666 d->sectors_per_track = 63;
1667 if (prefix_g) {
1668 d->chs_override = 1;
1669 d->heads = override_heads;
1670 d->sectors_per_track = override_spt;
1671 }
1672 bytespercyl = d->heads * d->sectors_per_track * 512;
1673 d->cylinders = d->total_size / bytespercyl;
1674 if (d->cylinders * bytespercyl < d->total_size)
1675 d->cylinders ++;
1676 }
1677
1678 d->rpms = 3600;
1679
1680 if (prefix_b)
1681 d->is_boot_device = 1;
1682
1683 d->writable = access(fname, W_OK) == 0? 1 : 0;
1684
1685 if (d->is_a_cdrom || prefix_r)
1686 d->writable = 0;
1687
1688 d->f = fopen(fname, d->writable? "r+" : "r");
1689 if (d->f == NULL) {
1690 perror(fname);
1691 exit(1);
1692 }
1693
1694 /* Calculate which ID to use: */
1695 if (prefix_id == -1) {
1696 int free = 0, collision = 1;
1697
1698 while (collision) {
1699 collision = 0;
1700 d2 = machine->first_diskimage;
1701 while (d2 != NULL) {
1702 /* (don't compare against ourselves :) */
1703 if (d2 == d) {
1704 d2 = d2->next;
1705 continue;
1706 }
1707 if (d2->id == free && d2->type == d->type) {
1708 collision = 1;
1709 break;
1710 }
1711 d2 = d2->next;
1712 }
1713 if (!collision)
1714 id = free;
1715 else
1716 free ++;
1717 }
1718 } else {
1719 id = prefix_id;
1720 d2 = machine->first_diskimage;
1721 while (d2 != NULL) {
1722 /* (don't compare against ourselves :) */
1723 if (d2 == d) {
1724 d2 = d2->next;
1725 continue;
1726 }
1727 if (d2->id == id && d2->type == d->type) {
1728 fprintf(stderr, "disk image id %i "
1729 "already in use\n", id);
1730 exit(1);
1731 }
1732 d2 = d2->next;
1733 }
1734 }
1735
1736 d->id = id;
1737
1738 return id;
1739 }
1740
1741
1742 /*
1743 * diskimage_bootdev():
1744 *
1745 * Returns the disk id of the device which we're booting from. If typep is
1746 * non-NULL, the type is returned as well.
1747 *
1748 * If no disk was used as boot device, then -1 is returned. (In practice,
1749 * this is used to fake network (tftp) boot.)
1750 */
1751 int diskimage_bootdev(struct machine *machine, int *typep)
1752 {
1753 struct diskimage *d;
1754
1755 d = machine->first_diskimage;
1756 while (d != NULL) {
1757 if (d->is_boot_device) {
1758 if (typep != NULL)
1759 *typep = d->type;
1760 return d->id;
1761 }
1762 d = d->next;
1763 }
1764
1765 d = machine->first_diskimage;
1766 if (d != NULL) {
1767 if (typep != NULL)
1768 *typep = d->type;
1769 return d->id;
1770 }
1771
1772 return -1;
1773 }
1774
1775
1776 /*
1777 * diskimage_getname():
1778 *
1779 * Returns 1 if a valid disk image name was returned, 0 otherwise.
1780 */
1781 int diskimage_getname(struct machine *machine, int id, int type,
1782 char *buf, size_t bufsize)
1783 {
1784 struct diskimage *d = machine->first_diskimage;
1785
1786 if (buf == NULL)
1787 return 0;
1788
1789 while (d != NULL) {
1790 if (d->type == type && d->id == id) {
1791 char *p = strrchr(d->fname, '/');
1792 if (p == NULL)
1793 p = d->fname;
1794 else
1795 p ++;
1796 snprintf(buf, bufsize, "%s", p);
1797 return 1;
1798 }
1799 d = d->next;
1800 }
1801 return 0;
1802 }
1803
1804
1805 /*
1806 * diskimage_is_a_cdrom():
1807 *
1808 * Returns 1 if a disk image is a CDROM, 0 otherwise.
1809 */
1810 int diskimage_is_a_cdrom(struct machine *machine, int id, int type)
1811 {
1812 struct diskimage *d = machine->first_diskimage;
1813
1814 while (d != NULL) {
1815 if (d->type == type && d->id == id)
1816 return d->is_a_cdrom;
1817 d = d->next;
1818 }
1819 return 0;
1820 }
1821
1822
1823 /*
1824 * diskimage_is_a_tape():
1825 *
1826 * Returns 1 if a disk image is a tape, 0 otherwise.
1827 *
1828 * (Used in src/machine.c, to select 'rz' vs 'tz' for DECstation
1829 * boot strings.)
1830 */
1831 int diskimage_is_a_tape(struct machine *machine, int id, int type)
1832 {
1833 struct diskimage *d = machine->first_diskimage;
1834
1835 while (d != NULL) {
1836 if (d->type == type && d->id == id)
1837 return d->is_a_tape;
1838 d = d->next;
1839 }
1840 return 0;
1841 }
1842
1843
1844 /*
1845 * diskimage_dump_info():
1846 *
1847 * Debug dump of all diskimages that are loaded for a specific machine.
1848 */
1849 void diskimage_dump_info(struct machine *machine)
1850 {
1851 int iadd = DEBUG_INDENTATION;
1852 struct diskimage *d = machine->first_diskimage;
1853
1854 while (d != NULL) {
1855 debug("diskimage: %s\n", d->fname);
1856 debug_indentation(iadd);
1857
1858 switch (d->type) {
1859 case DISKIMAGE_SCSI:
1860 debug("SCSI");
1861 break;
1862 case DISKIMAGE_IDE:
1863 debug("IDE");
1864 break;
1865 case DISKIMAGE_FLOPPY:
1866 debug("FLOPPY");
1867 break;
1868 default:
1869 debug("UNKNOWN type %i", d->type);
1870 }
1871
1872 debug(" %s", d->is_a_tape? "TAPE" :
1873 (d->is_a_cdrom? "CD-ROM" : "DISK"));
1874 debug(" id %i, ", d->id);
1875 debug("%s, ", d->writable? "read/write" : "read-only");
1876
1877 if (d->type == DISKIMAGE_FLOPPY)
1878 debug("%lli KB", (long long) (d->total_size / 1024));
1879 else
1880 debug("%lli MB", (long long) (d->total_size / 1048576));
1881
1882 if (d->type == DISKIMAGE_FLOPPY || d->chs_override)
1883 debug(" (CHS=%i,%i,%i)", d->cylinders, d->heads,
1884 d->sectors_per_track);
1885 else
1886 debug(" (%lli sectors)", (long long)
1887 (d->total_size / 512));
1888
1889 if (d->is_boot_device)
1890 debug(" (BOOT)");
1891 debug("\n");
1892
1893 debug_indentation(-iadd);
1894
1895 d = d->next;
1896 }
1897 }
1898

  ViewVC Help
Powered by ViewVC 1.1.26