FreeBSD/Linux Kernel Cross Reference
sys/scsi/rz_disk.c
1 /*
2 * Mach Operating System
3 * Copyright (c) 1993,1992,1991,1990 Carnegie Mellon University
4 * All Rights Reserved.
5 *
6 * Permission to use, copy, modify and distribute this software and its
7 * documentation is hereby granted, provided that both the copyright
8 * notice and this permission notice appear in all copies of the
9 * software, derivative works or modified versions, and any portions
10 * thereof, and that both notices appear in supporting documentation.
11 *
12 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
13 * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
14 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
15 *
16 * Carnegie Mellon requests users of this software to return to
17 *
18 * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU
19 * School of Computer Science
20 * Carnegie Mellon University
21 * Pittsburgh PA 15213-3890
22 *
23 * any improvements or extensions that they make and grant Carnegie Mellon
24 * the rights to redistribute these changes.
25 */
26 /*
27 * HISTORY
28 * $Log: rz_disk.c,v $
29 * Revision 2.26 93/11/17 18:43:33 dbg
30 * Added include of kern/time_out.h.
31 * [93/05/21 dbg]
32 *
33 * Revision 2.25 93/08/10 15:19:44 mrt
34 * Fix DIOCxxx ioctls to deal with MAXPARTITION or MAXPARTITION+1
35 * partitions. We'll do this better next release.
36 * [93/08/09 rvb]
37 *
38 * Revision 2.24 93/08/03 12:34:06 mrt
39 * No default spl protection for start routine.
40 * [93/07/29 23:31:54 af]
41 *
42 * Revision 2.23 93/05/15 19:42:50 mrt
43 * machparam.h -> machspl.h
44 *
45 * Revision 2.22 93/05/10 21:22:28 rvb
46 * Removed depends on DEV_BSIZE.
47 * Do not assume read_capacity will work (violations of standard..)
48 * Try to set the logical block size to the default U*X wants,
49 * but take whatever the drive wants if not. For CDROMs.
50 * Write back the label where we found it.
51 * [93/05/06 10:13:33 af]
52 *
53 * Revision 2.21 93/03/09 11:18:27 danner
54 * If there is no recognizable label, we must set p_size of the
55 * MAXPARTITION slice. We use MAXPARTITION to do ABSOLUTE reads.
56 * [93/02/23 rvb]
57 *
58 * Revision 2.20 93/03/09 10:57:38 danner
59 * Merge botch in scdisk_writelabel(), was still setting tgt->ior
60 * before grab_it().
61 * To accomodate a weird "feature" in some Adaptec microcode and
62 * slowpoke <unnamed> disk.. retry if the unit is not yet ready
63 * when we come to read it.
64 * [93/02/23 af]
65 *
66 * Revision 2.19 93/01/14 17:55:21 danner
67 * Made all stack-allocated buffers aligned.
68 * [92/08/02 af]
69 *
70 * Revision 2.18 92/08/03 17:53:30 jfriedl
71 * removed silly prototypes
72 * [92/08/02 jfriedl]
73 *
74 * Revision 2.17 92/05/21 17:23:32 jfriedl
75 * Fixed return-value types (most now void).
76 * Cleanup to quiet gcc warnings.
77 * [92/05/16 jfriedl]
78 *
79 * Revision 2.16 92/04/06 23:23:01 rpd
80 * On bad errors, be loquacious and decode all sense data.
81 * Do not retry IO_INTERNAL operations, cuz we donno what it is.
82 * [92/04/06 af]
83 *
84 * Revision 2.15 92/04/03 12:17:54 rpd
85 * With af, fixed berkeley label processing not to trash tgt->ior.
86 * Use PARTITION_ABSOLUTE to read absolute sectors from the
87 * disk, by reading/writing on the absolute partition.
88 * [92/04/01 rvb]
89 * Merged bernadat's change to mainline. Made conditional
90 * on i386 vs AT386.
91 * [92/03/16 rvb]
92 * Call machine dependant set_status/get_status
93 * if flavor is not a known one.
94 * [92/03/04 bernadat]
95 *
96 * Revision 2.14 92/02/23 22:44:21 elf
97 * Changed the interface of a number of functions not to
98 * require the scsi_softc pointer any longer. It was
99 * mostly unused, now it can be found via tgt->masterno.
100 * Added DEV_GET_SIZE flavor for get status.
101 * Disabled PERF code.
102 * [92/02/22 19:06:12 af]
103 *
104 * Revision 2.13 91/10/09 16:17:00 af
105 * Do not tolerate a negative io_count (sanity).
106 * [91/10/05 10:21:09 af]
107 *
108 * Revision 2.12.1.1 91/09/01 17:18:33 af
109 * Fixed blooper in setdisklabel(), now disk labels work on 3.0.
110 * Zero io_error before using the ior.
111 *
112 * Revision 2.12 91/08/24 12:27:52 af
113 * Spl defs. Search for label. MultiP locks.
114 * [91/08/02 03:53:45 af]
115 *
116 * Bcopy casts.
117 *
118 * Revision 2.11 91/07/09 23:22:28 danner
119 * Upgraded to support new label technology.
120 * [91/07/09 11:15:50 danner]
121 *
122 * Revision 2.10 91/06/25 20:56:16 rpd
123 * Tweaks to make gcc happy.
124 *
125 * Revision 2.9 91/06/19 11:57:01 rvb
126 * File moved here from mips/PMAX since it is now "MI" code, also
127 * used by Vax3100 and soon -- the omron luna88k.
128 * [91/06/04 rvb]
129 *
130 * Revision 2.8 91/05/14 17:26:29 mrt
131 * Correcting copyright
132 *
133 * Revision 2.7 91/05/13 06:04:25 af
134 * Fixed a type declaration, now an HBA with unlimited DMA
135 * capability sez so by setting the max_dma_size to -1.
136 * [91/04/05 13:08:43 af]
137 *
138 * Use long forms of read/write commands on disks bigger than 1Gb,
139 * which we automatically recognize from the read_capacity.
140 * Allocate io_req_t records properly.
141 * Check for transactions that aborted due to scsi bus resets.
142 * Issue a request_sense on bad transactions, log soft errors
143 * and only make fail bad errors.
144 * [91/03/29 17:03:03 af]
145 *
146 * Revision 2.6 91/02/14 14:35:22 mrt
147 * Added (optional and disabled) code for checksumming.
148 * [91/02/12 12:58:27 af]
149 *
150 * Bug in strategy routine: for xfers larger than
151 * max_dma was not setting the io_count right at
152 * the end of the transfer. Now new fsck works.
153 * [90/12/10 17:26:47 af]
154 *
155 * Revision 2.5 91/02/05 17:43:47 mrt
156 * Added author notices
157 * [91/02/04 11:16:50 mrt]
158 *
159 * Changed to use new Mach copyright
160 * [91/02/02 12:15:37 mrt]
161 *
162 * Revision 2.4 90/12/20 17:01:09 jeffreyh
163 * Bug in strategy routine: for xfers larger than
164 * max_dma was not setting the io_count right at
165 * the end of the transfer. Now new fsck works.
166 * [90/12/10 17:26:47 af]
167 *
168 * Revision 2.3 90/12/05 23:33:57 af
169 * Use and prefer BSD/OSF labels.
170 * [90/12/03 23:33:43 af]
171 *
172 * Revision 2.1.1.1 90/11/01 03:43:47 af
173 * Created.
174 * [90/10/21 af]
175 */
176 /*
177 * File: rz_disk.c
178 * Author: Alessandro Forin, Carnegie Mellon University
179 * Date: 10/90
180 *
181 * Top layer of the SCSI driver: interface with the MI.
182 * This file contains operations specific to disk-like devices.
183 */
184
185 #include <machine/machspl.h> /* spl definitions */
186 #include <mach/std_types.h>
187 #include <scsi/compat_30.h>
188
189 #ifdef MACH_KERNEL
190 #include <kern/time_out.h> /* for hz, timeout, untimeout */
191 #else /*MACH_KERNEL*/
192 #include <sys/kernel.h> /* for hz */
193 #endif /*MACH_KERNEL*/
194
195 #include <scsi/scsi.h>
196 #include <scsi/scsi_defs.h>
197 #include <scsi/rz.h>
198 #include <scsi/rz_labels.h>
199
200 extern void scdisk_read(), scdisk_write(),
201 scsi_long_read(), scsi_long_write();
202
203 void scdisk_start(); /* forwards */
204 void scdisk_start_rw();
205 unsigned dkcksum();
206
207 /*
208 * Label used if the disk does not have one
209 */
210 struct disklabel scsi_default_label =
211 {
212 DISKMAGIC, DTYPE_SCSI, 0,
213 "SCSI", "",
214 DEV_BSIZE, 1, 1, 1, 1, 1, 0, 0, 0,
215 3600, 1, 1, 1, 0, 0, 0,
216 {0,}, {0,},
217 DISKMAGIC, 0,
218 8, 8192, 8192,
219 {{ -1, 0, 1024, FS_BSDFFS, 8, 3 },
220 { -1, 0, 1024, FS_BSDFFS, 8, 3 },
221 { -1, 0, 1024, FS_BSDFFS, 8, 3 },
222 { -1, 0, 1024, FS_BSDFFS, 8, 3 },
223 { -1, 0, 1024, FS_BSDFFS, 8, 3 },
224 { -1, 0, 1024, FS_BSDFFS, 8, 3 },
225 { -1, 0, 1024, FS_BSDFFS, 8, 3 },
226 { -1, 0, 1024, FS_BSDFFS, 8, 3 }}
227 };
228
229
230 /*
231 * Specialized side of the open routine for disks
232 */
233 scsi_ret_t scdisk_open(
234 target_info_t *tgt,
235 io_req_t req)
236 {
237 register int i, dev_bsize;
238 scsi_ret_t ret = SCSI_RET_SUCCESS;
239 unsigned int disk_size, secs_per_cyl, sector_size;
240 scsi_rcap_data_t *cap;
241 struct disklabel *label;
242 io_req_t ior;
243 void (*readfun)() = scdisk_read;
244 char *data = (char *)0;
245
246 if (tgt->flags & TGT_ONLINE)
247 return SCSI_RET_SUCCESS;
248
249 /*
250 * Dummy ior for proper sync purposes
251 */
252 io_req_alloc(ior,0);
253 ior->io_next = 0;
254 ior->io_count = 0;
255
256 /*
257 * Set the LBN to DEV_BSIZE with a MODE SELECT.
258 * If this fails we try a couple other tricks.
259 */
260 dev_bsize = 0;
261 for (i = 0; i < 5; i++) {
262 ior->io_op = IO_INTERNAL;
263 ior->io_error = 0;
264 ret = scdisk_mode_select(tgt, DEV_BSIZE, ior, 0, 0, 0);
265 if (ret == SCSI_RET_SUCCESS) {
266 dev_bsize = DEV_BSIZE;
267 break;
268 }
269 if (ret == SCSI_RET_RETRY) {
270 timeout(wakeup, tgt, 2*hz);
271 await(tgt);
272 }
273 if (ret == SCSI_RET_DEVICE_DOWN)
274 goto done;
275 }
276 #if 0
277 if (ret != SCSI_RET_SUCCESS) {
278 scsi_error( tgt, SCSI_ERR_MSEL, ret, 0);
279 ret = D_INVALID_SIZE;
280 goto done;
281 }
282 #endif
283 /*
284 * Do a READ CAPACITY to get max size. Check LBN too.
285 */
286 for (i = 0; i < 5; i++) {
287 ior->io_op = IO_INTERNAL;
288 ior->io_error = 0;
289 ret = scsi_read_capacity(tgt, 0, ior);
290 if (ret == SCSI_RET_SUCCESS)
291 break;
292 }
293 if (ret == SCSI_RET_SUCCESS) {
294 int val;
295
296 cap = (scsi_rcap_data_t*) tgt->cmd_ptr;
297 disk_size = (cap->lba1<<24) |
298 (cap->lba2<<16) |
299 (cap->lba3<< 8) |
300 cap->lba4;
301 if (scsi_debug)
302 printf("rz%d holds %d blocks\n", tgt->unit_no, disk_size);
303 val = (cap->blen1<<24) |
304 (cap->blen2<<16) |
305 (cap->blen3<<8 ) |
306 cap->blen4;
307 if (dev_bsize == 0)
308 dev_bsize = val;
309 else
310 if (val != dev_bsize) panic("read capacity bad");
311
312 if (disk_size > SCSI_CMD_READ_MAX_LBA)
313 tgt->flags |= TGT_BIG;
314
315 } else {
316 printf("Unknown disk capacity??\n");
317 disk_size = -1;
318 }
319 /*
320 * Mandatory long-form commands ?
321 */
322 if (BGET(scsi_use_long_form,(unsigned char)tgt->masterno,tgt->target_id))
323 tgt->flags |= TGT_BIG;
324 if (tgt->flags & TGT_BIG)
325 readfun = scsi_long_read;
326
327 /*
328 * Some CDROMS truly dislike 512 as LBN.
329 * Use a MODE_SENSE to cover for this case.
330 */
331 if (dev_bsize == 0) {
332 scsi_mode_sense_data_t *m;
333
334 ior->io_op = IO_INTERNAL;
335 ior->io_error = 0;
336 ret = scsi_mode_sense(tgt, 0/*pagecode*/, 32/*?*/, ior);
337 if (ret == SCSI_RET_SUCCESS) {
338 m = (scsi_mode_sense_data_t *) tgt->cmd_ptr;
339 dev_bsize =
340 m->bdesc[0].blen_msb << 16 |
341 m->bdesc[0].blen << 8 |
342 m->bdesc[0].blen_lsb;
343 }
344 }
345
346 /*
347 * Find out about the phys disk geometry
348 */
349
350 ior->io_op = IO_INTERNAL;
351 ior->io_error = 0;
352 ret = scsi_read_capacity( tgt, 1, ior);
353 if (ret == SCSI_RET_SUCCESS) {
354 cap = (scsi_rcap_data_t*) tgt->cmd_ptr;
355 secs_per_cyl = (cap->lba1<<24) | (cap->lba2<<16) |
356 (cap->lba3<< 8) | cap->lba4;
357 secs_per_cyl += 1;
358 sector_size = (cap->blen1<<24) | (cap->blen2<<16) |
359 (cap->blen3<<8 ) | cap->blen4;
360 } else {
361 sector_size = dev_bsize ? dev_bsize : DEV_BSIZE;
362 secs_per_cyl = disk_size;
363 }
364 if (dev_bsize == 0)
365 dev_bsize = sector_size;
366
367 if (scsi_debug)
368 printf("rz%d: %d sect/cyl %d bytes/sec\n", tgt->unit_no,
369 secs_per_cyl, sector_size);
370
371 /*
372 * At this point, one way or other, we are committed to
373 * a given disk capacity and sector size.
374 */
375 tgt->block_size = dev_bsize;
376
377 /*
378 * Get partition table off pack
379 */
380
381 /* first look for a BSD label */
382 #ifdef MACH_KERNEL
383 data = (char *)kalloc(dev_bsize);
384 #else /*MACH_KERNEL*/
385 data = (char *)ior->io_data;
386 #endif /*MACH_KERNEL*/
387
388 ior->io_data = data;
389 ior->io_count = dev_bsize;
390 ior->io_op = IO_READ;
391 ior->io_error = 0;
392 tgt->ior = ior;
393 (*readfun)( tgt, LABELSECTOR, ior);
394 iowait(ior);
395
396 {
397 /* Search for BSD label, might be a bit further along */
398 register int j;
399 boolean_t found;
400
401 for (j = LABELOFFSET, found = FALSE;
402 j < (dev_bsize-sizeof(struct disklabel));
403 j += sizeof(int)) {
404 label = (struct disklabel *) &data[j];
405 if (label->d_magic == DISKMAGIC &&
406 label->d_magic2 == DISKMAGIC) {
407 found = TRUE;
408 break;
409 }
410 }
411 if (found)
412 tgt->dev_info.disk.labeloffset = j;
413 else {
414 tgt->dev_info.disk.labeloffset = LABELOFFSET;
415 label = 0;
416 }
417 tgt->dev_info.disk.labelsector = LABELSECTOR;
418 }
419
420 if (label) {
421 if (scsi_debug)
422 printf("{Using BSD label}");
423 tgt->dev_info.disk.l = *label;
424 } else {
425 /* then look for a vendor's label, but first
426 fill in defaults and what we found */
427
428 label = &tgt->dev_info.disk.l;
429 *label = scsi_default_label;
430 label->d_secsize = sector_size;
431 label->d_secpercyl = secs_per_cyl;
432 label->d_secperunit = disk_size;
433
434 ior->io_data = data;
435 if (!rz_vendor_label(tgt, label, ior)) {
436
437 printf("rz%d: %s\n", tgt->unit_no,
438 "No valid disk label, using defaults");
439 /* Validate partitions a and c for initialization */
440 tgt->dev_info.disk.l.d_partitions[0].p_offset = 0;
441 tgt->dev_info.disk.l.d_partitions[0].p_size = disk_size;
442 tgt->dev_info.disk.l.d_partitions[2].p_offset = 0;
443 tgt->dev_info.disk.l.d_partitions[2].p_size = disk_size;
444 tgt->dev_info.disk.l.d_partitions[MAXPARTITIONS].p_offset = 0;
445 tgt->dev_info.disk.l.d_partitions[MAXPARTITIONS].p_size = -1;
446 }
447 label->d_checksum = 0;
448 label->d_checksum = dkcksum(label);
449 }
450
451 ret = SCSI_RET_SUCCESS;
452 done:
453 #ifdef MACH_KERNEL
454 if (data) kfree((vm_offset_t) data, dev_bsize);
455 #endif /*MACH_KERNEL*/
456
457 io_req_free(ior);
458 return ret;
459 }
460
461 /*
462 * Disk strategy routine
463 */
464 io_return_t
465 scdisk_strategy(
466 register io_req_t ior)
467 {
468 target_info_t *tgt;
469 register scsi_softc_t *sc;
470 register int i = ior->io_unit, part;
471 register unsigned rec, max;
472 spl_t s;
473
474 sc = scsi_softc[rzcontroller(i)];
475 tgt = sc->target[rzslave(i)];
476 part = rzpartition(i);
477
478 /*
479 * Validate request
480 */
481
482 /* readonly ? */
483 if ((tgt->flags & TGT_READONLY) &&
484 (ior->io_op & (IO_READ|IO_INTERNAL) == 0)) {
485 ior->io_error = D_READ_ONLY;
486 ior->io_op |= IO_ERROR;
487 ior->io_residual = ior->io_count;
488 iodone(ior);
489 return ior->io_error;
490 }
491
492 rec = ior->io_recnum;
493 max = tgt->dev_info.disk.l.d_partitions[part].p_size;
494 if (max == -1)
495 max = tgt->dev_info.disk.l.d_secperunit -
496 tgt->dev_info.disk.l.d_partitions[part].p_offset;
497 i = (ior->io_count + tgt->block_size - 1) / tgt->block_size;
498 if (((rec + i) > max) || (ior->io_count < 0) ||
499 #if later
500 ((rec <= LABELSECTOR) && ((tgt->flags & TGT_WRITE_LABEL) == 0))
501 #else
502 FALSE
503 #endif
504 ) {
505 ior->io_error = D_INVALID_SIZE;
506 ior->io_op |= IO_ERROR;
507 ior->io_residual = ior->io_count;
508 iodone(ior);
509 return ior->io_error;
510 }
511 /*
512 * Find location on disk: secno and cyl (for disksort)
513 */
514 rec += tgt->dev_info.disk.l.d_partitions[part].p_offset;
515 ior->io_residual = rec / tgt->dev_info.disk.l.d_secpercyl;
516
517 /*
518 * Enqueue operation
519 */
520 s = splbio();
521 simple_lock(&tgt->target_lock);
522 if (tgt->ior) {
523 disksort(tgt->ior, ior);
524 simple_unlock(&tgt->target_lock);
525 splx(s);
526 } else {
527 ior->io_next = 0;
528 tgt->ior = ior;
529 simple_unlock(&tgt->target_lock);
530 splx(s);
531
532 scdisk_start(tgt,FALSE);
533 }
534
535 return D_SUCCESS;
536 }
537
538 /*#define CHECKSUM*/
539 #ifdef CHECKSUM
540 int max_checksum_size = 0x2000;
541 #endif /*CHECKSUM*/
542
543 /*
544 * Start/completion routine for disks
545 */
546 void scdisk_start(
547 target_info_t *tgt,
548 boolean_t done)
549 {
550 register io_req_t ior = tgt->ior;
551 register unsigned part;
552 #ifdef CHECKSUM
553 register unsigned secno;
554 #endif
555 if (ior == 0)
556 return;
557
558 if (tgt->flags & TGT_BBR_ACTIVE)
559 {
560 scdisk_bbr_start(tgt, done);
561 return;
562 }
563
564 if (done) {
565 register unsigned int xferred;
566 unsigned int max_dma_data;
567
568 max_dma_data = scsi_softc[(unsigned char)tgt->masterno]->max_dma_data;
569 /* see if we must retry */
570 if ((tgt->done == SCSI_RET_RETRY) &&
571 ((ior->io_op & IO_INTERNAL) == 0)) {
572 delay(1000000);/*XXX*/
573 goto start;
574 } else
575 /* got a bus reset ? pifff.. */
576 if ((tgt->done == (SCSI_RET_ABORTED|SCSI_RET_RETRY)) &&
577 ((ior->io_op & IO_INTERNAL) == 0)) {
578 if ((xferred = ior->io_residual) != 0) {
579 ior->io_data -= xferred;
580 ior->io_count += xferred;
581 ior->io_recnum -= xferred / tgt->block_size;
582 ior->io_residual = 0;
583 }
584 goto start;
585 } else
586 /*
587 * Quickly check for errors: if anything goes wrong
588 * we do a request sense, see if that is what we did.
589 */
590 if (tgt->cur_cmd == SCSI_CMD_REQUEST_SENSE) {
591 scsi_sense_data_t *sns;
592 unsigned int blockno;
593 char *outcome;
594
595 ior->io_op = ior->io_temporary;
596
597 sns = (scsi_sense_data_t *)tgt->cmd_ptr;
598 if (sns->addr_valid)
599 blockno = sns->u.xtended.info0 << 24 |
600 sns->u.xtended.info1 << 16 |
601 sns->u.xtended.info2 << 8 |
602 sns->u.xtended.info3;
603 else {
604 part = rzpartition(ior->io_unit);
605 blockno = tgt->dev_info.disk.l.d_partitions[part].p_offset;
606 blockno += ior->io_recnum;
607 }
608
609 if (scsi_check_sense_data(tgt, sns)) {
610 ior->io_error = 0;
611 if ((tgt->done == SCSI_RET_RETRY) &&
612 ((ior->io_op & IO_INTERNAL) == 0)) {
613 delay(1000000);/*XXX*/
614 goto start;
615 }
616 outcome = "Recovered";
617 } else {
618 outcome = "Unrecoverable";
619 ior->io_error = D_IO_ERROR;
620 ior->io_op |= IO_ERROR;
621 }
622 if ((tgt->flags & TGT_OPTIONAL_CMD) == 0) {
623 printf("%s Error, rz%d: %s%s%d\n", outcome,
624 tgt->target_id + (tgt->masterno * 8),
625 (ior->io_op & IO_READ) ? "Read" :
626 ((ior->io_op & IO_INTERNAL) ? "(command)" : "Write"),
627 " disk error, phys block no. ", blockno);
628
629 scsi_print_sense_data(sns);
630
631 /*
632 * On fatal read/write errors try replacing the bad block
633 * The bbr routine will return TRUE iff it took control
634 * over the target for all subsequent operations. In this
635 * event, the queue of requests is effectively frozen.
636 */
637 if (ior->io_error &&
638 ((sns->error_class == SCSI_SNS_XTENDED_SENSE_DATA) &&
639 ((sns->u.xtended.sense_key == SCSI_SNS_HW_ERR) ||
640 (sns->u.xtended.sense_key == SCSI_SNS_MEDIUM_ERR))) &&
641 scdisk_bad_block_repl(tgt, blockno))
642 return;
643 }
644 }
645
646 /*
647 * See if we had errors
648 */
649 else if (tgt->done != SCSI_RET_SUCCESS) {
650
651 if (tgt->done == SCSI_RET_NEED_SENSE) {
652
653 ior->io_temporary = ior->io_op;
654 ior->io_op = IO_INTERNAL;
655 scsi_request_sense(tgt, ior, 0);
656 return;
657
658 } else if (tgt->done == SCSI_RET_DEVICE_DOWN) {
659 ior->io_error = D_DEVICE_DOWN;
660 ior->io_op |= IO_ERROR;
661 } else {
662 printf("%s%x\n", "?rz_disk Disk error, ret=x", tgt->done);
663 ior->io_error = D_IO_ERROR;
664 ior->io_op |= IO_ERROR;
665 }
666 }
667 /*
668 * No errors.
669 * See if we requested more than the max
670 * (We use io_residual in a flip-side way here)
671 */
672 else if (ior->io_count > (xferred = max_dma_data)) {
673 ior->io_residual += xferred;
674 ior->io_count -= xferred;
675 ior->io_data += xferred;
676 ior->io_recnum += xferred / tgt->block_size;
677 goto start;
678 }
679 else if (xferred = ior->io_residual) {
680 ior->io_data -= xferred;
681 ior->io_count += xferred;
682 ior->io_recnum -= xferred / tgt->block_size;
683 ior->io_residual = 0;
684 } /* that's it */
685
686 #ifdef CHECKSUM
687 if ((ior->io_op & IO_READ) && (ior->io_count < max_checksum_size)) {
688 part = rzpartition(ior->io_unit);
689 secno = ior->io_recnum + tgt->dev_info.disk.l.d_partitions[part].p_offset;
690 scdisk_bcheck(secno, ior->io_data, ior->io_count);
691 }
692 #endif /*CHECKSUM*/
693
694 /* dequeue next one */
695 {
696 io_req_t next;
697
698 simple_lock(&tgt->target_lock);
699 next = ior->io_next;
700 tgt->ior = next;
701 simple_unlock(&tgt->target_lock);
702
703 iodone(ior);
704 if (next == 0)
705 return;
706
707 ior = next;
708 }
709
710 #ifdef CHECKSUM
711 if (((ior->io_op & IO_READ) == 0) && (ior->io_count < max_checksum_size)) {
712 part = rzpartition(ior->io_unit);
713 secno = ior->io_recnum + tgt->dev_info.disk.l.d_partitions[part].p_offset;
714 scdisk_checksum(secno, ior->io_data, ior->io_count);
715 }
716 #endif /*CHECKSUM*/
717 }
718 ior->io_residual = 0;
719 start:
720 scdisk_start_rw( tgt, ior);
721 }
722
723 void scdisk_start_rw(
724 target_info_t *tgt,
725 register io_req_t ior)
726 {
727 unsigned int part, secno;
728 register boolean_t long_form;
729
730 part = rzpartition(ior->io_unit);
731 secno = ior->io_recnum + tgt->dev_info.disk.l.d_partitions[part].p_offset;
732
733 /* Use long form if either big block addresses or
734 the size is more than we can fit in one byte */
735 long_form = (tgt->flags & TGT_BIG) ||
736 (ior->io_count > (256 * tgt->block_size));
737 if (ior->io_op & IO_READ)
738 (long_form ? scsi_long_read : scdisk_read)(tgt, secno, ior);
739 else if ((ior->io_op & IO_INTERNAL) == 0)
740 (long_form ? scsi_long_write : scdisk_write)(tgt, secno, ior);
741 }
742
743 #include <sys/ioctl.h>
744 #ifdef ULTRIX_COMPAT
745 #include <mips/PMAX/rzdisk.h>
746 #endif /*ULTRIX_COMPAT*/
747
748 io_return_t
749 scdisk_get_status(
750 int dev,
751 target_info_t *tgt,
752 dev_flavor_t flavor,
753 dev_status_t status,
754 natural_t *status_count)
755 {
756 struct disklabel *lp;
757
758 lp = &tgt->dev_info.disk.l;
759
760 switch (flavor) {
761 #ifdef MACH_KERNEL
762 case DEV_GET_SIZE:
763
764 status[DEV_GET_SIZE_DEVICE_SIZE] = lp->d_partitions[rzpartition(dev)].p_size * lp->d_secsize;
765 status[DEV_GET_SIZE_RECORD_SIZE] = tgt->block_size;
766 *status_count = DEV_GET_SIZE_COUNT;
767 break;
768 #endif
769
770 case DIOCGDINFO:
771 *(struct disklabel *)status = *lp;
772 #ifdef MACH_KERNEL
773 *status_count = sizeof(struct disklabel)/sizeof(int);
774 #endif /*MACH_KERNEL*/
775 break;
776
777 case DIOCGDINFO - (0x10<<16):
778 *(struct disklabel *)status = *lp;
779 #ifdef MACH_KERNEL
780 *status_count = sizeof(struct disklabel)/sizeof(int) - 4;
781 #endif MACH_KERNEL
782 break;
783
784 #ifdef MACH_KERNEL
785 #else /*MACH_KERNEL*/
786 #if ULTRIX_COMPAT
787 case SCSI_MODE_SENSE: /*_IOWR(p, 9, struct mode_sel_sns_params) */
788 break;
789 case DIOCGETPT: /*_IOR(p, 1, struct pt) */
790 case SCSI_GET_SENSE: /*_IOR(p, 10, struct extended_sense) */
791 return ul_disk_ioctl(tgt, flavor, status, status_count);
792 #endif /*ULTRIX_COMPAT*/
793 #endif /*!MACH_KERNEL*/
794
795 #if 0
796 case DIOCRFORMAT:
797 break;
798 #endif
799 default:
800 #ifdef i386
801 return scsi_i386_get_status(dev, tgt, flavor, status, status_count);
802 #else /*i386*/
803 return D_INVALID_OPERATION;
804 #endif /*i386*/
805 }
806 return D_SUCCESS;
807 }
808
809 io_return_t
810 scdisk_set_status(
811 int dev,
812 target_info_t *tgt,
813 dev_flavor_t flavor,
814 dev_status_t status,
815 natural_t status_count)
816 {
817 io_return_t error = D_SUCCESS;
818 struct disklabel *lp;
819
820 lp = &tgt->dev_info.disk.l;
821
822
823 switch (flavor) {
824 case DIOCSRETRIES:
825 #ifdef MACH_KERNEL
826 if (status_count != sizeof(int))
827 return D_INVALID_SIZE;
828 #endif /* MACH_KERNEL */
829 scsi_bbr_retries = *(int *)status;
830 break;
831
832 case DIOCWLABEL:
833 case DIOCWLABEL - (0x10<<16):
834 if (*(int*)status)
835 tgt->flags |= TGT_WRITE_LABEL;
836 else
837 tgt->flags &= ~TGT_WRITE_LABEL;
838 break;
839 case DIOCSDINFO:
840 case DIOCSDINFO - (0x10<<16):
841 case DIOCWDINFO:
842 case DIOCWDINFO - (0x10<<16):
843 #ifdef MACH_KERNEL
844 if (status_count != sizeof(struct disklabel) / sizeof(int))
845 return D_INVALID_SIZE;
846 #endif /* MACH_KERNEL */
847 error = setdisklabel(lp, (struct disklabel*) status);
848 if (error || (flavor == DIOCSDINFO) || (flavor == DIOCSDINFO - (0x10<<16)))
849 return error;
850 error = scdisk_writelabel(tgt);
851 break;
852
853 #ifdef MACH_KERNEL
854 #else /*MACH_KERNEL*/
855 #if ULTRIX_COMPAT
856 case SCSI_FORMAT_UNIT: /*_IOW(p, 4, struct format_params) */
857 case SCSI_REASSIGN_BLOCK: /*_IOW(p, 5, struct reassign_params) */
858 case SCSI_READ_DEFECT_DATA: /*_IOW(p, 6, struct read_defect_params) */
859 case SCSI_VERIFY_DATA: /*_IOW(p, 7, struct verify_params) */
860 case SCSI_MODE_SELECT: /*_IOW(p, 8, struct mode_sel_sns_params) */
861 case SCSI_MODE_SENSE: /*_IOW(p, 9, struct mode_sel_sns_params) */
862 case SCSI_GET_INQUIRY_DATA: /*_IOW(p, 11, struct inquiry_info) */
863 return ul_disk_ioctl(tgt, flavor, status, status_count);
864 #endif /*ULTRIX_COMPAT*/
865 #endif /*!MACH_KERNEL*/
866
867 #if notyet
868 case DIOCWFORMAT:
869 case DIOCSBAD: /* ?? how ? */
870 #endif
871 default:
872 #ifdef i386
873 error = scsi_i386_set_status(dev, tgt, flavor, status, status_count);
874 #else /*i386*/
875 error = D_INVALID_OPERATION;
876 #endif /*i386*/
877 }
878 return error;
879 }
880
881 static io_return_t
882 grab_it(
883 target_info_t *tgt,
884 io_req_t ior)
885 {
886 spl_t s;
887
888 s = splbio();
889 simple_lock(&tgt->target_lock);
890 if (!tgt->ior)
891 tgt->ior = ior;
892 simple_unlock(&tgt->target_lock);
893 splx(s);
894
895 if (tgt->ior != ior)
896 return D_ALREADY_OPEN;
897 return D_SUCCESS;
898 }
899
900 /* Write back a label to the disk */
901 io_return_t
902 scdisk_writelabel(
903 target_info_t *tgt)
904 {
905 io_req_t ior;
906 char *data = (char *)0;
907 struct disklabel *label;
908 io_return_t error;
909 int dev_bsize = tgt->block_size;
910
911 io_req_alloc(ior,0);
912 #ifdef MACH_KERNEL
913 data = (char *)kalloc(dev_bsize);
914 #else /*MACH_KERNEL*/
915 data = (char *)ior->io_data;
916 #endif /*MACH_KERNEL*/
917 ior->io_next = 0;
918 ior->io_prev = 0;
919 ior->io_data = data;
920 ior->io_count = dev_bsize;
921 ior->io_op = IO_READ;
922 ior->io_error = 0;
923
924 if (grab_it(tgt, ior) != D_SUCCESS) {
925 error = D_ALREADY_OPEN;
926 goto ret;
927 }
928
929 scdisk_read( tgt, tgt->dev_info.disk.labelsector, ior);
930 iowait(ior);
931 if (error = ior->io_error)
932 goto ret;
933
934 label = (struct disklabel *) &data[tgt->dev_info.disk.labeloffset];
935 *label = tgt->dev_info.disk.l;
936
937 ior->io_next = 0;
938 ior->io_prev = 0;
939 ior->io_data = data;
940 ior->io_count = dev_bsize;
941 ior->io_op = IO_WRITE;
942
943 while (grab_it(tgt, ior) != D_SUCCESS) ; /* ahem */
944
945 scdisk_write( tgt, tgt->dev_info.disk.labelsector, ior);
946 iowait(ior);
947
948 error = ior->io_error;
949 ret:
950 #ifdef MACH_KERNEL
951 if (data) kfree((vm_offset_t) data, dev_bsize);
952 #endif /*MACH_KERNEL*/
953 io_req_free(ior);
954 return error;
955 }
956
957 #ifdef MACH_KERNEL
958 #else /*MACH_KERNEL*/
959 #if ULTRIX_COMPAT
960
961 io_return_t ul_disk_ioctl(tgt, flavor, status, status_count)
962 target_info_t *tgt;
963 dev_flavor_t flavor;
964 dev_status_t status;
965 natural_t status_count;
966 {
967 io_return_t ret;
968 scsi_ret_t err = SCSI_RET_ABORTED;/*xxx*/
969 io_req_t ior;
970
971 if (!suser())
972 return EACCES;
973
974 ior = geteblk(sizeof(struct defect_descriptors));
975 ior->io_next = 0;
976 ior->io_count = 0;
977 ior->io_op = IO_INTERNAL;
978 ior->io_error = 0;
979 ior->io_recnum = 0;
980 ior->io_residual = 0;
981
982 switch (flavor) {
983
984 case DIOCGETPT: { /*_IOR(p, 1, struct pt) */
985 scsi_dec_label_t *p;
986 struct disklabel *lp;
987 int i;
988
989 lp = &tgt->dev_info.disk.l;
990 p = (scsi_dec_label_t *)status;
991
992 p->magic = DEC_PARTITION_MAGIC;
993 p->in_use = 1;
994 for (i = 0; i < 8; i++) {
995 p->partitions[i].n_sectors = lp->d_partitions[i].p_size;
996 p->partitions[i].offset = lp->d_partitions[i].p_offset;
997 }
998 err = SCSI_RET_SUCCESS;
999 }
1000 break;
1001
1002 case SCSI_GET_SENSE: { /*_IOR(p, 10, struct extended_sense) */
1003 scsi_sense_data_t *s;
1004
1005 s = (scsi_sense_data_t*)tgt->cmd_ptr;
1006 bcopy(s, status, sizeof(*s) + s->u.xtended.add_len - 1);
1007 err = SCSI_RET_SUCCESS;
1008 /* only once */
1009 bzero(tgt->cmd_ptr, sizeof(scsi_sense_data_t));
1010 }
1011 break;
1012
1013 case SCSI_GET_INQUIRY_DATA: { /*_IOR(p, 11, struct inquiry_info) */
1014 struct mode_sel_sns_params *ms;
1015
1016 ms = (struct mode_sel_sns_params*)status;
1017 err = scsi_inquiry( tgt, SCSI_INQ_STD_DATA);
1018 if (copyout(tgt->cmd_ptr, ms->msp_addr, sizeof(struct inquiry_info))){
1019 ret = EFAULT;
1020 goto out;
1021 }
1022 }
1023 break;
1024
1025 case SCSI_FORMAT_UNIT: { /*_IOW(p, 4, struct format_params) */
1026 struct format_params *fp;
1027 struct defect_descriptors *df;
1028 unsigned char mode;
1029 unsigned int old_timeout;
1030
1031 fp = (struct format_params *)status;
1032 df = (struct defect_descriptors*)ior->io_data;
1033 if (fp->fp_length != 0) {
1034 if (copyin(fp->fp_addr, df, sizeof(*df))) {
1035 ret = EFAULT;
1036 goto out;
1037 }
1038 ior->io_count = sizeof(*df);
1039 } else
1040 ior->io_count = 0;
1041 mode = fp->fp_format & SCSI_CMD_FMT_LIST_TYPE;
1042 switch (fp->fp_defects) {
1043 case VENDOR_DEFECTS:
1044 mode |= SCSI_CMD_FMT_FMTDATA|SCSI_CMD_FMT_CMPLIST;
1045 break;
1046 case KNOWN_DEFECTS:
1047 mode |= SCSI_CMD_FMT_FMTDATA;
1048 break;
1049 case NO_DEFECTS:
1050 default:
1051 break;
1052 }
1053 old_timeout = scsi_watchdog_period;
1054 scsi_watchdog_period = 60*60; /* 1 hour should be enough, I hope */
1055 err = scsi_format_unit( tgt, mode, fp->fp_pattern,
1056 fp->fp_interleave, ior);
1057 scsi_watchdog_period = old_timeout;
1058 /* Make sure we re-read all info afresh */
1059 tgt->flags = TGT_ALIVE |
1060 (tgt->flags & (TGT_REMOVABLE_MEDIA|TGT_FULLY_PROBED));
1061 }
1062 break;
1063
1064 case SCSI_REASSIGN_BLOCK: { /*_IOW(p, 5, struct reassign_params) */
1065 struct reassign_params *r;
1066 int ndef;
1067
1068 r = (struct reassign_params*) status;
1069 ndef = r->rp_header.defect_len0 | (r->rp_header.defect_len1 >> 8);
1070 ndef >>= 2;
1071 tgt->ior = ior;
1072 (void) scsi_reassign_blocks( tgt, &r->rp_lbn3, ndef, ior);
1073 iowait(ior);
1074 err = tgt->done;
1075 }
1076 break;
1077
1078 case SCSI_READ_DEFECT_DATA: { /*_IOW(p, 6, struct read_defect_params) */
1079 struct read_defect_params *dp;
1080
1081 dp = (struct read_defect_params *)status;
1082 ior->io_count = ior->io_alloc_size;
1083 if (dp->rdp_alclen > ior->io_count)
1084 dp->rdp_alclen = ior->io_count;
1085 else
1086 ior->io_count = dp->rdp_alclen;
1087 ior->io_op |= IO_READ;
1088 tgt->ior = ior;
1089 err = scsi_read_defect(tgt, dp->rdp_format|0x18, ior);
1090 if (copyout(ior->io_data, dp->rdp_addr, dp->rdp_alclen)) {
1091 ret = EFAULT;
1092 goto out;
1093 }
1094 }
1095 break;
1096
1097 case SCSI_VERIFY_DATA: { /*_IOW(p, 7, struct verify_params) */
1098 struct verify_params *v;
1099 unsigned int old_timeout;
1100
1101 old_timeout = scsi_watchdog_period;
1102 scsi_watchdog_period = 5*60; /* 5 mins enough, I hope */
1103 v = (struct verify_params *)status;
1104 ior->io_count = 0;
1105 err = scdisk_verify( tgt, v->vp_lbn, v->vp_length, ior);
1106 scsi_watchdog_period = old_timeout;
1107 }
1108 break;
1109
1110 case SCSI_MODE_SELECT: { /*_IOW(p, 8, struct mode_sel_sns_params) */
1111 struct mode_sel_sns_params *ms;
1112
1113 ms = (struct mode_sel_sns_params*)status;
1114 if(copyin(ms->msp_addr, ior->io_data, ms->msp_length)) {
1115 ret = EFAULT;
1116 goto out;
1117 }
1118 err = scdisk_mode_select( tgt, DEV_BSIZE, ior, ior->io_data,
1119 ms->msp_length, ms->msp_setps);
1120 }
1121 break;
1122
1123 case SCSI_MODE_SENSE: { /*_IOWR(p, 9, struct mode_sel_sns_params) */
1124 struct mode_sel_sns_params *ms;
1125 unsigned char pagecode;
1126
1127 ms = (struct mode_sel_sns_params*)status;
1128 pagecode = (ms->msp_pgcode & 0x3f) | (ms->msp_pgctrl << 6);
1129 err = scsi_mode_sense( tgt, pagecode, ms->msp_length, ior);
1130 if (copyout(tgt->cmd_ptr, ms->msp_addr, ms->msp_length)){
1131 ret = EFAULT;
1132 goto out;
1133 }
1134 }
1135 break;
1136 }
1137
1138 ret = (err == SCSI_RET_SUCCESS) ? D_SUCCESS : D_IO_ERROR;
1139 if (ior->io_op & IO_ERROR)
1140 ret = D_IO_ERROR;
1141 out:
1142 brelse(ior);
1143 return ret;
1144 }
1145 #endif /*ULTRIX_COMPAT*/
1146 #endif /*!MACH_KERNEL*/
1147
1148 #ifdef CHECKSUM
1149
1150 #define SUMSIZE 0x10000
1151 #define SUMHASH(b) (((b)>>1) & (SUMSIZE - 1))
1152 struct {
1153 long blockno;
1154 long sum;
1155 } scdisk_checksums[SUMSIZE];
1156
1157 void scdisk_checksum(
1158 long bno,
1159 register unsigned int *addr,
1160 int size)
1161 {
1162 register int i = size/sizeof(int);
1163 register unsigned int sum = -1;
1164
1165 while (i-- > 0)
1166 sum ^= *addr++;
1167 scdisk_checksums[SUMHASH(bno)].blockno = bno;
1168 scdisk_checksums[SUMHASH(bno)].sum = sum;
1169 }
1170
1171 void scdisk_bcheck(
1172 long bno,
1173 register unsigned int *addr,
1174 int size)
1175 {
1176 register int i = size/sizeof(int);
1177 register unsigned int sum = -1;
1178 unsigned int *start = addr;
1179
1180 if (scdisk_checksums[SUMHASH(bno)].blockno != bno) {
1181 if (scsi_debug) printf("No checksum for block x%x\n", bno);
1182 return;
1183 }
1184
1185 while (i-- > 0)
1186 sum ^= *addr++;
1187
1188 if (scdisk_checksums[SUMHASH(bno)].sum != sum) {
1189 printf("Bad checksum (x%x != x%x), bno x%x size x%x at x%x\n",
1190 sum,
1191 scdisk_checksums[bno & (SUMSIZE - 1)].sum,
1192 bno, size, start);
1193 gimmeabreak();
1194 scdisk_checksums[SUMHASH(bno)].sum = sum;
1195 }
1196 }
1197
1198
1199 #endif /*CHECKSUM*/
1200
1201 /*#define PERF */
1202 #ifdef PERF
1203 int test_read_size = 512;
1204 int test_read_skew = 12;
1205 int test_read_skew_min = 0;
1206 int test_read_nreads = 1000;
1207 int test_read_bdev = 0;
1208
1209 #include <sys/time.h>
1210
1211 void test_read(int max)
1212 {
1213 int i, ssk, usecs;
1214 struct timeval start, stop;
1215
1216 if (max == 0)
1217 max = test_read_skew + 1;
1218 ssk = test_read_skew;
1219 for (i = test_read_skew_min; i < max; i++){
1220 test_read_skew = i;
1221
1222 start = time;
1223 read_test();
1224 stop = time;
1225
1226 usecs = stop.tv_usec - start.tv_usec;
1227 if (usecs < 0) {
1228 stop.tv_sec -= 1;
1229 usecs += 1000000;
1230 }
1231 printf("Skew %3d size %d count %d time %3d sec %d us\n",
1232 i, test_read_size, test_read_nreads,
1233 stop.tv_sec - start.tv_sec, usecs);
1234 }
1235 test_read_skew = ssk;
1236 }
1237
1238 void read_test(void)
1239 {
1240 static int buffer[(8192*2)/sizeof(int)];
1241 struct io_req io;
1242 register int i, rec;
1243
1244 bzero(&io, sizeof(io));
1245 io.io_unit = test_read_bdev;
1246 io.io_op = IO_READ;
1247 io.io_count = test_read_size;
1248 io.io_data = (char*) buffer;
1249
1250 for (rec = 0, i = 0; i < test_read_nreads; i++) {
1251 io.io_op = IO_READ;
1252 io.io_recnum = rec;
1253 scdisk_strategy(&io);
1254 rec += test_read_skew;
1255 iowait(&io);
1256 }
1257 }
1258
1259 void tur_test(void)
1260 {
1261 struct io_req io;
1262 register int i;
1263 char *a, *b;
1264 struct timeval start, stop;
1265
1266 bzero(&io, sizeof(io));
1267 io.io_unit = test_read_bdev;
1268 io.io_data = (char*)&io;/*unused but kernel space*/
1269
1270 start = time;
1271 for (i = 0; i < test_read_nreads; i++) {
1272 io.io_op = IO_INTERNAL;
1273 rz_check(io.io_unit, &a, &b);
1274 scsi_test_unit_ready(b,&io);
1275 }
1276 stop = time;
1277 i = stop.tv_usec - start.tv_usec;
1278 if (i < 0) {
1279 stop.tv_sec -= 1;
1280 i += 1000000;
1281 }
1282 printf("%d test-unit-ready took %3d sec %d us\n",
1283 test_read_nreads,
1284 stop.tv_sec - start.tv_sec, i);
1285 }
1286
1287 #endif /*PERF*/
1288
1289 /*#define WDEBUG*/
1290 #ifdef WDEBUG
1291
1292 int buggo_write_size = 8192;
1293 int buggo_dev = 1; /* rz0b */
1294 int buggo_out_buffer[8192/2];
1295 int buggo_in_buffer[8192/2];
1296
1297 int buggotest(n, pattern, verbose)
1298 {
1299 struct io_req io;
1300 register int i, rec;
1301
1302 if (n <= 0)
1303 n = 1;
1304
1305 if(pattern)
1306 for (i = 0; i < buggo_write_size/4; i++)
1307 buggo_out_buffer[i] = i + pattern;
1308
1309 for (i = 0; i < n; i++) {
1310 register int j;
1311
1312 buggo_out_buffer[0] = i + pattern;
1313 buggo_out_buffer[(buggo_write_size/4)-1] = i + pattern;
1314 bzero(&io, sizeof(io));
1315 io.io_unit = buggo_dev;
1316 io.io_data = (char*)buggo_out_buffer;
1317 io.io_op = IO_WRITE;
1318 io.io_count = buggo_write_size;
1319 io.io_recnum = i % 1024;
1320 scdisk_strategy(&io);
1321
1322 bzero(buggo_in_buffer, sizeof(buggo_in_buffer));
1323 iowait(&io);
1324
1325 if (verbose)
1326 printf("Done write with %x", io.io_error);
1327
1328 bzero(&io, sizeof(io));
1329 io.io_unit = buggo_dev;
1330 io.io_data = (char*)buggo_in_buffer;
1331 io.io_op = IO_READ;
1332 io.io_count = buggo_write_size;
1333 io.io_recnum = i % 1024;
1334 scdisk_strategy(&io);
1335 iowait(&io);
1336
1337 if (verbose)
1338 printf("Done read with %x", io.io_error);
1339
1340 for (j = 0; j < buggo_write_size/4; j++)
1341 if (buggo_out_buffer[j] != buggo_in_buffer[j]){
1342 printf("Difference at %d-th word: %x %x\n",
1343 buggo_out_buffer[j], buggo_in_buffer[j]);
1344 return i;
1345 }
1346 }
1347 printf("Test ok\n");
1348 return n;
1349 }
1350 #endif /*WDEBUG*/
Cache object: f05443a97123606c821bb524464e485c
|