| 1 | /* $NetBSD: ch.c,v 1.91 2016/11/20 15:37:19 mlelstv Exp $ */ |
| 2 | |
| 3 | /*- |
| 4 | * Copyright (c) 1996, 1997, 1998, 1999, 2004 The NetBSD Foundation, Inc. |
| 5 | * All rights reserved. |
| 6 | * |
| 7 | * This code is derived from software contributed to The NetBSD Foundation |
| 8 | * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility, |
| 9 | * NASA Ames Research Center. |
| 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 | * |
| 20 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS |
| 21 | * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| 22 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 23 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS |
| 24 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 30 | * POSSIBILITY OF SUCH DAMAGE. |
| 31 | */ |
| 32 | |
| 33 | #include <sys/cdefs.h> |
| 34 | __KERNEL_RCSID(0, "$NetBSD: ch.c,v 1.91 2016/11/20 15:37:19 mlelstv Exp $" ); |
| 35 | |
| 36 | #include <sys/param.h> |
| 37 | #include <sys/systm.h> |
| 38 | #include <sys/kernel.h> |
| 39 | #include <sys/errno.h> |
| 40 | #include <sys/ioctl.h> |
| 41 | #include <sys/buf.h> |
| 42 | #include <sys/proc.h> |
| 43 | #include <sys/chio.h> |
| 44 | #include <sys/device.h> |
| 45 | #include <sys/malloc.h> |
| 46 | #include <sys/conf.h> |
| 47 | #include <sys/fcntl.h> |
| 48 | #include <sys/vnode.h> |
| 49 | #include <sys/time.h> |
| 50 | #include <sys/select.h> |
| 51 | #include <sys/poll.h> |
| 52 | |
| 53 | #include <dev/scsipi/scsipi_all.h> |
| 54 | #include <dev/scsipi/scsi_all.h> |
| 55 | #include <dev/scsipi/scsi_changer.h> |
| 56 | #include <dev/scsipi/scsiconf.h> |
| 57 | |
| 58 | #define CHRETRIES 2 |
| 59 | #define CHTIMEOUT (5 * 60 * 1000) |
| 60 | |
| 61 | #define CHUNIT(x) (minor((x))) |
| 62 | |
| 63 | struct ch_softc { |
| 64 | device_t sc_dev; /* generic device info */ |
| 65 | struct scsipi_periph *sc_periph;/* our periph data */ |
| 66 | |
| 67 | u_int sc_events; /* event bitmask */ |
| 68 | struct selinfo sc_selq; /* select/poll queue for events */ |
| 69 | |
| 70 | int sc_flags; /* misc. info */ |
| 71 | |
| 72 | int sc_picker; /* current picker */ |
| 73 | |
| 74 | /* |
| 75 | * The following information is obtained from the |
| 76 | * element address assignment page. |
| 77 | */ |
| 78 | int sc_firsts[4]; /* firsts, indexed by CHET_* */ |
| 79 | int sc_counts[4]; /* counts, indexed by CHET_* */ |
| 80 | |
| 81 | /* |
| 82 | * The following mask defines the legal combinations |
| 83 | * of elements for the MOVE MEDIUM command. |
| 84 | */ |
| 85 | u_int8_t sc_movemask[4]; |
| 86 | |
| 87 | /* |
| 88 | * As above, but for EXCHANGE MEDIUM. |
| 89 | */ |
| 90 | u_int8_t sc_exchangemask[4]; |
| 91 | |
| 92 | /* |
| 93 | * Quirks; see below. |
| 94 | */ |
| 95 | int sc_settledelay; /* delay for settle */ |
| 96 | |
| 97 | }; |
| 98 | |
| 99 | /* sc_flags */ |
| 100 | #define CHF_ROTATE 0x01 /* picker can rotate */ |
| 101 | |
| 102 | /* Autoconfiguration glue */ |
| 103 | static int chmatch(device_t, cfdata_t, void *); |
| 104 | static void chattach(device_t, device_t, void *); |
| 105 | |
| 106 | CFATTACH_DECL_NEW(ch, sizeof(struct ch_softc), |
| 107 | chmatch, chattach, NULL, NULL); |
| 108 | |
| 109 | extern struct cfdriver ch_cd; |
| 110 | |
| 111 | static struct scsipi_inquiry_pattern ch_patterns[] = { |
| 112 | {T_CHANGER, T_REMOV, |
| 113 | "" , "" , "" }, |
| 114 | }; |
| 115 | |
| 116 | static dev_type_open(chopen); |
| 117 | static dev_type_close(chclose); |
| 118 | static dev_type_read(chread); |
| 119 | static dev_type_ioctl(chioctl); |
| 120 | static dev_type_poll(chpoll); |
| 121 | static dev_type_kqfilter(chkqfilter); |
| 122 | |
| 123 | const struct cdevsw ch_cdevsw = { |
| 124 | .d_open = chopen, |
| 125 | .d_close = chclose, |
| 126 | .d_read = chread, |
| 127 | .d_write = nowrite, |
| 128 | .d_ioctl = chioctl, |
| 129 | .d_stop = nostop, |
| 130 | .d_tty = notty, |
| 131 | .d_poll = chpoll, |
| 132 | .d_mmap = nommap, |
| 133 | .d_kqfilter = chkqfilter, |
| 134 | .d_discard = nodiscard, |
| 135 | .d_flag = D_OTHER | D_MPSAFE |
| 136 | }; |
| 137 | |
| 138 | /* SCSI glue */ |
| 139 | static int ch_interpret_sense(struct scsipi_xfer *); |
| 140 | |
| 141 | static const struct scsipi_periphsw ch_switch = { |
| 142 | ch_interpret_sense, /* check our error handler first */ |
| 143 | NULL, /* no queue; our commands are synchronous */ |
| 144 | NULL, /* have no async handler */ |
| 145 | NULL, /* nothing to be done when xfer is done */ |
| 146 | }; |
| 147 | |
| 148 | static int ch_move(struct ch_softc *, struct changer_move_request *); |
| 149 | static int ch_exchange(struct ch_softc *, |
| 150 | struct changer_exchange_request *); |
| 151 | static int ch_position(struct ch_softc *, |
| 152 | struct changer_position_request *); |
| 153 | static int ch_ielem(struct ch_softc *); |
| 154 | static int ch_ousergetelemstatus(struct ch_softc *, int, u_int8_t *); |
| 155 | static int ch_usergetelemstatus(struct ch_softc *, |
| 156 | struct changer_element_status_request *); |
| 157 | static int ch_getelemstatus(struct ch_softc *, int, int, void *, |
| 158 | size_t, int, int); |
| 159 | static int ch_setvoltag(struct ch_softc *, |
| 160 | struct changer_set_voltag_request *); |
| 161 | static int ch_get_params(struct ch_softc *, int); |
| 162 | static void ch_get_quirks(struct ch_softc *, |
| 163 | struct scsipi_inquiry_pattern *); |
| 164 | static void ch_event(struct ch_softc *, u_int); |
| 165 | static int ch_map_element(struct ch_softc *, u_int16_t, int *, int *); |
| 166 | |
| 167 | static void ch_voltag_convert_in(const struct changer_volume_tag *, |
| 168 | struct changer_voltag *); |
| 169 | static int ch_voltag_convert_out(const struct changer_voltag *, |
| 170 | struct changer_volume_tag *); |
| 171 | |
| 172 | /* |
| 173 | * SCSI changer quirks. |
| 174 | */ |
| 175 | struct chquirk { |
| 176 | struct scsipi_inquiry_pattern cq_match; /* device id pattern */ |
| 177 | int cq_settledelay; /* settle delay, in seconds */ |
| 178 | }; |
| 179 | |
| 180 | static const struct chquirk chquirks[] = { |
| 181 | {{T_CHANGER, T_REMOV, |
| 182 | "SPECTRA" , "9000" , "0200" }, |
| 183 | 75}, |
| 184 | }; |
| 185 | |
| 186 | static int |
| 187 | chmatch(device_t parent, cfdata_t match, |
| 188 | void *aux) |
| 189 | { |
| 190 | struct scsipibus_attach_args *sa = aux; |
| 191 | int priority; |
| 192 | |
| 193 | (void)scsipi_inqmatch(&sa->sa_inqbuf, |
| 194 | (void *)ch_patterns, sizeof(ch_patterns) / sizeof(ch_patterns[0]), |
| 195 | sizeof(ch_patterns[0]), &priority); |
| 196 | |
| 197 | return (priority); |
| 198 | } |
| 199 | |
| 200 | static void |
| 201 | chattach(device_t parent, device_t self, void *aux) |
| 202 | { |
| 203 | struct ch_softc *sc = device_private(self); |
| 204 | struct scsipibus_attach_args *sa = aux; |
| 205 | struct scsipi_periph *periph = sa->sa_periph; |
| 206 | |
| 207 | sc->sc_dev = self; |
| 208 | selinit(&sc->sc_selq); |
| 209 | |
| 210 | /* Glue into the SCSI bus */ |
| 211 | sc->sc_periph = periph; |
| 212 | periph->periph_dev = sc->sc_dev; |
| 213 | periph->periph_switch = &ch_switch; |
| 214 | |
| 215 | printf("\n" ); |
| 216 | |
| 217 | /* |
| 218 | * Find out our device's quirks. |
| 219 | */ |
| 220 | ch_get_quirks(sc, &sa->sa_inqbuf); |
| 221 | |
| 222 | /* |
| 223 | * Some changers require a long time to settle out, to do |
| 224 | * tape inventory, for instance. |
| 225 | */ |
| 226 | if (sc->sc_settledelay) { |
| 227 | printf("%s: waiting %d seconds for changer to settle...\n" , |
| 228 | device_xname(sc->sc_dev), sc->sc_settledelay); |
| 229 | delay(1000000 * sc->sc_settledelay); |
| 230 | } |
| 231 | |
| 232 | /* |
| 233 | * Get information about the device. Note we can't use |
| 234 | * interrupts yet. |
| 235 | */ |
| 236 | if (ch_get_params(sc, XS_CTL_DISCOVERY|XS_CTL_IGNORE_MEDIA_CHANGE)) |
| 237 | printf("%s: offline\n" , device_xname(sc->sc_dev)); |
| 238 | else { |
| 239 | #define PLURAL(c) (c) == 1 ? "" : "s" |
| 240 | printf("%s: %d slot%s, %d drive%s, %d picker%s, %d portal%s\n" , |
| 241 | device_xname(sc->sc_dev), |
| 242 | sc->sc_counts[CHET_ST], PLURAL(sc->sc_counts[CHET_ST]), |
| 243 | sc->sc_counts[CHET_DT], PLURAL(sc->sc_counts[CHET_DT]), |
| 244 | sc->sc_counts[CHET_MT], PLURAL(sc->sc_counts[CHET_MT]), |
| 245 | sc->sc_counts[CHET_IE], PLURAL(sc->sc_counts[CHET_IE])); |
| 246 | #undef PLURAL |
| 247 | #ifdef CHANGER_DEBUG |
| 248 | printf("%s: move mask: 0x%x 0x%x 0x%x 0x%x\n" , |
| 249 | device_xname(sc->sc_dev), |
| 250 | sc->sc_movemask[CHET_MT], sc->sc_movemask[CHET_ST], |
| 251 | sc->sc_movemask[CHET_IE], sc->sc_movemask[CHET_DT]); |
| 252 | printf("%s: exchange mask: 0x%x 0x%x 0x%x 0x%x\n" , |
| 253 | device_xname(sc->sc_dev), |
| 254 | sc->sc_exchangemask[CHET_MT], sc->sc_exchangemask[CHET_ST], |
| 255 | sc->sc_exchangemask[CHET_IE], sc->sc_exchangemask[CHET_DT]); |
| 256 | #endif /* CHANGER_DEBUG */ |
| 257 | } |
| 258 | |
| 259 | /* Default the current picker. */ |
| 260 | sc->sc_picker = sc->sc_firsts[CHET_MT]; |
| 261 | } |
| 262 | |
| 263 | static int |
| 264 | chopen(dev_t dev, int flags, int fmt, struct lwp *l) |
| 265 | { |
| 266 | struct ch_softc *sc; |
| 267 | struct scsipi_periph *periph; |
| 268 | struct scsipi_adapter *adapt; |
| 269 | int unit, error; |
| 270 | |
| 271 | unit = CHUNIT(dev); |
| 272 | sc = device_lookup_private(&ch_cd, unit); |
| 273 | if (sc == NULL) |
| 274 | return (ENXIO); |
| 275 | |
| 276 | periph = sc->sc_periph; |
| 277 | adapt = periph->periph_channel->chan_adapter; |
| 278 | |
| 279 | /* |
| 280 | * Only allow one open at a time. |
| 281 | */ |
| 282 | if (periph->periph_flags & PERIPH_OPEN) |
| 283 | return (EBUSY); |
| 284 | |
| 285 | if ((error = scsipi_adapter_addref(adapt)) != 0) |
| 286 | return (error); |
| 287 | |
| 288 | /* |
| 289 | * Make sure the unit is on-line. If a UNIT ATTENTION |
| 290 | * occurs, we will mark that an Init-Element-Status is |
| 291 | * needed in ch_get_params(). |
| 292 | * |
| 293 | * We ignore NOT READY in case e.g a magazine isn't actually |
| 294 | * loaded into the changer or a tape isn't in the drive. |
| 295 | */ |
| 296 | error = scsipi_test_unit_ready(periph, XS_CTL_IGNORE_NOT_READY); |
| 297 | if (error) |
| 298 | goto bad; |
| 299 | |
| 300 | periph->periph_flags |= PERIPH_OPEN; |
| 301 | |
| 302 | /* |
| 303 | * Make sure our parameters are up to date. |
| 304 | */ |
| 305 | if ((error = ch_get_params(sc, 0)) != 0) |
| 306 | goto bad; |
| 307 | |
| 308 | return (0); |
| 309 | |
| 310 | bad: |
| 311 | scsipi_adapter_delref(adapt); |
| 312 | periph->periph_flags &= ~PERIPH_OPEN; |
| 313 | return (error); |
| 314 | } |
| 315 | |
| 316 | static int |
| 317 | chclose(dev_t dev, int flags, int fmt, struct lwp *l) |
| 318 | { |
| 319 | struct ch_softc *sc = device_lookup_private(&ch_cd, CHUNIT(dev)); |
| 320 | struct scsipi_periph *periph = sc->sc_periph; |
| 321 | struct scsipi_adapter *adapt = periph->periph_channel->chan_adapter; |
| 322 | |
| 323 | scsipi_wait_drain(periph); |
| 324 | |
| 325 | scsipi_adapter_delref(adapt); |
| 326 | |
| 327 | sc->sc_events = 0; |
| 328 | |
| 329 | periph->periph_flags &= ~PERIPH_OPEN; |
| 330 | return (0); |
| 331 | } |
| 332 | |
| 333 | static int |
| 334 | chread(dev_t dev, struct uio *uio, int flags) |
| 335 | { |
| 336 | struct ch_softc *sc = device_lookup_private(&ch_cd, CHUNIT(dev)); |
| 337 | int error; |
| 338 | |
| 339 | if (uio->uio_resid != CHANGER_EVENT_SIZE) |
| 340 | return (EINVAL); |
| 341 | |
| 342 | /* |
| 343 | * Read never blocks; if there are no events pending, we just |
| 344 | * return an all-clear bitmask. |
| 345 | */ |
| 346 | error = uiomove(&sc->sc_events, CHANGER_EVENT_SIZE, uio); |
| 347 | if (error == 0) |
| 348 | sc->sc_events = 0; |
| 349 | return (error); |
| 350 | } |
| 351 | |
| 352 | static int |
| 353 | chioctl(dev_t dev, u_long cmd, void *data, int flags, struct lwp *l) |
| 354 | { |
| 355 | struct ch_softc *sc = device_lookup_private(&ch_cd, CHUNIT(dev)); |
| 356 | int error = 0; |
| 357 | |
| 358 | /* |
| 359 | * If this command can change the device's state, we must |
| 360 | * have the device open for writing. |
| 361 | */ |
| 362 | switch (cmd) { |
| 363 | case CHIOGPICKER: |
| 364 | case CHIOGPARAMS: |
| 365 | case OCHIOGSTATUS: |
| 366 | break; |
| 367 | |
| 368 | default: |
| 369 | if ((flags & FWRITE) == 0) |
| 370 | return (EBADF); |
| 371 | } |
| 372 | |
| 373 | switch (cmd) { |
| 374 | case CHIOMOVE: |
| 375 | error = ch_move(sc, (struct changer_move_request *)data); |
| 376 | break; |
| 377 | |
| 378 | case CHIOEXCHANGE: |
| 379 | error = ch_exchange(sc, |
| 380 | (struct changer_exchange_request *)data); |
| 381 | break; |
| 382 | |
| 383 | case CHIOPOSITION: |
| 384 | error = ch_position(sc, |
| 385 | (struct changer_position_request *)data); |
| 386 | break; |
| 387 | |
| 388 | case CHIOGPICKER: |
| 389 | *(int *)data = sc->sc_picker - sc->sc_firsts[CHET_MT]; |
| 390 | break; |
| 391 | |
| 392 | case CHIOSPICKER: |
| 393 | { |
| 394 | int new_picker = *(int *)data; |
| 395 | |
| 396 | if (new_picker > (sc->sc_counts[CHET_MT] - 1)) |
| 397 | return (EINVAL); |
| 398 | sc->sc_picker = sc->sc_firsts[CHET_MT] + new_picker; |
| 399 | break; |
| 400 | } |
| 401 | |
| 402 | case CHIOGPARAMS: |
| 403 | { |
| 404 | struct changer_params *cp = (struct changer_params *)data; |
| 405 | |
| 406 | cp->cp_curpicker = sc->sc_picker - sc->sc_firsts[CHET_MT]; |
| 407 | cp->cp_npickers = sc->sc_counts[CHET_MT]; |
| 408 | cp->cp_nslots = sc->sc_counts[CHET_ST]; |
| 409 | cp->cp_nportals = sc->sc_counts[CHET_IE]; |
| 410 | cp->cp_ndrives = sc->sc_counts[CHET_DT]; |
| 411 | break; |
| 412 | } |
| 413 | |
| 414 | case CHIOIELEM: |
| 415 | error = ch_ielem(sc); |
| 416 | if (error == 0) { |
| 417 | sc->sc_periph->periph_flags |= PERIPH_MEDIA_LOADED; |
| 418 | } |
| 419 | break; |
| 420 | |
| 421 | case OCHIOGSTATUS: |
| 422 | { |
| 423 | struct ochanger_element_status_request *cesr = |
| 424 | (struct ochanger_element_status_request *)data; |
| 425 | |
| 426 | error = ch_ousergetelemstatus(sc, cesr->cesr_type, |
| 427 | cesr->cesr_data); |
| 428 | break; |
| 429 | } |
| 430 | |
| 431 | case CHIOGSTATUS: |
| 432 | error = ch_usergetelemstatus(sc, |
| 433 | (struct changer_element_status_request *)data); |
| 434 | break; |
| 435 | |
| 436 | case CHIOSVOLTAG: |
| 437 | error = ch_setvoltag(sc, |
| 438 | (struct changer_set_voltag_request *)data); |
| 439 | break; |
| 440 | |
| 441 | /* Implement prevent/allow? */ |
| 442 | |
| 443 | default: |
| 444 | error = scsipi_do_ioctl(sc->sc_periph, dev, cmd, data, |
| 445 | flags, l); |
| 446 | break; |
| 447 | } |
| 448 | |
| 449 | return (error); |
| 450 | } |
| 451 | |
| 452 | static int |
| 453 | chpoll(dev_t dev, int events, struct lwp *l) |
| 454 | { |
| 455 | struct ch_softc *sc = device_lookup_private(&ch_cd, CHUNIT(dev)); |
| 456 | int revents; |
| 457 | |
| 458 | revents = events & (POLLOUT | POLLWRNORM); |
| 459 | |
| 460 | if ((events & (POLLIN | POLLRDNORM)) == 0) |
| 461 | return (revents); |
| 462 | |
| 463 | if (sc->sc_events == 0) |
| 464 | revents |= events & (POLLIN | POLLRDNORM); |
| 465 | else |
| 466 | selrecord(l, &sc->sc_selq); |
| 467 | |
| 468 | return (revents); |
| 469 | } |
| 470 | |
| 471 | static void |
| 472 | filt_chdetach(struct knote *kn) |
| 473 | { |
| 474 | struct ch_softc *sc = kn->kn_hook; |
| 475 | |
| 476 | SLIST_REMOVE(&sc->sc_selq.sel_klist, kn, knote, kn_selnext); |
| 477 | } |
| 478 | |
| 479 | static int |
| 480 | filt_chread(struct knote *kn, long hint) |
| 481 | { |
| 482 | struct ch_softc *sc = kn->kn_hook; |
| 483 | |
| 484 | if (sc->sc_events == 0) |
| 485 | return (0); |
| 486 | kn->kn_data = CHANGER_EVENT_SIZE; |
| 487 | return (1); |
| 488 | } |
| 489 | |
| 490 | static const struct filterops chread_filtops = |
| 491 | { 1, NULL, filt_chdetach, filt_chread }; |
| 492 | |
| 493 | static const struct filterops chwrite_filtops = |
| 494 | { 1, NULL, filt_chdetach, filt_seltrue }; |
| 495 | |
| 496 | static int |
| 497 | chkqfilter(dev_t dev, struct knote *kn) |
| 498 | { |
| 499 | struct ch_softc *sc = device_lookup_private(&ch_cd, CHUNIT(dev)); |
| 500 | struct klist *klist; |
| 501 | |
| 502 | switch (kn->kn_filter) { |
| 503 | case EVFILT_READ: |
| 504 | klist = &sc->sc_selq.sel_klist; |
| 505 | kn->kn_fop = &chread_filtops; |
| 506 | break; |
| 507 | |
| 508 | case EVFILT_WRITE: |
| 509 | klist = &sc->sc_selq.sel_klist; |
| 510 | kn->kn_fop = &chwrite_filtops; |
| 511 | break; |
| 512 | |
| 513 | default: |
| 514 | return (EINVAL); |
| 515 | } |
| 516 | |
| 517 | kn->kn_hook = sc; |
| 518 | |
| 519 | SLIST_INSERT_HEAD(klist, kn, kn_selnext); |
| 520 | |
| 521 | return (0); |
| 522 | } |
| 523 | |
| 524 | static int |
| 525 | ch_interpret_sense(struct scsipi_xfer *xs) |
| 526 | { |
| 527 | struct scsipi_periph *periph = xs->xs_periph; |
| 528 | struct scsi_sense_data *sense = &xs->sense.scsi_sense; |
| 529 | struct ch_softc *sc = device_private(periph->periph_dev); |
| 530 | u_int16_t asc_ascq; |
| 531 | |
| 532 | /* |
| 533 | * If the periph is already recovering, just do the |
| 534 | * normal error recovering. |
| 535 | */ |
| 536 | if (periph->periph_flags & PERIPH_RECOVERING) |
| 537 | return (EJUSTRETURN); |
| 538 | |
| 539 | /* |
| 540 | * If it isn't an extended or extended/deferred error, let |
| 541 | * the generic code handle it. |
| 542 | */ |
| 543 | if (SSD_RCODE(sense->response_code) != SSD_RCODE_CURRENT && |
| 544 | SSD_RCODE(sense->response_code) != SSD_RCODE_DEFERRED) |
| 545 | return (EJUSTRETURN); |
| 546 | |
| 547 | /* |
| 548 | * We're only interested in condtions that |
| 549 | * indicate potential inventory violation. |
| 550 | * |
| 551 | * We use ASC/ASCQ codes for this. |
| 552 | */ |
| 553 | |
| 554 | asc_ascq = (((u_int16_t) sense->asc) << 8) | |
| 555 | sense->ascq; |
| 556 | |
| 557 | switch (asc_ascq) { |
| 558 | case 0x2800: |
| 559 | /* "Not Ready To Ready Transition (Medium May Have Changed)" */ |
| 560 | case 0x2900: |
| 561 | /* "Power On, Reset, or Bus Device Reset Occurred" */ |
| 562 | sc->sc_periph->periph_flags &= ~PERIPH_MEDIA_LOADED; |
| 563 | /* |
| 564 | * Enqueue an Element-Status-Changed event, and wake up |
| 565 | * any processes waiting for them. |
| 566 | */ |
| 567 | if ((xs->xs_control & XS_CTL_IGNORE_MEDIA_CHANGE) == 0) |
| 568 | ch_event(sc, CHEV_ELEMENT_STATUS_CHANGED); |
| 569 | break; |
| 570 | default: |
| 571 | break; |
| 572 | } |
| 573 | |
| 574 | return (EJUSTRETURN); |
| 575 | } |
| 576 | |
| 577 | static void |
| 578 | ch_event(struct ch_softc *sc, u_int event) |
| 579 | { |
| 580 | |
| 581 | sc->sc_events |= event; |
| 582 | selnotify(&sc->sc_selq, 0, 0); |
| 583 | } |
| 584 | |
| 585 | static int |
| 586 | ch_move(struct ch_softc *sc, struct changer_move_request *cm) |
| 587 | { |
| 588 | struct scsi_move_medium cmd; |
| 589 | u_int16_t fromelem, toelem; |
| 590 | |
| 591 | /* |
| 592 | * Check arguments. |
| 593 | */ |
| 594 | if ((cm->cm_fromtype > CHET_DT) || (cm->cm_totype > CHET_DT)) |
| 595 | return (EINVAL); |
| 596 | if ((cm->cm_fromunit > (sc->sc_counts[cm->cm_fromtype] - 1)) || |
| 597 | (cm->cm_tounit > (sc->sc_counts[cm->cm_totype] - 1))) |
| 598 | return (ENODEV); |
| 599 | |
| 600 | /* |
| 601 | * Check the request against the changer's capabilities. |
| 602 | */ |
| 603 | if ((sc->sc_movemask[cm->cm_fromtype] & (1 << cm->cm_totype)) == 0) |
| 604 | return (ENODEV); |
| 605 | |
| 606 | /* |
| 607 | * Calculate the source and destination elements. |
| 608 | */ |
| 609 | fromelem = sc->sc_firsts[cm->cm_fromtype] + cm->cm_fromunit; |
| 610 | toelem = sc->sc_firsts[cm->cm_totype] + cm->cm_tounit; |
| 611 | |
| 612 | /* |
| 613 | * Build the SCSI command. |
| 614 | */ |
| 615 | memset(&cmd, 0, sizeof(cmd)); |
| 616 | cmd.opcode = MOVE_MEDIUM; |
| 617 | _lto2b(sc->sc_picker, cmd.tea); |
| 618 | _lto2b(fromelem, cmd.src); |
| 619 | _lto2b(toelem, cmd.dst); |
| 620 | if (cm->cm_flags & CM_INVERT) |
| 621 | cmd.flags |= MOVE_MEDIUM_INVERT; |
| 622 | |
| 623 | /* |
| 624 | * Send command to changer. |
| 625 | */ |
| 626 | return (scsipi_command(sc->sc_periph, (void *)&cmd, sizeof(cmd), 0, 0, |
| 627 | CHRETRIES, CHTIMEOUT, NULL, 0)); |
| 628 | } |
| 629 | |
| 630 | static int |
| 631 | ch_exchange(struct ch_softc *sc, struct changer_exchange_request *ce) |
| 632 | { |
| 633 | struct scsi_exchange_medium cmd; |
| 634 | u_int16_t src, dst1, dst2; |
| 635 | |
| 636 | /* |
| 637 | * Check arguments. |
| 638 | */ |
| 639 | if ((ce->ce_srctype > CHET_DT) || (ce->ce_fdsttype > CHET_DT) || |
| 640 | (ce->ce_sdsttype > CHET_DT)) |
| 641 | return (EINVAL); |
| 642 | if ((ce->ce_srcunit > (sc->sc_counts[ce->ce_srctype] - 1)) || |
| 643 | (ce->ce_fdstunit > (sc->sc_counts[ce->ce_fdsttype] - 1)) || |
| 644 | (ce->ce_sdstunit > (sc->sc_counts[ce->ce_sdsttype] - 1))) |
| 645 | return (ENODEV); |
| 646 | |
| 647 | /* |
| 648 | * Check the request against the changer's capabilities. |
| 649 | */ |
| 650 | if (((sc->sc_exchangemask[ce->ce_srctype] & |
| 651 | (1 << ce->ce_fdsttype)) == 0) || |
| 652 | ((sc->sc_exchangemask[ce->ce_fdsttype] & |
| 653 | (1 << ce->ce_sdsttype)) == 0)) |
| 654 | return (ENODEV); |
| 655 | |
| 656 | /* |
| 657 | * Calculate the source and destination elements. |
| 658 | */ |
| 659 | src = sc->sc_firsts[ce->ce_srctype] + ce->ce_srcunit; |
| 660 | dst1 = sc->sc_firsts[ce->ce_fdsttype] + ce->ce_fdstunit; |
| 661 | dst2 = sc->sc_firsts[ce->ce_sdsttype] + ce->ce_sdstunit; |
| 662 | |
| 663 | /* |
| 664 | * Build the SCSI command. |
| 665 | */ |
| 666 | memset(&cmd, 0, sizeof(cmd)); |
| 667 | cmd.opcode = EXCHANGE_MEDIUM; |
| 668 | _lto2b(sc->sc_picker, cmd.tea); |
| 669 | _lto2b(src, cmd.src); |
| 670 | _lto2b(dst1, cmd.fdst); |
| 671 | _lto2b(dst2, cmd.sdst); |
| 672 | if (ce->ce_flags & CE_INVERT1) |
| 673 | cmd.flags |= EXCHANGE_MEDIUM_INV1; |
| 674 | if (ce->ce_flags & CE_INVERT2) |
| 675 | cmd.flags |= EXCHANGE_MEDIUM_INV2; |
| 676 | |
| 677 | /* |
| 678 | * Send command to changer. |
| 679 | */ |
| 680 | return (scsipi_command(sc->sc_periph, (void *)&cmd, sizeof(cmd), 0, 0, |
| 681 | CHRETRIES, CHTIMEOUT, NULL, 0)); |
| 682 | } |
| 683 | |
| 684 | static int |
| 685 | ch_position(struct ch_softc *sc, struct changer_position_request *cp) |
| 686 | { |
| 687 | struct scsi_position_to_element cmd; |
| 688 | u_int16_t dst; |
| 689 | |
| 690 | /* |
| 691 | * Check arguments. |
| 692 | */ |
| 693 | if (cp->cp_type > CHET_DT) |
| 694 | return (EINVAL); |
| 695 | if (cp->cp_unit > (sc->sc_counts[cp->cp_type] - 1)) |
| 696 | return (ENODEV); |
| 697 | |
| 698 | /* |
| 699 | * Calculate the destination element. |
| 700 | */ |
| 701 | dst = sc->sc_firsts[cp->cp_type] + cp->cp_unit; |
| 702 | |
| 703 | /* |
| 704 | * Build the SCSI command. |
| 705 | */ |
| 706 | memset(&cmd, 0, sizeof(cmd)); |
| 707 | cmd.opcode = POSITION_TO_ELEMENT; |
| 708 | _lto2b(sc->sc_picker, cmd.tea); |
| 709 | _lto2b(dst, cmd.dst); |
| 710 | if (cp->cp_flags & CP_INVERT) |
| 711 | cmd.flags |= POSITION_TO_ELEMENT_INVERT; |
| 712 | |
| 713 | /* |
| 714 | * Send command to changer. |
| 715 | */ |
| 716 | return (scsipi_command(sc->sc_periph, (void *)&cmd, sizeof(cmd), 0, 0, |
| 717 | CHRETRIES, CHTIMEOUT, NULL, 0)); |
| 718 | } |
| 719 | |
| 720 | /* |
| 721 | * Perform a READ ELEMENT STATUS on behalf of the user, and return to |
| 722 | * the user only the data the user is interested in. This returns the |
| 723 | * old data format. |
| 724 | */ |
| 725 | static int |
| 726 | ch_ousergetelemstatus(struct ch_softc *sc, int chet, u_int8_t *uptr) |
| 727 | { |
| 728 | struct read_element_status_header *st_hdrp, st_hdr; |
| 729 | struct read_element_status_page_header *pg_hdrp; |
| 730 | struct read_element_status_descriptor *desc; |
| 731 | size_t size, desclen; |
| 732 | void *data; |
| 733 | int avail, i, error = 0; |
| 734 | u_int8_t user_data; |
| 735 | |
| 736 | /* |
| 737 | * If there are no elements of the requested type in the changer, |
| 738 | * the request is invalid. |
| 739 | */ |
| 740 | if (sc->sc_counts[chet] == 0) |
| 741 | return (EINVAL); |
| 742 | |
| 743 | /* |
| 744 | * Do the request the user wants, but only read the status header. |
| 745 | * This will tell us the amount of storage we must allocate in |
| 746 | * order to read all data. |
| 747 | */ |
| 748 | error = ch_getelemstatus(sc, sc->sc_firsts[chet], |
| 749 | sc->sc_counts[chet], &st_hdr, sizeof(st_hdr), 0, 0); |
| 750 | if (error) |
| 751 | return (error); |
| 752 | |
| 753 | size = sizeof(struct read_element_status_header) + |
| 754 | _3btol(st_hdr.nbytes); |
| 755 | |
| 756 | /* |
| 757 | * We must have at least room for the status header and |
| 758 | * one page header (since we only ask for one element type |
| 759 | * at a time). |
| 760 | */ |
| 761 | if (size < (sizeof(struct read_element_status_header) + |
| 762 | sizeof(struct read_element_status_page_header))) |
| 763 | return (EIO); |
| 764 | |
| 765 | /* |
| 766 | * Allocate the storage and do the request again. |
| 767 | */ |
| 768 | data = malloc(size, M_DEVBUF, M_WAITOK); |
| 769 | error = ch_getelemstatus(sc, sc->sc_firsts[chet], |
| 770 | sc->sc_counts[chet], data, size, 0, 0); |
| 771 | if (error) |
| 772 | goto done; |
| 773 | |
| 774 | st_hdrp = (struct read_element_status_header *)data; |
| 775 | pg_hdrp = (struct read_element_status_page_header *)((u_long)st_hdrp + |
| 776 | sizeof(struct read_element_status_header)); |
| 777 | desclen = _2btol(pg_hdrp->edl); |
| 778 | |
| 779 | /* |
| 780 | * Fill in the user status array. |
| 781 | */ |
| 782 | avail = _2btol(st_hdrp->count); |
| 783 | |
| 784 | if (avail != sc->sc_counts[chet]) |
| 785 | printf("%s: warning, READ ELEMENT STATUS avail != count\n" , |
| 786 | device_xname(sc->sc_dev)); |
| 787 | |
| 788 | desc = (struct read_element_status_descriptor *)((u_long)data + |
| 789 | sizeof(struct read_element_status_header) + |
| 790 | sizeof(struct read_element_status_page_header)); |
| 791 | for (i = 0; i < avail; ++i) { |
| 792 | user_data = desc->flags1; |
| 793 | error = copyout(&user_data, &uptr[i], avail); |
| 794 | if (error) |
| 795 | break; |
| 796 | desc = (struct read_element_status_descriptor *)((u_long)desc |
| 797 | + desclen); |
| 798 | } |
| 799 | |
| 800 | done: |
| 801 | if (data != NULL) |
| 802 | free(data, M_DEVBUF); |
| 803 | return (error); |
| 804 | } |
| 805 | |
| 806 | /* |
| 807 | * Perform a READ ELEMENT STATUS on behalf of the user. This returns |
| 808 | * the new (more complete) data format. |
| 809 | */ |
| 810 | static int |
| 811 | ch_usergetelemstatus(struct ch_softc *sc, |
| 812 | struct changer_element_status_request *cesr) |
| 813 | { |
| 814 | struct scsipi_channel *chan = sc->sc_periph->periph_channel; |
| 815 | struct scsipi_periph *dtperiph; |
| 816 | struct read_element_status_header *st_hdrp, st_hdr; |
| 817 | struct read_element_status_page_header *pg_hdrp; |
| 818 | struct read_element_status_descriptor *desc; |
| 819 | struct changer_volume_tag *avol, *pvol; |
| 820 | size_t size, desclen, stddesclen, offset; |
| 821 | int first, avail, i, error = 0; |
| 822 | void *data; |
| 823 | void *uvendptr; |
| 824 | struct changer_element_status ces; |
| 825 | |
| 826 | /* |
| 827 | * Check arguments. |
| 828 | */ |
| 829 | if (cesr->cesr_type > CHET_DT) |
| 830 | return (EINVAL); |
| 831 | if (sc->sc_counts[cesr->cesr_type] == 0) |
| 832 | return (ENODEV); |
| 833 | if (cesr->cesr_unit > (sc->sc_counts[cesr->cesr_type] - 1)) |
| 834 | return (ENODEV); |
| 835 | if (cesr->cesr_count > |
| 836 | (sc->sc_counts[cesr->cesr_type] + cesr->cesr_unit)) |
| 837 | return (EINVAL); |
| 838 | |
| 839 | /* |
| 840 | * Do the request the user wants, but only read the status header. |
| 841 | * This will tell us the amount of storage we must allocate |
| 842 | * in order to read all the data. |
| 843 | */ |
| 844 | error = ch_getelemstatus(sc, sc->sc_firsts[cesr->cesr_type] + |
| 845 | cesr->cesr_unit, cesr->cesr_count, &st_hdr, sizeof(st_hdr), 0, |
| 846 | cesr->cesr_flags); |
| 847 | if (error) |
| 848 | return (error); |
| 849 | |
| 850 | size = sizeof(struct read_element_status_header) + |
| 851 | _3btol(st_hdr.nbytes); |
| 852 | |
| 853 | /* |
| 854 | * We must have at least room for the status header and |
| 855 | * one page header (since we only ask for oen element type |
| 856 | * at a time). |
| 857 | */ |
| 858 | if (size < (sizeof(struct read_element_status_header) + |
| 859 | sizeof(struct read_element_status_page_header))) |
| 860 | return (EIO); |
| 861 | |
| 862 | /* |
| 863 | * Allocate the storage and do the request again. |
| 864 | */ |
| 865 | data = malloc(size, M_DEVBUF, M_WAITOK); |
| 866 | error = ch_getelemstatus(sc, sc->sc_firsts[cesr->cesr_type] + |
| 867 | cesr->cesr_unit, cesr->cesr_count, data, size, 0, |
| 868 | cesr->cesr_flags); |
| 869 | if (error) |
| 870 | goto done; |
| 871 | |
| 872 | st_hdrp = (struct read_element_status_header *)data; |
| 873 | pg_hdrp = (struct read_element_status_page_header *)((u_long)st_hdrp + |
| 874 | sizeof(struct read_element_status_header)); |
| 875 | desclen = _2btol(pg_hdrp->edl); |
| 876 | |
| 877 | /* |
| 878 | * Fill in the user status array. |
| 879 | */ |
| 880 | first = _2btol(st_hdrp->fear); |
| 881 | if (first < (sc->sc_firsts[cesr->cesr_type] + cesr->cesr_unit) || |
| 882 | first >= (sc->sc_firsts[cesr->cesr_type] + cesr->cesr_unit + |
| 883 | cesr->cesr_count)) { |
| 884 | error = EIO; |
| 885 | goto done; |
| 886 | } |
| 887 | first -= sc->sc_firsts[cesr->cesr_type] + cesr->cesr_unit; |
| 888 | |
| 889 | avail = _2btol(st_hdrp->count); |
| 890 | if (avail <= 0 || avail > cesr->cesr_count) { |
| 891 | error = EIO; |
| 892 | goto done; |
| 893 | } |
| 894 | |
| 895 | offset = sizeof(struct read_element_status_header) + |
| 896 | sizeof(struct read_element_status_page_header); |
| 897 | |
| 898 | for (i = 0; i < cesr->cesr_count; i++) { |
| 899 | memset(&ces, 0, sizeof(ces)); |
| 900 | if (i < first || i >= (first + avail)) { |
| 901 | error = copyout(&ces, &cesr->cesr_data[i], |
| 902 | sizeof(ces)); |
| 903 | if (error) |
| 904 | goto done; |
| 905 | } |
| 906 | |
| 907 | desc = (struct read_element_status_descriptor *) |
| 908 | ((char *)data + offset); |
| 909 | stddesclen = sizeof(struct read_element_status_descriptor); |
| 910 | offset += desclen; |
| 911 | |
| 912 | ces.ces_flags = CESTATUS_STATUS_VALID; |
| 913 | |
| 914 | /* |
| 915 | * The SCSI flags conveniently map directly to the |
| 916 | * chio API flags. |
| 917 | */ |
| 918 | ces.ces_flags |= (desc->flags1 & 0x3f); |
| 919 | |
| 920 | ces.ces_asc = desc->sense_code; |
| 921 | ces.ces_ascq = desc->sense_qual; |
| 922 | |
| 923 | /* |
| 924 | * For Data Transport elemenets, get the SCSI ID and LUN, |
| 925 | * and attempt to map them to a device name if they're |
| 926 | * on the same SCSI bus. |
| 927 | */ |
| 928 | if (desc->dt_scsi_flags & READ_ELEMENT_STATUS_DT_IDVALID) { |
| 929 | ces.ces_target = desc->dt_scsi_addr; |
| 930 | ces.ces_flags |= CESTATUS_TARGET_VALID; |
| 931 | } |
| 932 | if (desc->dt_scsi_flags & READ_ELEMENT_STATUS_DT_LUVALID) { |
| 933 | ces.ces_lun = desc->dt_scsi_flags & |
| 934 | READ_ELEMENT_STATUS_DT_LUNMASK; |
| 935 | ces.ces_flags |= CESTATUS_LUN_VALID; |
| 936 | } |
| 937 | if (desc->dt_scsi_flags & READ_ELEMENT_STATUS_DT_NOTBUS) |
| 938 | ces.ces_flags |= CESTATUS_NOTBUS; |
| 939 | else if ((ces.ces_flags & |
| 940 | (CESTATUS_TARGET_VALID|CESTATUS_LUN_VALID)) == |
| 941 | (CESTATUS_TARGET_VALID|CESTATUS_LUN_VALID)) { |
| 942 | if (ces.ces_target < chan->chan_ntargets && |
| 943 | ces.ces_lun < chan->chan_nluns && |
| 944 | (dtperiph = scsipi_lookup_periph(chan, |
| 945 | ces.ces_target, ces.ces_lun)) != NULL && |
| 946 | dtperiph->periph_dev != NULL) { |
| 947 | strlcpy(ces.ces_xname, |
| 948 | device_xname(dtperiph->periph_dev), |
| 949 | sizeof(ces.ces_xname)); |
| 950 | ces.ces_flags |= CESTATUS_XNAME_VALID; |
| 951 | } |
| 952 | } |
| 953 | |
| 954 | if (desc->flags2 & READ_ELEMENT_STATUS_INVERT) |
| 955 | ces.ces_flags |= CESTATUS_INVERTED; |
| 956 | |
| 957 | if (desc->flags2 & READ_ELEMENT_STATUS_SVALID) { |
| 958 | if (ch_map_element(sc, _2btol(desc->ssea), |
| 959 | &ces.ces_from_type, &ces.ces_from_unit)) |
| 960 | ces.ces_flags |= CESTATUS_FROM_VALID; |
| 961 | } |
| 962 | |
| 963 | /* |
| 964 | * Extract volume tag information. |
| 965 | */ |
| 966 | switch (pg_hdrp->flags & |
| 967 | (READ_ELEMENT_STATUS_PVOLTAG|READ_ELEMENT_STATUS_AVOLTAG)) { |
| 968 | case (READ_ELEMENT_STATUS_PVOLTAG|READ_ELEMENT_STATUS_AVOLTAG): |
| 969 | pvol = (struct changer_volume_tag *)(desc + 1); |
| 970 | avol = pvol + 1; |
| 971 | break; |
| 972 | |
| 973 | case READ_ELEMENT_STATUS_PVOLTAG: |
| 974 | pvol = (struct changer_volume_tag *)(desc + 1); |
| 975 | avol = NULL; |
| 976 | break; |
| 977 | |
| 978 | case READ_ELEMENT_STATUS_AVOLTAG: |
| 979 | pvol = NULL; |
| 980 | avol = (struct changer_volume_tag *)(desc + 1); |
| 981 | break; |
| 982 | |
| 983 | default: |
| 984 | avol = pvol = NULL; |
| 985 | break; |
| 986 | } |
| 987 | |
| 988 | if (pvol != NULL) { |
| 989 | ch_voltag_convert_in(pvol, &ces.ces_pvoltag); |
| 990 | ces.ces_flags |= CESTATUS_PVOL_VALID; |
| 991 | stddesclen += sizeof(struct changer_volume_tag); |
| 992 | } |
| 993 | if (avol != NULL) { |
| 994 | ch_voltag_convert_in(avol, &ces.ces_avoltag); |
| 995 | ces.ces_flags |= CESTATUS_AVOL_VALID; |
| 996 | stddesclen += sizeof(struct changer_volume_tag); |
| 997 | } |
| 998 | |
| 999 | /* |
| 1000 | * Compute vendor-specific length. Note the 4 reserved |
| 1001 | * bytes between the volume tags and the vendor-specific |
| 1002 | * data. Copy it out of the user wants it. |
| 1003 | */ |
| 1004 | stddesclen += 4; |
| 1005 | if (desclen > stddesclen) |
| 1006 | ces.ces_vendor_len = desclen - stddesclen; |
| 1007 | |
| 1008 | if (ces.ces_vendor_len != 0 && cesr->cesr_vendor_data != NULL) { |
| 1009 | error = copyin(&cesr->cesr_vendor_data[i], &uvendptr, |
| 1010 | sizeof(uvendptr)); |
| 1011 | if (error) |
| 1012 | goto done; |
| 1013 | error = copyout((void *)((u_long)desc + stddesclen), |
| 1014 | uvendptr, ces.ces_vendor_len); |
| 1015 | if (error) |
| 1016 | goto done; |
| 1017 | } |
| 1018 | |
| 1019 | /* |
| 1020 | * Now copy out the status descriptor we've constructed. |
| 1021 | */ |
| 1022 | error = copyout(&ces, &cesr->cesr_data[i], sizeof(ces)); |
| 1023 | if (error) |
| 1024 | goto done; |
| 1025 | } |
| 1026 | |
| 1027 | done: |
| 1028 | if (data != NULL) |
| 1029 | free(data, M_DEVBUF); |
| 1030 | return (error); |
| 1031 | } |
| 1032 | |
| 1033 | static int |
| 1034 | ch_getelemstatus(struct ch_softc *sc, int first, int count, void *data, |
| 1035 | size_t datalen, int scsiflags, int flags) |
| 1036 | { |
| 1037 | struct scsi_read_element_status cmd; |
| 1038 | |
| 1039 | /* |
| 1040 | * Build SCSI command. |
| 1041 | */ |
| 1042 | memset(&cmd, 0, sizeof(cmd)); |
| 1043 | cmd.opcode = READ_ELEMENT_STATUS; |
| 1044 | cmd.byte2 = ELEMENT_TYPE_ALL; |
| 1045 | if (flags & CESR_VOLTAGS) |
| 1046 | cmd.byte2 |= READ_ELEMENT_STATUS_VOLTAG; |
| 1047 | _lto2b(first, cmd.sea); |
| 1048 | _lto2b(count, cmd.count); |
| 1049 | _lto3b(datalen, cmd.len); |
| 1050 | |
| 1051 | /* |
| 1052 | * Send command to changer. |
| 1053 | */ |
| 1054 | return (scsipi_command(sc->sc_periph, (void *)&cmd, sizeof(cmd), |
| 1055 | (void *)data, datalen, |
| 1056 | CHRETRIES, CHTIMEOUT, NULL, scsiflags | XS_CTL_DATA_IN)); |
| 1057 | } |
| 1058 | |
| 1059 | static int |
| 1060 | ch_setvoltag(struct ch_softc *sc, struct changer_set_voltag_request *csvr) |
| 1061 | { |
| 1062 | struct scsi_send_volume_tag cmd; |
| 1063 | struct changer_volume_tag voltag; |
| 1064 | void *data = NULL; |
| 1065 | size_t datalen = 0; |
| 1066 | int error; |
| 1067 | u_int16_t dst; |
| 1068 | |
| 1069 | /* |
| 1070 | * Check arguments. |
| 1071 | */ |
| 1072 | if (csvr->csvr_type > CHET_DT) |
| 1073 | return (EINVAL); |
| 1074 | if (csvr->csvr_unit > (sc->sc_counts[csvr->csvr_type] - 1)) |
| 1075 | return (ENODEV); |
| 1076 | |
| 1077 | dst = sc->sc_firsts[csvr->csvr_type] + csvr->csvr_unit; |
| 1078 | |
| 1079 | /* |
| 1080 | * Build the SCSI command. |
| 1081 | */ |
| 1082 | memset(&cmd, 0, sizeof(cmd)); |
| 1083 | cmd.opcode = SEND_VOLUME_TAG; |
| 1084 | _lto2b(dst, cmd.eaddr); |
| 1085 | |
| 1086 | #define ALTERNATE (csvr->csvr_flags & CSVR_ALTERNATE) |
| 1087 | |
| 1088 | switch (csvr->csvr_flags & CSVR_MODE_MASK) { |
| 1089 | case CSVR_MODE_SET: |
| 1090 | cmd.sac = ALTERNATE ? SAC_ASSERT_ALT : SAC_ASSERT_PRIMARY; |
| 1091 | break; |
| 1092 | |
| 1093 | case CSVR_MODE_REPLACE: |
| 1094 | cmd.sac = ALTERNATE ? SAC_REPLACE_ALT : SAC_REPLACE_PRIMARY; |
| 1095 | break; |
| 1096 | |
| 1097 | case CSVR_MODE_CLEAR: |
| 1098 | cmd.sac = ALTERNATE ? SAC_UNDEFINED_ALT : SAC_UNDEFINED_PRIMARY; |
| 1099 | break; |
| 1100 | |
| 1101 | default: |
| 1102 | return (EINVAL); |
| 1103 | } |
| 1104 | |
| 1105 | #undef ALTERNATE |
| 1106 | |
| 1107 | if (cmd.sac < SAC_UNDEFINED_PRIMARY) { |
| 1108 | error = ch_voltag_convert_out(&csvr->csvr_voltag, &voltag); |
| 1109 | if (error) |
| 1110 | return (error); |
| 1111 | data = &voltag; |
| 1112 | datalen = sizeof(voltag); |
| 1113 | _lto2b(datalen, cmd.length); |
| 1114 | } |
| 1115 | |
| 1116 | /* |
| 1117 | * Send command to changer. |
| 1118 | */ |
| 1119 | return (scsipi_command(sc->sc_periph, (void *)&cmd, sizeof(cmd), |
| 1120 | (void *)data, datalen, CHRETRIES, CHTIMEOUT, NULL, |
| 1121 | datalen ? XS_CTL_DATA_OUT : 0)); |
| 1122 | } |
| 1123 | |
| 1124 | static int |
| 1125 | ch_ielem(struct ch_softc *sc) |
| 1126 | { |
| 1127 | int tmo; |
| 1128 | struct scsi_initialize_element_status cmd; |
| 1129 | |
| 1130 | /* |
| 1131 | * Build SCSI command. |
| 1132 | */ |
| 1133 | memset(&cmd, 0, sizeof(cmd)); |
| 1134 | cmd.opcode = INITIALIZE_ELEMENT_STATUS; |
| 1135 | |
| 1136 | /* |
| 1137 | * Send command to changer. |
| 1138 | * |
| 1139 | * The problem is, how long to allow for the command? |
| 1140 | * It can take a *really* long time, and also depends |
| 1141 | * on unknowable factors such as whether there are |
| 1142 | * *almost* readable labels on tapes that a barcode |
| 1143 | * reader is trying to decipher. |
| 1144 | * |
| 1145 | * I'm going to make this long enough to allow 5 minutes |
| 1146 | * per element plus an initial 10 minute wait. |
| 1147 | */ |
| 1148 | tmo = sc->sc_counts[CHET_MT] + |
| 1149 | sc->sc_counts[CHET_ST] + |
| 1150 | sc->sc_counts[CHET_IE] + |
| 1151 | sc->sc_counts[CHET_DT]; |
| 1152 | tmo *= 5 * 60 * 1000; |
| 1153 | tmo += (10 * 60 * 1000); |
| 1154 | |
| 1155 | return (scsipi_command(sc->sc_periph, (void *)&cmd, sizeof(cmd), 0, 0, |
| 1156 | CHRETRIES, tmo, NULL, XS_CTL_IGNORE_ILLEGAL_REQUEST)); |
| 1157 | } |
| 1158 | |
| 1159 | /* |
| 1160 | * Ask the device about itself and fill in the parameters in our |
| 1161 | * softc. |
| 1162 | */ |
| 1163 | static int |
| 1164 | ch_get_params(struct ch_softc *sc, int scsiflags) |
| 1165 | { |
| 1166 | struct scsi_mode_sense_data { |
| 1167 | struct scsi_mode_parameter_header_6 ; |
| 1168 | union { |
| 1169 | struct page_element_address_assignment ea; |
| 1170 | struct page_transport_geometry_parameters tg; |
| 1171 | struct page_device_capabilities cap; |
| 1172 | } pages; |
| 1173 | } sense_data; |
| 1174 | int error, from; |
| 1175 | u_int8_t *moves, *exchanges; |
| 1176 | |
| 1177 | /* |
| 1178 | * Grab info from the element address assignment page. |
| 1179 | */ |
| 1180 | memset(&sense_data, 0, sizeof(sense_data)); |
| 1181 | error = scsipi_mode_sense(sc->sc_periph, SMS_DBD, 0x1d, |
| 1182 | &sense_data.header, sizeof(sense_data), |
| 1183 | scsiflags, CHRETRIES, 6000); |
| 1184 | if (error) { |
| 1185 | aprint_error_dev(sc->sc_dev, "could not sense element address page\n" ); |
| 1186 | return (error); |
| 1187 | } |
| 1188 | |
| 1189 | sc->sc_firsts[CHET_MT] = _2btol(sense_data.pages.ea.mtea); |
| 1190 | sc->sc_counts[CHET_MT] = _2btol(sense_data.pages.ea.nmte); |
| 1191 | sc->sc_firsts[CHET_ST] = _2btol(sense_data.pages.ea.fsea); |
| 1192 | sc->sc_counts[CHET_ST] = _2btol(sense_data.pages.ea.nse); |
| 1193 | sc->sc_firsts[CHET_IE] = _2btol(sense_data.pages.ea.fieea); |
| 1194 | sc->sc_counts[CHET_IE] = _2btol(sense_data.pages.ea.niee); |
| 1195 | sc->sc_firsts[CHET_DT] = _2btol(sense_data.pages.ea.fdtea); |
| 1196 | sc->sc_counts[CHET_DT] = _2btol(sense_data.pages.ea.ndte); |
| 1197 | |
| 1198 | /* XXX ask for transport geometry page XXX */ |
| 1199 | |
| 1200 | /* |
| 1201 | * Grab info from the capabilities page. |
| 1202 | */ |
| 1203 | memset(&sense_data, 0, sizeof(sense_data)); |
| 1204 | /* |
| 1205 | * XXX: Note: not all changers can deal with disabled block descriptors |
| 1206 | */ |
| 1207 | error = scsipi_mode_sense(sc->sc_periph, SMS_DBD, 0x1f, |
| 1208 | &sense_data.header, sizeof(sense_data), |
| 1209 | scsiflags, CHRETRIES, 6000); |
| 1210 | if (error) { |
| 1211 | aprint_error_dev(sc->sc_dev, "could not sense capabilities page\n" ); |
| 1212 | return (error); |
| 1213 | } |
| 1214 | |
| 1215 | memset(sc->sc_movemask, 0, sizeof(sc->sc_movemask)); |
| 1216 | memset(sc->sc_exchangemask, 0, sizeof(sc->sc_exchangemask)); |
| 1217 | moves = &sense_data.pages.cap.move_from_mt; |
| 1218 | exchanges = &sense_data.pages.cap.exchange_with_mt; |
| 1219 | for (from = CHET_MT; from <= CHET_DT; ++from) { |
| 1220 | sc->sc_movemask[from] = moves[from]; |
| 1221 | sc->sc_exchangemask[from] = exchanges[from]; |
| 1222 | } |
| 1223 | |
| 1224 | #ifdef CH_AUTOMATIC_IELEM_POLICY |
| 1225 | /* |
| 1226 | * If we need to do an Init-Element-Status, |
| 1227 | * do that now that we know what's in the changer. |
| 1228 | */ |
| 1229 | if ((scsiflags & XS_CTL_IGNORE_MEDIA_CHANGE) == 0) { |
| 1230 | if ((sc->sc_periph->periph_flags & PERIPH_MEDIA_LOADED) == 0) |
| 1231 | error = ch_ielem(sc); |
| 1232 | if (error == 0) |
| 1233 | sc->sc_periph->periph_flags |= PERIPH_MEDIA_LOADED; |
| 1234 | else |
| 1235 | sc->sc_periph->periph_flags &= ~PERIPH_MEDIA_LOADED; |
| 1236 | } |
| 1237 | #endif |
| 1238 | return (error); |
| 1239 | } |
| 1240 | |
| 1241 | static void |
| 1242 | ch_get_quirks(struct ch_softc *sc, struct scsipi_inquiry_pattern *inqbuf) |
| 1243 | { |
| 1244 | const struct chquirk *match; |
| 1245 | int priority; |
| 1246 | |
| 1247 | sc->sc_settledelay = 0; |
| 1248 | |
| 1249 | match = scsipi_inqmatch(inqbuf, chquirks, |
| 1250 | sizeof(chquirks) / sizeof(chquirks[0]), |
| 1251 | sizeof(chquirks[0]), &priority); |
| 1252 | if (priority != 0) |
| 1253 | sc->sc_settledelay = match->cq_settledelay; |
| 1254 | } |
| 1255 | |
| 1256 | static int |
| 1257 | ch_map_element(struct ch_softc *sc, u_int16_t elem, int *typep, int *unitp) |
| 1258 | { |
| 1259 | int chet; |
| 1260 | |
| 1261 | for (chet = CHET_MT; chet <= CHET_DT; chet++) { |
| 1262 | if (elem >= sc->sc_firsts[chet] && |
| 1263 | elem < (sc->sc_firsts[chet] + sc->sc_counts[chet])) { |
| 1264 | *typep = chet; |
| 1265 | *unitp = elem - sc->sc_firsts[chet]; |
| 1266 | return (1); |
| 1267 | } |
| 1268 | } |
| 1269 | return (0); |
| 1270 | } |
| 1271 | |
| 1272 | static void |
| 1273 | ch_voltag_convert_in(const struct changer_volume_tag *sv, |
| 1274 | struct changer_voltag *cv) |
| 1275 | { |
| 1276 | int i; |
| 1277 | |
| 1278 | memset(cv, 0, sizeof(struct changer_voltag)); |
| 1279 | |
| 1280 | /* |
| 1281 | * Copy the volume tag string from the SCSI representation. |
| 1282 | * Per the SCSI-2 spec, we stop at the first blank character. |
| 1283 | */ |
| 1284 | for (i = 0; i < sizeof(sv->volid); i++) { |
| 1285 | if (sv->volid[i] == ' ') |
| 1286 | break; |
| 1287 | cv->cv_tag[i] = sv->volid[i]; |
| 1288 | } |
| 1289 | cv->cv_tag[i] = '\0'; |
| 1290 | |
| 1291 | cv->cv_serial = _2btol(sv->volseq); |
| 1292 | } |
| 1293 | |
| 1294 | static int |
| 1295 | ch_voltag_convert_out(const struct changer_voltag *cv, |
| 1296 | struct changer_volume_tag *sv) |
| 1297 | { |
| 1298 | int i; |
| 1299 | |
| 1300 | memset(sv, ' ', sizeof(struct changer_volume_tag)); |
| 1301 | |
| 1302 | for (i = 0; i < sizeof(sv->volid); i++) { |
| 1303 | if (cv->cv_tag[i] == '\0') |
| 1304 | break; |
| 1305 | /* |
| 1306 | * Limit the character set to what is suggested in |
| 1307 | * the SCSI-2 spec. |
| 1308 | */ |
| 1309 | if ((cv->cv_tag[i] < '0' || cv->cv_tag[i] > '9') && |
| 1310 | (cv->cv_tag[i] < 'A' || cv->cv_tag[i] > 'Z') && |
| 1311 | (cv->cv_tag[i] != '_')) |
| 1312 | return (EINVAL); |
| 1313 | sv->volid[i] = cv->cv_tag[i]; |
| 1314 | } |
| 1315 | |
| 1316 | _lto2b(cv->cv_serial, sv->volseq); |
| 1317 | |
| 1318 | return (0); |
| 1319 | } |
| 1320 | |