xref: /netbsd-src/sys/dev/scsipi/ch.c (revision 81b108b45f75f89f1e3ffad9fb6f074e771c0935)
1 /*	$NetBSD: ch.c,v 1.21 1996/04/19 00:02:29 christos Exp $	*/
2 
3 /*
4  * Copyright (c) 1996 Jason R. Thorpe <thorpej@and.com>
5  * All rights reserved.
6  *
7  * Partially based on an autochanger driver written by Stefan Grefen
8  * and on an autochanger driver written by the Systems Programming Group
9  * at the University of Utah Computer Science Department.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgements:
21  *	This product includes software developed by Jason R. Thorpe
22  *	for And Communications, http://www.and.com/
23  * 4. The name of the author may not be used to endorse or promote products
24  *    derived from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
27  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
30  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
31  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
33  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
34  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  */
38 
39 #include <sys/param.h>
40 #include <sys/systm.h>
41 #include <sys/errno.h>
42 #include <sys/ioctl.h>
43 #include <sys/buf.h>
44 #include <sys/proc.h>
45 #include <sys/user.h>
46 #include <sys/chio.h>
47 #include <sys/device.h>
48 #include <sys/malloc.h>
49 #include <sys/conf.h>
50 
51 #include <scsi/scsi_all.h>
52 #include <scsi/scsi_changer.h>
53 #include <scsi/scsiconf.h>
54 
55 #define CHRETRIES	2
56 #define CHUNIT(x)	(minor((x)))
57 
58 struct ch_softc {
59 	struct device	sc_dev;		/* generic device info */
60 	struct scsi_link *sc_link;	/* link in the SCSI bus */
61 
62 	int		sc_picker;	/* current picker */
63 
64 	/*
65 	 * The following information is obtained from the
66 	 * element address assignment page.
67 	 */
68 	int		sc_firsts[4];	/* firsts, indexed by CHET_* */
69 	int		sc_counts[4];	/* counts, indexed by CHET_* */
70 
71 	/*
72 	 * The following mask defines the legal combinations
73 	 * of elements for the MOVE MEDIUM command.
74 	 */
75 	u_int8_t	sc_movemask[4];
76 
77 	/*
78 	 * As above, but for EXCHANGE MEDIUM.
79 	 */
80 	u_int8_t	sc_exchangemask[4];
81 
82 	int		flags;		/* misc. info */
83 };
84 
85 /* sc_flags */
86 #define CHF_ROTATE	0x01		/* picker can rotate */
87 
88 /* Autoconfiguration glue */
89 int	chmatch __P((struct device *, void *, void *));
90 void	chattach __P((struct device *, struct device *, void *));
91 
92 struct cfattach ch_ca = {
93 	sizeof(struct ch_softc), chmatch, chattach
94 };
95 
96 struct cfdriver ch_cd = {
97 	NULL, "ch", DV_DULL
98 };
99 
100 struct scsi_inquiry_pattern ch_patterns[] = {
101 	{T_CHANGER, T_REMOV,
102 	 "",		"",		""},
103 };
104 
105 /* SCSI glue */
106 struct scsi_device ch_switch = {
107 	NULL, NULL, NULL, NULL
108 };
109 
110 int	ch_move __P((struct ch_softc *, struct changer_move *));
111 int	ch_exchange __P((struct ch_softc *, struct changer_exchange *));
112 int	ch_position __P((struct ch_softc *, struct changer_position *));
113 int	ch_usergetelemstatus __P((struct ch_softc *, int, u_int8_t *));
114 int	ch_getelemstatus __P((struct ch_softc *, int, int, caddr_t, size_t));
115 int	ch_get_params __P((struct ch_softc *, int));
116 
117 int
118 chmatch(parent, match, aux)
119 	struct device *parent;
120 	void *match, *aux;
121 {
122 	struct scsibus_attach_args *sa = aux;
123 	int priority;
124 
125 	(void)scsi_inqmatch(sa->sa_inqbuf,
126 	    (caddr_t)ch_patterns, sizeof(ch_patterns)/sizeof(ch_patterns[0]),
127 	    sizeof(ch_patterns[0]), &priority);
128 
129 	return (priority);
130 }
131 
132 void
133 chattach(parent, self, aux)
134 	struct device *parent, *self;
135 	void *aux;
136 {
137 	struct ch_softc *sc = (struct ch_softc *)self;
138 	struct scsibus_attach_args *sa = aux;
139 	struct scsi_link *link = sa->sa_sc_link;
140 
141 	/* Glue into the SCSI bus */
142 	sc->sc_link = link;
143 	link->device = &ch_switch;
144 	link->device_softc = sc;
145 	link->openings = 1;
146 
147 	printf("\n");
148 
149 	/*
150 	 * Get information about the device.  Note we can't use
151 	 * interrupts yet.
152 	 */
153 	if (ch_get_params(sc, SCSI_AUTOCONF))
154 		printf("%s: offline\n", sc->sc_dev.dv_xname);
155 	else {
156 		printf("%s: %d slot%s, %d drive%s, %d picker%s",
157 		    sc->sc_dev.dv_xname,
158 		    sc->sc_counts[CHET_ST], (sc->sc_counts[CHET_ST] > 1) ?
159 		    "s" : "",
160 		    sc->sc_counts[CHET_DT], (sc->sc_counts[CHET_DT] > 1) ?
161 		    "s" : "",
162 		    sc->sc_counts[CHET_MT], (sc->sc_counts[CHET_MT] > 1) ?
163 		    "s" : "");
164 		if (sc->sc_counts[CHET_IE])
165 			printf(", %d portal%s", sc->sc_counts[CHET_IE],
166 			    (sc->sc_counts[CHET_IE] > 1) ? "s" : "");
167 		printf("\n");
168 #ifdef CHANGER_DEBUG
169 		printf("%s: move mask: 0x%x 0x%x 0x%x 0x%x\n",
170 		    sc->sc_dev.dv_xname,
171 		    sc->sc_movemask[CHET_MT], sc->sc_movemask[CHET_ST],
172 		    sc->sc_movemask[CHET_IE], sc->sc_movemask[CHET_DT]);
173 		printf("%s: exchange mask: 0x%x 0x%x 0x%x 0x%x\n",
174 		    sc->sc_dev.dv_xname,
175 		    sc->sc_exchangemask[CHET_MT], sc->sc_exchangemask[CHET_ST],
176 		    sc->sc_exchangemask[CHET_IE], sc->sc_exchangemask[CHET_DT]);
177 #endif /* CHANGER_DEBUG */
178 	}
179 
180 	/* Default the current picker. */
181 	sc->sc_picker = sc->sc_firsts[CHET_MT];
182 }
183 
184 int
185 chopen(dev, flags, fmt, p)
186 	dev_t dev;
187 	int flags, fmt;
188 	struct proc *p;
189 {
190 	struct ch_softc *sc;
191 	int unit, error = 0;
192 
193 	unit = CHUNIT(dev);
194 	if ((unit >= ch_cd.cd_ndevs) ||
195 	    ((sc = ch_cd.cd_devs[unit]) == NULL))
196 		return (ENXIO);
197 
198 	/*
199 	 * Only allow one open at a time.
200 	 */
201 	if (sc->sc_link->flags & SDEV_OPEN)
202 		return (EBUSY);
203 
204 	sc->sc_link->flags |= SDEV_OPEN;
205 
206 	/*
207 	 * Absorb any unit attention errors.  Ignore "not ready"
208 	 * since this might occur if e.g. a tape isn't actually
209 	 * loaded in the drive.
210 	 */
211 	error = scsi_test_unit_ready(sc->sc_link,
212 	    SCSI_IGNORE_NOT_READY|SCSI_IGNORE_MEDIA_CHANGE);
213 	if (error)
214 		goto bad;
215 
216 	/*
217 	 * Make sure our parameters are up to date.
218 	 */
219 	if ((error = ch_get_params(sc, 0)) != 0)
220 		goto bad;
221 
222 	return (0);
223 
224  bad:
225 	sc->sc_link->flags &= ~SDEV_OPEN;
226 	return (error);
227 }
228 
229 int
230 chclose(dev, flags, fmt, p)
231 	dev_t dev;
232 	int flags, fmt;
233 	struct proc *p;
234 {
235 	struct ch_softc *sc = ch_cd.cd_devs[CHUNIT(dev)];
236 
237 	sc->sc_link->flags &= ~SDEV_OPEN;
238 	return (0);
239 }
240 
241 int
242 chioctl(dev, cmd, data, flags, p)
243 	dev_t dev;
244 	u_long cmd;
245 	caddr_t data;
246 	int flags;
247 	struct proc *p;
248 {
249 	struct ch_softc *sc = ch_cd.cd_devs[CHUNIT(dev)];
250 	int error = 0;
251 
252 	switch (cmd) {
253 	case CHIOMOVE:
254 		error = ch_move(sc, (struct changer_move *)data);
255 		break;
256 
257 	case CHIOEXCHANGE:
258 		error = ch_exchange(sc, (struct changer_exchange *)data);
259 		break;
260 
261 	case CHIOPOSITION:
262 		error = ch_position(sc, (struct changer_position *)data);
263 		break;
264 
265 	case CHIOGPICKER:
266 		*(int *)data = sc->sc_picker - sc->sc_firsts[CHET_MT];
267 		break;
268 
269 	case CHIOSPICKER:	{
270 		int new_picker = *(int *)data;
271 
272 		if (new_picker > (sc->sc_counts[CHET_MT] - 1))
273 			return (EINVAL);
274 		sc->sc_picker = sc->sc_firsts[CHET_MT] + new_picker;
275 		break;		}
276 
277 	case CHIOGPARAMS:	{
278 		struct changer_params *cp = (struct changer_params *)data;
279 
280 		cp->cp_curpicker = sc->sc_picker - sc->sc_firsts[CHET_MT];
281 		cp->cp_npickers = sc->sc_counts[CHET_MT];
282 		cp->cp_nslots = sc->sc_counts[CHET_ST];
283 		cp->cp_nportals = sc->sc_counts[CHET_IE];
284 		cp->cp_ndrives = sc->sc_counts[CHET_DT];
285 		break;		}
286 
287 	case CHIOGSTATUS:	{
288 		struct changer_element_status *ces =
289 		    (struct changer_element_status *)data;
290 
291 		error = ch_usergetelemstatus(sc, ces->ces_type, ces->ces_data);
292 		break;		}
293 
294 	/* Implement prevent/allow? */
295 
296 	default:
297 		error = scsi_do_ioctl(sc->sc_link, dev, cmd, data, flags, p);
298 		break;
299 	}
300 
301 	return (error);
302 }
303 
304 int
305 ch_move(sc, cm)
306 	struct ch_softc *sc;
307 	struct changer_move *cm;
308 {
309 	struct scsi_move_medium cmd;
310 	u_int16_t fromelem, toelem;
311 
312 	/*
313 	 * Check arguments.
314 	 */
315 	if ((cm->cm_fromtype > CHET_DT) || (cm->cm_totype > CHET_DT))
316 		return (EINVAL);
317 	if ((cm->cm_fromunit > (sc->sc_counts[cm->cm_fromtype] - 1)) ||
318 	    (cm->cm_tounit > (sc->sc_counts[cm->cm_totype] - 1)))
319 		return (ENODEV);
320 
321 	/*
322 	 * Check the request against the changer's capabilities.
323 	 */
324 	if ((sc->sc_movemask[cm->cm_fromtype] & (1 << cm->cm_totype)) == 0)
325 		return (EINVAL);
326 
327 	/*
328 	 * Calculate the source and destination elements.
329 	 */
330 	fromelem = sc->sc_firsts[cm->cm_fromtype] + cm->cm_fromunit;
331 	toelem = sc->sc_firsts[cm->cm_totype] + cm->cm_tounit;
332 
333 	/*
334 	 * Build the SCSI command.
335 	 */
336 	bzero(&cmd, sizeof(cmd));
337 	cmd.opcode = MOVE_MEDIUM;
338 	_lto2b(sc->sc_picker, cmd.tea);
339 	_lto2b(fromelem, cmd.src);
340 	_lto2b(toelem, cmd.dst);
341 	if (cm->cm_flags & CM_INVERT)
342 		cmd.flags |= MOVE_MEDIUM_INVERT;
343 
344 	/*
345 	 * Send command to changer.
346 	 */
347 	return (scsi_scsi_cmd(sc->sc_link, (struct scsi_generic *)&cmd,
348 	    sizeof(cmd), NULL, 0, CHRETRIES, 100000, NULL, 0));
349 }
350 
351 int
352 ch_exchange(sc, ce)
353 	struct ch_softc *sc;
354 	struct changer_exchange *ce;
355 {
356 	struct scsi_exchange_medium cmd;
357 	u_int16_t src, dst1, dst2;
358 
359 	/*
360 	 * Check arguments.
361 	 */
362 	if ((ce->ce_srctype > CHET_DT) || (ce->ce_fdsttype > CHET_DT) ||
363 	    (ce->ce_sdsttype > CHET_DT))
364 		return (EINVAL);
365 	if ((ce->ce_srcunit > (sc->sc_counts[ce->ce_srctype] - 1)) ||
366 	    (ce->ce_fdstunit > (sc->sc_counts[ce->ce_fdsttype] - 1)) ||
367 	    (ce->ce_sdstunit > (sc->sc_counts[ce->ce_sdsttype] - 1)))
368 		return (ENODEV);
369 
370 	/*
371 	 * Check the request against the changer's capabilities.
372 	 */
373 	if (((sc->sc_exchangemask[ce->ce_srctype] &
374 	     (1 << ce->ce_fdsttype)) == 0) ||
375 	    ((sc->sc_exchangemask[ce->ce_fdsttype] &
376 	     (1 << ce->ce_sdsttype)) == 0))
377 		return (EINVAL);
378 
379 	/*
380 	 * Calculate the source and destination elements.
381 	 */
382 	src = sc->sc_firsts[ce->ce_srctype] + ce->ce_srcunit;
383 	dst1 = sc->sc_firsts[ce->ce_fdsttype] + ce->ce_fdstunit;
384 	dst2 = sc->sc_firsts[ce->ce_sdsttype] + ce->ce_sdstunit;
385 
386 	/*
387 	 * Build the SCSI command.
388 	 */
389 	bzero(&cmd, sizeof(cmd));
390 	cmd.opcode = EXCHANGE_MEDIUM;
391 	_lto2b(sc->sc_picker, cmd.tea);
392 	_lto2b(src, cmd.src);
393 	_lto2b(dst1, cmd.fdst);
394 	_lto2b(dst2, cmd.sdst);
395 	if (ce->ce_flags & CE_INVERT1)
396 		cmd.flags |= EXCHANGE_MEDIUM_INV1;
397 	if (ce->ce_flags & CE_INVERT2)
398 		cmd.flags |= EXCHANGE_MEDIUM_INV2;
399 
400 	/*
401 	 * Send command to changer.
402 	 */
403 	return (scsi_scsi_cmd(sc->sc_link, (struct scsi_generic *)&cmd,
404 	    sizeof(cmd), NULL, 0, CHRETRIES, 100000, NULL, 0));
405 }
406 
407 int
408 ch_position(sc, cp)
409 	struct ch_softc *sc;
410 	struct changer_position *cp;
411 {
412 	struct scsi_position_to_element cmd;
413 	u_int16_t dst;
414 
415 	/*
416 	 * Check arguments.
417 	 */
418 	if (cp->cp_type > CHET_DT)
419 		return (EINVAL);
420 	if (cp->cp_unit > (sc->sc_counts[cp->cp_type] - 1))
421 		return (ENODEV);
422 
423 	/*
424 	 * Calculate the destination element.
425 	 */
426 	dst = sc->sc_firsts[cp->cp_type] + cp->cp_unit;
427 
428 	/*
429 	 * Build the SCSI command.
430 	 */
431 	bzero(&cmd, sizeof(cmd));
432 	cmd.opcode = POSITION_TO_ELEMENT;
433 	_lto2b(sc->sc_picker, cmd.tea);
434 	_lto2b(dst, cmd.dst);
435 	if (cp->cp_flags & CP_INVERT)
436 		cmd.flags |= POSITION_TO_ELEMENT_INVERT;
437 
438 	/*
439 	 * Send command to changer.
440 	 */
441 	return (scsi_scsi_cmd(sc->sc_link, (struct scsi_generic *)&cmd,
442 	    sizeof(cmd), NULL, 0, CHRETRIES, 100000, NULL, 0));
443 }
444 
445 /*
446  * Perform a READ ELEMENT STATUS on behalf of the user, and return to
447  * the user only the data the user is interested in (i.e. an array of
448  * flags bytes).
449  */
450 int
451 ch_usergetelemstatus(sc, chet, uptr)
452 	struct ch_softc *sc;
453 	int chet;
454 	u_int8_t *uptr;
455 {
456 	struct read_element_status_header *st_hdr;
457 	struct read_element_status_page_header *pg_hdr;
458 	struct read_element_status_descriptor *desc;
459 	caddr_t data = NULL;
460 	size_t size, desclen;
461 	int avail, i, error = 0;
462 	u_int8_t *user_data = NULL;
463 
464 	/*
465 	 * If there are no elements of the requested type in the changer,
466 	 * the request is invalid.
467 	 */
468 	if (sc->sc_counts[chet] == 0)
469 		return (EINVAL);
470 
471 	/*
472 	 * Request one descriptor for the given element type.  This
473 	 * is used to determine the size of the descriptor so that
474 	 * we can allocate enough storage for all of them.  We assume
475 	 * that the first one can fit into 1k.
476 	 */
477 	data = (caddr_t)malloc(1024, M_DEVBUF, M_WAITOK);
478 	error = ch_getelemstatus(sc, sc->sc_firsts[chet], 1, data, 1024);
479 	if (error)
480 		goto done;
481 
482 	st_hdr = (struct read_element_status_header *)data;
483 	pg_hdr = (struct read_element_status_page_header *)((u_long)st_hdr +
484 	    sizeof(struct read_element_status_header));
485 	desclen = _2btol(pg_hdr->edl);
486 
487 	size = sizeof(struct read_element_status_header) +
488 	    sizeof(struct read_element_status_page_header) +
489 	    (desclen * sc->sc_counts[chet]);
490 
491 	/*
492 	 * Reallocate storage for descriptors and get them from the
493 	 * device.
494 	 */
495 	free(data, M_DEVBUF);
496 	data = (caddr_t)malloc(size, M_DEVBUF, M_WAITOK);
497 	error = ch_getelemstatus(sc, sc->sc_firsts[chet],
498 	    sc->sc_counts[chet], data, size);
499 	if (error)
500 		goto done;
501 
502 	/*
503 	 * Fill in the user status array.
504 	 */
505 	st_hdr = (struct read_element_status_header *)data;
506 	avail = _2btol(st_hdr->count);
507 	if (avail != sc->sc_counts[chet])
508 		printf("%s: warning, READ ELEMENT STATUS avail != count\n",
509 		    sc->sc_dev.dv_xname);
510 
511 	user_data = (u_int8_t *)malloc(avail, M_DEVBUF, M_WAITOK);
512 
513 	desc = (struct read_element_status_descriptor *)((u_long)data +
514 	    sizeof(struct read_element_status_header) +
515 	    sizeof(struct read_element_status_page_header));
516 	for (i = 0; i < avail; ++i) {
517 		user_data[i] = desc->flags1;
518 		(u_long)desc += desclen;
519 	}
520 
521 	/* Copy flags array out to userspace. */
522 	error = copyout(user_data, uptr, avail);
523 
524  done:
525 	if (data != NULL)
526 		free(data, M_DEVBUF);
527 	if (user_data != NULL)
528 		free(user_data, M_DEVBUF);
529 	return (error);
530 }
531 
532 int
533 ch_getelemstatus(sc, first, count, data, datalen)
534 	struct ch_softc *sc;
535 	int first, count;
536 	caddr_t data;
537 	size_t datalen;
538 {
539 	struct scsi_read_element_status cmd;
540 
541 	/*
542 	 * Build SCSI command.
543 	 */
544 	bzero(&cmd, sizeof(cmd));
545 	cmd.opcode = READ_ELEMENT_STATUS;
546 	_lto2b(first, cmd.sea);
547 	_lto2b(count, cmd.count);
548 	_lto3b(datalen, cmd.len);
549 
550 	/*
551 	 * Send command to changer.
552 	 */
553 	return (scsi_scsi_cmd(sc->sc_link, (struct scsi_generic *)&cmd,
554 	    sizeof(cmd), (u_char *)data, datalen, CHRETRIES, 100000, NULL, 0));
555 }
556 
557 
558 /*
559  * Ask the device about itself and fill in the parameters in our
560  * softc.
561  */
562 int
563 ch_get_params(sc, scsiflags)
564 	struct ch_softc *sc;
565 	int scsiflags;
566 {
567 	struct scsi_mode_sense cmd;
568 	struct scsi_mode_sense_data {
569 		struct scsi_mode_header header;
570 		union {
571 			struct page_element_address_assignment ea;
572 			struct page_transport_geometry_parameters tg;
573 			struct page_device_capabilities cap;
574 		} pages;
575 	} sense_data;
576 	int error, from;
577 	u_int8_t *moves, *exchanges;
578 
579 	/*
580 	 * Grab info from the element address assignment page.
581 	 */
582 	bzero(&cmd, sizeof(cmd));
583 	bzero(&sense_data, sizeof(sense_data));
584 	cmd.opcode = MODE_SENSE;
585 	cmd.byte2 |= 0x08;	/* disable block descriptors */
586 	cmd.page = 0x1d;
587 	cmd.length = (sizeof(sense_data) & 0xff);
588 	error = scsi_scsi_cmd(sc->sc_link, (struct scsi_generic *)&cmd,
589 	    sizeof(cmd), (u_char *)&sense_data, sizeof(sense_data), CHRETRIES,
590 	    6000, NULL, scsiflags | SCSI_DATA_IN);
591 	if (error) {
592 		printf("%s: could not sense element address page\n",
593 		    sc->sc_dev.dv_xname);
594 		return (error);
595 	}
596 
597 	sc->sc_firsts[CHET_MT] = _2btol(sense_data.pages.ea.mtea);
598 	sc->sc_counts[CHET_MT] = _2btol(sense_data.pages.ea.nmte);
599 	sc->sc_firsts[CHET_ST] = _2btol(sense_data.pages.ea.fsea);
600 	sc->sc_counts[CHET_ST] = _2btol(sense_data.pages.ea.nse);
601 	sc->sc_firsts[CHET_IE] = _2btol(sense_data.pages.ea.fieea);
602 	sc->sc_counts[CHET_IE] = _2btol(sense_data.pages.ea.niee);
603 	sc->sc_firsts[CHET_DT] = _2btol(sense_data.pages.ea.fdtea);
604 	sc->sc_counts[CHET_DT] = _2btol(sense_data.pages.ea.ndte);
605 
606 	/* XXX ask for page trasport geom */
607 
608 	/*
609 	 * Grab info from the capabilities page.
610 	 */
611 	bzero(&cmd, sizeof(cmd));
612 	bzero(&sense_data, sizeof(sense_data));
613 	cmd.opcode = MODE_SENSE;
614 	cmd.byte2 |= 0x08;	/* disable block descriptors */
615 	cmd.page = 0x1f;
616 	cmd.length = (sizeof(sense_data) & 0xff);
617 	error = scsi_scsi_cmd(sc->sc_link, (struct scsi_generic *)&cmd,
618 	    sizeof(cmd), (u_char *)&sense_data, sizeof(sense_data), CHRETRIES,
619 	    6000, NULL, scsiflags | SCSI_DATA_IN);
620 	if (error) {
621 		printf("%s: could not sense capabilities page\n",
622 		    sc->sc_dev.dv_xname);
623 		return (error);
624 	}
625 
626 	bzero(sc->sc_movemask, sizeof(sc->sc_movemask));
627 	bzero(sc->sc_exchangemask, sizeof(sc->sc_exchangemask));
628 	moves = &sense_data.pages.cap.move_from_mt;
629 	exchanges = &sense_data.pages.cap.exchange_with_mt;
630 	for (from = CHET_MT; from <= CHET_DT; ++from) {
631 		sc->sc_movemask[from] = moves[from];
632 		sc->sc_exchangemask[from] = exchanges[from];
633 	}
634 
635 	sc->sc_link->flags |= SDEV_MEDIA_LOADED;
636 	return (0);
637 }
638