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_FREEBSD 25 26 /* Functions for system-level CD-ROM audio control */ 27 28 #include <sys/types.h> 29 #include <sys/stat.h> 30 #include <fcntl.h> 31 #include <errno.h> 32 #include <unistd.h> 33 #include <sys/cdio.h> 34 35 #include "SDL_cdrom.h" 36 #include "../SDL_syscdrom.h" 37 38 39 /* The maximum number of CD-ROM drives we'll detect */ 40 #define MAX_DRIVES 16 41 42 /* A list of available CD-ROM drives */ 43 static char *SDL_cdlist[MAX_DRIVES]; 44 static dev_t SDL_cdmode[MAX_DRIVES]; 45 46 /* The system-dependent CD control functions */ 47 static const char *SDL_SYS_CDName(int drive); 48 static int SDL_SYS_CDOpen(int drive); 49 static int SDL_SYS_CDGetTOC(SDL_CD *cdrom); 50 static CDstatus SDL_SYS_CDStatus(SDL_CD *cdrom, int *position); 51 static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length); 52 static int SDL_SYS_CDPause(SDL_CD *cdrom); 53 static int SDL_SYS_CDResume(SDL_CD *cdrom); 54 static int SDL_SYS_CDStop(SDL_CD *cdrom); 55 static int SDL_SYS_CDEject(SDL_CD *cdrom); 56 static void SDL_SYS_CDClose(SDL_CD *cdrom); 57 58 /* Some ioctl() errno values which occur when the tray is empty */ 59 #define ERRNO_TRAYEMPTY(errno) \ 60 ((errno == EIO) || (errno == ENOENT) || (errno == EINVAL)) 61 62 /* Check a drive to see if it is a CD-ROM */ 63 static int CheckDrive(char *drive, struct stat *stbuf) 64 { 65 int is_cd, cdfd; 66 struct ioc_read_subchannel info; 67 68 /* If it doesn't exist, return -1 */ 69 if ( stat(drive, stbuf) < 0 ) { 70 return(-1); 71 } 72 73 /* If it does exist, verify that it's an available CD-ROM */ 74 is_cd = 0; 75 if ( S_ISCHR(stbuf->st_mode) || S_ISBLK(stbuf->st_mode) ) { 76 cdfd = open(drive, (O_RDONLY|O_EXCL|O_NONBLOCK), 0); 77 if ( cdfd >= 0 ) { 78 info.address_format = CD_MSF_FORMAT; 79 info.data_format = CD_CURRENT_POSITION; 80 info.data_len = 0; 81 info.data = NULL; 82 /* Under Linux, EIO occurs when a disk is not present. 83 This isn't 100% reliable, so we use the USE_MNTENT 84 code above instead. 85 */ 86 if ( (ioctl(cdfd, CDIOCREADSUBCHANNEL, &info) == 0) || 87 ERRNO_TRAYEMPTY(errno) ) { 88 is_cd = 1; 89 } 90 close(cdfd); 91 } 92 } 93 return(is_cd); 94 } 95 96 /* Add a CD-ROM drive to our list of valid drives */ 97 static void AddDrive(char *drive, struct stat *stbuf) 98 { 99 int i; 100 101 if ( SDL_numcds < MAX_DRIVES ) { 102 /* Check to make sure it's not already in our list. 103 This can happen when we see a drive via symbolic link. 104 */ 105 for ( i=0; i<SDL_numcds; ++i ) { 106 if ( stbuf->st_rdev == SDL_cdmode[i] ) { 107 #ifdef DEBUG_CDROM 108 fprintf(stderr, "Duplicate drive detected: %s == %s\n", drive, SDL_cdlist[i]); 109 #endif 110 return; 111 } 112 } 113 114 /* Add this drive to our list */ 115 i = SDL_numcds; 116 SDL_cdlist[i] = SDL_strdup(drive); 117 if ( SDL_cdlist[i] == NULL ) { 118 SDL_OutOfMemory(); 119 return; 120 } 121 SDL_cdmode[i] = stbuf->st_rdev; 122 ++SDL_numcds; 123 #ifdef DEBUG_CDROM 124 fprintf(stderr, "Added CD-ROM drive: %s\n", drive); 125 #endif 126 } 127 } 128 129 int SDL_SYS_CDInit(void) 130 { 131 /* checklist: /dev/cdrom,/dev/cd?c /dev/acd?c 132 /dev/matcd?c /dev/mcd?c /dev/scd?c */ 133 static char *checklist[] = { 134 "cdrom", "?0 cd?", "?0 acd?", "?0 matcd?", "?0 mcd?", "?0 scd?",NULL 135 }; 136 char *SDLcdrom; 137 int i, j, exists; 138 char drive[32]; 139 struct stat stbuf; 140 141 /* Fill in our driver capabilities */ 142 SDL_CDcaps.Name = SDL_SYS_CDName; 143 SDL_CDcaps.Open = SDL_SYS_CDOpen; 144 SDL_CDcaps.GetTOC = SDL_SYS_CDGetTOC; 145 SDL_CDcaps.Status = SDL_SYS_CDStatus; 146 SDL_CDcaps.Play = SDL_SYS_CDPlay; 147 SDL_CDcaps.Pause = SDL_SYS_CDPause; 148 SDL_CDcaps.Resume = SDL_SYS_CDResume; 149 SDL_CDcaps.Stop = SDL_SYS_CDStop; 150 SDL_CDcaps.Eject = SDL_SYS_CDEject; 151 SDL_CDcaps.Close = SDL_SYS_CDClose; 152 153 /* Look in the environment for our CD-ROM drive list */ 154 SDLcdrom = SDL_getenv("SDL_CDROM"); /* ':' separated list of devices */ 155 if ( SDLcdrom != NULL ) { 156 char *cdpath, *delim; 157 size_t len = SDL_strlen(SDLcdrom)+1; 158 cdpath = SDL_stack_alloc(char, len); 159 if ( cdpath != NULL ) { 160 SDL_strlcpy(cdpath, SDLcdrom, len); 161 SDLcdrom = cdpath; 162 do { 163 delim = SDL_strchr(SDLcdrom, ':'); 164 if ( delim ) { 165 *delim++ = '\0'; 166 } 167 if ( CheckDrive(SDLcdrom, &stbuf) > 0 ) { 168 AddDrive(SDLcdrom, &stbuf); 169 } 170 if ( delim ) { 171 SDLcdrom = delim; 172 } else { 173 SDLcdrom = NULL; 174 } 175 } while ( SDLcdrom ); 176 SDL_stack_free(cdpath); 177 } 178 179 /* If we found our drives, there's nothing left to do */ 180 if ( SDL_numcds > 0 ) { 181 return(0); 182 } 183 } 184 185 /* Scan the system for CD-ROM drives */ 186 for ( i=0; checklist[i]; ++i ) { 187 if ( checklist[i][0] == '?' ) { 188 char *insert; 189 exists = 1; 190 for ( j=checklist[i][1]; exists; ++j ) { 191 SDL_snprintf(drive, SDL_arraysize(drive), "/dev/%sc", &checklist[i][3]); 192 insert = SDL_strchr(drive, '?'); 193 if ( insert != NULL ) { 194 *insert = j; 195 } 196 switch (CheckDrive(drive, &stbuf)) { 197 /* Drive exists and is a CD-ROM */ 198 case 1: 199 AddDrive(drive, &stbuf); 200 break; 201 /* Drive exists, but isn't a CD-ROM */ 202 case 0: 203 break; 204 /* Drive doesn't exist */ 205 case -1: 206 exists = 0; 207 break; 208 } 209 } 210 } else { 211 SDL_snprintf(drive, SDL_arraysize(drive), "/dev/%s", checklist[i]); 212 if ( CheckDrive(drive, &stbuf) > 0 ) { 213 AddDrive(drive, &stbuf); 214 } 215 } 216 } 217 return(0); 218 } 219 220 /* General ioctl() CD-ROM command function */ 221 static int SDL_SYS_CDioctl(int id, int command, void *arg) 222 { 223 int retval; 224 225 retval = ioctl(id, command, arg); 226 if ( retval < 0 ) { 227 SDL_SetError("ioctl() error: %s", strerror(errno)); 228 } 229 return(retval); 230 } 231 232 static const char *SDL_SYS_CDName(int drive) 233 { 234 return(SDL_cdlist[drive]); 235 } 236 237 static int SDL_SYS_CDOpen(int drive) 238 { 239 return(open(SDL_cdlist[drive], (O_RDONLY|O_EXCL|O_NONBLOCK), 0)); 240 } 241 242 static int SDL_SYS_CDGetTOC(SDL_CD *cdrom) 243 { 244 struct ioc_toc_header toc; 245 int i, okay; 246 struct ioc_read_toc_entry entry; 247 struct cd_toc_entry data; 248 249 okay = 0; 250 if ( SDL_SYS_CDioctl(cdrom->id, CDIOREADTOCHEADER, &toc) == 0 ) { 251 cdrom->numtracks = toc.ending_track-toc.starting_track+1; 252 if ( cdrom->numtracks > SDL_MAX_TRACKS ) { 253 cdrom->numtracks = SDL_MAX_TRACKS; 254 } 255 /* Read all the track TOC entries */ 256 for ( i=0; i<=cdrom->numtracks; ++i ) { 257 if ( i == cdrom->numtracks ) { 258 cdrom->track[i].id = 0xAA; /* CDROM_LEADOUT */ 259 } else { 260 cdrom->track[i].id = toc.starting_track+i; 261 } 262 entry.starting_track = cdrom->track[i].id; 263 entry.address_format = CD_MSF_FORMAT; 264 entry.data_len = sizeof(data); 265 entry.data = &data; 266 if ( SDL_SYS_CDioctl(cdrom->id, CDIOREADTOCENTRYS, 267 &entry) < 0 ) { 268 break; 269 } else { 270 cdrom->track[i].type = data.control; 271 cdrom->track[i].offset = MSF_TO_FRAMES( 272 data.addr.msf.minute, 273 data.addr.msf.second, 274 data.addr.msf.frame); 275 cdrom->track[i].length = 0; 276 if ( i > 0 ) { 277 cdrom->track[i-1].length = 278 cdrom->track[i].offset- 279 cdrom->track[i-1].offset; 280 } 281 } 282 } 283 if ( i == (cdrom->numtracks+1) ) { 284 okay = 1; 285 } 286 } 287 return(okay ? 0 : -1); 288 } 289 290 /* Get CD-ROM status */ 291 static CDstatus SDL_SYS_CDStatus(SDL_CD *cdrom, int *position) 292 { 293 CDstatus status; 294 struct ioc_toc_header toc; 295 struct ioc_read_subchannel info; 296 struct cd_sub_channel_info data; 297 298 info.address_format = CD_MSF_FORMAT; 299 info.data_format = CD_CURRENT_POSITION; 300 info.track = 0; 301 info.data_len = sizeof(data); 302 info.data = &data; 303 if ( ioctl(cdrom->id, CDIOCREADSUBCHANNEL, &info) < 0 ) { 304 if ( ERRNO_TRAYEMPTY(errno) ) { 305 status = CD_TRAYEMPTY; 306 } else { 307 status = CD_ERROR; 308 } 309 } else { 310 switch (data.header.audio_status) { 311 case CD_AS_AUDIO_INVALID: 312 case CD_AS_NO_STATUS: 313 /* Try to determine if there's a CD available */ 314 if (ioctl(cdrom->id,CDIOREADTOCHEADER,&toc)==0) 315 status = CD_STOPPED; 316 else 317 status = CD_TRAYEMPTY; 318 break; 319 case CD_AS_PLAY_COMPLETED: 320 status = CD_STOPPED; 321 break; 322 case CD_AS_PLAY_IN_PROGRESS: 323 status = CD_PLAYING; 324 break; 325 case CD_AS_PLAY_PAUSED: 326 status = CD_PAUSED; 327 break; 328 default: 329 status = CD_ERROR; 330 break; 331 } 332 } 333 if ( position ) { 334 if ( status == CD_PLAYING || (status == CD_PAUSED) ) { 335 *position = MSF_TO_FRAMES( 336 data.what.position.absaddr.msf.minute, 337 data.what.position.absaddr.msf.second, 338 data.what.position.absaddr.msf.frame); 339 } else { 340 *position = 0; 341 } 342 } 343 return(status); 344 } 345 346 /* Start play */ 347 static int SDL_SYS_CDPlay(SDL_CD *cdrom, int start, int length) 348 { 349 struct ioc_play_msf playtime; 350 351 FRAMES_TO_MSF(start, 352 &playtime.start_m, &playtime.start_s, &playtime.start_f); 353 FRAMES_TO_MSF(start+length, 354 &playtime.end_m, &playtime.end_s, &playtime.end_f); 355 #ifdef DEBUG_CDROM 356 fprintf(stderr, "Trying to play from %d:%d:%d to %d:%d:%d\n", 357 playtime.cdmsf_min0, playtime.cdmsf_sec0, playtime.cdmsf_frame0, 358 playtime.cdmsf_min1, playtime.cdmsf_sec1, playtime.cdmsf_frame1); 359 #endif 360 ioctl(cdrom->id, CDIOCSTART, 0); 361 return(SDL_SYS_CDioctl(cdrom->id, CDIOCPLAYMSF, &playtime)); 362 } 363 364 /* Pause play */ 365 static int SDL_SYS_CDPause(SDL_CD *cdrom) 366 { 367 return(SDL_SYS_CDioctl(cdrom->id, CDIOCPAUSE, 0)); 368 } 369 370 /* Resume play */ 371 static int SDL_SYS_CDResume(SDL_CD *cdrom) 372 { 373 return(SDL_SYS_CDioctl(cdrom->id, CDIOCRESUME, 0)); 374 } 375 376 /* Stop play */ 377 static int SDL_SYS_CDStop(SDL_CD *cdrom) 378 { 379 return(SDL_SYS_CDioctl(cdrom->id, CDIOCSTOP, 0)); 380 } 381 382 /* Eject the CD-ROM */ 383 static int SDL_SYS_CDEject(SDL_CD *cdrom) 384 { 385 return(SDL_SYS_CDioctl(cdrom->id, CDIOCEJECT, 0)); 386 } 387 388 /* Close the CD-ROM handle */ 389 static void SDL_SYS_CDClose(SDL_CD *cdrom) 390 { 391 close(cdrom->id); 392 } 393 394 void SDL_SYS_CDQuit(void) 395 { 396 int i; 397 398 if ( SDL_numcds > 0 ) { 399 for ( i=0; i<SDL_numcds; ++i ) { 400 SDL_free(SDL_cdlist[i]); 401 } 402 SDL_numcds = 0; 403 } 404 } 405 406 #endif /* SDL_CDROM_FREEBSD */ 407