xref: /illumos-gate/usr/src/cmd/hal/utils/cdutils.c (revision e6996a4debb6ae502e3265067551ae19866a317a)
1 /***************************************************************************
2  *
3  * cdutils.c : CD/DVD utilities
4  *
5  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
6  * Use is subject to license terms.
7  *
8  * Licensed under the Academic Free License version 2.1
9  *
10  **************************************************************************/
11 
12 #pragma ident	"%Z%%M%	%I%	%E% SMI"
13 
14 #ifdef HAVE_CONFIG_H
15 #  include <config.h>
16 #endif
17 
18 #include <stdio.h>
19 #include <sys/types.h>
20 #include <sys/scsi/impl/uscsi.h>
21 #include <string.h>
22 #include <strings.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <sys/dkio.h>
28 #include <libintl.h>
29 
30 #include <logger.h>
31 
32 #include "cdutils.h"
33 
34 #define	RQLEN	32
35 #define SENSE_KEY(rqbuf)        (rqbuf[2])      /* scsi error category */
36 #define ASC(rqbuf)              (rqbuf[12])     /* additional sense code */
37 #define ASCQ(rqbuf)             (rqbuf[13])     /* ASC qualifier */
38 
39 #define	GET16(a) (((a)[0] << 8) | (a)[1])
40 #define	GET32(a) (((a)[0] << 24) | ((a)[1] << 16) | ((a)[2] << 8) | (a)[3])
41 
42 #define	CD_USCSI_TIMEOUT	60
43 
44 void
45 uscsi_cmd_init(struct uscsi_cmd *scmd, char *cdb, int cdblen)
46 {
47 	bzero(scmd, sizeof (*scmd));
48 	bzero(cdb, cdblen);
49 	scmd->uscsi_cdb = cdb;
50 }
51 
52 int
53 uscsi(int fd, struct uscsi_cmd *scmd)
54 {
55 	char		rqbuf[RQLEN];
56 	int		ret;
57 	int		i, retries, total_retries;
58 	int		max_retries = 20;
59 
60 	scmd->uscsi_flags |= USCSI_RQENABLE;
61 	scmd->uscsi_rqlen = RQLEN;
62 	scmd->uscsi_rqbuf = rqbuf;
63 
64 	for (retries = 0; retries < max_retries; retries++) {
65 		scmd->uscsi_status = 0;
66 		memset(rqbuf, 0, RQLEN);
67 
68 		ret = ioctl(fd, USCSICMD, scmd);
69 
70 		if ((ret == 0) && (scmd->uscsi_status == 2)) {
71 			ret = -1;
72 			errno = EIO;
73 		}
74 		if ((ret < 0) && (scmd->uscsi_status == 2)) {
75 			/*
76 			 * The drive is not ready to recieve commands but
77 			 * may be in the process of becoming ready.
78 			 * sleep for a short time then retry command.
79 			 * SENSE/ASC = 2/4 : not ready
80 			 * ASCQ = 0  Not Reportable.
81 			 * ASCQ = 1  Becoming ready.
82 			 * ASCQ = 4  FORMAT in progress.
83 			 * ASCQ = 7  Operation in progress.
84 			 */
85 			if ((SENSE_KEY(rqbuf) == 2) && (ASC(rqbuf) == 4) &&
86 			    ((ASCQ(rqbuf) == 0) || (ASCQ(rqbuf) == 1) ||
87 			    (ASCQ(rqbuf) == 4)) || (ASCQ(rqbuf) == 7)) {
88 				total_retries++;
89 				sleep(1);
90 				continue;
91 			}
92 
93 			/*
94 			 * Device is not ready to transmit or a device reset
95 			 * has occurred. wait for a short period of time then
96 			 * retry the command.
97 			 */
98 			if ((SENSE_KEY(rqbuf) == 6) && ((ASC(rqbuf) == 0x28) ||
99 			    (ASC(rqbuf) == 0x29))) {
100 				sleep(1);
101 				total_retries++;
102 				continue;
103 			}
104 			/*
105 			 * Blank Sense, we don't know what the error is or if
106 			 * the command succeeded, Hope for the best. Some
107 			 * drives return blank sense periodically and will
108 			 * fail if this is removed.
109 			 */
110 			if ((SENSE_KEY(rqbuf) == 0) && (ASC(rqbuf) == 0) &&
111 			    (ASCQ(rqbuf) == 0)) {
112 				ret = 0;
113 				break;
114 			}
115 
116 			HAL_DEBUG (("cmd: 0x%02x ret:%i status:%02x "
117 			    " sense: %02x ASC: %02x ASCQ:%02x\n",
118 			    (uchar_t)scmd->uscsi_cdb[0], ret,
119 			    scmd->uscsi_status,
120 			    (uchar_t)SENSE_KEY(rqbuf),
121 			    (uchar_t)ASC(rqbuf), (uchar_t)ASCQ(rqbuf)));
122 		}
123 
124 		break;
125 	}
126 
127 	if (retries) {
128 		HAL_DEBUG (("total retries: %d\n", total_retries));
129 	}
130 
131 	return (ret);
132 }
133 
134 int
135 mode_sense(int fd, uchar_t pc, int dbd, int page_len, uchar_t *buffer)
136 {
137 	struct uscsi_cmd scmd;
138 	char cdb[16];
139 
140 	uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
141 	scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
142 	scmd.uscsi_buflen = page_len;
143 	scmd.uscsi_bufaddr = (char *)buffer;
144 	scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
145 	scmd.uscsi_cdblen = 0xa;
146 	scmd.uscsi_cdb[0] = 0x5a; /* MODE SENSE 10 */
147 	if (dbd) {
148 		scmd.uscsi_cdb[1] = 0x8; /* no block descriptors */
149 	}
150 	scmd.uscsi_cdb[2] = pc;
151 	scmd.uscsi_cdb[7] = (page_len >> 8) & 0xff;
152 	scmd.uscsi_cdb[8] = page_len & 0xff;
153 
154 	return (uscsi(fd, &scmd) == 0);
155 }
156 
157 /*
158  * will get the mode page only i.e. will strip off the header.
159  */
160 int
161 get_mode_page(int fd, int page_no, int pc, int buf_len, uchar_t *buffer, int *plen)
162 {
163 	int ret;
164 	uchar_t byte2;
165 	uchar_t buf[256];
166 	uint_t header_len, page_len, copy_cnt;
167 
168 	byte2 = (uchar_t)(((pc << 6) & 0xC0) | (page_no & 0x3f));
169 
170 	/* Ask 254 bytes only to make our IDE driver happy */
171 	if ((ret = mode_sense(fd, byte2, 1, 254, buf)) == 0) {
172 		return (0);
173 	}
174 
175 	header_len = 8 + GET16(&buf[6]);
176 	page_len = buf[header_len + 1] + 2;
177 
178 	copy_cnt = (page_len > buf_len) ? buf_len : page_len;
179 	(void) memcpy(buffer, &buf[header_len], copy_cnt);
180 
181 	if (plen) {
182 		*plen = page_len;
183 	}
184 
185 	return (1);
186 }
187 
188 /* Get information about the Logical Unit's capabilities */
189 int
190 get_configuration(int fd, uint16_t feature, int bufsize, uchar_t *buf)
191 {
192 	struct uscsi_cmd scmd;
193 	char cdb[16];
194 
195 	uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
196 	scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
197 	scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
198 	scmd.uscsi_cdb[0] = 0x46; /* GET CONFIGURATION */
199 	scmd.uscsi_cdb[1] = 0x2; /* request type */
200 	scmd.uscsi_cdb[2] = (feature >> 8) & 0xff; /* starting feature # */
201 	scmd.uscsi_cdb[3] = feature & 0xff;
202 	scmd.uscsi_cdb[7] = (bufsize >> 8) & 0xff; /* allocation length */
203 	scmd.uscsi_cdb[8] = bufsize & 0xff;
204 	scmd.uscsi_cdblen = 10;
205 	scmd.uscsi_bufaddr = (char *)buf;
206 	scmd.uscsi_buflen = bufsize;
207 
208 	return (uscsi(fd, &scmd) == 0);
209 }
210 
211 boolean_t
212 get_current_profile(int fd, int *profile)
213 {
214 	size_t i;
215 	uchar_t smallbuf[4];
216 	size_t buflen;
217 	uchar_t *bufp;
218 	int ret = B_FALSE;
219 
220 	/* first determine amount of memory needed to hold all profiles */
221 	if (get_configuration(fd, 0, 4, &smallbuf[0])) {
222 		buflen = GET32(smallbuf) + 4;
223 		bufp = (uchar_t *)malloc(buflen);
224 
225 	 	/* now get all profiles */
226 		if (get_configuration(fd, 0, buflen, bufp)) {
227 			*profile = GET16(&bufp[6]);
228 			ret = B_TRUE;
229 		}
230 		free(bufp);
231 	}
232 
233 	return (ret);
234 }
235 
236 void
237 walk_profiles(int fd, int (*f)(void *, int, boolean_t), void *arg)
238 {
239 	size_t i;
240 	uint16_t profile, current_profile;
241 	uchar_t smallbuf[4];
242 	size_t buflen;
243 	uchar_t *bufp;
244 	int ret;
245 
246 	/* first determine amount of memory needed to hold all profiles */
247 	if (get_configuration(fd, 0, 4, &smallbuf[0])) {
248 		buflen = GET32(smallbuf) + 4;
249 		bufp = (uchar_t *)malloc(buflen);
250 
251 	 	/* now get all profiles */
252 		if (get_configuration(fd, 0, buflen, bufp)) {
253 			current_profile = GET16(&bufp[6]);
254 			for (i = 8 + 4;  i < buflen; i += 4) {
255 				profile = GET16(&bufp[i]);
256 				ret = f(arg, profile, (profile == current_profile));
257 				if (ret == CDUTIL_WALK_STOP) {
258 					break;
259 				}
260 			}
261 		}
262 
263 		free(bufp);
264 	}
265 }
266 
267 /* retrieve speed list from the Write Speed Performance Descriptor Blocks
268  */
269 void
270 get_write_speeds(uchar_t *page, int n, intlist_t **speeds, int *n_speeds, intlist_t **speeds_mem)
271 {
272 	uchar_t	*p = page + 2;
273 	int	i;
274 	intlist_t **nextp;
275 	intlist_t *current;
276 	boolean_t skip;
277 
278 	*n_speeds = 0;
279 	*speeds = NULL;
280 	*speeds_mem = (intlist_t *)calloc(n, sizeof (intlist_t));
281 	if (*speeds_mem == NULL) {
282 		return;
283 	}
284 
285 	for (i = 0; i < n; i++, p += 4) {
286 		current = &(*speeds_mem)[i];
287 		current->val = GET16(p);
288 
289 		/* keep the list sorted */
290 		skip = B_FALSE;
291 		for (nextp = speeds; *nextp != NULL; nextp = &((*nextp)->next)) {
292 			if (current->val == (*nextp)->val) {
293 				skip = B_TRUE; /* skip duplicates */
294 				break;
295 			} else if (current->val > (*nextp)->val) {
296 				break;
297 			}
298 		}
299 		if (!skip) {
300 			current->next = *nextp;
301 			*nextp = current;
302 			*n_speeds++;
303 		}
304 	}
305 }
306 
307 void
308 get_read_write_speeds(int fd, int *read_speed, int *write_speed,
309     intlist_t **speeds, int *n_speeds, intlist_t **speeds_mem)
310 {
311 	int page_len;
312 	uchar_t	p[254];
313 	int n; /* number of write speed performance descriptor blocks */
314 
315 	*read_speed = *write_speed = 0;
316 	*speeds = *speeds_mem = NULL;
317 
318 	if (!get_mode_page(fd, 0x2A, 0, sizeof (p), p, &page_len)) {
319 		return;
320 	}
321 
322 	if (page_len > 8) {
323 		*read_speed = GET16(&p[8]);
324 	}
325 	if (page_len > 18) {
326 		*write_speed = GET16(&p[18]);
327 	}
328 	if (page_len < 28) {
329 		printf("MMC-2\n");
330 		return;
331 	} else {
332 		printf("MMC-3\n");
333 	}
334 
335 	*write_speed = GET16(&p[28]);
336 
337 	if (page_len < 30) {
338 		return;
339 	}
340 
341 	/* retrieve speed list */
342 	n = GET16(&p[30]);
343 	n = min(n, (sizeof (p) - 32) / 4);
344 
345 	get_write_speeds(&p[32], n, speeds, n_speeds, speeds_mem);
346 
347 	if (*speeds != NULL) {
348 		*write_speed = max(*write_speed, (*speeds)[0].val);
349 	}
350 }
351 
352 boolean_t
353 get_disc_info(int fd, disc_info_t *di)
354 {
355 	struct uscsi_cmd scmd;
356 	char cdb[16];
357 	uint8_t	buf[32];
358 	int bufsize = sizeof (buf);
359 
360 	bzero(buf, bufsize);
361 	uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
362 	scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
363 	scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
364 	scmd.uscsi_cdb[0] = 0x51; /* READ DISC INFORMATION */
365 	scmd.uscsi_cdb[7] = (bufsize >> 8) & 0xff; /* allocation length */
366 	scmd.uscsi_cdb[8] = bufsize & 0xff;
367 	scmd.uscsi_cdblen = 10;
368 	scmd.uscsi_bufaddr = (char *)buf;
369 	scmd.uscsi_buflen = bufsize;
370 
371 	if ((uscsi(fd, &scmd)) != 0) {
372 		return (B_FALSE);
373 	}
374 
375 	/*
376 	 * According to MMC-5 6.22.3.2, the Disc Information Length should be
377 	 * 32+8*(Number of OPC Tables). Some devices, like U3 sticks, return 0.
378 	 */
379 	if (GET16(&buf[0]) < 32) {
380 		return (B_FALSE);
381 	}
382 
383 	di->disc_status = buf[2] & 0x03;
384 	di->erasable = buf[2] & 0x10;
385 	if ((buf[21] != 0) && (buf[21] != 0xff)) {
386 		di->capacity = ((buf[21] * 60) + buf[22]) * 75;
387 	} else {
388 		di->capacity = 0;
389 	}
390 
391 	return (B_TRUE);
392 }
393 
394 /*
395  * returns current/maximum format capacity in bytes
396  */
397 boolean_t
398 read_format_capacity(int fd, uint64_t *capacity)
399 {
400 	struct uscsi_cmd scmd;
401 	char cdb[16];
402 	uint8_t	buf[32];
403 	int bufsize = sizeof (buf);
404 	uint32_t num_blocks;
405 	uint32_t block_len;
406 
407 	bzero(buf, bufsize);
408 	uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
409 	scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
410 	scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
411 	scmd.uscsi_cdb[0] = 0x23; /* READ FORMAT CAPACITIRES */
412 	scmd.uscsi_cdb[7] = (bufsize >> 8) & 0xff; /* allocation length */
413 	scmd.uscsi_cdb[8] = bufsize & 0xff;
414 	scmd.uscsi_cdblen = 12;
415 	scmd.uscsi_bufaddr = (char *)buf;
416 	scmd.uscsi_buflen = bufsize;
417 
418 	if ((uscsi(fd, &scmd)) != 0) {
419 		return (B_FALSE);
420 	}
421 
422 	num_blocks = (uint32_t)(buf[4] << 24) + (buf[5] << 16) + (buf[6] << 8) + buf[7];
423 	block_len = (uint32_t)(buf[9] << 16) + (buf[10] << 8) + buf[11];
424 	*capacity = (uint64_t)num_blocks * block_len;
425 
426 	return (B_TRUE);
427 }
428 
429 boolean_t
430 get_media_info(int fd, struct dk_minfo *minfop)
431 {
432 	return (ioctl(fd, DKIOCGMEDIAINFO, minfop) != -1);
433 }
434 
435 /*
436  * given current profile, use the best method for determining
437  * disc capacity (in bytes)
438  */
439 boolean_t
440 get_disc_capacity_for_profile(int fd, int profile, uint64_t *capacity)
441 {
442 	struct dk_minfo	mi;
443 	disc_info_t	di;
444 	boolean_t	ret = B_FALSE;
445 
446 	switch (profile) {
447 	case 0x08: /* CD-ROM */
448 	case 0x10: /* DVD-ROM */
449 		if (get_media_info(fd, &mi) && (mi.dki_capacity > 1)) {
450 			*capacity = mi.dki_capacity * mi.dki_lbsize;
451 			ret = B_TRUE;
452 		}
453 		break;
454 	default:
455 		if (read_format_capacity(fd, capacity) && (*capacity > 0)) {
456 			ret = B_TRUE;
457 		} else if (get_disc_info(fd, &di) && (di.capacity > 0)) {
458 			if (get_media_info(fd, &mi)) {
459 				*capacity = di.capacity * mi.dki_lbsize;
460 				ret = B_TRUE;
461 			}
462 		}
463 	}
464 
465 	return (ret);
466 }
467 
468 boolean_t
469 read_toc(int fd, int format, int trackno, int buflen, uchar_t *buf)
470 {
471 	struct uscsi_cmd scmd;
472 	char cdb[16];
473 
474 	bzero(buf, buflen);
475 	uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
476 	scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
477 	scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
478 	scmd.uscsi_cdb[0] = 0x43 /* READ_TOC_CMD */;
479 	scmd.uscsi_cdb[2] = format & 0xf;
480 	scmd.uscsi_cdb[6] = trackno;
481 	scmd.uscsi_cdb[8] = buflen & 0xff;
482 	scmd.uscsi_cdb[7] = (buflen >> 8) & 0xff;
483 	scmd.uscsi_cdblen = 10;
484 	scmd.uscsi_bufaddr = (char *)buf;
485 	scmd.uscsi_buflen = buflen;
486 
487 	if ((uscsi(fd, &scmd)) != 0) {
488         	return (B_FALSE);
489 	}
490 
491 	return (B_TRUE);
492 }
493