1 /* $OpenBSD: mmc.c,v 1.32 2020/09/01 17:20:02 krw Exp $ */ 2 /* 3 * Copyright (c) 2006 Michael Coulter <mjc@openbsd.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18 #include <sys/limits.h> 19 #include <sys/time.h> 20 #include <sys/types.h> 21 #include <sys/scsiio.h> 22 #include <sys/param.h> /* setbit, isset */ 23 #include <scsi/cd.h> 24 #include <scsi/scsi_all.h> 25 #include <scsi/scsi_disk.h> 26 #include <err.h> 27 #include <errno.h> 28 #include <fcntl.h> 29 #include <stdio.h> 30 #include <string.h> 31 #include <time.h> 32 #include <unistd.h> 33 #include "extern.h" 34 35 extern int fd; 36 extern u_int8_t mediacap[]; 37 extern char *cdname; 38 extern int verbose; 39 40 #define SCSI_GET_CONFIGURATION 0x46 41 42 #define MMC_FEATURE_HDR_LEN 8 43 44 static const struct { 45 u_int16_t id; 46 char *name; 47 } mmc_feature[] = { 48 { 0x0000, "Profile List" }, 49 { 0x0001, "Core" }, 50 { 0x0002, "Morphing" }, 51 { 0x0003, "Removable Medium" }, 52 { 0x0004, "Write Protect" }, 53 { 0x0010, "Random Readable" }, 54 { 0x001d, "Multi-Read" }, 55 { 0x001e, "CD Read" }, 56 { 0x001f, "DVD Read" }, 57 { 0x0020, "Random Writable" }, 58 { 0x0021, "Incremental Streaming Writable" }, 59 { 0x0022, "Sector Erasable" }, 60 { 0x0023, "Formattable" }, 61 { 0x0024, "Hardware Defect Management" }, 62 { 0x0025, "Write Once" }, 63 { 0x0026, "Restricted Overwrite" }, 64 { 0x0027, "CD-RW CAV Write" }, 65 { 0x0028, "MRW" }, 66 { 0x0029, "Enhanced Defect Reporting" }, 67 { 0x002a, "DVD+RW" }, 68 { 0x002b, "DVD+R" }, 69 { 0x002c, "Rigid Restricted Overwrite" }, 70 { 0x002d, "CD Track at Once (TAO)" }, 71 { 0x002e, "CD Mastering (Session at Once)" }, 72 { 0x002f, "DVD-RW Write" }, 73 { 0x0030, "DDCD-ROM (Legacy)" }, 74 { 0x0031, "DDCD-R (Legacy)" }, 75 { 0x0032, "DDCD-RW (Legacy)" }, 76 { 0x0033, "Layer Jump Recording" }, 77 { 0x0037, "CD-RW Media Write Support" }, 78 { 0x0038, "BD-R Pseudo-Overwrite (POW)" }, 79 { 0x003a, "DVD+RW Dual Layer" }, 80 { 0x003b, "DVD+R Dual Layer" }, 81 { 0x0040, "BD Read" }, 82 { 0x0041, "BD Write" }, 83 { 0x0042, "Timely Safe Recording (TSR)" }, 84 { 0x0050, "HD DVD Read" }, 85 { 0x0051, "HD DVD Write" }, 86 { 0x0080, "Hybrid Disc" }, 87 { 0x0100, "Power Management" }, 88 { 0x0101, "S.M.A.R.T." }, 89 { 0x0102, "Embedded Changer" }, 90 { 0x0103, "CD Audio External Play (Legacy)" }, 91 { 0x0104, "Microcode Upgrade" }, 92 { 0x0105, "Timeout" }, 93 { 0x0106, "DVD CSS" }, 94 { 0x0107, "Real Time Streaming" }, 95 { 0x0108, "Drive Serial Number" }, 96 { 0x0109, "Media Serial Number" }, 97 { 0x010a, "Disc Control Blocks (DCBs)" }, 98 { 0x010b, "DVD CPRM" }, 99 { 0x010c, "Firmware Information" }, 100 { 0x010d, "AACS" }, 101 { 0x0110, "VCPS" }, 102 { 0, NULL } 103 }; 104 105 static const struct { 106 u_int16_t id; 107 char *name; 108 } mmc_profile[] = { 109 { 0x0001, "Re-writable disk, capable of changing behaviour" }, 110 { 0x0002, "Re-writable, with removable media" }, 111 { 0x0003, "Magneto-Optical disk with sector erase capability" }, 112 { 0x0004, "Optical write once" }, 113 { 0x0005, "Advance Storage -- Magneto-Optical" }, 114 { 0x0008, "Read only Compact Disc" }, 115 { 0x0009, "Write once Compact Disc" }, 116 { 0x000a, "Re-writable Compact Disc" }, 117 { 0x0010, "Read only DVD" }, 118 { 0x0011, "Write once DVD using Sequential recording" }, 119 { 0x0012, "Re-writable DVD" }, 120 { 0x0013, "Re-recordable DVD using Restricted Overwrite" }, 121 { 0x0014, "Re-recordable DVD using Sequential recording" }, 122 { 0x0015, "Dual Layer DVD-R using Sequential recording" }, 123 { 0x0016, "Dual Layer DVD-R using Layer Jump recording" }, 124 { 0x001a, "DVD+ReWritable" }, 125 { 0x001b, "DVD+Recordable" }, 126 { 0x0020, "DDCD-ROM" }, 127 { 0x0021, "DDCD-R" }, 128 { 0x0022, "DDCD-RW" }, 129 { 0x002a, "DVD+Rewritable Dual Layer" }, 130 { 0x002b, "DVD+Recordable Dual Layer" }, 131 { 0x003e, "Blu-ray Disc ROM" }, 132 { 0x003f, "Blu-ray Disc Recordable -- Sequential Recording Mode" }, 133 { 0x0040, "Blu-ray Disc Recordable -- Random Recording Mode" }, 134 { 0x0041, "Blu-ray Disc Rewritable" }, 135 { 0x004e, "Read-only HD DVD" }, 136 { 0x004f, "Write-once HD DVD" }, 137 { 0x0050, "Rewritable HD DVD" }, 138 { 0, NULL } 139 }; 140 141 int 142 get_media_type(void) 143 { 144 scsireq_t scr; 145 char buf[32]; 146 u_char disctype; 147 int rv, error; 148 149 rv = MEDIATYPE_UNKNOWN; 150 memset(buf, 0, sizeof(buf)); 151 memset(&scr, 0, sizeof(scr)); 152 153 scr.cmd[0] = READ_TOC; 154 scr.cmd[1] = 0x2; /* MSF */ 155 scr.cmd[2] = 0x4; /* ATIP */ 156 scr.cmd[8] = 0x20; 157 158 scr.flags = SCCMD_ESCAPE | SCCMD_READ; 159 scr.databuf = buf; 160 scr.datalen = sizeof(buf); 161 scr.cmdlen = 10; 162 scr.timeout = 120000; 163 scr.senselen = SENSEBUFLEN; 164 165 error = ioctl(fd, SCIOCCOMMAND, &scr); 166 if (error != -1 && scr.retsts == SCCMD_OK && scr.datalen_used > 7) { 167 disctype = (buf[6] >> 6) & 0x1; 168 if (disctype == 0) 169 rv = MEDIATYPE_CDR; 170 else if (disctype == 1) 171 rv = MEDIATYPE_CDRW; 172 } 173 174 return (rv); 175 } 176 177 int 178 get_media_capabilities(u_int8_t *cap, int rt) 179 { 180 scsireq_t scr; 181 u_char buf[4096]; 182 u_int32_t i, dlen; 183 u_int16_t feature, profile, tmp; 184 u_int8_t feature_len; 185 int current, error, j, k; 186 187 memset(cap, 0, MMC_FEATURE_MAX / NBBY); 188 memset(buf, 0, sizeof(buf)); 189 memset(&scr, 0, sizeof(scr)); 190 191 scr.cmd[0] = SCSI_GET_CONFIGURATION; 192 scr.cmd[1] = rt; 193 tmp = htobe16(sizeof(buf)); 194 memcpy(scr.cmd + 7, &tmp, sizeof(u_int16_t)); 195 196 scr.flags = SCCMD_ESCAPE | SCCMD_READ; 197 scr.databuf = buf; 198 scr.datalen = sizeof(buf); 199 scr.cmdlen = 10; 200 scr.timeout = 120000; 201 scr.senselen = SENSEBUFLEN; 202 203 error = ioctl(fd, SCIOCCOMMAND, &scr); 204 if (error == -1 || scr.retsts != SCCMD_OK) 205 return (-1); 206 if (scr.datalen_used < MMC_FEATURE_HDR_LEN) 207 return (-1); /* Can't get the header. */ 208 209 /* Include the whole header in the length. */ 210 dlen = betoh32(*(u_int32_t *)buf) + 4; 211 if (dlen > scr.datalen_used) 212 dlen = scr.datalen_used; 213 214 if (verbose > 1) 215 printf("Features:\n"); 216 for (i = MMC_FEATURE_HDR_LEN; i + 3 < dlen; i += feature_len) { 217 feature_len = buf[i + 3] + 4; 218 if (feature_len + i > dlen) 219 break; 220 221 feature = betoh16(*(u_int16_t *)(buf + i)); 222 if (feature >= MMC_FEATURE_MAX) 223 break; 224 225 if (verbose > 1) { 226 printf("0x%04x", feature); 227 for (j = 0; mmc_feature[j].name != NULL; j++) 228 if (feature == mmc_feature[j].id) 229 break; 230 if (mmc_feature[j].name == NULL) 231 printf(" <Undocumented>"); 232 else 233 printf(" %s", mmc_feature[j].name); 234 if (feature_len > 4) 235 printf(" (%d bytes of data)", feature_len - 4); 236 printf("\n"); 237 if (verbose > 2) { 238 printf(" "); 239 for (j = i; j < i + feature_len; j++) { 240 printf("%02x", buf[j]); 241 if ((j + 1) == (i + feature_len)) 242 printf("\n"); 243 else if ((j > i) && ((j - i + 1) % 16 244 == 0)) 245 printf("\n "); 246 else if ((j - i) == 3) 247 printf("|"); 248 else 249 printf(" "); 250 } 251 } 252 } 253 if (feature == 0 && verbose > 1) { 254 if (verbose > 2) 255 printf(" Profiles:\n"); 256 for (j = i + 4; j < i + feature_len; j += 4) { 257 profile = betoh16(*(u_int16_t *)(buf+j)); 258 current = buf[j+2] == 1; 259 if (verbose < 3 && !current) 260 continue; 261 if (current) 262 printf(" * "); 263 else 264 printf(" "); 265 printf("0x%04x", profile); 266 for (k = 0; mmc_profile[k].name != NULL; k++) 267 if (profile == mmc_profile[k].id) 268 break; 269 if (mmc_profile[k].name == NULL) 270 printf(" <Undocumented>"); 271 else 272 printf(" %s", mmc_profile[k].name); 273 printf(" %s\n", current ? "[Current Profile]" : 274 "" ); 275 } 276 } 277 setbit(cap, feature); 278 } 279 280 return (0); 281 } 282 283 static int 284 set_speed(int wspeed) 285 { 286 scsireq_t scr; 287 int r; 288 289 memset(&scr, 0, sizeof(scr)); 290 scr.cmd[0] = SET_CD_SPEED; 291 scr.cmd[1] = (isset(mediacap, MMC_FEATURE_CDRW_CAV)) != 0; 292 *(u_int16_t *)(scr.cmd + 2) = htobe16(DRIVE_SPEED_OPTIMAL); 293 *(u_int16_t *)(scr.cmd + 4) = htobe16(wspeed); 294 295 scr.cmdlen = 12; 296 scr.datalen = 0; 297 scr.timeout = 120000; 298 scr.flags = SCCMD_ESCAPE; 299 scr.senselen = SENSEBUFLEN; 300 301 r = ioctl(fd, SCIOCCOMMAND, &scr); 302 return (r == 0 ? scr.retsts : -1); 303 } 304 305 int 306 blank(void) 307 { 308 struct scsi_blank *scb; 309 scsireq_t scr; 310 int r; 311 312 bzero(&scr, sizeof(scr)); 313 scb = (struct scsi_blank *)scr.cmd; 314 scb->opcode = BLANK; 315 scb->byte2 |= BLANK_MINIMAL; 316 scr.cmdlen = sizeof(*scb); 317 scr.datalen = 0; 318 scr.timeout = 120000; 319 scr.flags = SCCMD_ESCAPE; 320 scr.senselen = SENSEBUFLEN; 321 322 r = ioctl(fd, SCIOCCOMMAND, &scr); 323 return (r == 0 ? scr.retsts : -1); 324 } 325 326 int 327 unit_ready(void) 328 { 329 struct scsi_test_unit_ready *scb; 330 scsireq_t scr; 331 int r; 332 333 bzero(&scr, sizeof(scr)); 334 scb = (struct scsi_test_unit_ready *)scr.cmd; 335 scb->opcode = TEST_UNIT_READY; 336 scr.cmdlen = sizeof(*scb); 337 scr.datalen = 0; 338 scr.timeout = 120000; 339 scr.flags = SCCMD_ESCAPE; 340 scr.senselen = SENSEBUFLEN; 341 342 r = ioctl(fd, SCIOCCOMMAND, &scr); 343 return (r == 0 ? scr.retsts : -1); 344 } 345 346 int 347 synchronize_cache(void) 348 { 349 struct scsi_synchronize_cache *scb; 350 scsireq_t scr; 351 int r; 352 353 bzero(&scr, sizeof(scr)); 354 scb = (struct scsi_synchronize_cache *)scr.cmd; 355 scb->opcode = SYNCHRONIZE_CACHE; 356 scr.cmdlen = sizeof(*scb); 357 scr.datalen = 0; 358 scr.timeout = 120000; 359 scr.flags = SCCMD_ESCAPE; 360 scr.senselen = SENSEBUFLEN; 361 362 r = ioctl(fd, SCIOCCOMMAND, &scr); 363 return (r == 0 ? scr.retsts : -1); 364 } 365 366 int 367 close_session(void) 368 { 369 struct scsi_close_track *scb; 370 scsireq_t scr; 371 int r; 372 373 bzero(&scr, sizeof(scr)); 374 scb = (struct scsi_close_track *)scr.cmd; 375 scb->opcode = CLOSE_TRACK; 376 scb->closefunc = CT_CLOSE_SESS; 377 scr.cmdlen = sizeof(*scb); 378 scr.datalen = 0; 379 scr.timeout = 120000; 380 scr.flags = SCCMD_ESCAPE; 381 scr.senselen = SENSEBUFLEN; 382 383 r = ioctl(fd, SCIOCCOMMAND, &scr); 384 return (r == 0 ? scr.retsts : -1); 385 } 386 387 int 388 writetao(struct track_head *thp) 389 { 390 u_char modebuf[70], bdlen; 391 struct track_info *tr; 392 int r, track = 0; 393 394 if ((r = mode_sense_write(modebuf)) != SCCMD_OK) { 395 warnx("mode sense failed: %d", r); 396 return (r); 397 } 398 bdlen = modebuf[7]; 399 modebuf[2+8+bdlen] |= 0x40; /* Buffer Underrun Free Enable */ 400 modebuf[2+8+bdlen] |= 0x01; /* change write type to TAO */ 401 402 SLIST_FOREACH(tr, thp, track_list) { 403 track++; 404 switch (tr->type) { 405 case 'd': 406 modebuf[3+8+bdlen] = 0x04; /* track mode = data */ 407 modebuf[4+8+bdlen] = 0x08; /* 2048 block track mode */ 408 modebuf[8+8+bdlen] = 0x00; /* turn off XA */ 409 break; 410 case 'a': 411 modebuf[3+8+bdlen] = 0x00; /* track mode = audio */ 412 modebuf[4+8+bdlen] = 0x00; /* 2352 block track mode */ 413 modebuf[8+8+bdlen] = 0x00; /* turn off XA */ 414 break; 415 default: 416 warn("impossible tracktype detected"); 417 break; 418 } 419 while (unit_ready() != SCCMD_OK) 420 continue; 421 if ((r = mode_select_write(modebuf)) != SCCMD_OK) { 422 warnx("mode select failed: %d", r); 423 return (r); 424 } 425 426 set_speed(tr->speed); 427 writetrack(tr, track); 428 synchronize_cache(); 429 } 430 fprintf(stderr, "Closing session.\n"); 431 close_session(); 432 return (0); 433 } 434 435 int 436 writetrack(struct track_info *tr, int track) 437 { 438 struct timespec ts, ots, ats; 439 u_char databuf[65536], nblk; 440 u_int end_lba, lba, tmp; 441 scsireq_t scr; 442 int r; 443 444 nblk = 65535/tr->blklen; 445 bzero(&scr, sizeof(scr)); 446 scr.timeout = 300000; 447 scr.cmd[0] = WRITE_10; 448 scr.cmd[1] = 0x00; 449 scr.cmd[8] = nblk; /* Transfer length in blocks (LSB) */ 450 scr.cmdlen = 10; 451 scr.databuf = (caddr_t)databuf; 452 scr.datalen = nblk * tr->blklen; 453 scr.senselen = SENSEBUFLEN; 454 scr.flags = SCCMD_ESCAPE|SCCMD_WRITE; 455 456 timespecclear(&ots); 457 ats.tv_sec = 1; 458 ats.tv_nsec = 0; 459 460 if (get_nwa(&lba) != SCCMD_OK) { 461 warnx("cannot get next writable address"); 462 return (-1); 463 } 464 tmp = htobe32(lba); /* update lba in cdb */ 465 memcpy(&scr.cmd[2], &tmp, sizeof(tmp)); 466 467 if (tr->sz / tr->blklen + 1 > UINT_MAX || tr->sz < tr->blklen) { 468 warnx("file %s has invalid size", tr->file); 469 return (-1); 470 } 471 if (tr->sz % tr->blklen) { 472 warnx("file %s is not multiple of block length %d", 473 tr->file, tr->blklen); 474 end_lba = tr->sz / tr->blklen + lba + 1; 475 } else { 476 end_lba = tr->sz / tr->blklen + lba; 477 } 478 if (lseek(tr->fd, tr->off, SEEK_SET) == -1) 479 err(1, "seek failed for file %s", tr->file); 480 while (lba < end_lba && nblk != 0) { 481 while (lba + nblk <= end_lba) { 482 read(tr->fd, databuf, nblk * tr->blklen); 483 scr.cmd[8] = nblk; 484 scr.datalen = nblk * tr->blklen; 485 again: 486 r = ioctl(fd, SCIOCCOMMAND, &scr); 487 if (r != 0) { 488 printf("\r%60s", ""); 489 warn("ioctl failed while attempting to write"); 490 return (-1); 491 } 492 if (scr.retsts == SCCMD_SENSE && scr.sense[2] == 0x2) { 493 usleep(1000); 494 goto again; 495 } 496 if (scr.retsts != SCCMD_OK) { 497 printf("\r%60s", ""); 498 warnx("ioctl returned bad status while " 499 "attempting to write: %d", 500 scr.retsts); 501 return (r); 502 } 503 lba += nblk; 504 505 clock_gettime(CLOCK_MONOTONIC, &ts); 506 if (lba == end_lba || timespeccmp(&ts, &ots, >)) { 507 fprintf(stderr, 508 "\rtrack %02d '%c' %08u/%08u %3d%%", 509 track, tr->type, 510 lba, end_lba, 100 * lba / end_lba); 511 timespecadd(&ts, &ats, &ots); 512 } 513 tmp = htobe32(lba); /* update lba in cdb */ 514 memcpy(&scr.cmd[2], &tmp, sizeof(tmp)); 515 } 516 nblk--; 517 } 518 printf("\n"); 519 close(tr->fd); 520 return (0); 521 } 522 523 int 524 mode_sense_write(unsigned char buf[]) 525 { 526 struct scsi_mode_sense_big *scb; 527 scsireq_t scr; 528 int r; 529 530 bzero(&scr, sizeof(scr)); 531 scb = (struct scsi_mode_sense_big *)scr.cmd; 532 scb->opcode = MODE_SENSE_BIG; 533 /* XXX: need to set disable block descriptors and check SCSI drive */ 534 scb->page = WRITE_PARAM_PAGE; 535 scb->length[1] = 0x46; /* 16 for the header + size from pg. 89 mmc-r10a.pdf */ 536 scr.cmdlen = sizeof(*scb); 537 scr.timeout = 4000; 538 scr.senselen = SENSEBUFLEN; 539 scr.datalen= 0x46; 540 scr.flags = SCCMD_ESCAPE|SCCMD_READ; 541 scr.databuf = (caddr_t)buf; 542 543 r = ioctl(fd, SCIOCCOMMAND, &scr); 544 return (r == 0 ? scr.retsts : -1); 545 } 546 547 int 548 mode_select_write(unsigned char buf[]) 549 { 550 struct scsi_mode_select_big *scb; 551 scsireq_t scr; 552 int r; 553 554 bzero(&scr, sizeof(scr)); 555 scb = (struct scsi_mode_select_big *)scr.cmd; 556 scb->opcode = MODE_SELECT_BIG; 557 558 /* 559 * INF-8020 says bit 4 in byte 2 is '1' 560 * INF-8090 refers to it as 'PF(1)' then doesn't 561 * describe it. 562 */ 563 scb->byte2 = 0x10; 564 scb->length[1] = 2 + buf[1] + 256 * buf[0]; 565 scr.timeout = 4000; 566 scr.senselen = SENSEBUFLEN; 567 scr.cmdlen = sizeof(*scb); 568 scr.datalen = 2 + buf[1] + 256 * buf[0]; 569 scr.flags = SCCMD_ESCAPE|SCCMD_WRITE; 570 scr.databuf = (caddr_t)buf; 571 572 r = ioctl(fd, SCIOCCOMMAND, &scr); 573 return (r == 0 ? scr.retsts : -1); 574 } 575 576 int 577 get_disc_size(off_t *availblk) 578 { 579 u_char databuf[28]; 580 struct scsi_read_track_info *scb; 581 scsireq_t scr; 582 int r, tmp; 583 584 bzero(&scr, sizeof(scr)); 585 scb = (struct scsi_read_track_info *)scr.cmd; 586 scr.timeout = 4000; 587 scr.senselen = SENSEBUFLEN; 588 scb->opcode = READ_TRACK_INFO; 589 scb->addrtype = RTI_TRACK; 590 scb->addr[3] = 1; 591 scb->data_len[1] = 0x1c; 592 scr.cmdlen = sizeof(*scb); 593 scr.datalen= 0x1c; 594 scr.flags = SCCMD_ESCAPE|SCCMD_READ; 595 scr.databuf = (caddr_t)databuf; 596 597 r = ioctl(fd, SCIOCCOMMAND, &scr); 598 memcpy(&tmp, &databuf[16], sizeof(tmp)); 599 *availblk = betoh32(tmp); 600 return (r == 0 ? scr.retsts : -1); 601 } 602 603 int 604 get_nwa(int *nwa) 605 { 606 u_char databuf[28]; 607 scsireq_t scr; 608 int r, tmp; 609 610 bzero(&scr, sizeof(scr)); 611 scr.timeout = 4000; 612 scr.senselen = SENSEBUFLEN; 613 scr.cmd[0] = READ_TRACK_INFO; 614 scr.cmd[1] = 0x01; 615 scr.cmd[5] = 0xff; /* Invisible Track */ 616 scr.cmd[7] = 0x00; 617 scr.cmd[8] = 0x1c; 618 scr.cmdlen = 10; 619 scr.datalen= 0x1c; 620 scr.flags = SCCMD_ESCAPE|SCCMD_READ; 621 scr.databuf = (caddr_t)databuf; 622 623 r = ioctl(fd, SCIOCCOMMAND, &scr); 624 memcpy(&tmp, &databuf[12], sizeof(tmp)); 625 *nwa = betoh32(tmp); 626 return (r == 0 ? scr.retsts : -1); 627 } 628