Home | History | Annotate | Download | only in bsdi
      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