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