Deleted Added
sdiff udiff text old ( 86014 ) new ( 90215 )
full compact
1/*-
2 * Copyright (c) 1998,1999,2000,2001,2002 S�ren Schmidt <sos@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer,
10 * without modification, immediately at the beginning of the file.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 *
28 * $FreeBSD: head/sys/dev/ata/atapi-tape.c 90215 2002-02-04 19:23:40Z sos $
29 */
30
31#include <sys/param.h>
32#include <sys/systm.h>
33#include <sys/ata.h>
34#include <sys/kernel.h>
35#include <sys/conf.h>
36#include <sys/malloc.h>
37#include <sys/bio.h>
38#include <sys/bus.h>
39#include <sys/mtio.h>
40#include <sys/disklabel.h>
41#include <sys/devicestat.h>
42#include <dev/ata/ata-all.h>
43#include <dev/ata/atapi-all.h>
44#include <dev/ata/atapi-tape.h>
45
46/* device structures */
47static d_open_t astopen;
48static d_close_t astclose;
49static d_ioctl_t astioctl;
50static d_strategy_t aststrategy;
51static struct cdevsw ast_cdevsw = {
52 /* open */ astopen,
53 /* close */ astclose,
54 /* read */ physread,
55 /* write */ physwrite,
56 /* ioctl */ astioctl,
57 /* poll */ nopoll,
58 /* mmap */ nommap,
59 /* strategy */ aststrategy,
60 /* name */ "ast",
61 /* maj */ 119,
62 /* dump */ nodump,
63 /* psize */ nopsize,
64 /* flags */ D_TAPE | D_TRACKCLOSE,
65};
66
67/* prototypes */
68static int ast_sense(struct ast_softc *);
69static void ast_describe(struct ast_softc *);
70static int ast_done(struct atapi_request *);
71static int ast_mode_sense(struct ast_softc *, int, void *, int);
72static int ast_mode_select(struct ast_softc *, void *, int);
73static int ast_write_filemark(struct ast_softc *, u_int8_t);
74static int ast_read_position(struct ast_softc *, int, struct ast_readposition *);
75static int ast_space(struct ast_softc *, u_int8_t, int32_t);
76static int ast_locate(struct ast_softc *, int, u_int32_t);
77static int ast_prevent_allow(struct ast_softc *stp, int);
78static int ast_load_unload(struct ast_softc *, u_int8_t);
79static int ast_rewind(struct ast_softc *);
80static int ast_erase(struct ast_softc *);
81
82/* internal vars */
83static u_int32_t ast_lun_map = 0;
84static u_int64_t ast_total = 0;
85static MALLOC_DEFINE(M_AST, "AST driver", "ATAPI tape driver buffers");
86
87int
88astattach(struct ata_device *atadev)
89{
90 struct ast_softc *stp;
91 struct ast_readposition position;
92 dev_t dev;
93
94 stp = malloc(sizeof(struct ast_softc), M_AST, M_NOWAIT | M_ZERO);
95 if (!stp) {
96 ata_prtdev(atadev, "out of memory\n");
97 return -1;
98 }
99
100 stp->device = atadev;
101 stp->lun = ata_get_lun(&ast_lun_map);
102 ata_set_name(atadev, "ast", stp->lun);
103 bioq_init(&stp->queue);
104
105 if (ast_sense(stp)) {
106 free(stp, M_AST);
107 return -1;
108 }
109
110 if (!strcmp(atadev->param->model, "OnStream DI-30")) {
111 struct ast_transferpage transfer;
112 struct ast_identifypage identify;
113
114 stp->flags |= F_ONSTREAM;
115 bzero(&transfer, sizeof(struct ast_transferpage));
116 ast_mode_sense(stp, ATAPI_TAPE_TRANSFER_PAGE,
117 &transfer, sizeof(transfer));
118 bzero(&identify, sizeof(struct ast_identifypage));
119 ast_mode_sense(stp, ATAPI_TAPE_IDENTIFY_PAGE,
120 &identify, sizeof(identify));
121 strncpy(identify.ident, "FBSD", 4);
122 ast_mode_select(stp, &identify, sizeof(identify));
123 ast_read_position(stp, 0, &position);
124 }
125
126 devstat_add_entry(&stp->stats, "ast", stp->lun, DEV_BSIZE,
127 DEVSTAT_NO_ORDERED_TAGS,
128 DEVSTAT_TYPE_SEQUENTIAL | DEVSTAT_TYPE_IF_IDE,
129 DEVSTAT_PRIORITY_TAPE);
130 dev = make_dev(&ast_cdevsw, dkmakeminor(stp->lun, 0, 0),
131 UID_ROOT, GID_OPERATOR, 0640, "ast%d", stp->lun);
132 dev->si_drv1 = stp;
133 dev->si_iosize_max = 252 * DEV_BSIZE;
134 stp->dev1 = dev;
135 dev = make_dev(&ast_cdevsw, dkmakeminor(stp->lun, 0, 1),
136 UID_ROOT, GID_OPERATOR, 0640, "nast%d", stp->lun);
137 dev->si_drv1 = stp;
138 dev->si_iosize_max = 252 * DEV_BSIZE;
139 stp->dev2 = dev;
140 stp->device->flags |= ATA_D_MEDIA_CHANGED;
141 ast_describe(stp);
142 atadev->driver = stp;
143 return 0;
144}
145
146void
147astdetach(struct ata_device *atadev)
148{
149 struct ast_softc *stp = atadev->driver;
150 struct bio *bp;
151
152 while ((bp = bioq_first(&stp->queue))) {
153 bioq_remove(&stp->queue, bp);
154 biofinish(bp, NULL, ENXIO);
155 }
156 destroy_dev(stp->dev1);
157 destroy_dev(stp->dev2);
158 devstat_remove_entry(&stp->stats);
159 ata_free_name(atadev);
160 ata_free_lun(&ast_lun_map, stp->lun);
161 free(stp, M_AST);
162 atadev->driver = NULL;
163}
164
165static int
166ast_sense(struct ast_softc *stp)
167{
168 int count, error = 0;
169
170 /* get drive capabilities, some drives needs this repeated */
171 for (count = 0 ; count < 5 ; count++) {
172 if (!(error = ast_mode_sense(stp, ATAPI_TAPE_CAP_PAGE,
173 &stp->cap, sizeof(stp->cap)))) {
174 if (stp->cap.blk32k)
175 stp->blksize = 32768;
176 if (stp->cap.blk1024)
177 stp->blksize = 1024;
178 if (stp->cap.blk512)
179 stp->blksize = 512;
180 if (!stp->blksize)
181 continue;
182 stp->cap.max_speed = ntohs(stp->cap.max_speed);
183 stp->cap.max_defects = ntohs(stp->cap.max_defects);
184 stp->cap.ctl = ntohs(stp->cap.ctl);
185 stp->cap.speed = ntohs(stp->cap.speed);
186 stp->cap.buffer_size = ntohs(stp->cap.buffer_size);
187 return 0;
188 }
189 }
190 return 1;
191}
192
193static void
194ast_describe(struct ast_softc *stp)
195{
196 if (bootverbose) {
197 ata_prtdev(stp->device, "<%.40s/%.8s> tape drive at ata%d as %s\n",
198 stp->device->param->model, stp->device->param->revision,
199 device_get_unit(stp->device->channel->dev),
200 (stp->device->unit == ATA_MASTER) ? "master" : "slave");
201 ata_prtdev(stp->device, "%dKB/s, ", stp->cap.max_speed);
202 printf("transfer limit %d blk%s, ",
203 stp->cap.ctl, (stp->cap.ctl > 1) ? "s" : "");
204 printf("%dKB buffer, ", (stp->cap.buffer_size * DEV_BSIZE) / 1024);
205 printf("%s\n", ata_mode2str(stp->device->mode));
206 ata_prtdev(stp->device, "Medium: ");
207 switch (stp->cap.medium_type) {
208 case 0x00:
209 printf("none"); break;
210 case 0x17:
211 printf("Travan 1 (400 Mbyte)"); break;
212 case 0xb6:
213 printf("Travan 4 (4 Gbyte)"); break;
214 case 0xda:
215 printf("OnStream ADR (15Gyte)"); break;
216 default:
217 printf("unknown (0x%x)", stp->cap.medium_type);
218 }
219 if (stp->cap.readonly) printf(", readonly");
220 if (stp->cap.reverse) printf(", reverse");
221 if (stp->cap.eformat) printf(", eformat");
222 if (stp->cap.qfa) printf(", qfa");
223 if (stp->cap.lock) printf(", lock");
224 if (stp->cap.locked) printf(", locked");
225 if (stp->cap.prevent) printf(", prevent");
226 if (stp->cap.eject) printf(", eject");
227 if (stp->cap.disconnect) printf(", disconnect");
228 if (stp->cap.ecc) printf(", ecc");
229 if (stp->cap.compress) printf(", compress");
230 if (stp->cap.blk512) printf(", 512b");
231 if (stp->cap.blk1024) printf(", 1024b");
232 if (stp->cap.blk32k) printf(", 32kb");
233 printf("\n");
234 }
235 else {
236 ata_prtdev(stp->device, "TAPE <%.40s> at ata%d-%s %s\n",
237 stp->device->param->model,
238 device_get_unit(stp->device->channel->dev),
239 (stp->device->unit == ATA_MASTER) ? "master" : "slave",
240 ata_mode2str(stp->device->mode));
241 }
242}
243
244static int
245astopen(dev_t dev, int flags, int fmt, struct thread *td)
246{
247 struct ast_softc *stp = dev->si_drv1;
248
249 if (!stp)
250 return ENXIO;
251
252 if (count_dev(dev) > 1)
253 return EBUSY;
254
255 atapi_test_ready(stp->device);
256
257 if (stp->cap.lock)
258 ast_prevent_allow(stp, 1);
259
260 if (ast_sense(stp))
261 ata_prtdev(stp->device, "sense media type failed\n");
262
263 stp->device->flags &= ~ATA_D_MEDIA_CHANGED;
264 stp->flags &= ~(F_DATA_WRITTEN | F_FM_WRITTEN);
265 ast_total = 0;
266 return 0;
267}
268
269static int
270astclose(dev_t dev, int flags, int fmt, struct thread *td)
271{
272 struct ast_softc *stp = dev->si_drv1;
273
274 /* flush buffers, some drives fail here, they should report ctl = 0 */
275 if (stp->cap.ctl && (stp->flags & F_DATA_WRITTEN))
276 ast_write_filemark(stp, 0);
277
278 /* write filemark if data written to tape */
279 if (!(stp->flags & F_ONSTREAM) &&
280 (stp->flags & (F_DATA_WRITTEN | F_FM_WRITTEN)) == F_DATA_WRITTEN)
281 ast_write_filemark(stp, WF_WRITE);
282
283 /* if minor is even rewind on close */
284 if (!(minor(dev) & 0x01))
285 ast_rewind(stp);
286
287 if (stp->cap.lock && count_dev(dev) == 1)
288 ast_prevent_allow(stp, 0);
289
290 stp->flags &= F_CTL_WARN;
291#ifdef AST_DEBUG
292 ata_prtdev(stp->device, "%llu total bytes transferred\n", ast_total);
293#endif
294 return 0;
295}
296
297static int
298astioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct thread *td)
299{
300 struct ast_softc *stp = dev->si_drv1;
301 int error = 0;
302
303 switch (cmd) {
304 case MTIOCGET:
305 {
306 struct mtget *g = (struct mtget *) addr;
307
308 bzero(g, sizeof(struct mtget));
309 g->mt_type = 7;
310 g->mt_density = 1;
311 g->mt_blksiz = stp->blksize;
312 g->mt_comp = stp->cap.compress;
313 g->mt_density0 = 0; g->mt_density1 = 0;
314 g->mt_density2 = 0; g->mt_density3 = 0;
315 g->mt_blksiz0 = 0; g->mt_blksiz1 = 0;
316 g->mt_blksiz2 = 0; g->mt_blksiz3 = 0;
317 g->mt_comp0 = 0; g->mt_comp1 = 0;
318 g->mt_comp2 = 0; g->mt_comp3 = 0;
319 break;
320 }
321 case MTIOCTOP:
322 {
323 int i;
324 struct mtop *mt = (struct mtop *)addr;
325
326 switch ((int16_t) (mt->mt_op)) {
327
328 case MTWEOF:
329 for (i=0; i < mt->mt_count && !error; i++)
330 error = ast_write_filemark(stp, WF_WRITE);
331 break;
332
333 case MTFSF:
334 if (mt->mt_count)
335 error = ast_space(stp, SP_FM, mt->mt_count);
336 break;
337
338 case MTBSF:
339 if (mt->mt_count)
340 error = ast_space(stp, SP_FM, -(mt->mt_count));
341 break;
342
343 case MTREW:
344 error = ast_rewind(stp);
345 break;
346
347 case MTOFFL:
348 error = ast_load_unload(stp, SS_EJECT);
349 break;
350
351 case MTNOP:
352 error = ast_write_filemark(stp, 0);
353 break;
354
355 case MTERASE:
356 error = ast_erase(stp);
357 break;
358
359 case MTEOD:
360 error = ast_space(stp, SP_EOD, 0);
361 break;
362
363 case MTRETENS:
364 error = ast_load_unload(stp, SS_RETENSION | SS_LOAD);
365 break;
366
367 case MTFSR:
368 case MTBSR:
369 case MTCACHE:
370 case MTNOCACHE:
371 case MTSETBSIZ:
372 case MTSETDNSTY:
373 case MTCOMP:
374 default:
375 error = EINVAL;
376 }
377 break;
378 }
379 case MTIOCRDSPOS:
380 {
381 struct ast_readposition position;
382
383 if ((error = ast_read_position(stp, 0, &position)))
384 break;
385 *(u_int32_t *)addr = position.tape;
386 break;
387 }
388 case MTIOCRDHPOS:
389 {
390 struct ast_readposition position;
391
392 if ((error = ast_read_position(stp, 1, &position)))
393 break;
394 *(u_int32_t *)addr = position.tape;
395 break;
396 }
397 case MTIOCSLOCATE:
398 error = ast_locate(stp, 0, *(u_int32_t *)addr);
399 break;
400 case MTIOCHLOCATE:
401 error = ast_locate(stp, 1, *(u_int32_t *)addr);
402 break;
403 default:
404 error = ENOTTY;
405 }
406 return error;
407}
408
409static void
410aststrategy(struct bio *bp)
411{
412 struct ast_softc *stp = bp->bio_dev->si_drv1;
413 int s;
414
415 if (stp->device->flags & ATA_D_DETACHING) {
416 biofinish(bp, NULL, ENXIO);
417 return;
418 }
419
420 /* if it's a null transfer, return immediatly. */
421 if (bp->bio_bcount == 0) {
422 bp->bio_resid = 0;
423 biodone(bp);
424 return;
425 }
426 if (!(bp->bio_cmd == BIO_READ) && stp->flags & F_WRITEPROTECT) {
427 biofinish(bp, NULL, EPERM);
428 return;
429 }
430
431 /* check for != blocksize requests */
432 if (bp->bio_bcount % stp->blksize) {
433 ata_prtdev(stp->device, "transfers must be multiple of %d\n",
434 stp->blksize);
435 biofinish(bp, NULL, EIO);
436 return;
437 }
438
439 /* warn about transfers bigger than the device suggests */
440 if (bp->bio_bcount > stp->blksize * stp->cap.ctl) {
441 if ((stp->flags & F_CTL_WARN) == 0) {
442 ata_prtdev(stp->device, "WARNING: CTL exceeded %ld>%d\n",
443 bp->bio_bcount, stp->blksize * stp->cap.ctl);
444 stp->flags |= F_CTL_WARN;
445 }
446 }
447
448 s = splbio();
449 bioq_insert_tail(&stp->queue, bp);
450 ata_start(stp->device->channel);
451 splx(s);
452}
453
454void
455ast_start(struct ata_device *atadev)
456{
457 struct ast_softc *stp = atadev->driver;
458 struct bio *bp = bioq_first(&stp->queue);
459 u_int32_t blkcount;
460 int8_t ccb[16];
461
462 if (!bp)
463 return;
464
465 bzero(ccb, sizeof(ccb));
466
467 if (bp->bio_cmd == BIO_READ)
468 ccb[0] = ATAPI_READ;
469 else
470 ccb[0] = ATAPI_WRITE;
471
472 bioq_remove(&stp->queue, bp);
473 blkcount = bp->bio_bcount / stp->blksize;
474
475 ccb[1] = 1;
476 ccb[2] = blkcount>>16;
477 ccb[3] = blkcount>>8;
478 ccb[4] = blkcount;
479
480 devstat_start_transaction(&stp->stats);
481
482 atapi_queue_cmd(stp->device, ccb, bp->bio_data, blkcount * stp->blksize,
483 (bp->bio_cmd == BIO_READ) ? ATPR_F_READ : 0,
484 120, ast_done, bp);
485}
486
487static int
488ast_done(struct atapi_request *request)
489{
490 struct bio *bp = request->driver;
491 struct ast_softc *stp = request->device->driver;
492
493 if (request->error) {
494 bp->bio_error = request->error;
495 bp->bio_flags |= BIO_ERROR;
496 }
497 else {
498 if (!(bp->bio_cmd == BIO_READ))
499 stp->flags |= F_DATA_WRITTEN;
500 bp->bio_resid = bp->bio_bcount - request->donecount;
501 ast_total += (bp->bio_bcount - bp->bio_resid);
502 }
503 biofinish(bp, &stp->stats, 0);
504 return 0;
505}
506
507static int
508ast_mode_sense(struct ast_softc *stp, int page, void *pagebuf, int pagesize)
509{
510 int8_t ccb[16] = { ATAPI_MODE_SENSE, 0x08, page, pagesize>>8, pagesize,
511 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
512 int error;
513
514 error = atapi_queue_cmd(stp->device, ccb, pagebuf, pagesize, ATPR_F_READ,
515 10, NULL, NULL);
516#ifdef AST_DEBUG
517 atapi_dump("ast: mode sense ", pagebuf, pagesize);
518#endif
519 return error;
520}
521
522static int
523ast_mode_select(struct ast_softc *stp, void *pagebuf, int pagesize)
524{
525 int8_t ccb[16] = { ATAPI_MODE_SELECT, 0x10, 0, pagesize>>8, pagesize,
526 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
527
528#ifdef AST_DEBUG
529 ata_prtdev(stp->device, "modeselect pagesize=%d\n", pagesize);
530 atapi_dump("mode select ", pagebuf, pagesize);
531#endif
532 return atapi_queue_cmd(stp->device, ccb, pagebuf, pagesize, 0,
533 10, NULL, NULL);
534}
535
536static int
537ast_write_filemark(struct ast_softc *stp, u_int8_t function)
538{
539 int8_t ccb[16] = { ATAPI_WEOF, 0x01, 0, 0, function, 0, 0, 0,
540 0, 0, 0, 0, 0, 0, 0, 0 };
541 int error;
542
543 if (stp->flags & F_ONSTREAM)
544 ccb[4] = 0x00; /* only flush buffers supported */
545 else {
546 if (function) {
547 if (stp->flags & F_FM_WRITTEN)
548 stp->flags &= ~F_DATA_WRITTEN;
549 else
550 stp->flags |= F_FM_WRITTEN;
551 }
552 }
553 error = atapi_queue_cmd(stp->device, ccb, NULL, 0, 0, 10, NULL, NULL);
554 if (error)
555 return error;
556 return atapi_wait_dsc(stp->device, 10*60);
557}
558
559static int
560ast_read_position(struct ast_softc *stp, int hard,
561 struct ast_readposition *position)
562{
563 int8_t ccb[16] = { ATAPI_READ_POSITION, (hard ? 0x01 : 0), 0, 0, 0, 0, 0, 0,
564 0, 0, 0, 0, 0, 0, 0, 0 };
565 int error;
566
567 error = atapi_queue_cmd(stp->device, ccb, (caddr_t)position,
568 sizeof(struct ast_readposition), ATPR_F_READ, 10,
569 NULL, NULL);
570 position->tape = ntohl(position->tape);
571 position->host = ntohl(position->host);
572 return error;
573}
574
575static int
576ast_space(struct ast_softc *stp, u_int8_t function, int32_t count)
577{
578 int8_t ccb[16] = { ATAPI_SPACE, function, count>>16, count>>8, count,
579 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
580
581 return atapi_queue_cmd(stp->device, ccb, NULL, 0, 0, 60*60, NULL, NULL);
582}
583
584static int
585ast_locate(struct ast_softc *stp, int hard, u_int32_t pos)
586{
587 int8_t ccb[16] = { ATAPI_LOCATE, 0x01 | (hard ? 0x4 : 0), 0,
588 pos>>24, pos>>16, pos>>8, pos,
589 0, 0, 0, 0, 0, 0, 0, 0, 0 };
590 int error;
591
592 error = atapi_queue_cmd(stp->device, ccb, NULL, 0, 0, 10, NULL, NULL);
593 if (error)
594 return error;
595 return atapi_wait_dsc(stp->device, 60*60);
596}
597
598static int
599ast_prevent_allow(struct ast_softc *stp, int lock)
600{
601 int8_t ccb[16] = { ATAPI_PREVENT_ALLOW, 0, 0, 0, lock,
602 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
603
604 return atapi_queue_cmd(stp->device, ccb, NULL, 0, 0,30, NULL, NULL);
605}
606
607static int
608ast_load_unload(struct ast_softc *stp, u_int8_t function)
609{
610 int8_t ccb[16] = { ATAPI_START_STOP, 0x01, 0, 0, function, 0, 0, 0,
611 0, 0, 0, 0, 0, 0, 0, 0 };
612 int error;
613
614 if ((function & SS_EJECT) && !stp->cap.eject)
615 return 0;
616 error = atapi_queue_cmd(stp->device, ccb, NULL, 0, 0, 10, NULL, NULL);
617 if (error)
618 return error;
619 tsleep((caddr_t)&error, PRIBIO, "astlu", 1 * hz);
620 if (function == SS_EJECT)
621 return 0;
622 return atapi_wait_dsc(stp->device, 60*60);
623}
624
625static int
626ast_rewind(struct ast_softc *stp)
627{
628 int8_t ccb[16] = { ATAPI_REZERO, 0x01, 0, 0, 0, 0, 0, 0,
629 0, 0, 0, 0, 0, 0, 0, 0 };
630 int error;
631
632 error = atapi_queue_cmd(stp->device, ccb, NULL, 0, 0, 10, NULL, NULL);
633 if (error)
634 return error;
635 return atapi_wait_dsc(stp->device, 60*60);
636}
637
638static int
639ast_erase(struct ast_softc *stp)
640{
641 int8_t ccb[16] = { ATAPI_ERASE, 3, 0, 0, 0, 0, 0, 0,
642 0, 0, 0, 0, 0, 0, 0, 0 };
643 int error;
644
645 if ((error = ast_rewind(stp)))
646 return error;
647
648 return atapi_queue_cmd(stp->device, ccb, NULL, 0, 0, 60*60, NULL, NULL);
649}