File indexing completed on 2024-04-21 04:54:22

0001 /*
0002  * This file is part of WorkMan, the civilized CD player library
0003  * Copyright (C) 1991-1997 by Steven Grimm <koreth@midwinter.com>
0004  * Copyright (C) by Dirk Försterling <milliByte@DeathsDoor.com>
0005  *
0006  * This library is free software; you can redistribute it and/or
0007  * modify it under the terms of the GNU Library General Public
0008  * License as published by the Free Software Foundation; either
0009  * version 2 of the License, or (at your option) any later version.
0010  *
0011  * This library is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014  * Library General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU Library General Public
0017  * License along with this library; if not, write to the Free
0018  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0019  *
0020  *
0021  * plat_freebsd.c
0022  *
0023  * FreeBSD-specific drive control routines.
0024  *
0025  * Todd Pfaff, 3/20/94
0026  *
0027  */
0028 
0029 #if defined(__FreeBSD__) || defined(__FreeBSD) || defined(__NetBSD__) || defined (__NetBSD) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
0030 
0031 
0032 #include <errno.h>
0033 #include <stdio.h>
0034 #include <fcntl.h>
0035 #include <string.h>
0036 #include <unistd.h>
0037 #include <sys/types.h>
0038 #include <sys/param.h>
0039 #include <sys/mount.h>
0040 #include <sys/stat.h>
0041 
0042 #include "include/wm_config.h"
0043 
0044 #include <sys/time.h>
0045 #include <sys/ioctl.h>
0046 #include <sys/cdio.h>
0047 
0048 #if defined(__OpenBSD__)
0049 # define MSF_MINUTES 1
0050 # define MSF_SECONDS 2
0051 # define MSF_FRAMES 3
0052 # include <sys/scsiio.h>
0053 # include "/sys/scsi/scsi_all.h"
0054 # include "/sys/scsi/scsi_cd.h"
0055 
0056 #elif defined(__NetBSD__)
0057 
0058 # include <sys/scsiio.h>
0059 # include <dev/scsipi/scsipi_cd.h>
0060 
0061 /* Fix https://bugs.kde.org/show_bug.cgi?id=154718 by wrapping calls to removed
0062  * functions with their replacements in NetBSD 3.0
0063  */
0064 # if __NetBSD_Version__ >= 299000900     /* 2.99.9 */
0065 #  include <sys/statvfs.h>
0066 #  define statfs statvfs
0067 #  define fstatfs fstatvfs
0068 # endif
0069 
0070 #else /* Not OpenBSD, not NetBSD, therefore (probably) FreeBSD */
0071 # define LEFT_PORT 0
0072 # define RIGHT_PORT 1
0073 # if defined (__FreeBSD__)
0074 # if (__FreeBSD_version < 300000) && (__FreeBSD_kernel_version < 300000)
0075 #  include <scsi.h>
0076 # endif
0077 # endif
0078 
0079 #endif /* if defined(__OpenBSD__) */
0080 
0081 #include "include/wm_struct.h"
0082 #include "include/wm_platform.h"
0083 #include "include/wm_cdrom.h"
0084 #include "include/wm_scsi.h"
0085 #include "include/wm_helpers.h"
0086 #include "include/wm_cdtext.h"
0087 
0088 #define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM
0089 
0090 void *malloc();
0091 
0092 int min_volume = 10;
0093 int max_volume = 255;
0094 
0095 
0096 /*--------------------------------------------------------*
0097  * Initialize the drive.  A no-op for the generic driver.
0098  *--------------------------------------------------------*/
0099 int
0100 gen_init(struct wm_drive *d)
0101 {
0102     return 0;
0103 } /* gen_init() */
0104 
0105 
0106 /*-------------------------------------------------------------------*
0107  * Open the CD device and figure out what kind of drive is attached.
0108  *-------------------------------------------------------------------*/
0109 int
0110 gen_open(struct wm_drive *d)
0111 {
0112     if (d->fd > -1) {
0113         wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "gen_open(): [device is open (fd=%d)]\n", d->fd);
0114         return 0;
0115     }
0116 
0117     d->fd = open(d->cd_device, 0);
0118     if (d->fd < 0) {
0119         if (errno == EACCES)
0120             return -EACCES;
0121 
0122         /* No CD in drive. */
0123         return 1;
0124     }
0125 
0126     return 0;
0127 } /* gen_open() */
0128 
0129 /*---------------------------------------------*
0130  * Send an arbitrary SCSI command to a device.
0131  *
0132  *---------------------------------------------*/
0133 int
0134 gen_scsi(struct wm_drive *d, unsigned char *cdb, int cdblen,
0135     void *retbuf, int retbuflen, int getreply)
0136 {
0137     return -1;
0138 } /* wm_scsi() */
0139 
0140 int
0141 gen_close( struct wm_drive *d )
0142 {
0143     if(d->fd > -1) {
0144         wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closing the device\n");
0145         close(d->fd);
0146         d->fd = -1;
0147     }
0148     return 0;
0149 }
0150 
0151 /*--------------------------------------------------------------------------*
0152  * Get the current status of the drive: the current play mode, the absolute
0153  * position from start of disc (in frames), and the current track and index
0154  * numbers if the CD is playing or paused.
0155  *--------------------------------------------------------------------------*/
0156 int
0157 gen_get_drive_status(struct wm_drive *d, int oldmode,
0158                      int *mode, int *pos, int *track, int *index)
0159 {
0160     struct ioc_read_subchannel sc;
0161     struct cd_sub_channel_info scd;
0162 
0163     /* If we can't get status, the CD is ejected, so default to that. */
0164     *mode = WM_CDM_EJECTED;
0165 
0166     sc.address_format   = CD_MSF_FORMAT;
0167     sc.data_format      = CD_CURRENT_POSITION;
0168     sc.track        = 0;
0169     sc.data_len     = sizeof(scd);
0170     sc.data         = (struct cd_sub_channel_info *)&scd;
0171 
0172     /* Is the device open? */
0173     if (d->fd < 0) {
0174         switch (d->proto.open(d)) {
0175         case -1: /* error */
0176             return -1;
0177 
0178         case 1: /* retry */
0179             return 0;
0180         }
0181     }
0182 
0183     if (ioctl(d->fd, CDIOCREADSUBCHANNEL, &sc)) {
0184         /*
0185         * #ifdef __NetBSD__
0186         *
0187         * Denis Bourez <denis@rsn.fdn.fr> told me, that closing the
0188         * device is mandatory for FreeBSD, too.
0189         */
0190         /* we need to release the device so the kernel will notice
0191         reloaded media */
0192         d->proto.close(d);
0193         /*
0194         * #endif
0195         */
0196         return 0;   /* ejected */
0197     }
0198 
0199     switch (scd.header.audio_status) {
0200     case CD_AS_PLAY_IN_PROGRESS:
0201         *mode = WM_CDM_PLAYING;
0202         break;
0203 
0204     case CD_AS_PLAY_PAUSED:
0205         if (oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_PAUSED)
0206             *mode = WM_CDM_PAUSED;
0207         else
0208             *mode = WM_CDM_STOPPED;
0209         break;
0210 
0211     case CD_AS_PLAY_COMPLETED:
0212         *mode = WM_CDM_TRACK_DONE; /* waiting for next track. */
0213         break;
0214 
0215     case CD_AS_NO_STATUS:
0216     case 0:
0217         *mode = WM_CDM_STOPPED;
0218         break;
0219     }
0220 
0221     switch(*mode) {
0222     case WM_CDM_PLAYING:
0223     case WM_CDM_PAUSED:
0224         *pos = scd.what.position.absaddr.msf.minute * 60 * 75 +
0225             scd.what.position.absaddr.msf.second * 75 +
0226             scd.what.position.absaddr.msf.frame;
0227         *track = scd.what.position.track_number;
0228         *index = scd.what.position.index_number;
0229         break;      
0230     }
0231 
0232     return 0;
0233 } /* gen_get_drive_status() */
0234 
0235 
0236 /*-------------------------------------*
0237  * Get the number of tracks on the CD.
0238  *-------------------------------------*/
0239 int
0240 gen_get_trackcount(struct wm_drive *d, int *tracks)
0241 {
0242     struct ioc_toc_header hdr;
0243 
0244     if (ioctl(d->fd, CDIOREADTOCHEADER, &hdr) == -1)
0245         return -1;
0246 
0247     *tracks = hdr.ending_track - hdr.starting_track + 1;
0248 
0249     return 0;
0250 } /* gen_get_trackcount() */
0251 
0252 /*-----------------------------------------------------------------------*
0253  * Get the start time and mode (data or audio) of a track.
0254  *
0255  * XXX - this should get cached, but that means keeping track of ejects.
0256  *-----------------------------------------------------------------------*/
0257 int
0258 gen_get_trackinfo(struct wm_drive *d, int track, int *data, int *startframe)
0259 {
0260     struct ioc_read_toc_entry toc;
0261     struct cd_toc_entry toc_buffer;
0262 
0263     bzero((char *)&toc_buffer, sizeof(toc_buffer));
0264     toc.address_format = CD_MSF_FORMAT;
0265     toc.starting_track = track;
0266     toc.data_len = sizeof(toc_buffer);
0267     toc.data = &toc_buffer;
0268 
0269     if (ioctl(d->fd, CDIOREADTOCENTRYS, &toc))
0270         return -1;
0271 
0272     *data = ((toc_buffer.control & 0x4) != 0);
0273 
0274     *startframe = toc_buffer.addr.msf.minute*60*75 +
0275         toc_buffer.addr.msf.second * 75 +
0276         toc_buffer.addr.msf.frame;
0277 
0278     return 0;
0279 } /* gen_get_trackinfo() */
0280 
0281 /*-------------------------------------*
0282  * Get the number of frames on the CD.
0283  *-------------------------------------*/
0284 int
0285 gen_get_cdlen(struct wm_drive *d, int *frames)
0286 {
0287     int tmp;
0288     struct ioc_toc_header hdr;
0289     int status;
0290 
0291 #define LEADOUT 0xaa            /* see scsi.c.  what a hack! */
0292     return gen_get_trackinfo(d, LEADOUT, &tmp, frames);
0293 } /* gen_get_cdlen() */
0294 
0295 
0296 /*------------------------------------------------------------*
0297  * Play the CD from one position to another (both in frames.)
0298  *------------------------------------------------------------*/
0299 int
0300 gen_play(struct wm_drive *d, int start, int end)
0301 {
0302     struct ioc_play_msf msf;
0303 
0304     msf.start_m = start / (60*75);
0305     msf.start_s = (start % (60*75)) / 75;
0306     msf.start_f = start % 75;
0307     msf.end_m   = end / (60*75);
0308     msf.end_s   = (end % (60*75)) / 75;
0309     msf.end_f   = end % 75;
0310 
0311     if (ioctl(d->fd, CDIOCSTART))
0312         return -1;
0313 
0314     if (ioctl(d->fd, CDIOCPLAYMSF, &msf))
0315         return -2;
0316 
0317     return 0;
0318 } /* gen_play() */
0319 
0320 /*---------------*
0321  * Pause the CD.
0322  *---------------*/
0323 int
0324 gen_pause( struct wm_drive *d )
0325 {
0326     return ioctl(d->fd, CDIOCPAUSE);
0327 } /* gen_pause() */
0328 
0329 /*-------------------------------------------------*
0330  * Resume playing the CD (assuming it was paused.)
0331  *-------------------------------------------------*/
0332 int
0333 gen_resume( struct wm_drive *d )
0334 {
0335     return ioctl(d->fd, CDIOCRESUME);
0336 } /* gen_resume() */
0337 
0338 /*--------------*
0339  * Stop the CD.
0340  *--------------*/
0341 int
0342 gen_stop( struct wm_drive *d)
0343 {
0344     return ioctl(d->fd, CDIOCSTOP);
0345 } /* gen_stop() */
0346 
0347 /*----------------------------------------*
0348  * Eject the current CD, if there is one.
0349  *----------------------------------------*/
0350 int
0351 gen_eject( struct wm_drive *d )
0352 {
0353     /* On some systems, we can check to see if the CD is mounted. */
0354     struct stat stbuf;
0355     struct statfs buf;
0356     int rval;
0357 
0358     if (fstat(d->fd, &stbuf) != 0)
0359         return -2;
0360 
0361     /* Is this a mounted filesystem? */
0362     if (fstatfs(stbuf.st_rdev, &buf) == 0)
0363         return -3;
0364 
0365     rval = ioctl(d->fd, CDIOCALLOW);
0366 
0367     if (rval == 0)
0368         rval = ioctl(d->fd, CDIOCEJECT);
0369 
0370     if (rval == 0)
0371         rval = ioctl(d->fd, CDIOCPREVENT);
0372 
0373     d->proto.close(d);
0374 
0375     return rval;
0376 } /* gen_eject() */
0377 
0378 /*----------------------------------------*
0379  * Close the CD tray
0380  *----------------------------------------*/
0381 
0382 int
0383 gen_closetray(struct wm_drive *d)
0384 {
0385 #ifdef CAN_CLOSE
0386     if(!d->proto.close(d)) {
0387         return d->proto.open(d);
0388     } else {
0389         return -1;
0390     } 
0391 #else
0392     /* Always succeed if the drive can't close */
0393     return 0;
0394 #endif /* CAN_CLOSE */
0395 } /* gen_closetray() */
0396 
0397 
0398 /*---------------------------------------------------------------------------*
0399  * scale_volume(vol, max)
0400  *
0401  * Return a volume value suitable for passing to the CD-ROM drive.  "vol"
0402  * is a volume slider setting; "max" is the slider's maximum value.
0403  *
0404  * On Sun and DEC CD-ROM drives, the amount of sound coming out the jack
0405  * increases much faster toward the top end of the volume scale than it
0406  * does at the bottom.  To make up for this, we make the volume scale look
0407  * sort of logarithmic (actually an upside-down inverse square curve) so
0408  * that the volume value passed to the drive changes less and less as you
0409  * approach the maximum slider setting.  The actual formula looks like
0410  *
0411  *     (max^2 - (max - vol)^2) * (max_volume - min_volume)
0412  * v = --------------------------------------------------- + min_volume
0413  *                           max^2
0414  *
0415  * If your system's volume settings aren't broken in this way, something
0416  * like the following should work:
0417  *
0418  *  return ((vol * (max_volume - min_volume)) / max + min_volume);
0419  *---------------------------------------------------------------------------*/
0420 static int
0421 scale_volume(int vol, int max)
0422 {
0423     return ((vol * (max_volume - min_volume)) / max + min_volume);
0424 } /* scale_volume() */
0425 
0426 /*---------------------------------------------------------------------------*
0427  * unscale_volume(cd_vol, max)
0428  *
0429  * Given a value between min_volume and max_volume, return the volume slider
0430  * value needed to achieve that value.
0431  *
0432  * Rather than perform floating-point calculations to reverse the above
0433  * formula, we simply do a binary search of scale_volume()'s return values.
0434  *--------------------------------------------------------------------------*/
0435 static int
0436 unscale_volume( int cd_vol, int max )
0437 {
0438     int vol = 0, top = max, bot = 0, scaled;
0439 
0440     while (bot <= top) {
0441         vol = (top + bot) / 2;
0442         scaled = scale_volume(vol, max);
0443         if (cd_vol == scaled)
0444             break;
0445         if (cd_vol < scaled)
0446             top = vol - 1;
0447         else
0448             bot = vol + 1;
0449     }
0450 
0451     if (vol < 0)
0452         vol = 0;
0453     else if (vol > max)
0454         vol = max;
0455 
0456     return vol;
0457 } /* unscale_volume() */
0458 
0459 /*---------------------------------------------------------------------*
0460  * Set the volume level for the left and right channels.  Their values
0461  * range from 0 to 100.
0462  *---------------------------------------------------------------------*/
0463 int
0464 gen_set_volume(struct wm_drive *d, int left, int right)
0465 {
0466     struct ioc_vol vol;
0467 
0468     if (left < 0)   /* don't laugh, I saw this happen once! */
0469         left = 0;
0470     if (right < 0)
0471         right = 0;
0472 
0473     bzero((char *)&vol, sizeof(vol));
0474 
0475     vol.vol[LEFT_PORT] = left;
0476     vol.vol[RIGHT_PORT] = right;
0477 
0478     if (ioctl(d->fd, CDIOCSETVOL, &vol))
0479         return -1;
0480 
0481     return 0;
0482 } /* gen_set_volume() */
0483 
0484 /*---------------------------------------------------------------------*
0485  * Read the initial volume from the drive, if available.  Each channel
0486  * ranges from 0 to 100, with -1 indicating data not available.
0487  *---------------------------------------------------------------------*/
0488 int
0489 gen_get_volume( struct wm_drive *d, int *left, int *right )
0490 {
0491     struct ioc_vol vol;
0492 
0493     if (d->fd > -1) {
0494         bzero((char *)&vol, sizeof(vol));
0495 
0496         if (ioctl(d->fd, CDIOCGETVOL, &vol))
0497             *left = *right = -1;
0498         else {
0499             *left = vol.vol[LEFT_PORT];
0500             *right = vol.vol[RIGHT_PORT];
0501         }
0502     } else {
0503         *left = *right = -1;
0504     }
0505 
0506     return 0;
0507 } /* gen_get_volume() */
0508 
0509 int gen_scale_volume(int *left, int *right)
0510 {
0511     /* Adjust the volume to make up for the CD-ROM drive's weirdness. */
0512     *left = scale_volume(*left, 100);
0513     *right = scale_volume(*right, 100);
0514 
0515     return 0;
0516 }
0517 
0518 int gen_unscale_volume(int *left, int *right)
0519 {
0520     *left = unscale_volume(*left, 100);
0521     *right = unscale_volume(*right, 100);
0522 
0523     return 0;
0524 }
0525 
0526 #endif /* If FreeBSD or NetBSD */
0527