wmclockmon: update change-log
[dockapps.git] / ascd / libworkman / scsi.c
blob0e3539ff05cc0777d5085dc8676c262b73960588
1 /*
2 * $Id: scsi.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 * Frontend functions for sending raw SCSI commands to the CD-ROM drive.
26 * These depend on wm_scsi(), which should be defined in each platform
27 * module.
30 static char scsi_id[] = "$Id: scsi.c,v 1.8 1999/06/17 06:48:03 dirk Exp $";
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include "include/wm_config.h"
36 #include "include/wm_struct.h"
37 #include "include/wm_scsi.h"
38 #include "include/wm_platform.h"
39 #include "include/wm_helpers.h"
40 #include "include/wm_cdrom.h"
42 #define SCMD_INQUIRY 0x12
43 #define SCMD_MODE_SELECT 0x15
44 #define SCMD_MODE_SENSE 0x1a
45 #define SCMD_START_STOP 0x1b
46 #define SCMD_PREVENT 0x1e
47 #define SCMD_READ_SUBCHANNEL 0x42
48 #define SCMD_READ_TOC 0x43
49 #define SCMD_PLAY_AUDIO_MSF 0x47
50 #define SCMD_PAUSE_RESUME 0x4b
52 #define SUBQ_STATUS_INVALID 0x00
53 #define SUBQ_STATUS_PLAY 0x11
54 #define SUBQ_STATUS_PAUSE 0x12
55 #define SUBQ_STATUS_DONE 0x13
56 #define SUBQ_STATUS_ERROR 0x14
57 #define SUBQ_STATUS_NONE 0x15
58 #define SUBQ_STATUS_NO_DISC 0x17 /* Illegal, but Toshiba returns it. */
59 #define SUBQ_ILLEGAL 0xff
61 #define PAGE_AUDIO 0x0e
62 #define LEADOUT 0xaa
64 #define WM_MSG_CLASS WM_MSG_CLASS_SCSI
67 * Send a SCSI command over the bus, with all the CDB bytes specified
68 * as unsigned char parameters. This doesn't use varargs because some
69 * systems have stdargs instead and the number of bytes in a CDB is
70 * limited to 12 anyway.
72 * d Drive structure
73 * buf Buffer for data, both sending and receiving
74 * len Size of buffer
75 * dir TRUE if the command expects data to be returned in the buffer.
76 * a0- CDB bytes. Either 6, 10, or 12 of them, depending on the command.
78 /*VARARGS4*/
79 int
80 sendscsi( struct wm_drive *d, void *buf,
81 unsigned int len, int dir,
82 unsigned char a0, unsigned char a1,
83 unsigned char a2, unsigned char a3,
84 unsigned char a4, unsigned char a5,
85 unsigned char a6, unsigned char a7,
86 unsigned char a8, unsigned char a9,
87 unsigned char a10, unsigned char a11 )
90 int cdblen = 0;
91 unsigned char cdb[12];
93 cdb[0] = a0;
94 cdb[1] = a1;
95 cdb[2] = a2;
96 cdb[3] = a3;
97 cdb[4] = a4;
98 cdb[5] = a5;
100 switch ((a0 >> 5) & 7) {
101 case 0:
102 cdblen = 6;
103 break;
105 case 5:
106 cdb[10] = a10;
107 cdb[11] = a11;
108 cdblen = 12;
110 case 1:
111 case 2:
112 case 6: /* assume 10-byte vendor-specific codes for now */
113 cdb[6] = a6;
114 cdb[7] = a7;
115 cdb[8] = a8;
116 cdb[9] = a9;
117 if (! cdblen)
118 cdblen = 10;
119 break;
122 return (wm_scsi(d, cdb, cdblen, buf, len, dir));
126 * Send a MODE SENSE command and return the results (minus header cruft)
127 * in a user buffer.
129 * d Drive structure
130 * page Number of page to query (plus page control bits, if any)
131 * buf Result buffer
134 wm_scsi_mode_sense( struct wm_drive *d, unsigned char page, unsigned char *buf )
136 unsigned char pagebuf[255];
137 int status, i, len, offset;
139 status = sendscsi(d, pagebuf, sizeof(pagebuf), 1, SCMD_MODE_SENSE, 0,
140 page, 0, sizeof(pagebuf), 0,0,0,0,0,0,0);
141 if (status < 0)
142 return (status);
145 * The first byte of the returned data is the transfer length. Then
146 * two more bytes and the length of whatever header blocks are in
147 * front of the page we want.
149 len = pagebuf[0] - pagebuf[3] - 3;
150 offset = pagebuf[3] + 4;
151 for (i = 0; i < len; i++)
152 buf[i] = pagebuf[offset + i];
154 return (0);
158 * Send a MODE SELECT command.
160 * d Drive structure
161 * buf Page buffer (no need to put on block descriptors)
162 * len Size of page
165 wm_scsi_mode_select(d, buf, len)
166 struct wm_drive *d;
167 unsigned char *buf;
168 unsigned char len;
170 unsigned char pagebuf[255];
171 int i;
173 pagebuf[0] = pagebuf[1] = pagebuf[2] = pagebuf[3] = 0;
174 for (i = 0; i < (int) len; i++)
175 pagebuf[i + 4] = buf[i];
177 return (sendscsi(d, pagebuf, len + 4, 0, SCMD_MODE_SELECT, 0x10, 0,
178 0, len + 4, 0,0,0,0,0,0,0));
182 * Send an INQUIRY command to get the drive type.
184 * d Drive structure
185 * vendor Buffer for vendor name (8 bytes + null)
186 * model Buffer for model name (16 bytes + null)
187 * rev Buffer for revision level (4 bytes + null)
189 * The above string lengths apply to the SCSI INQUIRY command. The
190 * actual WorkMan drive structure reserves 31 bytes + NULL per entry.
192 * If the model name begins with "CD-ROM" and zero or more spaces, that will
193 * all be stripped off since it's just extra junk to WorkMan.
196 wm_scsi_get_drive_type( struct wm_drive *d, char *vendor,
197 char *model, char *rev )
199 /* removed unsigned*/
200 char *s, *t, buf[36];
202 wm_lib_message(WM_MSG_LEVEL_INFO|WM_MSG_CLASS, "Sending SCSI inquiry command...");
203 if (sendscsi(d, buf, sizeof(buf), 1, SCMD_INQUIRY, 0, 0, 0, sizeof(buf), 0,0,0,0,0,0,0))
205 sprintf( vendor, WM_STR_GENVENDOR);
206 sprintf( model, WM_STR_GENMODEL);
207 sprintf( rev, WM_STR_GENREV);
208 wm_lib_message(WM_MSG_LEVEL_ERROR|WM_MSG_CLASS, "\nSCSI inquiry command not supported by the hardware\n");
209 return (WM_ERR_SCSI_INQUIRY_FAILED);
211 wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "sent.\n");
213 memcpy(vendor, buf + 8, 8);
214 vendor[8] = '\0';
215 memcpy(model, buf + 16, 16);
216 model[16] = '\0';
217 memcpy(rev, buf + 32, 4);
218 rev[4] = '\0';
219 wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "SCSI Inquiry result: [%s|%s|%s]\n", vendor, model, rev);
222 /* Remove "CD-ROM " from the model. */
223 if (! strncmp(model, "CD-ROM", 6))
225 s = model + 6;
226 t = model;
227 while (*s == ' ' || *s == '\t')
228 s++;
229 while( (*t++ = *s++) )
232 wm_lib_message(WM_MSG_LEVEL_INFO|WM_MSG_CLASS, "Cooked data: %s %s rev. %s\n", vendor, model,rev);
233 return (0);
234 } /* wm_scsi_get_drive_type() */
237 * Send a SCSI-2 PAUSE/RESUME command. "resume" is 1 to resume, 0 to pause.
240 wm_scsi2_pause_resume(d, resume)
241 struct wm_drive *d;
242 int resume;
244 return (sendscsi(d, NULL, 0, 0, SCMD_PAUSE_RESUME, 0, 0, 0, 0, 0, 0,
245 0, resume ? 1 : 0, 0,0,0));
249 * Send a SCSI-2 "prevent media removal" command. "prevent" is 1 to lock
250 * caddy in.
253 wm_scsi2_prevent(d, prevent)
254 struct wm_drive *d;
255 int prevent;
257 return (sendscsi(d, NULL, 0, 0, SCMD_PREVENT, 0, 0, 0, 0, 0, 0,
258 0, prevent ? 1 : 0, 0,0,0));
262 * Send a SCSI-2 PLAY AUDIO MSF command. Pass the starting and ending
263 * frame numbers.
266 wm_scsi2_play(d, sframe, eframe)
267 struct wm_drive *d;
268 int sframe, eframe;
270 return (sendscsi(d, NULL, 0, 0, SCMD_PLAY_AUDIO_MSF, 0, 0,
271 sframe / (60 * 75), (sframe / 75) % 60, sframe % 75,
272 eframe / (60 * 75), (eframe / 75) % 60, eframe % 75,
273 0,0,0));
277 * Send a SCSI-2 READ TOC command to get the data for a particular track.
278 * Fill in track information from the returned data.
281 wm_scsi2_get_trackinfo(d, track, data, startframe)
282 struct wm_drive *d;
283 int track, *data, *startframe;
285 unsigned char buf[12]; /* one track's worth of info */
287 if (sendscsi(d, buf, sizeof(buf), 1, SCMD_READ_TOC, 2,
288 0, 0, 0, 0, track, sizeof(buf) / 256,
289 sizeof(buf) % 256, 0,0,0))
290 return (-1);
292 *data = buf[5] & 4 ? 1 : 0;
293 *startframe = buf[9] * 60 * 75 + buf[10] * 75 + buf[11];
295 return (0);
299 * Get the starting frame for the leadout area (which should be the same as
300 * the length of the disc as far as WorkMan is concerned).
303 wm_scsi2_get_cdlen(d, frames)
304 struct wm_drive *d;
305 int *frames;
307 int tmp;
309 return (wm_scsi2_get_trackinfo(d, LEADOUT, &tmp, frames));
313 * Get the current status of the drive by sending the appropriate SCSI-2
314 * READ SUB-CHANNEL command.
317 wm_scsi2_get_drive_status(d, oldmode, mode, pos, track, index)
318 struct wm_drive *d;
319 enum wm_cd_modes oldmode, *mode;
320 int *pos, *track, *index;
322 unsigned char buf[48];
324 /* If we can't get status, the CD is ejected, so default to that. */
325 *mode = WM_CDM_EJECTED;
327 /* Is the device open? */
328 if (d->fd < 0)
331 * stupid somehow, but necessary this time
333 switch( wmcd_open( d ) ) {
335 case -1: /* error */
336 return (-1);
338 case 1: /* retry */
339 return (0);
343 /* If we can't read status, the CD has been ejected. */
344 buf[1] = SUBQ_ILLEGAL;
345 if (sendscsi(d, buf, sizeof(buf), 1, SCMD_READ_SUBCHANNEL, 2, 64, 1,
346 0, 0, 0, sizeof(buf) / 256, sizeof(buf) % 256, 0,0,0))
347 return (0);
349 switch (buf[1]) {
350 case SUBQ_STATUS_PLAY:
351 *mode = WM_CDM_PLAYING;
352 *track = buf[6];
353 *index = buf[7];
354 *pos = buf[9] * 60 * 75 + buf[10] * 75 + buf[11];
355 break;
357 case SUBQ_STATUS_PAUSE:
358 if (oldmode == WM_CDM_PLAYING || oldmode == WM_CDM_PAUSED)
360 *mode = WM_CDM_PAUSED;
361 *track = buf[6];
362 *index = buf[7];
363 *pos = buf[9] * 60 * 75 +
364 buf[10] * 75 +
365 buf[11];
367 else
368 *mode = WM_CDM_STOPPED;
369 break;
372 * SUBQ_STATUS_DONE is sometimes returned when the CD is idle,
373 * even though the spec says it should only be returned when an
374 * audio play operation finishes.
376 case SUBQ_STATUS_DONE:
377 case SUBQ_STATUS_NONE:
378 case SUBQ_STATUS_INVALID:
379 if (oldmode == WM_CDM_PLAYING)
380 *mode = WM_CDM_TRACK_DONE;
381 else
382 *mode = WM_CDM_STOPPED;
383 break;
386 * This usually means there's no disc in the drive.
388 case SUBQ_STATUS_NO_DISC:
389 break;
392 * This usually means the user ejected the CD manually.
394 case SUBQ_STATUS_ERROR:
395 break;
397 case SUBQ_ILLEGAL: /* call didn't really succeed */
398 break;
400 default:
401 *mode = WM_CDM_UNKNOWN;
402 #ifdef DEBUG
403 if( getenv( "WORKMAN_DEBUG" ) != NULL )
404 printf("wm_scsi2_get_drive_status: status is 0x%x\n",
405 buf[1]);
406 #endif
407 break;
410 return (0);
414 * Get the number of tracks on the CD using the SCSI-2 READ TOC command.
417 wm_scsi2_get_trackcount(d, tracks)
418 struct wm_drive *d;
419 int *tracks;
421 unsigned char buf[4];
423 if (sendscsi(d, buf, sizeof(buf), 1, SCMD_READ_TOC, 0,
424 0, 0, 0, 0, 0, sizeof(buf) / 256,
425 sizeof(buf) % 256, 0,0,0))
426 return (-1);
428 *tracks = buf[3] - buf[2] + 1;
429 return (0);
433 * Pause the CD.
436 wm_scsi2_pause(d)
437 struct wm_drive *d;
439 return (wm_scsi2_pause_resume(d, 0));
443 * Resume playing after a pause.
446 wm_scsi2_resume(d)
447 struct wm_drive *d;
449 return (wm_scsi2_pause_resume(d, 1));
453 * Stop playing the CD by sending a START STOP UNIT command.
456 wm_scsi2_stop(d)
457 struct wm_drive *d;
459 return (sendscsi(d, NULL, 0, 0, SCMD_START_STOP, 0, 0,0,0,0,0,0,0,0,0,0));
463 * Eject the CD by sending a START STOP UNIT command.
466 wm_scsi2_eject(struct wm_drive *d)
468 /* Unlock the disc (possibly unnecessary). */
469 if (wm_scsi2_prevent(d, 0))
470 return (-1);
471 wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "Issuing START_STOP for ejecting...\n");
472 return (sendscsi(d, NULL, 0, 0, SCMD_START_STOP, 2, 0,0,0,0,0,0,0,0,0,0));
476 * Something like a dummy. The SCSI-2 specs are too hard for me to
477 * understand here...
479 * If you have the correct command handy, please send the code to
480 * milliByte@DeathsDoor.com
483 wm_scsi2_closetray(struct wm_drive *d)
485 wm_lib_message(WM_MSG_LEVEL_DEBUG|WM_MSG_CLASS, "Issuing START_STOP for closing...\n");
486 return (sendscsi(d, NULL, 0,0, SCMD_START_STOP, 2, 0,0,0,0,0,0,0,0,0,0));
490 * Get the volume by doing a MODE SENSE command.
493 wm_scsi2_get_volume(d, left, right)
494 struct wm_drive *d;
495 int *left, *right;
497 unsigned char mode[16];
499 *left = *right = -1;
501 /* Get the current audio parameters first. */
502 if (wm_scsi_mode_sense(d, PAGE_AUDIO, mode))
503 return (-1);
505 *left = ((int) mode[9] * 100) / 255;
506 *right = ((int) mode[11] * 100) / 255;
508 return (0);
512 * Set the volume by doing a MODE SELECT command.
515 wm_scsi2_set_volume(d, left, right)
516 struct wm_drive *d;
517 int left, right;
519 unsigned char mode[16];
521 /* Get the current audio parameters first. */
522 if (wm_scsi_mode_sense(d, PAGE_AUDIO, mode))
523 return (-1);
525 /* Tweak the volume part of the parameters. */
526 mode[9] = (left * 255) / 100;
527 mode[11] = (right * 255) / 100;
529 /* And send them back to the drive. */
530 return (wm_scsi_mode_select(d, mode, sizeof(mode)));