8322 nl: misleading-indentation
[unleashed/tickless.git] / usr / src / cmd / hal / utils / cdutils.c
bloba32e2f58b8087f5323619c952bf0c472a8435191
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 **************************************************************************/
13 #ifdef HAVE_CONFIG_H
14 # include <config.h>
15 #endif
17 #include <stdio.h>
18 #include <sys/types.h>
19 #include <sys/scsi/impl/uscsi.h>
20 #include <string.h>
21 #include <strings.h>
22 #include <unistd.h>
23 #include <stdlib.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <sys/dkio.h>
27 #include <libintl.h>
29 #include <logger.h>
31 #include "cdutils.h"
33 #define RQLEN 32
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
43 void
44 uscsi_cmd_init(struct uscsi_cmd *scmd, char *cdb, int cdblen)
46 bzero(scmd, sizeof (*scmd));
47 bzero(cdb, cdblen);
48 scmd->uscsi_cdb = cdb;
51 int
52 uscsi(int fd, struct uscsi_cmd *scmd)
54 char rqbuf[RQLEN];
55 int ret;
56 int i, retries, total_retries;
57 int max_retries = 20;
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)) {
70 ret = -1;
71 errno = EIO;
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)) {
87 total_retries++;
88 sleep(1);
89 continue;
93 * Device is not ready to transmit or a device reset
94 * has occurred. wait for a short period of time then
95 * retry the command.
97 if ((SENSE_KEY(rqbuf) == 6) && ((ASC(rqbuf) == 0x28) ||
98 (ASC(rqbuf) == 0x29))) {
99 sleep(1);
100 total_retries++;
101 continue;
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)) {
111 ret = 0;
112 break;
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,
118 scmd->uscsi_status,
119 (uchar_t)SENSE_KEY(rqbuf),
120 (uchar_t)ASC(rqbuf), (uchar_t)ASCQ(rqbuf)));
123 break;
126 if (retries) {
127 HAL_DEBUG (("total retries: %d\n", total_retries));
130 return (ret);
134 mode_sense(int fd, uchar_t pc, int dbd, int page_len, uchar_t *buffer)
136 struct uscsi_cmd scmd;
137 char cdb[16];
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 */
146 if (dbd) {
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)
162 int ret;
163 uchar_t byte2;
164 uchar_t buf[256];
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) {
171 return (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);
180 if (plen) {
181 *plen = page_len;
184 return (1);
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;
192 char cdb[16];
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);
210 boolean_t
211 get_current_profile(int fd, int *profile)
213 size_t i;
214 uchar_t smallbuf[8];
215 size_t buflen;
216 uchar_t *bufp;
217 int ret = B_FALSE;
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
224 * of bytes.
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]);
233 ret = B_TRUE;
235 free(bufp);
238 return (ret);
241 void
242 walk_profiles(int fd, int (*f)(void *, int, boolean_t), void *arg)
244 size_t i;
245 uint16_t profile, current_profile;
246 uchar_t smallbuf[8];
247 size_t buflen;
248 uchar_t *bufp;
249 int ret;
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
256 * of bytes.
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) {
269 break;
274 free(bufp);
278 /* retrieve speed list from the Write Speed Performance Descriptor Blocks
280 void
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;
284 int i;
285 intlist_t **nextp;
286 intlist_t *current;
287 boolean_t skip;
289 *n_speeds = 0;
290 *speeds = NULL;
291 *speeds_mem = (intlist_t *)calloc(n, sizeof (intlist_t));
292 if (*speeds_mem == NULL) {
293 return;
296 for (i = 0; i < n; i++, p += 4) {
297 current = &(*speeds_mem)[i];
298 current->val = GET16(p);
300 /* keep the list sorted */
301 skip = B_FALSE;
302 for (nextp = speeds; *nextp != NULL; nextp = &((*nextp)->next)) {
303 if (current->val == (*nextp)->val) {
304 skip = B_TRUE; /* skip duplicates */
305 break;
306 } else if (current->val > (*nextp)->val) {
307 break;
310 if (!skip) {
311 current->next = *nextp;
312 *nextp = current;
313 *n_speeds++;
318 void
319 get_read_write_speeds(int fd, int *read_speed, int *write_speed,
320 intlist_t **speeds, int *n_speeds, intlist_t **speeds_mem)
322 int page_len;
323 uchar_t p[254];
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)) {
330 return;
333 if (page_len > 8) {
334 *read_speed = GET16(&p[8]);
336 if (page_len > 18) {
337 *write_speed = GET16(&p[18]);
339 if (page_len < 28) {
340 printf("MMC-2\n");
341 return;
342 } else {
343 printf("MMC-3\n");
346 *write_speed = GET16(&p[28]);
348 if (page_len < 30) {
349 return;
352 /* retrieve speed list */
353 n = GET16(&p[30]);
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);
363 boolean_t
364 get_disc_info(int fd, disc_info_t *di)
366 struct uscsi_cmd scmd;
367 char cdb[16];
368 uint8_t buf[32];
369 int bufsize = sizeof (buf);
371 bzero(buf, bufsize);
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) {
383 return (B_FALSE);
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) {
392 return (B_FALSE);
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;
399 } else {
400 di->capacity = 0;
403 return (B_TRUE);
407 * returns current/maximum format capacity in bytes
409 boolean_t
410 read_format_capacity(int fd, uint64_t *capacity)
412 struct uscsi_cmd scmd;
413 char cdb[16];
414 uint8_t buf[32];
415 int bufsize = sizeof (buf);
416 uint32_t num_blocks;
417 uint32_t block_len;
419 bzero(buf, bufsize);
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) {
431 return (B_FALSE);
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;
438 return (B_TRUE);
441 boolean_t
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)
451 boolean_t
452 get_disc_capacity_for_profile(int fd, int profile, uint64_t *capacity)
454 struct dk_minfo mi;
455 disc_info_t di;
456 boolean_t ret = B_FALSE;
458 switch (profile) {
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;
463 ret = B_TRUE;
465 break;
466 default:
467 if (read_format_capacity(fd, capacity) && (*capacity > 0)) {
468 ret = B_TRUE;
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;
472 ret = B_TRUE;
477 return (ret);
480 boolean_t
481 read_toc(int fd, int format, int trackno, int buflen, uchar_t *buf)
483 struct uscsi_cmd scmd;
484 char cdb[16];
486 bzero(buf, buflen);
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) {
500 return (B_FALSE);
503 return (B_TRUE);