1 /* 2 SDL - Simple DirectMedia Layer 3 Copyright (C) 1997-2006 Sam Lantinga 4 5 This library is free software; you can redistribute it and/or 6 modify it under the terms of the GNU Lesser General Public 7 License as published by the Free Software Foundation; either 8 version 2.1 of the License, or (at your option) any later version. 9 10 This library is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser General Public 16 License along with this library; if not, write to the Free Software 17 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 19 Sam Lantinga 20 slouken (at) libsdl.org 21 */ 22 #include "SDL_config.h" 23 24 #ifdef SDL_CDROM_BSDI 25 26 /* 27 * Functions for system-level CD-ROM audio control for BSD/OS 4.x 28 * This started life out as a copy of the freebsd/SDL_cdrom.c file but was 29 * heavily modified. Works for standard (MMC) SCSI and ATAPI CDrom drives. 30 * 31 * Steven Schultz - sms (at) to.gd-es.com 32 */ 33 34 #include <sys/types.h> 35 #include <sys/stat.h> 36 #include <fcntl.h> 37 #include <err.h> 38 #include <unistd.h> 39 #include <sys/ioctl.h> 40 #include </sys/dev/scsi/scsi.h> 41 #include </sys/dev/scsi/scsi_ioctl.h> 42 43 #include "SDL_cdrom.h" 44 #include "../SDL_syscdrom.h" 45 46 /* 47 * The msf_to_frame and frame_to_msf were yanked from libcdrom and inlined 48 * here so that -lcdrom doesn't have to be dragged in for something so simple. 49 */ 50 51 #define FRAMES_PER_SECOND 75 52 #define FRAMES_PER_MINUTE (FRAMES_PER_SECOND * 60) 53 54 int 55 msf_to_frame(int minute, int second, int frame) 56 { 57 return(minute * FRAMES_PER_MINUTE + second * FRAMES_PER_SECOND + frame); 58 } 59 60 void 61 frame_to_msf(int frame, int *minp, int *secp, int *framep) 62 { 63 *minp = frame / FRAMES_PER_MINUTE; 64 *secp = (frame % FRAMES_PER_MINUTE) / FRAMES_PER_SECOND; 65 *framep = frame % FRAMES_PER_SECOND; 66 } 67 68 /* The maximum number of CD-ROM drives we'll detect */ 69 #define MAX_DRIVES 16 70 71 /* A list of available CD-ROM drives */ 72 static char *SDL_cdlist[MAX_DRIVES]; 73 static dev_t SDL_cdmode[MAX_DRIVES]; 74 75 /* The system-dependent CD control functions */ 76 static const char *SDL_SYS_CDName(int drive); 77 static int SDL_SYS_CDOpen(int drive); 78 static int SDL_SYS_CDGetTOC(SDL_CD *cdrom); 79 static CDstatus SDL_SYS_CDStatus(SDL_CD *cdrom, int *position); 80 static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length); 81 static int SDL_SYS_CDPause(SDL_CD *cdrom); 82 static int SDL_SYS_CDResume(SDL_CD *cdrom); 83 static int SDL_SYS_CDStop(SDL_CD *cdrom); 84 static int SDL_SYS_CDEject(SDL_CD *cdrom); 85 static void SDL_SYS_CDClose(SDL_CD *cdrom); 86 87 typedef struct scsi_cdb cdb_t; 88 89 static int scsi_cmd(int fd, 90 struct scsi_cdb *cdb, 91 int cdblen, 92 int rw, 93 caddr_t data, 94 int datalen, 95 struct scsi_user_cdb *sus) 96 { 97 int scsistatus; 98 unsigned char *cp; 99 struct scsi_user_cdb suc; 100 101 /* safety checks */ 102 if (!cdb) return(-1); 103 if (rw != SUC_READ && rw != SUC_WRITE) return(-1); 104 105 suc.suc_flags = rw; 106 suc.suc_cdblen = cdblen; 107 bcopy(cdb, suc.suc_cdb, cdblen); 108 suc.suc_datalen = datalen; 109 suc.suc_data = data; 110 suc.suc_timeout = 10; /* 10 secs max for TUR or SENSE */ 111 if (ioctl(fd, SCSIRAWCDB, &suc) == -1) 112 return(-11); 113 scsistatus = suc.suc_sus.sus_status; 114 cp = suc.suc_sus.sus_sense; 115 116 /* 117 * If a place to copy the sense data back to has been provided then the 118 * caller is responsible for checking the errors and printing any information 119 * out if the status was not successful. 120 */ 121 if (scsistatus != 0 && !sus) 122 { 123 fprintf(stderr,"scsistatus = %x cmd = %x\n", 124 scsistatus, cdb[0]); 125 fprintf(stderr, "sense %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x\n", 126 cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], 127 cp[6], cp[7], cp[8], cp[9], cp[10], cp[11], 128 cp[12], cp[13], cp[14], cp[15]); 129 return(1); 130 } 131 if (sus) 132 bcopy(&suc, sus, sizeof (struct scsi_user_cdb)); 133 if (scsistatus) 134 return(1); /* Return non-zero for unsuccessful status */ 135 return(0); 136 } 137 138 /* request vendor brand and model */ 139 unsigned char *Inquiry(int fd) 140 { 141 static struct scsi_cdb6 cdb = 142 { 143 0x12, 144 0, 0, 0, 145 56, 146 0 147 }; 148 static unsigned char Inqbuffer[56]; 149 150 if (scsi_cmd(fd, (cdb_t *)&cdb, 6, SUC_READ, Inqbuffer, 151 sizeof(Inqbuffer), 0)) 152 return("\377"); 153 return(Inqbuffer); 154 } 155 156 #define ADD_SENSECODE 12 157 #define ADD_SC_QUALIFIER 13 158 159 int TestForMedium(int fd) 160 { 161 int sts, asc, ascq; 162 struct scsi_user_cdb sus; 163 static struct scsi_cdb6 cdb = 164 { 165 CMD_TEST_UNIT_READY, /* command */ 166 0, /* reserved */ 167 0, /* reserved */ 168 0, /* reserved */ 169 0, /* reserved */ 170 0 /* reserved */ 171 }; 172 173 again: sts = scsi_cmd(fd, (cdb_t *)&cdb, 6, SUC_READ, 0, 0, &sus); 174 asc = sus.suc_sus.sus_sense[ADD_SENSECODE]; 175 ascq = sus.suc_sus.sus_sense[ADD_SC_QUALIFIER]; 176 if (asc == 0x3a && ascq == 0x0) /* no medium */ 177 return(0); 178 if (asc == 0x28 && ascq == 0x0) /* medium changed */ 179 goto again; 180 if (asc == 0x4 && ascq == 0x1 ) /* coming ready */ 181 { 182 sleep(2); 183 goto again; 184 } 185 return(1); 186 } 187 188 /* Check a drive to see if it is a CD-ROM */ 189 static int CheckDrive(char *drive, struct stat *stbuf) 190 { 191 int is_cd = 0, cdfd; 192 char *p; 193 194 /* If it doesn't exist, return -1 */ 195 if ( stat(drive, stbuf) < 0 ) { 196 return(-1); 197 } 198 199 /* If it does exist, verify that it's an available CD-ROM */ 200 cdfd = open(drive, (O_RDONLY|O_EXCL|O_NONBLOCK), 0); 201 if ( cdfd >= 0 ) { 202 p = Inquiry(cdfd); 203 if (*p == TYPE_ROM) 204 is_cd = 1; 205 close(cdfd); 206 } 207 return(is_cd); 208 } 209 210 /* Add a CD-ROM drive to our list of valid drives */ 211 static void AddDrive(char *drive, struct stat *stbuf) 212 { 213 int i; 214 215 if ( SDL_numcds < MAX_DRIVES ) { 216 /* Check to make sure it's not already in our list. 217 This can happen when we see a drive via symbolic link. 218 */ 219 for ( i=0; i<SDL_numcds; ++i ) { 220 if ( stbuf->st_rdev == SDL_cdmode[i] ) { 221 #ifdef DEBUG_CDROM 222 fprintf(stderr, "Duplicate drive detected: %s == %s\n", drive, SDL_cdlist[i]); 223 #endif 224 return; 225 } 226 } 227 228 /* Add this drive to our list */ 229 i = SDL_numcds; 230 SDL_cdlist[i] = SDL_strdup(drive); 231 if ( SDL_cdlist[i] == NULL ) { 232 SDL_OutOfMemory(); 233 return; 234 } 235 SDL_cdmode[i] = stbuf->st_rdev; 236 ++SDL_numcds; 237 #ifdef DEBUG_CDROM 238 fprintf(stderr, "Added CD-ROM drive: %s\n", drive); 239 #endif 240 } 241 } 242 243 int SDL_SYS_CDInit(void) 244 { 245 /* checklist: /dev/rsr?c */ 246 static char *checklist[] = { 247 "?0 rsr?", NULL 248 }; 249 char *SDLcdrom; 250 int i, j, exists; 251 char drive[32]; 252 struct stat stbuf; 253 254 /* Fill in our driver capabilities */ 255 SDL_CDcaps.Name = SDL_SYS_CDName; 256 SDL_CDcaps.Open = SDL_SYS_CDOpen; 257 SDL_CDcaps.GetTOC = SDL_SYS_CDGetTOC; 258 SDL_CDcaps.Status = SDL_SYS_CDStatus; 259 SDL_CDcaps.Play = SDL_SYS_CDPlay; 260 SDL_CDcaps.Pause = SDL_SYS_CDPause; 261 SDL_CDcaps.Resume = SDL_SYS_CDResume; 262 SDL_CDcaps.Stop = SDL_SYS_CDStop; 263 SDL_CDcaps.Eject = SDL_SYS_CDEject; 264 SDL_CDcaps.Close = SDL_SYS_CDClose; 265 266 /* Look in the environment for our CD-ROM drive list */ 267 SDLcdrom = SDL_getenv("SDL_CDROM"); /* ':' separated list of devices */ 268 if ( SDLcdrom != NULL ) { 269 char *cdpath, *delim; 270 size_t len = SDL_strlen(SDLcdrom)+1; 271 cdpath = SDL_stack_alloc(char, len); 272 if ( cdpath != NULL ) { 273 SDL_strlcpy(cdpath, SDLcdrom, len); 274 SDLcdrom = cdpath; 275 do { 276 delim = SDL_strchr(SDLcdrom, ':'); 277 if ( delim ) { 278 *delim++ = '\0'; 279 } 280 if ( CheckDrive(SDLcdrom, &stbuf) > 0 ) { 281 AddDrive(SDLcdrom, &stbuf); 282 } 283 if ( delim ) { 284 SDLcdrom = delim; 285 } else { 286 SDLcdrom = NULL; 287 } 288 } while ( SDLcdrom ); 289 SDL_stack_free(cdpath); 290 } 291 292 /* If we found our drives, there's nothing left to do */ 293 if ( SDL_numcds > 0 ) { 294 return(0); 295 } 296 } 297 298 /* Scan the system for CD-ROM drives */ 299 for ( i=0; checklist[i]; ++i ) { 300 if ( checklist[i][0] == '?' ) { 301 char *insert; 302 exists = 1; 303 for ( j=checklist[i][1]; exists; ++j ) { 304 SDL_snprintf(drive, SDL_arraysize(drive), "/dev/%sc", &checklist[i][3]); 305 insert = SDL_strchr(drive, '?'); 306 if ( insert != NULL ) { 307 *insert = j; 308 } 309 switch (CheckDrive(drive, &stbuf)) { 310 /* Drive exists and is a CD-ROM */ 311 case 1: 312 AddDrive(drive, &stbuf); 313 break; 314 /* Drive exists, but isn't a CD-ROM */ 315 case 0: 316 break; 317 /* Drive doesn't exist */ 318 case -1: 319 exists = 0; 320 break; 321 } 322 } 323 } else { 324 SDL_snprintf(drive, SDL_arraysize(drive), "/dev/%s", checklist[i]); 325 if ( CheckDrive(drive, &stbuf) > 0 ) { 326 AddDrive(drive, &stbuf); 327 } 328 } 329 } 330 return(0); 331 } 332 333 static const char *SDL_SYS_CDName(int drive) 334 { 335 return(SDL_cdlist[drive]); 336 } 337 338 static int SDL_SYS_CDOpen(int drive) 339 { 340 return(open(SDL_cdlist[drive], O_RDONLY | O_NONBLOCK | O_EXCL, 0)); 341 } 342 343 static int SDL_SYS_CDGetTOC(SDL_CD *cdrom) 344 { 345 u_char cdb[10], buf[4], *p, *toc; 346 struct scsi_user_cdb sus; 347 int i, sts, first_track, last_track, ntracks, toc_size; 348 349 bzero(cdb, sizeof (cdb)); 350 cdb[0] = 0x43; /* Read TOC */ 351 cdb[1] = 0x2; /* MSF */ 352 cdb[8] = 4; /* size TOC header */ 353 sts = scsi_cmd(cdrom->id, (cdb_t *)cdb, 10, SUC_READ, buf, 4, &sus); 354 if (sts < 0) 355 return(-1); 356 first_track = buf[2]; 357 last_track = buf[3]; 358 ntracks = last_track - first_track + 1; 359 cdrom->numtracks = ntracks; 360 toc_size = 4 + (ntracks + 1) * 8; 361 toc = (u_char *)SDL_malloc(toc_size); 362 if (toc == NULL) 363 return(-1); 364 bzero(cdb, sizeof (cdb)); 365 cdb[0] = 0x43; 366 cdb[1] = 0x2; 367 cdb[6] = first_track; 368 cdb[7] = toc_size >> 8; 369 cdb[8] = toc_size & 0xff; 370 sts = scsi_cmd(cdrom->id, (cdb_t *)cdb, 10, SUC_READ, toc, toc_size, 371 &sus); 372 if (sts < 0) 373 { 374 SDL_free(toc); 375 return(-1); 376 } 377 378 for (i = 0, p = toc+4; i <= ntracks; i++, p+= 8) 379 { 380 if (i == ntracks) 381 cdrom->track[i].id = 0xAA; /* Leadout */ 382 else 383 cdrom->track[i].id = first_track + i; 384 if (p[1] & 0x20) 385 cdrom->track[i].type = SDL_DATA_TRACK; 386 else 387 cdrom->track[i].type = SDL_AUDIO_TRACK; 388 cdrom->track[i].offset = msf_to_frame(p[5], p[6], p[7]); 389 cdrom->track[i].length = 0; 390 if (i > 0) 391 cdrom->track[i-1].length = cdrom->track[i].offset - 392 cdrom->track[i-1].offset; 393 } 394 SDL_free(toc); 395 return(0); 396 } 397 398 /* Get CD-ROM status */ 399 static CDstatus SDL_SYS_CDStatus(SDL_CD *cdrom, int *position) 400 { 401 CDstatus status; 402 u_char cdb[10], buf[16]; 403 int sts; 404 struct scsi_user_cdb sus; 405 406 bzero(cdb, sizeof (cdb)); 407 cdb[0] = 0x42; /* read subq */ 408 cdb[1] = 0x2; /* MSF */ 409 cdb[2] = 0x40; /* q channel */ 410 cdb[3] = 1; /* current pos */ 411 cdb[7] = sizeof (buf) >> 8; 412 cdb[8] = sizeof (buf) & 0xff; 413 sts = scsi_cmd(cdrom->id, (cdb_t *)cdb, 10, SUC_READ, buf, sizeof (buf), 414 &sus); 415 if (sts < 0) 416 return(-1); 417 if (sts) 418 { 419 if (TestForMedium(cdrom->id) == 0) 420 status = CD_TRAYEMPTY; 421 else 422 status = CD_ERROR; 423 } 424 else 425 { 426 switch (buf[1]) 427 { 428 case 0x11: 429 status = CD_PLAYING; 430 break; 431 case 0x12: 432 status = CD_PAUSED; 433 break; 434 case 0x13: 435 case 0x14: 436 case 0x15: 437 status = CD_STOPPED; 438 break; 439 default: 440 status = CD_ERROR; 441 break; 442 } 443 } 444 if (position) 445 { 446 if ( status == CD_PLAYING || (status == CD_PAUSED) ) 447 *position = msf_to_frame(buf[9], buf[10], buf[11]); 448 else 449 *position = 0; 450 } 451 return(status); 452 } 453 454 /* Start play */ 455 static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length) 456 { 457 u_char cdb[10]; 458 int sts, minute, second, frame, eminute, esecond, eframe; 459 struct scsi_user_cdb sus; 460 461 bzero(cdb, sizeof(cdb)); 462 cdb[0] = 0x47; /* Play */ 463 frame_to_msf(start, &minute, &second, &frame); 464 frame_to_msf(start + length, &eminute, &esecond, &eframe); 465 cdb[3] = minute; 466 cdb[4] = second; 467 cdb[5] = frame; 468 cdb[6] = eminute; 469 cdb[7] = esecond; 470 cdb[8] = eframe; 471 sts = scsi_cmd(cdrom->id, (cdb_t *)cdb, 10, SUC_READ, 0, 0, &sus); 472 return(sts); 473 } 474 475 static int 476 pauseresume(SDL_CD *cdrom, int flag) 477 { 478 u_char cdb[10]; 479 struct scsi_user_cdb sus; 480 481 bzero(cdb, sizeof (cdb)); 482 cdb[0] = 0x4b; 483 cdb[8] = flag & 0x1; 484 return(scsi_cmd(cdrom->id, (cdb_t *)cdb, 10, SUC_READ, 0, 0, &sus)); 485 } 486 487 /* Pause play */ 488 static int SDL_SYS_CDPause(SDL_CD *cdrom) 489 { 490 return(pauseresume(cdrom, 0)); 491 } 492 493 /* Resume play */ 494 static int SDL_SYS_CDResume(SDL_CD *cdrom) 495 { 496 return(pauseresume(cdrom, 1)); 497 } 498 499 /* Stop play */ 500 static int SDL_SYS_CDStop(SDL_CD *cdrom) 501 { 502 u_char cdb[6]; 503 struct scsi_user_cdb sus; 504 505 bzero(cdb, sizeof (cdb)); 506 cdb[0] = 0x1b; /* stop */ 507 cdb[1] = 1; /* immediate */ 508 return(scsi_cmd(cdrom->id, (cdb_t *)cdb, 6, SUC_READ, 0, 0, &sus)); 509 } 510 511 /* Eject the CD-ROM */ 512 static int SDL_SYS_CDEject(SDL_CD *cdrom) 513 { 514 u_char cdb[6]; 515 struct scsi_user_cdb sus; 516 517 bzero(cdb, sizeof (cdb)); 518 cdb[0] = 0x1b; /* stop */ 519 cdb[1] = 1; /* immediate */ 520 cdb[4] = 2; /* eject */ 521 return(scsi_cmd(cdrom->id, (cdb_t *)cdb, 6, SUC_READ, 0, 0, &sus)); 522 } 523 524 /* Close the CD-ROM handle */ 525 static void SDL_SYS_CDClose(SDL_CD *cdrom) 526 { 527 close(cdrom->id); 528 } 529 530 void SDL_SYS_CDQuit(void) 531 { 532 int i; 533 534 if ( SDL_numcds > 0 ) { 535 for ( i=0; i<SDL_numcds; ++i ) { 536 SDL_free(SDL_cdlist[i]); 537 } 538 } 539 SDL_numcds = 0; 540 } 541 542 #endif /* SDL_CDROM_BSDI */ 543