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