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