rl.c revision 1.12 1 /* $NetBSD: rl.c,v 1.12 2002/07/22 21:48:38 hannken Exp $ */
2
3 /*
4 * Copyright (c) 2000 Ludd, University of Lule}, Sweden. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. All advertising materials mentioning features or use of this software
15 * must display the following acknowledgement:
16 * This product includes software developed at Ludd, University of
17 * Lule}, Sweden and its contributors.
18 * 4. The name of the author may not be used to endorse or promote products
19 * derived from this software without specific prior written permission
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 /*
34 * RL11/RLV11/RLV12 disk controller driver and
35 * RL01/RL02 disk device driver.
36 *
37 * TODO:
38 * Handle disk errors more gracefully
39 * Do overlapping seeks on multiple drives
40 *
41 * Implementation comments:
42 *
43 */
44
45 #include <sys/cdefs.h>
46 __KERNEL_RCSID(0, "$NetBSD: rl.c,v 1.12 2002/07/22 21:48:38 hannken Exp $");
47
48 #include <sys/param.h>
49 #include <sys/device.h>
50 #include <sys/systm.h>
51 #include <sys/conf.h>
52 #include <sys/disk.h>
53 #include <sys/disklabel.h>
54 #include <sys/buf.h>
55 #include <sys/stat.h>
56 #include <sys/dkio.h>
57 #include <sys/fcntl.h>
58
59 #include <ufs/ufs/dinode.h>
60 #include <ufs/ffs/fs.h>
61
62 #include <machine/bus.h>
63
64 #include <dev/qbus/ubavar.h>
65 #include <dev/qbus/rlreg.h>
66 #include <dev/qbus/rlvar.h>
67
68 #include "ioconf.h"
69 #include "locators.h"
70
71 static int rlcmatch(struct device *, struct cfdata *, void *);
72 static void rlcattach(struct device *, struct device *, void *);
73 static int rlcprint(void *, const char *);
74 static void rlcintr(void *);
75 static int rlmatch(struct device *, struct cfdata *, void *);
76 static void rlattach(struct device *, struct device *, void *);
77 static void rlcstart(struct rlc_softc *, struct buf *);
78 static void waitcrdy(struct rlc_softc *);
79 static void rlcreset(struct device *);
80 cdev_decl(rl);
81 bdev_decl(rl);
82
83 struct cfattach rlc_ca = {
84 sizeof(struct rlc_softc), rlcmatch, rlcattach
85 };
86
87 struct cfattach rl_ca = {
88 sizeof(struct rl_softc), rlmatch, rlattach
89 };
90
91 #define MAXRLXFER (RL_BPS * RL_SPT)
92 #define RLMAJOR 14
93
94 #define RL_WREG(reg, val) \
95 bus_space_write_2(sc->sc_iot, sc->sc_ioh, (reg), (val))
96 #define RL_RREG(reg) \
97 bus_space_read_2(sc->sc_iot, sc->sc_ioh, (reg))
98
99 static char *rlstates[] = {
100 "drive not loaded",
101 "drive spinning up",
102 "drive brushes out",
103 "drive loading heads",
104 "drive seeking",
105 "drive ready",
106 "drive unloading heads",
107 "drive spun down",
108 };
109
110 static char *
111 rlstate(struct rlc_softc *sc, int unit)
112 {
113 int i = 0;
114
115 do {
116 RL_WREG(RL_DA, RLDA_GS);
117 RL_WREG(RL_CS, RLCS_GS|(unit << RLCS_USHFT));
118 waitcrdy(sc);
119 } while (((RL_RREG(RL_CS) & RLCS_ERR) != 0) && i++ < 10);
120 if (i == 10)
121 return NULL;
122 i = RL_RREG(RL_MP) & RLMP_STATUS;
123 return rlstates[i];
124 }
125
126 void
127 waitcrdy(struct rlc_softc *sc)
128 {
129 int i;
130
131 for (i = 0; i < 1000; i++) {
132 DELAY(10000);
133 if (RL_RREG(RL_CS) & RLCS_CRDY)
134 return;
135 }
136 printf("%s: never got ready\n", sc->sc_dev.dv_xname); /* ?panic? */
137 }
138
139 int
140 rlcprint(void *aux, const char *name)
141 {
142 struct rlc_attach_args *ra = aux;
143
144 if (name)
145 printf("RL0%d at %s", ra->type & RLMP_DT ? '2' : '1', name);
146 printf(" drive %d", ra->hwid);
147 return UNCONF;
148 }
149
150 /*
151 * Force the controller to interrupt.
152 */
153 int
154 rlcmatch(struct device *parent, struct cfdata *cf, void *aux)
155 {
156 struct uba_attach_args *ua = aux;
157 struct rlc_softc ssc, *sc = &ssc;
158 int i;
159
160 sc->sc_iot = ua->ua_iot;
161 sc->sc_ioh = ua->ua_ioh;
162 /* Force interrupt by issuing a "Get Status" command */
163 RL_WREG(RL_DA, RLDA_GS);
164 RL_WREG(RL_CS, RLCS_GS|RLCS_IE);
165
166 for (i = 0; i < 100; i++) {
167 DELAY(100000);
168 if (RL_RREG(RL_CS) & RLCS_CRDY)
169 return 1;
170 }
171 return 0;
172 }
173
174 void
175 rlcattach(struct device *parent, struct device *self, void *aux)
176 {
177 struct rlc_softc *sc = (struct rlc_softc *)self;
178 struct uba_attach_args *ua = aux;
179 struct rlc_attach_args ra;
180 int i, error;
181
182 sc->sc_iot = ua->ua_iot;
183 sc->sc_ioh = ua->ua_ioh;
184 sc->sc_dmat = ua->ua_dmat;
185 uba_intr_establish(ua->ua_icookie, ua->ua_cvec,
186 rlcintr, sc, &sc->sc_intrcnt);
187 evcnt_attach_dynamic(&sc->sc_intrcnt, EVCNT_TYPE_INTR, ua->ua_evcnt,
188 sc->sc_dev.dv_xname, "intr");
189 uba_reset_establish(rlcreset, self);
190
191 printf("\n");
192
193 /*
194 * The RL11 can only have one transfer going at a time,
195 * and max transfer size is one track, so only one dmamap
196 * is needed.
197 */
198 error = bus_dmamap_create(sc->sc_dmat, MAXRLXFER, 1, MAXRLXFER, 0,
199 BUS_DMA_ALLOCNOW, &sc->sc_dmam);
200 if (error) {
201 printf(": Failed to allocate DMA map, error %d\n", error);
202 return;
203 }
204 bufq_alloc(&sc->sc_q, BUFQ_DISKSORT|BUFQ_SORT_CYLINDER);
205 for (i = 0; i < RL_MAXDPC; i++) {
206 waitcrdy(sc);
207 RL_WREG(RL_DA, RLDA_GS|RLDA_RST);
208 RL_WREG(RL_CS, RLCS_GS|(i << RLCS_USHFT));
209 waitcrdy(sc);
210 ra.type = RL_RREG(RL_MP);
211 ra.hwid = i;
212 if ((RL_RREG(RL_CS) & RLCS_ERR) == 0)
213 config_found(&sc->sc_dev, &ra, rlcprint);
214 }
215 }
216
217 int
218 rlmatch(struct device *parent, struct cfdata *cf, void *aux)
219 {
220 struct rlc_attach_args *ra = aux;
221
222 if (cf->cf_loc[RLCCF_DRIVE] != RLCCF_DRIVE_DEFAULT &&
223 cf->cf_loc[RLCCF_DRIVE] != ra->hwid)
224 return 0;
225 return 1;
226 }
227
228 void
229 rlattach(struct device *parent, struct device *self, void *aux)
230 {
231 struct rl_softc *rc = (struct rl_softc *)self;
232 struct rlc_attach_args *ra = aux;
233 struct disklabel *dl;
234
235 rc->rc_hwid = ra->hwid;
236 rc->rc_disk.dk_name = rc->rc_dev.dv_xname;
237 disk_attach(&rc->rc_disk);
238 dl = rc->rc_disk.dk_label;
239 dl->d_npartitions = 3;
240 strcpy(dl->d_typename, "RL01");
241 if (ra->type & RLMP_DT)
242 dl->d_typename[3] = '2';
243 dl->d_secsize = DEV_BSIZE; /* XXX - wrong, but OK for now */
244 dl->d_nsectors = RL_SPT/2;
245 dl->d_ntracks = RL_SPD;
246 dl->d_ncylinders = ra->type & RLMP_DT ? RL_TPS02 : RL_TPS01;
247 dl->d_secpercyl = dl->d_nsectors * dl->d_ntracks;
248 dl->d_secperunit = dl->d_ncylinders * dl->d_secpercyl;
249 dl->d_partitions[0].p_size = dl->d_partitions[2].p_size =
250 dl->d_secperunit;
251 dl->d_partitions[0].p_offset = dl->d_partitions[2].p_offset = 0;
252 dl->d_interleave = dl->d_headswitch = 1;
253 dl->d_bbsize = BBSIZE;
254 dl->d_sbsize = SBSIZE;
255 dl->d_rpm = 2400;
256 dl->d_type = DTYPE_DEC;
257 printf(": %s, %s\n", dl->d_typename,
258 rlstate((struct rlc_softc *)parent, ra->hwid));
259 }
260
261 int
262 rlopen(dev_t dev, int flag, int fmt, struct proc *p)
263 {
264 int part, unit, mask;
265 struct disklabel *dl;
266 struct rlc_softc *sc;
267 struct rl_softc *rc;
268 char *msg;
269
270 /*
271 * Make sure this is a reasonable open request.
272 */
273 unit = DISKUNIT(dev);
274 if (unit >= rl_cd.cd_ndevs)
275 return ENXIO;
276 rc = rl_cd.cd_devs[unit];
277 if (rc == 0)
278 return ENXIO;
279
280 sc = (struct rlc_softc *)rc->rc_dev.dv_parent;
281 /* Check that the disk actually is useable */
282 msg = rlstate(sc, rc->rc_hwid);
283 if (msg == NULL || msg == rlstates[RLMP_UNLOAD] ||
284 msg == rlstates[RLMP_SPUNDOWN])
285 return ENXIO;
286 /*
287 * If this is the first open; read in where on the disk we are.
288 */
289 dl = rc->rc_disk.dk_label;
290 if (rc->rc_state == DK_CLOSED) {
291 u_int16_t mp;
292 RL_WREG(RL_CS, RLCS_RHDR|(rc->rc_hwid << RLCS_USHFT));
293 waitcrdy(sc);
294 mp = RL_RREG(RL_MP);
295 rc->rc_head = ((mp & RLMP_HS) == RLMP_HS);
296 rc->rc_cyl = (mp >> 7) & 0777;
297 rc->rc_state = DK_OPEN;
298 /* Get disk label */
299 printf("%s: ", rc->rc_dev.dv_xname);
300 if ((msg = readdisklabel(MAKEDISKDEV(RLMAJOR,
301 rc->rc_dev.dv_unit, RAW_PART), rlstrategy, dl, NULL)))
302 printf("%s: ", msg);
303 printf("size %d sectors\n", dl->d_secperunit);
304 }
305 part = DISKPART(dev);
306 if (part >= dl->d_npartitions)
307 return ENXIO;
308
309 mask = 1 << part;
310 switch (fmt) {
311 case S_IFCHR:
312 rc->rc_disk.dk_copenmask |= mask;
313 break;
314 case S_IFBLK:
315 rc->rc_disk.dk_bopenmask |= mask;
316 break;
317 }
318 rc->rc_disk.dk_openmask |= mask;
319 return 0;
320 }
321
322 int
323 rlclose(dev_t dev, int flag, int fmt, struct proc *p)
324 {
325 int unit = DISKUNIT(dev);
326 struct rl_softc *rc = rl_cd.cd_devs[unit];
327 int mask = (1 << DISKPART(dev));
328
329 switch (fmt) {
330 case S_IFCHR:
331 rc->rc_disk.dk_copenmask &= ~mask;
332 break;
333 case S_IFBLK:
334 rc->rc_disk.dk_bopenmask &= ~mask;
335 break;
336 }
337 rc->rc_disk.dk_openmask =
338 rc->rc_disk.dk_copenmask | rc->rc_disk.dk_bopenmask;
339
340 if (rc->rc_disk.dk_openmask == 0)
341 rc->rc_state = DK_CLOSED; /* May change pack */
342 return 0;
343 }
344
345 void
346 rlstrategy(struct buf *bp)
347 {
348 struct disklabel *lp;
349 struct rlc_softc *sc;
350 struct rl_softc *rc;
351 int unit, s, err;
352 /*
353 * Make sure this is a reasonable drive to use.
354 */
355 unit = DISKUNIT(bp->b_dev);
356 if (unit > rl_cd.cd_ndevs || (rc = rl_cd.cd_devs[unit]) == NULL) {
357 bp->b_error = ENXIO;
358 bp->b_flags |= B_ERROR;
359 goto done;
360 }
361 if (rc->rc_state != DK_OPEN) /* How did we end up here at all? */
362 panic("rlstrategy: state impossible");
363
364 lp = rc->rc_disk.dk_label;
365 if ((err = bounds_check_with_label(bp, lp, 1)) <= 0)
366 goto done;
367
368 if (bp->b_bcount == 0)
369 goto done;
370
371 bp->b_rawblkno =
372 bp->b_blkno + lp->d_partitions[DISKPART(bp->b_dev)].p_offset;
373 bp->b_cylinder = bp->b_rawblkno / lp->d_secpercyl;
374 sc = (struct rlc_softc *)rc->rc_dev.dv_parent;
375
376 s = splbio();
377 BUFQ_PUT(&sc->sc_q, bp);
378 rlcstart(sc, 0);
379 splx(s);
380 return;
381
382 done: biodone(bp);
383 }
384
385 int
386 rlioctl(dev_t dev, u_long cmd, caddr_t addr, int flag, struct proc *p)
387 {
388 struct rl_softc *rc = rl_cd.cd_devs[DISKUNIT(dev)];
389 struct disklabel *lp = rc->rc_disk.dk_label;
390 int err = 0;
391 #ifdef __HAVE_OLD_DISKLABEL
392 struct disklabel newlabel;
393 #endif
394
395 switch (cmd) {
396 case DIOCGDINFO:
397 bcopy(lp, addr, sizeof (struct disklabel));
398 break;
399
400 #ifdef __HAVE_OLD_DISKLABEL
401 case ODIOCGDINFO:
402 newlabel = *lp;
403 if (newlabel.d_npartitions > OLDMAXPARTITIONS)
404 return ENOTTY;
405 bcopy(&newlabel, addr, sizeof (struct olddisklabel));
406 break;
407 #endif
408
409 case DIOCGPART:
410 ((struct partinfo *)addr)->disklab = lp;
411 ((struct partinfo *)addr)->part =
412 &lp->d_partitions[DISKPART(dev)];
413 break;
414
415 case DIOCSDINFO:
416 case DIOCWDINFO:
417 #ifdef __HAVE_OLD_DISKLABEL
418 case ODIOCWDINFO:
419 case ODIOCSDINFO:
420 #endif
421 {
422 struct disklabel *tp;
423
424 #ifdef __HAVE_OLD_DISKLABEL
425 if (cmd == ODIOCSDINFO || cmd == ODIOCWDINFO) {
426 memset(&newlabel, 0, sizeof newlabel);
427 memcpy(&newlabel, addr, sizeof (struct olddisklabel));
428 tp = &newlabel;
429 } else
430 #endif
431 tp = (struct disklabel *)addr;
432
433 if ((flag & FWRITE) == 0)
434 err = EBADF;
435 else
436 err = ((
437 #ifdef __HAVE_OLD_DISKLABEL
438 cmd == ODIOCSDINFO ||
439 #endif
440 cmd == DIOCSDINFO) ?
441 setdisklabel(lp, tp, 0, 0) :
442 writedisklabel(dev, rlstrategy, lp, 0));
443 break;
444 }
445
446 case DIOCWLABEL:
447 if ((flag & FWRITE) == 0)
448 err = EBADF;
449 break;
450
451 default:
452 err = ENOTTY;
453 }
454 return err;
455 }
456
457 int
458 rlsize(dev_t dev)
459 {
460 struct disklabel *dl;
461 struct rl_softc *rc;
462 int size, unit = DISKUNIT(dev);
463
464 if ((unit >= rl_cd.cd_ndevs) || ((rc = rl_cd.cd_devs[unit]) == 0))
465 return -1;
466 dl = rc->rc_disk.dk_label;
467 size = dl->d_partitions[DISKPART(dev)].p_size *
468 (dl->d_secsize / DEV_BSIZE);
469 return size;
470 }
471
472 int
473 rldump(dev_t dev, daddr_t blkno, caddr_t va, size_t size)
474 {
475 /* Not likely... */
476 return 0;
477 }
478
479 int
480 rlread(dev_t dev, struct uio *uio, int ioflag)
481 {
482 return (physio(rlstrategy, NULL, dev, B_READ, minphys, uio));
483 }
484
485 int
486 rlwrite(dev_t dev, struct uio *uio, int ioflag)
487 {
488 return (physio(rlstrategy, NULL, dev, B_WRITE, minphys, uio));
489 }
490
491 static char *rlerr[] = {
492 "no",
493 "operation incomplete",
494 "read data CRC",
495 "header CRC",
496 "data late",
497 "header not found",
498 "",
499 "",
500 "non-existent memory",
501 "memory parity error",
502 "",
503 "",
504 "",
505 "",
506 "",
507 "",
508 };
509
510 void
511 rlcintr(void *arg)
512 {
513 struct rlc_softc *sc = arg;
514 struct buf *bp;
515 u_int16_t cs;
516
517 bp = sc->sc_active;
518 if (bp == 0) {
519 printf("%s: strange interrupt\n", sc->sc_dev.dv_xname);
520 return;
521 }
522 bus_dmamap_unload(sc->sc_dmat, sc->sc_dmam);
523 sc->sc_active = 0;
524 cs = RL_RREG(RL_CS);
525 if (cs & RLCS_ERR) {
526 int error = (cs & RLCS_ERRMSK) >> 10;
527
528 printf("%s: %s\n", sc->sc_dev.dv_xname, rlerr[error]);
529 bp->b_flags |= B_ERROR;
530 bp->b_error = EIO;
531 bp->b_resid = bp->b_bcount;
532 sc->sc_bytecnt = 0;
533 }
534 if (sc->sc_bytecnt == 0) /* Finished transfer */
535 biodone(bp);
536 rlcstart(sc, sc->sc_bytecnt ? bp : 0);
537 }
538
539 /*
540 * Start routine. First position the disk to the given position,
541 * then start reading/writing. An optimization would be to be able
542 * to handle overlapping seeks between disks.
543 */
544 void
545 rlcstart(struct rlc_softc *sc, struct buf *ob)
546 {
547 struct disklabel *lp;
548 struct rl_softc *rc;
549 struct buf *bp;
550 int bn, cn, sn, tn, blks, err;
551
552 if (sc->sc_active)
553 return; /* Already doing something */
554
555 if (ob == 0) {
556 bp = BUFQ_GET(&sc->sc_q);
557 if (bp == NULL)
558 return; /* Nothing to do */
559 sc->sc_bufaddr = bp->b_data;
560 sc->sc_diskblk = bp->b_rawblkno;
561 sc->sc_bytecnt = bp->b_bcount;
562 bp->b_resid = 0;
563 } else
564 bp = ob;
565 sc->sc_active = bp;
566
567 rc = rl_cd.cd_devs[DISKUNIT(bp->b_dev)];
568 bn = sc->sc_diskblk;
569 lp = rc->rc_disk.dk_label;
570 if (bn) {
571 cn = bn / lp->d_secpercyl;
572 sn = bn % lp->d_secpercyl;
573 tn = sn / lp->d_nsectors;
574 sn = sn % lp->d_nsectors;
575 } else
576 cn = sn = tn = 0;
577
578 /*
579 * Check if we have to position disk first.
580 */
581 if (rc->rc_cyl != cn || rc->rc_head != tn) {
582 u_int16_t da = RLDA_SEEK;
583 if (cn > rc->rc_cyl)
584 da |= ((cn - rc->rc_cyl) << RLDA_CYLSHFT) | RLDA_DIR;
585 else
586 da |= ((rc->rc_cyl - cn) << RLDA_CYLSHFT);
587 if (tn)
588 da |= RLDA_HSSEEK;
589 waitcrdy(sc);
590 RL_WREG(RL_DA, da);
591 RL_WREG(RL_CS, RLCS_SEEK | (rc->rc_hwid << RLCS_USHFT));
592 waitcrdy(sc);
593 rc->rc_cyl = cn;
594 rc->rc_head = tn;
595 }
596 RL_WREG(RL_DA, (cn << RLDA_CYLSHFT) | (tn ? RLDA_HSRW : 0) | (sn << 1));
597 blks = sc->sc_bytecnt/DEV_BSIZE;
598
599 if (sn + blks > RL_SPT/2)
600 blks = RL_SPT/2 - sn;
601 RL_WREG(RL_MP, -(blks*DEV_BSIZE)/2);
602 err = bus_dmamap_load(sc->sc_dmat, sc->sc_dmam, sc->sc_bufaddr,
603 (blks*DEV_BSIZE), (bp->b_flags & B_PHYS ? bp->b_proc : 0),
604 BUS_DMA_NOWAIT);
605 if (err)
606 panic("%s: bus_dmamap_load failed: %d",
607 sc->sc_dev.dv_xname, err);
608 RL_WREG(RL_BA, (sc->sc_dmam->dm_segs[0].ds_addr & 0xffff));
609
610 /* Count up vars */
611 sc->sc_bufaddr += (blks*DEV_BSIZE);
612 sc->sc_diskblk += blks;
613 sc->sc_bytecnt -= (blks*DEV_BSIZE);
614
615 if (bp->b_flags & B_READ)
616 RL_WREG(RL_CS, RLCS_IE|RLCS_RD|(rc->rc_hwid << RLCS_USHFT));
617 else
618 RL_WREG(RL_CS, RLCS_IE|RLCS_WD|(rc->rc_hwid << RLCS_USHFT));
619 }
620
621 /*
622 * Called once per controller when an ubareset occurs.
623 * Retracts all disks and restarts active transfers.
624 */
625 void
626 rlcreset(struct device *dev)
627 {
628 struct rlc_softc *sc = (struct rlc_softc *)dev;
629 struct rl_softc *rc;
630 int i;
631 u_int16_t mp;
632
633 for (i = 0; i < rl_cd.cd_ndevs; i++) {
634 if ((rc = rl_cd.cd_devs[i]) == NULL)
635 continue;
636 if (rc->rc_state != DK_OPEN)
637 continue;
638
639 printf(" %s", rc->rc_dev.dv_xname);
640 RL_WREG(RL_CS, RLCS_RHDR|(rc->rc_hwid << RLCS_USHFT));
641 waitcrdy(sc);
642 mp = RL_RREG(RL_MP);
643 rc->rc_head = ((mp & RLMP_HS) == RLMP_HS);
644 rc->rc_cyl = (mp >> 7) & 0777;
645 }
646 if (sc->sc_active == 0)
647 return;
648
649 BUFQ_PUT(&sc->sc_q, sc->sc_active);
650 sc->sc_active = 0;
651 rlcstart(sc, 0);
652 }
653