1 /***************************************************************************
3 * cdutils.c : CD/DVD utilities
5 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
6 * Use is subject to license terms.
8 * Licensed under the Academic Free License version 2.1
10 **************************************************************************/
18 #include <sys/types.h>
19 #include <sys/scsi/impl/uscsi.h>
34 #define SENSE_KEY(rqbuf) (rqbuf[2]) /* scsi error category */
35 #define ASC(rqbuf) (rqbuf[12]) /* additional sense code */
36 #define ASCQ(rqbuf) (rqbuf[13]) /* ASC qualifier */
38 #define GET16(a) (((a)[0] << 8) | (a)[1])
39 #define GET32(a) (((a)[0] << 24) | ((a)[1] << 16) | ((a)[2] << 8) | (a)[3])
41 #define CD_USCSI_TIMEOUT 60
44 uscsi_cmd_init(struct uscsi_cmd
*scmd
, char *cdb
, int cdblen
)
46 bzero(scmd
, sizeof (*scmd
));
48 scmd
->uscsi_cdb
= cdb
;
52 uscsi(int fd
, struct uscsi_cmd
*scmd
)
56 int i
, retries
, total_retries
;
59 scmd
->uscsi_flags
|= USCSI_RQENABLE
;
60 scmd
->uscsi_rqlen
= RQLEN
;
61 scmd
->uscsi_rqbuf
= rqbuf
;
63 for (retries
= 0; retries
< max_retries
; retries
++) {
64 scmd
->uscsi_status
= 0;
65 memset(rqbuf
, 0, RQLEN
);
67 ret
= ioctl(fd
, USCSICMD
, scmd
);
69 if ((ret
== 0) && (scmd
->uscsi_status
== 2)) {
73 if ((ret
< 0) && (scmd
->uscsi_status
== 2)) {
75 * The drive is not ready to recieve commands but
76 * may be in the process of becoming ready.
77 * sleep for a short time then retry command.
78 * SENSE/ASC = 2/4 : not ready
79 * ASCQ = 0 Not Reportable.
80 * ASCQ = 1 Becoming ready.
81 * ASCQ = 4 FORMAT in progress.
82 * ASCQ = 7 Operation in progress.
84 if ((SENSE_KEY(rqbuf
) == 2) && (ASC(rqbuf
) == 4) &&
85 ((ASCQ(rqbuf
) == 0) || (ASCQ(rqbuf
) == 1) ||
86 (ASCQ(rqbuf
) == 4)) || (ASCQ(rqbuf
) == 7)) {
93 * Device is not ready to transmit or a device reset
94 * has occurred. wait for a short period of time then
97 if ((SENSE_KEY(rqbuf
) == 6) && ((ASC(rqbuf
) == 0x28) ||
98 (ASC(rqbuf
) == 0x29))) {
104 * Blank Sense, we don't know what the error is or if
105 * the command succeeded, Hope for the best. Some
106 * drives return blank sense periodically and will
107 * fail if this is removed.
109 if ((SENSE_KEY(rqbuf
) == 0) && (ASC(rqbuf
) == 0) &&
110 (ASCQ(rqbuf
) == 0)) {
115 HAL_DEBUG (("cmd: 0x%02x ret:%i status:%02x "
116 " sense: %02x ASC: %02x ASCQ:%02x\n",
117 (uchar_t
)scmd
->uscsi_cdb
[0], ret
,
119 (uchar_t
)SENSE_KEY(rqbuf
),
120 (uchar_t
)ASC(rqbuf
), (uchar_t
)ASCQ(rqbuf
)));
127 HAL_DEBUG (("total retries: %d\n", total_retries
));
134 mode_sense(int fd
, uchar_t pc
, int dbd
, int page_len
, uchar_t
*buffer
)
136 struct uscsi_cmd scmd
;
139 uscsi_cmd_init(&scmd
, cdb
, sizeof (cdb
));
140 scmd
.uscsi_flags
= USCSI_READ
|USCSI_SILENT
;
141 scmd
.uscsi_buflen
= page_len
;
142 scmd
.uscsi_bufaddr
= (char *)buffer
;
143 scmd
.uscsi_timeout
= CD_USCSI_TIMEOUT
;
144 scmd
.uscsi_cdblen
= 0xa;
145 scmd
.uscsi_cdb
[0] = 0x5a; /* MODE SENSE 10 */
147 scmd
.uscsi_cdb
[1] = 0x8; /* no block descriptors */
149 scmd
.uscsi_cdb
[2] = pc
;
150 scmd
.uscsi_cdb
[7] = (page_len
>> 8) & 0xff;
151 scmd
.uscsi_cdb
[8] = page_len
& 0xff;
153 return (uscsi(fd
, &scmd
) == 0);
157 * will get the mode page only i.e. will strip off the header.
160 get_mode_page(int fd
, int page_no
, int pc
, int buf_len
, uchar_t
*buffer
, int *plen
)
165 uint_t header_len
, page_len
, copy_cnt
;
167 byte2
= (uchar_t
)(((pc
<< 6) & 0xC0) | (page_no
& 0x3f));
169 /* Ask 254 bytes only to make our IDE driver happy */
170 if ((ret
= mode_sense(fd
, byte2
, 1, 254, buf
)) == 0) {
174 header_len
= 8 + GET16(&buf
[6]);
175 page_len
= buf
[header_len
+ 1] + 2;
177 copy_cnt
= (page_len
> buf_len
) ? buf_len
: page_len
;
178 (void) memcpy(buffer
, &buf
[header_len
], copy_cnt
);
187 /* Get information about the Logical Unit's capabilities */
189 get_configuration(int fd
, uint16_t feature
, int bufsize
, uchar_t
*buf
)
191 struct uscsi_cmd scmd
;
194 uscsi_cmd_init(&scmd
, cdb
, sizeof (cdb
));
195 scmd
.uscsi_flags
= USCSI_READ
|USCSI_SILENT
;
196 scmd
.uscsi_timeout
= CD_USCSI_TIMEOUT
;
197 scmd
.uscsi_cdb
[0] = 0x46; /* GET CONFIGURATION */
198 scmd
.uscsi_cdb
[1] = 0x2; /* request type */
199 scmd
.uscsi_cdb
[2] = (feature
>> 8) & 0xff; /* starting feature # */
200 scmd
.uscsi_cdb
[3] = feature
& 0xff;
201 scmd
.uscsi_cdb
[7] = (bufsize
>> 8) & 0xff; /* allocation length */
202 scmd
.uscsi_cdb
[8] = bufsize
& 0xff;
203 scmd
.uscsi_cdblen
= 10;
204 scmd
.uscsi_bufaddr
= (char *)buf
;
205 scmd
.uscsi_buflen
= bufsize
;
207 return (uscsi(fd
, &scmd
) == 0);
211 get_current_profile(int fd
, int *profile
)
220 * first determine amount of memory needed to hold all profiles.
221 * The first four bytes of smallbuf concatenated tell us the
222 * number of bytes of memory we need but do not take themselves
223 * into account. Therefore, add four to allocate that number
226 if (get_configuration(fd
, 0, 8, &smallbuf
[0])) {
227 buflen
= GET32(smallbuf
) + 4;
228 bufp
= (uchar_t
*)malloc(buflen
);
230 /* now get all profiles */
231 if (get_configuration(fd
, 0, buflen
, bufp
)) {
232 *profile
= GET16(&bufp
[6]);
242 walk_profiles(int fd
, int (*f
)(void *, int, boolean_t
), void *arg
)
245 uint16_t profile
, current_profile
;
252 * first determine amount of memory needed to hold all profiles.
253 * The first four bytes of smallbuf concatenated tell us the
254 * number of bytes of memory we need but do not take themselves
255 * into account. Therefore, add four to allocate that number
258 if (get_configuration(fd
, 0, 8, &smallbuf
[0])) {
259 buflen
= GET32(smallbuf
) + 4;
260 bufp
= (uchar_t
*)malloc(buflen
);
262 /* now get all profiles */
263 if (get_configuration(fd
, 0, buflen
, bufp
)) {
264 current_profile
= GET16(&bufp
[6]);
265 for (i
= 8 + 4; i
< buflen
; i
+= 4) {
266 profile
= GET16(&bufp
[i
]);
267 ret
= f(arg
, profile
, (profile
== current_profile
));
268 if (ret
== CDUTIL_WALK_STOP
) {
278 /* retrieve speed list from the Write Speed Performance Descriptor Blocks
281 get_write_speeds(uchar_t
*page
, int n
, intlist_t
**speeds
, int *n_speeds
, intlist_t
**speeds_mem
)
283 uchar_t
*p
= page
+ 2;
291 *speeds_mem
= (intlist_t
*)calloc(n
, sizeof (intlist_t
));
292 if (*speeds_mem
== NULL
) {
296 for (i
= 0; i
< n
; i
++, p
+= 4) {
297 current
= &(*speeds_mem
)[i
];
298 current
->val
= GET16(p
);
300 /* keep the list sorted */
302 for (nextp
= speeds
; *nextp
!= NULL
; nextp
= &((*nextp
)->next
)) {
303 if (current
->val
== (*nextp
)->val
) {
304 skip
= B_TRUE
; /* skip duplicates */
306 } else if (current
->val
> (*nextp
)->val
) {
311 current
->next
= *nextp
;
319 get_read_write_speeds(int fd
, int *read_speed
, int *write_speed
,
320 intlist_t
**speeds
, int *n_speeds
, intlist_t
**speeds_mem
)
324 int n
; /* number of write speed performance descriptor blocks */
326 *read_speed
= *write_speed
= 0;
327 *speeds
= *speeds_mem
= NULL
;
329 if (!get_mode_page(fd
, 0x2A, 0, sizeof (p
), p
, &page_len
)) {
334 *read_speed
= GET16(&p
[8]);
337 *write_speed
= GET16(&p
[18]);
346 *write_speed
= GET16(&p
[28]);
352 /* retrieve speed list */
354 n
= min(n
, (sizeof (p
) - 32) / 4);
356 get_write_speeds(&p
[32], n
, speeds
, n_speeds
, speeds_mem
);
358 if (*speeds
!= NULL
) {
359 *write_speed
= max(*write_speed
, (*speeds
)[0].val
);
364 get_disc_info(int fd
, disc_info_t
*di
)
366 struct uscsi_cmd scmd
;
369 int bufsize
= sizeof (buf
);
372 uscsi_cmd_init(&scmd
, cdb
, sizeof (cdb
));
373 scmd
.uscsi_flags
= USCSI_READ
|USCSI_SILENT
;
374 scmd
.uscsi_timeout
= CD_USCSI_TIMEOUT
;
375 scmd
.uscsi_cdb
[0] = 0x51; /* READ DISC INFORMATION */
376 scmd
.uscsi_cdb
[7] = (bufsize
>> 8) & 0xff; /* allocation length */
377 scmd
.uscsi_cdb
[8] = bufsize
& 0xff;
378 scmd
.uscsi_cdblen
= 10;
379 scmd
.uscsi_bufaddr
= (char *)buf
;
380 scmd
.uscsi_buflen
= bufsize
;
382 if ((uscsi(fd
, &scmd
)) != 0) {
387 * According to MMC-5 6.22.3.2, the Disc Information Length should be
388 * 32+8*(Number of OPC Tables). Some devices, like U3 sticks, return 0.
389 * Yet some drives can return less than 32. We only need the first 22.
391 if (GET16(&buf
[0]) < 22) {
395 di
->disc_status
= buf
[2] & 0x03;
396 di
->erasable
= buf
[2] & 0x10;
397 if ((buf
[21] != 0) && (buf
[21] != 0xff)) {
398 di
->capacity
= ((buf
[21] * 60) + buf
[22]) * 75;
407 * returns current/maximum format capacity in bytes
410 read_format_capacity(int fd
, uint64_t *capacity
)
412 struct uscsi_cmd scmd
;
415 int bufsize
= sizeof (buf
);
420 uscsi_cmd_init(&scmd
, cdb
, sizeof (cdb
));
421 scmd
.uscsi_flags
= USCSI_READ
|USCSI_SILENT
;
422 scmd
.uscsi_timeout
= CD_USCSI_TIMEOUT
;
423 scmd
.uscsi_cdb
[0] = 0x23; /* READ FORMAT CAPACITIRES */
424 scmd
.uscsi_cdb
[7] = (bufsize
>> 8) & 0xff; /* allocation length */
425 scmd
.uscsi_cdb
[8] = bufsize
& 0xff;
426 scmd
.uscsi_cdblen
= 12;
427 scmd
.uscsi_bufaddr
= (char *)buf
;
428 scmd
.uscsi_buflen
= bufsize
;
430 if ((uscsi(fd
, &scmd
)) != 0) {
434 num_blocks
= (uint32_t)(buf
[4] << 24) + (buf
[5] << 16) + (buf
[6] << 8) + buf
[7];
435 block_len
= (uint32_t)(buf
[9] << 16) + (buf
[10] << 8) + buf
[11];
436 *capacity
= (uint64_t)num_blocks
* block_len
;
442 get_media_info(int fd
, struct dk_minfo
*minfop
)
444 return (ioctl(fd
, DKIOCGMEDIAINFO
, minfop
) != -1);
448 * given current profile, use the best method for determining
449 * disc capacity (in bytes)
452 get_disc_capacity_for_profile(int fd
, int profile
, uint64_t *capacity
)
456 boolean_t ret
= B_FALSE
;
459 case 0x08: /* CD-ROM */
460 case 0x10: /* DVD-ROM */
461 if (get_media_info(fd
, &mi
) && (mi
.dki_capacity
> 1)) {
462 *capacity
= mi
.dki_capacity
* mi
.dki_lbsize
;
467 if (read_format_capacity(fd
, capacity
) && (*capacity
> 0)) {
469 } else if (get_disc_info(fd
, &di
) && (di
.capacity
> 0)) {
470 if (get_media_info(fd
, &mi
)) {
471 *capacity
= di
.capacity
* mi
.dki_lbsize
;
481 read_toc(int fd
, int format
, int trackno
, int buflen
, uchar_t
*buf
)
483 struct uscsi_cmd scmd
;
487 uscsi_cmd_init(&scmd
, cdb
, sizeof (cdb
));
488 scmd
.uscsi_flags
= USCSI_READ
|USCSI_SILENT
;
489 scmd
.uscsi_timeout
= CD_USCSI_TIMEOUT
;
490 scmd
.uscsi_cdb
[0] = 0x43 /* READ_TOC_CMD */;
491 scmd
.uscsi_cdb
[2] = format
& 0xf;
492 scmd
.uscsi_cdb
[6] = trackno
;
493 scmd
.uscsi_cdb
[8] = buflen
& 0xff;
494 scmd
.uscsi_cdb
[7] = (buflen
>> 8) & 0xff;
495 scmd
.uscsi_cdblen
= 10;
496 scmd
.uscsi_bufaddr
= (char *)buf
;
497 scmd
.uscsi_buflen
= buflen
;
499 if ((uscsi(fd
, &scmd
)) != 0) {