1 /*
2  * This file is part of WorkMan, the civilized CD player library
3  * Copyright (C) 1991-1997 by Steven Grimm <koreth@midwinter.com>
4  * Copyright (C) by Dirk Försterling <milliByte@DeathsDoor.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the Free
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  *
21  * ULTRIX 4.2 drive control routines.
22  */
23 
24 #if defined(ultrix) || defined(__ultrix)
25 
26 #include <errno.h>
27 #include <stdio.h>
28 #include <sys/types.h>
29 #include <fcntl.h>
30 #include <sys/param.h>
31 #include <sys/stat.h>
32 #include <sys/time.h>
33 #include <ustat.h>
34 #include <string.h>
35 #include <sys/rzdisk.h>
36 #include <sys/cdrom.h>
37 
38 #include "include/wm_config.h"
39 #include "include/wm_cdtext.h"
40 #include "include/wm_struct.h"
41 
42 #define WM_MSG_CLASS WM_MSG_CLASS_PLATFORM
43 
44 /*
45  *   This structure will be filled with the TOC header and all entries.
46  * Ultrix doesn't seem to allow getting single TOC entries.
47  *                              - Chris Ross (cross@eng.umd.edu)
48  */
49 struct cd_toc_header_and_entries
50 {
51 	struct cd_toc_header cdth;
52 	struct cd_toc_entry cdte[CDROM_MAX_TRACK+1];
53 };
54 
55 void *malloc();
56 char *strchr();
57 
58 int	min_volume = 128;
59 int	max_volume = 255;
60 
61 char * ultrix_cd_device = NULL;
62 /*
63  * fgetline()
64  *
65  *   Simulate fgets, but joining continued lines in the output of uerf.
66  */
67 
68 #define BUF_SIZE        85              /* Max length of a (real) line */
69 
70 char *
fgetline(FILE * fp)71 fgetline( FILE *fp )
72 {
73 	static char     *retval = NULL;
74 	static char     holdbuf[BUF_SIZE + 1];
75 	char            tmp[BUF_SIZE + 1];
76 	char            *stmp;
77 
78 	if (!retval) {
79 		retval = malloc(BUF_SIZE * 3);  /* 3 lines can be joined */
80 		if (!retval)
81 			return(NULL);
82 		else
83 			*retval = '\0';
84     }
85 
86 	if (*holdbuf) {
87 		strcpy(retval, holdbuf);
88 		retval[strlen(retval)-1] = '\0';
89 		memset(holdbuf, 0, BUF_SIZE+1);
90     }
91 
92 	while (fgets(tmp, BUF_SIZE, fp)) {
93 		stmp = tmp + strspn(tmp, " \t");
94 		if (*stmp == '_') { /* Continuation line */
95 	  		retval[strlen(retval)-1] = '\0';   /* Trim off C/R */
96 	  		strcat(retval, stmp+1);
97 		} else {
98 	  		if (*retval) {
99 				strcpy(holdbuf, tmp);
100 				holdbuf[strlen(holdbuf)-1] = -1;
101 				return retval;
102 			} else {             /* First line read, keep reading */
103 				strcat(retval, stmp);
104 				retval[strlen(retval)-1] = '\0';
105 			}
106 		}
107     }
108 
109 	return NULL;
110 } /* fgetline() */
111 
112 /*
113  * find_cdrom
114  *
115  * Determine the name of the CD-ROM device.
116  *
117  * Read through the boot records (via a call to uerf) and find the SCSI
118  * address of the CD-ROM.  If the "CDROM" environment variable is set,
119  * use that instead.
120  */
121 const char*
find_cdrom()122 find_cdrom()
123 {
124 	char *data;
125 	FILE *uerf;
126 	int	fds[2];
127 	int	pid;
128 	const char* device = NULL;
129 
130 	device = getenv("CDROM");
131 
132 	if (device != NULL) {
133 		if(strncmp("/dev/", device, 5) || strstr(device, "/../"))
134 			return NULL;
135     }
136 
137 	pipe(fds);
138 
139 	if ((pid = fork()) == 0) {
140 		close(fds[0]);
141 		dup2(fds[1], 1);
142 		execl("/etc/uerf", "uerf", "-R", "-r", "300", (void *)0);
143 		execl("/usr/sbin/uerf", "uerf", "-R", "-r", "300", (void *)0);
144 		return NULL; /* _exit(1); */
145     } else if (pid < 0) {
146 		perror("fork");
147 		return NULL; /* exit(1); */
148     }
149 
150 	close(fds[1]);
151 	uerf = fdopen(fds[0], "r");
152 
153 	while (data = fgetline(uerf)) {
154     	if (strstr(data, "RRD42")) {
155 			char	*device_p;
156 
157 			ultrix_cd_device = (char *)malloc(sizeof("/dev/rrz##c"));
158 			strcpy(ultrix_cd_device, "/dev/r");
159 			device_p = strstr(data, "rz");
160 			device_p[(int)(strchr(device_p, ' ') - device_p)] = '\0';
161 			strcat(ultrix_cd_device, device_p);
162 			strcat(ultrix_cd_device, "c");
163 			device = ultrix_cd_device;
164 			break;
165 		}
166 	}
167 
168 	fclose(uerf);
169 
170 	if (device == NULL) {
171 		fprintf(stderr, "No cdrom (RRD42) is installed on this system\n");
172 		return NULL; /* exit(1); */
173     }
174 
175 	kill(pid, 15);
176 	(void)wait((int *)NULL);
177 	return device;
178 } /* find_cdrom() */
179 
180 /*
181  * initialize the drive.  a no-op for the generic driver.
182  */
183 int
gen_init(struct wm_drive * d)184 gen_init( struct wm_drive *d )
185 {
186 	 return 0;
187 } /* gen_init() */
188 
189 
190 /*
191  * Open the CD device and figure out what kind of drive is attached.
192  */
193 int
gen_open(struct wm_drive * d)194 gen_open( struct wm_drive *d )
195 {
196 	if (d->fd >= 0) {		/* Device already open? */
197 		wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "gen_open(): [device is open (fd=%d)]\n", d->fd);
198 		return 0;
199     }
200 
201 	d->fd = open(d->cd_device, 0);
202 	if (d->fd < 0) {
203 		if (errno == EACCES)
204 			return -EACCES;
205 		else if (errno != EINTR)
206 			return -6;
207 
208 		/* No CD in drive. */
209 		return 1;
210     }
211 
212 	return 0;
213 } /* gen_open() */
214 
215 /*
216  * Send an arbitrary SCSI command to a device.
217  */
218 int
gen_scsi(struct wm_drive * d,unsigned char * cdb,int cdblen,void * retbuf,int retbuflen,int getreply)219 gen_scsi( struct wm_drive *d, unsigned char *cdb, int cdblen,
220 	 void *retbuf, int retbuflen, int getreply )
221 {
222 	/* ULTRIX doesn't have a SCSI passthrough interface, does it? */
223 	return -1;
224 } /* gen_scsi() */
225 
226 int
gen_close(struct wm_drive * d)227 gen_close( struct wm_drive *d )
228 {
229 	if(d->fd > -1) {
230 		wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "closing the device\n");
231 		close(d->fd);
232 		d->fd = -1;
233 	}
234 	return 0;
235 }
236 
237 /*
238  * Get the current status of the drive: the current play mode, the absolute
239  * position from start of disc (in frames), and the current track and index
240  * numbers if the CD is playing or paused.
241  */
242 int
gen_get_drive_status(struct wm_drive * d,int oldmode,int * mode,int * pos,int * track,int * index)243 gen_get_drive_status( struct wm_drive *d, int oldmode,
244 		      int *mode, int *pos, int *track, int *index)
245 {
246 	struct cd_sub_channel sc;
247 	struct cd_subc_channel_data scd;
248 
249 	/* If we can't get status, the CD is ejected, so default to that. */
250 	*mode = WM_CDM_EJECTED;
251 
252 	sc.sch_address_format	= CDROM_MSF_FORMAT;
253 	sc.sch_data_format	= CDROM_CURRENT_POSITION;
254 	sc.sch_track_number	= 0;
255 	sc.sch_alloc_length	= sizeof(scd);
256 	sc.sch_buffer		= (caddr_t)&scd;
257 
258 	/* Is the device open? */
259 	if (d->fd < 0) {
260 		switch (d->proto.open(d)) {
261 		case -1:	/* error */
262 	  		return -1;
263 
264 		case 1:		/* retry */
265 	  		return 0;
266 		}
267     }
268 
269 	if (ioctl(d->fd, CDROM_READ_SUBCHANNEL, &sc))
270 		return 0;	/* ejected */
271 
272 	switch (scd.scd_header.sh_audio_status) {
273     case AS_PLAY_IN_PROGRESS:
274 		*mode = WM_CDM_PLAYING;
275       	break;
276 
277     case AS_PLAY_PAUSED:
278 		if (oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_PAUSED)
279 			*mode = WM_CDM_PAUSED;
280 		else
281 			*mode = WM_CDM_STOPPED;
282       	break;
283 
284     case AS_PLAY_COMPLETED:
285 		*mode = WM_CDM_TRACK_DONE; /* waiting for next track. */
286 		break;
287 
288     case AS_NO_STATUS:
289 		*mode = WM_CDM_STOPPED;
290 		break;
291     }
292 
293 	switch(*mode) {
294 	case WM_CDM_PLAYING:
295 	case WM_CDM_PAUSED:
296       	*pos = scd.scd_position_data.scp_absaddr.msf.m_units * 60 * 75 +
297 			scd.scd_position_data.scp_absaddr.msf.s_units * 75 +
298 			scd.scd_position_data.scp_absaddr.msf.f_units;
299       	*track = scd.scd_position_data.scp_track_number;
300       	*index = scd.scd_position_data.scp_index_number;
301 		break;
302 	}
303 
304   	return 0;
305 } /* gen_get_drive_status() */
306 
307 /*
308  * Get the number of tracks on the CD.
309  */
310 int
gen_get_trackcount(struct wm_drive * d,int * tracks)311 gen_get_trackcount( struct wm_drive *d, int *tracks )
312 {
313 	struct cd_toc_header hdr;
314 
315   	if (ioctl(d->fd, CDROM_TOC_HEADER, &hdr))
316     	return (-1);
317 
318   	*tracks = hdr.th_ending_track;
319 
320   	return 0;
321 } /* gen_get_trackcount() */
322 
323 /*
324  * Get the start time and mode (data or audio) of a track.
325  *
326  * XXX - this should get cached, but that means keeping track of ejects.
327  */
328 int
gen_get_trackinfo(struct wm_drive * d,int track,int * data,int * startframe)329 gen_get_trackinfo(struct wm_drive *d, int track, int *data, int *startframe)
330 {
331 	struct cd_toc				toc;
332 	struct cd_toc_header			hdr;
333 	struct cd_toc_header_and_entries	toc_buffer;
334 
335 	if (ioctl(d->fd, CDROM_TOC_HEADER, &hdr))
336 	    return (-1);
337 
338 	bzero((char *)&toc_buffer, sizeof(toc_buffer));
339 	toc.toc_address_format = CDROM_MSF_FORMAT;
340 	toc.toc_starting_track = 0;
341 	toc.toc_alloc_length = (u_short)(((hdr.th_data_len1 << 8) +
342 		hdr.th_data_len0) & 0xfff) + 2;
343 	toc.toc_buffer = (caddr_t)&toc_buffer;
344 
345 	if (ioctl(d->fd, CDROM_TOC_ENTRYS, &toc))
346 		return (-1);
347 
348 	if (track == 0)
349     	track = hdr.th_ending_track + 1;
350 
351 	*data = (toc_buffer.cdte[track-1].te_control & CDROM_DATA_TRACK) ? 1:0;
352 	*startframe = toc_buffer.cdte[track - 1].te_absaddr.msf.m_units*60*75 +
353 		toc_buffer.cdte[track - 1].te_absaddr.msf.s_units * 75 +
354 		toc_buffer.cdte[track - 1].te_absaddr.msf.f_units;
355 
356   	return 0;
357 } /* gen_get_trackinfo() */
358 
359 /*
360  * Get the number of frames on the CD.
361  */
362 int
gen_get_cdlen(struct wm_drive * d,int * frames)363 gen_get_cdlen(struct wm_drive *d, int *frames)
364 {
365 	int tmp;
366 	return gen_get_trackinfo(d, 0, &tmp, frames);
367 } /* gen_get_cdlen() */
368 
369 /*
370  * Play the CD from one position to another (both in frames.)
371  */
372 int
gen_play(struct wm_drive * d,int start,int end)373 gen_play( struct wm_drive *d, int start, int end )
374 {
375   	struct cd_play_audio_msf	msf;
376 
377 	msf.msf_starting_M_unit	= start / (60*75);
378 	msf.msf_starting_S_unit	= (start % (60*75)) / 75;
379 	msf.msf_starting_F_unit	= start % 75;
380 	msf.msf_ending_M_unit	= end / (60*75);
381 	msf.msf_ending_S_unit	= (end % (60*75)) / 75;
382 	msf.msf_ending_F_unit	= end % 75;
383 
384 	if (ioctl(d->fd, SCSI_START_UNIT))
385 		return -1;
386 
387 	if (ioctl(d->fd, CDROM_PLAY_MSF, &msf))
388 	    return -2;
389 
390   	return 0;
391 } /* gen_play() */
392 
393 /*
394  * Pause the CD.
395  */
396 int
gen_pause(struct wm_drive * d)397 gen_pause( struct wm_drive *d )
398 {
399   	return ioctl(d->fd, CDROM_PAUSE_PLAY);
400 } /* gen_pause() */
401 
402 /*
403  * Resume playing the CD (assuming it was paused.)
404  */
405 int
gen_resume(struct wm_drive * d)406 gen_resume( struct wm_drive *d )
407 {
408   	return ioctl(d->fd, CDROM_RESUME_PLAY);
409 } /* gen_resume() */
410 
411 /*
412  * Stop the CD.
413  */
414 int
gen_stop(struct wm_drive * d)415 gen_stop( struct wm_drive *d )
416 {
417   return ioctl(d->fd, SCSI_STOP_UNIT);
418 } /* gen_stop() */
419 
420 /*
421  * Eject the current CD, if there is one.
422  */
423 int
gen_eject(struct wm_drive * d)424 gen_eject(struct wm_drive *d)
425 {
426 	/* On some systems, we can check to see if the CD is mounted. */
427 	struct stat	stbuf;
428 	struct ustat	ust;
429 
430 	if (fstat(d->fd, &stbuf) != 0)
431 	    return -2;
432 
433 	/* Is this a mounted filesystem? */
434 	if (ustat(stbuf.st_rdev, &ust) == 0)
435 	    return -3;
436 
437 	return ioctl(d->fd, CDROM_EJECT_CADDY);
438 } /* gen_eject() */
439 
440 /*----------------------------------------*
441  * Close the CD tray
442  *
443  * Please edit and send changes to
444  * milliByte@DeathsDoor.com
445  *----------------------------------------*/
446 
447 int
gen_closetray(struct wm_drive * d)448 gen_closetray(struct wm_drive *d)
449 {
450 	return -1;
451 } /* gen_closetray() */
452 
453 
454 /*
455  * scale_volume(vol, max)
456  *
457  * Return a volume value suitable for passing to the CD-ROM drive.  "vol"
458  * is a volume slider setting; "max" is the slider's maximum value.
459  *
460  * On Sun and DEC CD-ROM drives, the amount of sound coming out the jack
461  * increases much faster toward the top end of the volume scale than it
462  * does at the bottom.  To make up for this, we make the volume scale look
463  * sort of logarithmic (actually an upside-down inverse square curve) so
464  * that the volume value passed to the drive changes less and less as you
465  * approach the maximum slider setting.  The actual formula looks like
466  *
467  *     (max^2 - (max - vol)^2) * (max_volume - min_volume)
468  * v = --------------------------------------------------- + min_volume
469  *                           max^2
470  *
471  * If your system's volume settings aren't broken in this way, something
472  * like the following should work:
473  *
474  *	return ((vol * (max_volume - min_volume)) / max + min_volume);
475  */
scale_volume(int vol,int max)476 scale_volume( int vol, int max )
477 {
478 	return ((max * max - (max - vol) * (max - vol)) *
479 		(max_volume - min_volume) / (max * max) + min_volume);
480 } /* scale_volume() */
481 
482 
483 /*
484  * unscale_volume(cd_vol, max)
485  *
486  * Given a value between min_volume and max_volume, return the volume slider
487  * value needed to achieve that value.
488  *
489  * Rather than perform floating-point calculations to reverse the above
490  * formula, we simply do a binary search of scale_volume()'s return values.
491  */
492 static int
unscale_volume(int cd_vol,int max)493 unscale_volume( int cd_vol, int max )
494 {
495 	int vol = 0, top = max, bot = 0, scaled;
496 
497 	while (bot <= top) {
498 		vol = (top + bot) / 2;
499 		scaled = scale_volume(vol, max);
500 		if (cd_vol == scaled)
501 			break;
502 		if (cd_vol < scaled)
503 			top = vol - 1;
504 		else
505 			bot = vol + 1;
506 	}
507 
508 	if (vol < 0)
509 		vol = 0;
510 	else if (vol > max)
511 		vol = max;
512 
513 	return vol;
514 } /* unscale_volume() */
515 
516 /*
517  * Set the volume level for the left and right channels.  Their values
518  * range from 0 to 100.
519  */
520 int
gen_set_volume(struct wm_drive * d,int left,int right)521 gen_set_volume( struct wm_drive *d, int left, int right )
522 {
523 	struct cd_playback pb;
524 	struct cd_playback_status ps;
525 	struct cd_playback_control pc;
526 
527 	left = scale_volume(left, 100);
528 	right = scale_volume(right, 100);
529 
530 	bzero((char *)&pb, sizeof(pb));
531 	bzero((char *)&ps, sizeof(ps));
532 	bzero((char *)&pc, sizeof(pc));
533 
534 	pb.pb_alloc_length = sizeof(ps);
535 	pb.pb_buffer = (caddr_t)&ps;
536 
537 	if (ioctl(d->fd, CDROM_PLAYBACK_STATUS, &pb))
538 		return -1;
539 
540 	pc.pc_chan0_select = ps.ps_chan0_select;
541 	pc.pc_chan0_volume = (left < CDROM_MIN_VOLUME) ?
542 		CDROM_MIN_VOLUME : (left > CDROM_MAX_VOLUME) ?
543 		CDROM_MAX_VOLUME : left;
544 	pc.pc_chan1_select = ps.ps_chan1_select;
545 	pc.pc_chan1_volume = (right < CDROM_MIN_VOLUME) ?
546 		CDROM_MIN_VOLUME : (right > CDROM_MAX_VOLUME) ?
547 		CDROM_MAX_VOLUME : right;
548 
549 	pb.pb_alloc_length = sizeof(pc);
550 	pb.pb_buffer = (caddr_t)&pc;
551 
552 	if (ioctl(d->fd, CDROM_PLAYBACK_CONTROL, &pb))
553 		return -1;
554 
555 	return 0;
556 } /* gen_set_volume() */
557 
558 /*
559  * Read the initial volume from the drive, if available.  Each channel
560  * ranges from 0 to 100, with -1 indicating data not available.
561  */
562 int
gen_get_volume(struct wm_drive * d,int * left,int * right)563 gen_get_volume(struct wm_drive *d, int *left, int *right)
564 {
565 	struct cd_playback		pb;
566 	struct cd_playback_status	ps;
567 
568 	bzero((char *)&pb, sizeof(pb));
569 	bzero((char *)&ps, sizeof(ps));
570 
571 	pb.pb_alloc_length = sizeof(ps);
572 	pb.pb_buffer = (caddr_t)&ps;
573 
574 	if (d->fd >= 0) {
575 		if (ioctl(d->fd, CDROM_PLAYBACK_STATUS, &pb))
576 			*left = *right = -1;
577 		else {
578 			*left = unscale_volume(ps.ps_chan0_volume, 100);
579 			*right = unscale_volume(ps.ps_chan1_volume, 100);
580 		}
581     } else
582 		*left = *right = -1;
583 
584   return 0;
585 } /* gen_get_volume() */
586 
587 #endif
588