2 * $Id: plat_linux.c,v 1.8 1999/06/17 06:48:03 dirk Exp $
4 * This file is part of WorkMan, the civilized CD player library
5 * (c) 1991-1997 by Steven Grimm (original author)
6 * (c) by Dirk Försterling (current 'author' = maintainer)
7 * The maintainer can be contacted by his e-mail address:
8 * milliByte@DeathsDoor.com
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
20 * You should have received a copy of the GNU Library General Public
21 * License along with this library; if not, write to the Free
22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 * Linux-specific drive control routines. Very similar to the Sun module.
29 /* Id for ident command */
30 static char plat_linux_id
[] = "$Id: plat_linux.c,v 1.8 1999/06/17 06:48:03 dirk Exp $";
39 #include <sys/types.h>
40 #include <sys/param.h>
41 #include <sys/socket.h>
45 #include "include/wm_config.h"
47 #if defined(BSD_MOUNTTEST)
51 * this is for glibc 2.x which defines ust structure in
55 #include <sys/ustat.h>
60 #include <sys/ioctl.h>
61 #include <linux/cdrom.h>
63 #include "include/wm_cdda.h"
64 #include "include/wm_struct.h"
65 #include "include/wm_platform.h"
66 #include "include/wm_cdrom.h"
67 #include "include/wm_scsi.h"
68 #include "include/wm_helpers.h"
71 #include <linux/soundcard.h>
72 #define CD_CHANNEL SOUND_MIXER_CD
75 #define max(a,b) ((a) > (b) ? (a) : (b))
77 #define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM
79 #ifdef LINUX_SCSI_PASSTHROUGH
80 /* this is from <scsi/scsi_ioctl.h> */
81 # define SCSI_IOCTL_SEND_COMMAND 1
89 char mixer_dev_name
[20] = "/dev/mixer";
91 extern char *cd_device
, *cddaslave_path
;
96 * Wait for an acknowledgement from the CDDA slave.
101 #if defined(BUILD_CDDA) && defined(WMCDDA_DONE) /* { */
102 struct cdda_block blk
;
105 if (read(fd
, &blk
, sizeof(blk
)) <= 0)
107 while (blk
.status
!= WMCDDA_ACK
);
113 /*--------------------------------------------------------*
114 * Initialize the drive. A no-op for the generic driver.
115 *--------------------------------------------------------*/
117 gen_init( struct wm_drive
*d
)
121 * Open and use the mixer device to set volume
123 if( ( mixer
= open( mixer_dev_name
, O_RDWR
, 0 ) ) == -1 )
125 perror( mixer_dev_name
);
133 * Try to initialize the CDDA slave. Returns 0 on error.
136 cdda_init( struct wm_drive
*d
)
138 #if defined(BUILD_CDDA) && defined(WMCDDA_DONE) /* { */
144 fprintf( stderr
, "slave okay\n" );
146 if (socketpair(AF_UNIX
, SOCK_STREAM
, 0, slavefds
))
148 perror("socketpair");
152 fprintf( stderr
, "going to fork\n" );
156 dup2(slavefds
[1], 1);
157 dup2(slavefds
[1], 0);
160 /* Try the default path first. */
161 execl(cddaslave_path
, cddaslave_path
, cd_device
, NULL
);
162 /* Search $PATH if that didn't work. */
163 execlp("cddaslave", "cddaslave", cd_device
, NULL
);
164 perror(cddaslave_path
);
175 cdda_slave
= slavefds
[0];
177 if (!get_ack(cdda_slave
))
179 fprintf( stderr
, "get_ack failed\n" );
187 #else /* BUILD_CDDA } { */
189 * If we're not building CDDA support, don't even bother trying.
196 * Turn off the CDDA slave.
199 cdda_kill( struct wm_drive
*d
)
203 write(cdda_slave
, "Q", 1);
211 /*-------------------------------------*
212 * Get the number of tracks on the CD.
213 *-------------------------------------*/
215 gen_get_trackcount(struct wm_drive
*d
, int *tracks
)
217 struct cdrom_tochdr hdr
;
219 if (ioctl(d
->fd
, CDROMREADTOCHDR
, &hdr
))
222 *tracks
= hdr
.cdth_trk1
;
226 /*---------------------------------------------------------*
227 * Get the start time and mode (data or audio) of a track.
228 *---------------------------------------------------------*/
230 gen_get_trackinfo(struct wm_drive
*d
, int track
, int *data
, int *startframe
)
232 struct cdrom_tocentry entry
;
234 entry
.cdte_track
= track
;
235 entry
.cdte_format
= CDROM_MSF
;
237 if (ioctl(d
->fd
, CDROMREADTOCENTRY
, &entry
))
240 *startframe
= entry
.cdte_addr
.msf
.minute
* 60 * 75 +
241 entry
.cdte_addr
.msf
.second
* 75 +
242 entry
.cdte_addr
.msf
.frame
;
243 *data
= entry
.cdte_ctrl
& CDROM_DATA_TRACK
? 1 : 0;
248 /*-------------------------------------*
249 * Get the number of frames on the CD.
250 *-------------------------------------*/
252 gen_get_cdlen(struct wm_drive
*d
, int *frames
)
256 return (gen_get_trackinfo(d
, CDROM_LEADOUT
, &tmp
, frames
));
260 /* Alarm signal handler.
261 static void do_nothing(int x) { x++; }
264 /*--------------------------------------------------------------------------*
265 * Get the current status of the drive: the current play mode, the absolute
266 * position from start of disc (in frames), and the current track and index
267 * numbers if the CD is playing or paused.
268 *--------------------------------------------------------------------------*/
270 gen_get_drive_status( struct wm_drive
*d
, enum wm_cd_modes oldmode
,
271 enum wm_cd_modes
*mode
, int *pos
, int *track
, int *index
)
273 struct cdrom_subchnl sc
;
276 static int prevpos
= 0;
279 /* If we can't get status, the CD is ejected, so default to that. */
280 *mode
= WM_CDM_EJECTED
;
282 /* Is the device open? */
285 switch (wmcd_open(d
))
296 sc
.cdsc_format
= CDROM_MSF
;
298 if (ioctl(d
->fd
, CDROMSUBCHNL
, &sc
))
301 switch (sc
.cdsc_audiostatus
) {
302 case CDROM_AUDIO_PLAY
:
303 *mode
= WM_CDM_PLAYING
;
304 *track
= sc
.cdsc_trk
;
305 *index
= sc
.cdsc_ind
;
306 *pos
= sc
.cdsc_absaddr
.msf
.minute
* 60 * 75 +
307 sc
.cdsc_absaddr
.msf
.second
* 75 +
308 sc
.cdsc_absaddr
.msf
.frame
;
312 if( (prevpos
- *pos
) < 75 )
314 *mode
= WM_CDM_TRACK_DONE
;
322 case CDROM_AUDIO_PAUSED
:
323 case CDROM_AUDIO_NO_STATUS
:
324 case CDROM_AUDIO_INVALID
: /**/
325 if (oldmode
== WM_CDM_PLAYING
|| oldmode
== WM_CDM_PAUSED
)
327 *mode
= WM_CDM_PAUSED
;
328 *track
= sc
.cdsc_trk
;
329 *index
= sc
.cdsc_ind
;
330 *pos
= sc
.cdsc_absaddr
.msf
.minute
* 60 * 75 +
331 sc
.cdsc_absaddr
.msf
.second
* 75 +
332 sc
.cdsc_absaddr
.msf
.frame
;
335 *mode
= WM_CDM_STOPPED
;
338 case CDROM_AUDIO_COMPLETED
:
339 *mode
= WM_CDM_TRACK_DONE
; /* waiting for next track. */
343 *mode
= WM_CDM_UNKNOWN
;
350 /*------------------------------------------------------------------------*
351 * scale_volume(vol, max)
353 * Return a volume value suitable for passing to the CD-ROM drive. "vol"
354 * is a volume slider setting; "max" is the slider's maximum value.
355 * This is not used if sound card support is enabled.
357 *------------------------------------------------------------------------*/
360 scale_volume( int vol
, int max
)
363 return ((max
* max
- (max
- vol
) * (max
- vol
)) *
364 (max_volume
- min_volume
) / (max
* max
) + min_volume
);
366 return ((vol
* (max_volume
- min_volume
)) / max
+ min_volume
);
368 } /* scale_volume() */
371 /*---------------------------------------------------------------------*
372 * Set the volume level for the left and right channels. Their values
373 * range from 0 to 100.
374 *---------------------------------------------------------------------*/
376 gen_set_volume( struct wm_drive
*d
, int left
, int right
)
379 struct cdrom_volctrl v
;
385 left
= left
< 0 ? 0 : left
> 100 ? 100 : left
;
386 right
= right
< 0 ? : right
> 100 ? 100 : right
;
388 vol
= ( 0x007f & left
) | ( 0x7f00 & ( right
<< 8 ) );
389 if( ioctl( mixer
, MIXER_WRITE( CD_CHANNEL
), &vol
) == -1 )
391 perror( "MIXER_WRITE" );
397 /* Adjust the volume to make up for the CD-ROM drive's weirdness. */
398 left
= scale_volume(left
, 100);
399 right
= scale_volume(right
, 100);
401 v
.channel0
= v
.channel2
= left
< 0 ? 0 : left
> 255 ? 255 : left
;
402 v
.channel1
= v
.channel3
= right
< 0 ? 0 : right
> 255 ? 255 : right
;
404 return (ioctl(d
->fd
, CDROMVOLCTRL
, &v
));
415 return (ioctl(d
->fd
, CDROMPAUSE
));
418 /*-------------------------------------------------*
419 * Resume playing the CD (assuming it was paused.)
420 *-------------------------------------------------*/
422 gen_resume(struct wm_drive
*d
)
424 return (ioctl(d
->fd
, CDROMRESUME
));
431 gen_stop(struct wm_drive
*d
)
433 return (ioctl(d
->fd
, CDROMSTOP
));
436 /*------------------------------------------------------------*
437 * Play the CD from one position to another (both in frames.)
438 *------------------------------------------------------------*/
440 gen_play(struct wm_drive
*d
, int start
, int end
)
442 struct cdrom_msf msf
;
444 msf
.cdmsf_min0
= start
/ (60*75);
445 msf
.cdmsf_sec0
= (start
% (60*75)) / 75;
446 msf
.cdmsf_frame0
= start
% 75;
447 msf
.cdmsf_min1
= end
/ (60*75);
448 msf
.cdmsf_sec1
= (end
% (60*75)) / 75;
449 msf
.cdmsf_frame1
= end
% 75;
452 if (ioctl(d
->fd
, CDROMSTART
))
455 if (ioctl(d
->fd
, CDROMPLAYMSF
, &msf
))
461 /*----------------------------------------*
462 * Eject the current CD, if there is one.
463 *----------------------------------------*/
465 gen_eject(struct wm_drive
*d
)
468 #if !defined(BSD_MOUNTTEST)
475 if (fstat(d
->fd
, &stbuf
) != 0)
478 /* Is this a mounted filesystem? */
479 #if !defined(BSD_MOUNTTEST)
480 if (ustat(stbuf
.st_rdev
, &ust
) == 0)
484 * This is the same test as in the WorkBone interface.
485 * I should eliminate it there, because there is no need
488 /* check if drive is mounted (from Mark Buckaway's cdplayer code) */
489 /* Changed it again (look at XPLAYCD from ???? */
490 /* It's better to check the device name rather than one device is */
491 /* mounted as iso9660. That prevents "no playing" if you have more*/
492 /* than one CD-ROM, and one of them is mounted, but it's not the */
494 if ((fp
= setmntent (MOUNTED
, "r")) == NULL
)
496 fprintf (stderr
, "Could not open %s: %s\n", MOUNTED
, strerror (errno
));
499 while ((mnt
= getmntent (fp
)) != NULL
)
501 if (strcmp (mnt
->mnt_fsname
, cd_device
) == 0)
503 fputs ("CDROM already mounted (according to mtab). Operation aborted.\n", stderr
);
509 #endif /* BSD_MOUNTTEST */
511 if (ioctl(d
->fd
, CDROMEJECT
))
514 * Things in "foobar_one" are left over from 1.4b3
515 * I put them here for further observation. In 1.4b3, however,
516 * that workaround didn't help at least for /dev/sbpcd
517 * (The tray closed just after ejecting because re-opening the
518 * device causes the tray to close)
519 *------------------*/
521 extern int intermittent_dev
523 * Some drives (drivers?) won't recognize a new CD if we leave the
526 if (intermittent_dev
)
536 /*----------------------------------------*
538 *----------------------------------------*/
540 int gen_closetray(struct wm_drive
*d
)
543 #ifdef CDROMCLOSETRAY
544 wm_lib_message(WM_MSG_LEVEL_DEBUG
|WM_MSG_CLASS
, "CDROMCLOSETRAY closing tray...\n");
545 if (ioctl(d
->fd
, CDROMCLOSETRAY
))
548 wm_lib_message(WM_MSG_LEVEL_DEBUG
|WM_MSG_CLASS
, "wmcd_reopen() closing tray...\n");
552 return(wmcd_reopen(d
));
556 #endif /* CDROMCLOSETRAY */
557 #else /* CAN_CLOSE */
558 /* Always succeed if the drive can't close. */
560 #endif /* CAN_CLOSE */
561 } /* gen_closetray() */
563 /*--------------------------------*
564 * Keep the CD open all the time.
565 * disabled, analogous to 1.4b3
566 *--------------------------------*
575 for (fd = 0; fd < 256; fd++)
581 if ((fd = open("/tmp/cd.lock", O_RDWR | O_CREAT, 0666)) < 0)
587 if (fcntl(fd, F_SETLK, &fl) < 0)
590 if (open(cd_device, 0) >= 0)
600 /*---------------------------------------------------------------------*
601 * Read the initial volume from the drive, if available. Each channel
602 * ranges from 0 to 100, with -1 indicating data not available.
603 *---------------------------------------------------------------------*/
605 gen_get_volume( struct wm_drive
*d
, int *left
, int *right
)
607 #if defined(BUILD_CDDA) && defined(WMCDDA_DONE) /* { */
608 struct cdda_block blk
;
612 write(cdda_slave
, "G", 1);
614 read(cdda_slave
, &blk
, sizeof(blk
));
616 *left
= *right
= (blk
.volume
* 100 + 254) / 255;
618 if (blk
.balance
< 110)
619 *right
= (((blk
.volume
* blk
.balance
+ 127) / 128) *
621 else if (blk
.balance
> 146)
622 *left
= (((blk
.volume
* (255 - blk
.balance
) +
623 127) / 128) * 100 + 254) / 255;
631 if( ioctl( mixer
, MIXER_READ( CD_CHANNEL
), &vol
) == -1 )
633 perror( "MIXER_READ" );
636 *right
= 0x007f & ( vol
>> 8 );
637 *left
= 0x007f & vol
;
640 /* Suns, HPs, Linux, NEWS can't read the volume; oh well */
648 #ifdef BUILD_CDDA /* { */
651 * Tell the CDDA slave to set the play direction.
654 gen_set_direction( int newdir
)
656 unsigned char buf
[2];
662 write(cdda_slave
, buf
, 2);
668 * Tell the CDDA slave to set the play speed.
671 gen_set_speed( int speed
)
673 unsigned char buf
[2];
679 write(cdda_slave
, buf
, 2);
685 * Tell the CDDA slave to set the loudness level.
688 gen_set_loudness( int loud
)
690 unsigned char buf
[2];
696 write(cdda_slave
, buf
, 2);
702 * Tell the CDDA slave to start (or stop) saving to a file.
705 gen_save( char *filename
)
709 if (filename
== NULL
|| filename
[0] == '\0')
712 len
= strlen(filename
);
713 write(cdda_slave
, "F", 1);
714 write(cdda_slave
, &len
, sizeof(len
));
716 write(cdda_slave
, filename
, len
);
720 #endif /* BUILD_CDDA } */
722 /*---------------------------------------------*
723 * Send an arbitrary SCSI command to a device.
724 *---------------------------------------------*/
726 wm_scsi( struct wm_drive
*d
, unsigned char *cdb
, int cdblen
,
727 void *retbuf
, int retbuflen
, int getreply
)
729 #ifdef LINUX_SCSI_PASSTHROUGH
734 cmdsize
= 2 * sizeof(int);
737 if (getreply
) cmdsize
+= max(cdblen
, retbuflen
);
738 else cmdsize
+= (cdblen
+ retbuflen
);
740 else cmdsize
+= cdblen
;
742 cmd
= malloc(cmdsize
);
746 ((int*)cmd
)[0] = cdblen
+ ((retbuf
&& !getreply
) ? retbuflen
: 0);
747 ((int*)cmd
)[1] = ((retbuf
&& getreply
) ? retbuflen
: 0);
749 memcpy(cmd
+ 2*sizeof(int), cdb
, cdblen
);
750 if (retbuf
&& !getreply
)
751 memcpy(cmd
+ 2*sizeof(int) + cdblen
, retbuf
, retbuflen
);
753 if (ioctl(d
->fd
, SCSI_IOCTL_SEND_COMMAND
, cmd
))
755 wm_lib_message(WM_MSG_LEVEL_DEBUG
|WM_MSG_CLASS
, "%s: ioctl() failure\n", __FILE__
);
756 wm_lib_message(WM_MSG_LEVEL_DEBUG
|WM_MSG_CLASS
, "command buffer is:\n");
757 wm_lib_message(WM_MSG_LEVEL_DEBUG
|WM_MSG_CLASS
, "%02x %02x %02x %02x %02x %02x\n",
758 cmd
[0], cmd
[1], cmd
[2], cmd
[3], cmd
[4], cmd
[5]);
763 if (retbuf
&& getreply
)
764 memcpy(retbuf
, cmd
+ 2*sizeof(int), retbuflen
);
769 #else /* Linux SCSI passthrough*/
774 /*---------------------------------------------------------------------------*
775 * Open the CD device and figure out what kind of drive is attached.
776 *---------------------------------------------------------------------------*/
778 wmcd_open( struct wm_drive
*d
)
781 static int warned
= 0;
783 char vendor
[32], model
[32], rev
[32];
785 if (cd_device
== NULL
)
786 cd_device
= DEFAULT_CD_DEVICE
;
788 if (d
->fd
>= 0) /* Device already open? */
790 wm_lib_message(WM_MSG_LEVEL_DEBUG
|WM_MSG_CLASS
, "wmcd_open(): [device is open (fd=%d)]\n", d
->fd
);
794 d
->fd
= open(cd_device
, O_RDONLY
| O_NONBLOCK
);
802 "As root, please run\n\nchmod 666 %s\n\n%s\n", cd_device
,
803 "to give yourself permission to access the CD-ROM device.");
807 /* Hack proposed by Carey Evans, introduced by Debian maintainer :
808 * treat EIO like ENXIO since some Linux drives do never return ENXIO
810 else if ((errno
!= ENXIO
) && (errno
!= EIO
) && (errno
!= ENOMEDIUM
))
816 /* No CD in drive. */
823 fprintf(stderr
, "Thank you.\n");
826 /* Now fill in the relevant parts of the wm_drive structure. */
829 #ifdef LINUX_SCSI_PASSTHROUGH
830 /* Can we figure out the drive type? */
831 wm_scsi_get_drive_type(d
, vendor
, model
, rev
);
833 *d
= *(find_drive_struct(vendor
, model
, rev
));
834 wm_drive_settype(vendor
, model
, rev
);
842 * Re-Open the device if it is open.
845 wmcd_reopen( struct wm_drive
*d
)
850 wm_lib_message(WM_MSG_LEVEL_DEBUG
|WM_MSG_CLASS
, "wmcd_reopen ");
851 if (d
->fd
>= 0) /* Device really open? */
853 wm_lib_message(WM_MSG_LEVEL_DEBUG
|WM_MSG_CLASS
, "closes the device and ");
854 status
= close( d
->fd
); /* close it! */
855 /* we know, that the file is closed, do we? */
859 wm_lib_message(WM_MSG_LEVEL_DEBUG
|WM_MSG_CLASS
, "calls wmcd_open()\n");
860 status
= wmcd_open( d
); /* open it as usual */
862 } while ( status
!= 0 );
864 } /* wmcd_reopen() */