xref: /illumos-gate/usr/src/cmd/hal/utils/cdutils.c (revision 8cd4c226)
1 /***************************************************************************
2  *
3  * cdutils.c : CD/DVD utilities
4  *
5  * Copyright 2009 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 
13 #ifdef HAVE_CONFIG_H
14 #  include <config.h>
15 #endif
16 
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>
28 
29 #include <logger.h>
30 
31 #include "cdutils.h"
32 
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 */
37 
38 #define	GET16(a) (((a)[0] << 8) | (a)[1])
39 #define	GET32(a) (((a)[0] << 24) | ((a)[1] << 16) | ((a)[2] << 8) | (a)[3])
40 
41 #define	CD_USCSI_TIMEOUT	60
42 
43 void
uscsi_cmd_init(struct uscsi_cmd * scmd,char * cdb,int cdblen)44 uscsi_cmd_init(struct uscsi_cmd *scmd, char *cdb, int cdblen)
45 {
46 	bzero(scmd, sizeof (*scmd));
47 	bzero(cdb, cdblen);
48 	scmd->uscsi_cdb = cdb;
49 }
50 
51 int
uscsi(int fd,struct uscsi_cmd * scmd)52 uscsi(int fd, struct uscsi_cmd *scmd)
53 {
54 	char		rqbuf[RQLEN];
55 	int		ret;
56 	int		i, retries, total_retries;
57 	int		max_retries = 20;
58 
59 	scmd->uscsi_flags |= USCSI_RQENABLE;
60 	scmd->uscsi_rqlen = RQLEN;
61 	scmd->uscsi_rqbuf = rqbuf;
62 
63 	for (retries = 0; retries < max_retries; retries++) {
64 		scmd->uscsi_status = 0;
65 		memset(rqbuf, 0, RQLEN);
66 
67 		ret = ioctl(fd, USCSICMD, scmd);
68 
69 		if ((ret == 0) && (scmd->uscsi_status == 2)) {
70 			ret = -1;
71 			errno = EIO;
72 		}
73 		if ((ret < 0) && (scmd->uscsi_status == 2)) {
74 			/*
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.
83 			 */
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;
90 			}
91 
92 			/*
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.
96 			 */
97 			if ((SENSE_KEY(rqbuf) == 6) && ((ASC(rqbuf) == 0x28) ||
98 			    (ASC(rqbuf) == 0x29))) {
99 				sleep(1);
100 				total_retries++;
101 				continue;
102 			}
103 			/*
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.
108 			 */
109 			if ((SENSE_KEY(rqbuf) == 0) && (ASC(rqbuf) == 0) &&
110 			    (ASCQ(rqbuf) == 0)) {
111 				ret = 0;
112 				break;
113 			}
114 
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)));
121 		}
122 
123 		break;
124 	}
125 
126 	if (retries) {
127 		HAL_DEBUG (("total retries: %d\n", total_retries));
128 	}
129 
130 	return (ret);
131 }
132 
133 int
mode_sense(int fd,uchar_t pc,int dbd,int page_len,uchar_t * buffer)134 mode_sense(int fd, uchar_t pc, int dbd, int page_len, uchar_t *buffer)
135 {
136 	struct uscsi_cmd scmd;
137 	char cdb[16];
138 
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 */
148 	}
149 	scmd.uscsi_cdb[2] = pc;
150 	scmd.uscsi_cdb[7] = (page_len >> 8) & 0xff;
151 	scmd.uscsi_cdb[8] = page_len & 0xff;
152 
153 	return (uscsi(fd, &scmd) == 0);
154 }
155 
156 /*
157  * will get the mode page only i.e. will strip off the header.
158  */
159 int
get_mode_page(int fd,int page_no,int pc,int buf_len,uchar_t * buffer,int * plen)160 get_mode_page(int fd, int page_no, int pc, int buf_len, uchar_t *buffer, int *plen)
161 {
162 	int ret;
163 	uchar_t byte2;
164 	uchar_t buf[256];
165 	uint_t header_len, page_len, copy_cnt;
166 
167 	byte2 = (uchar_t)(((pc << 6) & 0xC0) | (page_no & 0x3f));
168 
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);
172 	}
173 
174 	header_len = 8 + GET16(&buf[6]);
175 	page_len = buf[header_len + 1] + 2;
176 
177 	copy_cnt = (page_len > buf_len) ? buf_len : page_len;
178 	(void) memcpy(buffer, &buf[header_len], copy_cnt);
179 
180 	if (plen) {
181 		*plen = page_len;
182 	}
183 
184 	return (1);
185 }
186 
187 /* Get information about the Logical Unit's capabilities */
188 int
get_configuration(int fd,uint16_t feature,int bufsize,uchar_t * buf)189 get_configuration(int fd, uint16_t feature, int bufsize, uchar_t *buf)
190 {
191 	struct uscsi_cmd scmd;
192 	char cdb[16];
193 
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;
206 
207 	return (uscsi(fd, &scmd) == 0);
208 }
209 
210 boolean_t
get_current_profile(int fd,int * profile)211 get_current_profile(int fd, int *profile)
212 {
213 	size_t i;
214 	uchar_t smallbuf[8];
215 	size_t buflen;
216 	uchar_t *bufp;
217 	int ret = B_FALSE;
218 
219 	/*
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.
225 	 */
226 	if (get_configuration(fd, 0, 8, &smallbuf[0])) {
227 		buflen = GET32(smallbuf) + 4;
228 		bufp = (uchar_t *)malloc(buflen);
229 
230 	 	/* now get all profiles */
231 		if (get_configuration(fd, 0, buflen, bufp)) {
232 			*profile = GET16(&bufp[6]);
233 			ret = B_TRUE;
234 		}
235 		free(bufp);
236 	}
237 
238 	return (ret);
239 }
240 
241 void
walk_profiles(int fd,int (* f)(void *,int,boolean_t),void * arg)242 walk_profiles(int fd, int (*f)(void *, int, boolean_t), void *arg)
243 {
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;
250 
251 	/*
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.
257 	 */
258 	if (get_configuration(fd, 0, 8, &smallbuf[0])) {
259 		buflen = GET32(smallbuf) + 4;
260 		bufp = (uchar_t *)malloc(buflen);
261 
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;
270 				}
271 			}
272 		}
273 
274 		free(bufp);
275 	}
276 }
277 
278 /* retrieve speed list from the Write Speed Performance Descriptor Blocks
279  */
280 void
get_write_speeds(uchar_t * page,int n,intlist_t ** speeds,int * n_speeds,intlist_t ** speeds_mem)281 get_write_speeds(uchar_t *page, int n, intlist_t **speeds, int *n_speeds, intlist_t **speeds_mem)
282 {
283 	uchar_t	*p = page + 2;
284 	int	i;
285 	intlist_t **nextp;
286 	intlist_t *current;
287 	boolean_t skip;
288 
289 	*n_speeds = 0;
290 	*speeds = NULL;
291 	*speeds_mem = (intlist_t *)calloc(n, sizeof (intlist_t));
292 	if (*speeds_mem == NULL) {
293 		return;
294 	}
295 
296 	for (i = 0; i < n; i++, p += 4) {
297 		current = &(*speeds_mem)[i];
298 		current->val = GET16(p);
299 
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;
308 			}
309 		}
310 		if (!skip) {
311 			current->next = *nextp;
312 			*nextp = current;
313 			*n_speeds++;
314 		}
315 	}
316 }
317 
318 void
get_read_write_speeds(int fd,int * read_speed,int * write_speed,intlist_t ** speeds,int * n_speeds,intlist_t ** speeds_mem)319 get_read_write_speeds(int fd, int *read_speed, int *write_speed,
320     intlist_t **speeds, int *n_speeds, intlist_t **speeds_mem)
321 {
322 	int page_len;
323 	uchar_t	p[254];
324 	int n; /* number of write speed performance descriptor blocks */
325 
326 	*read_speed = *write_speed = 0;
327 	*speeds = *speeds_mem = NULL;
328 
329 	if (!get_mode_page(fd, 0x2A, 0, sizeof (p), p, &page_len)) {
330 		return;
331 	}
332 
333 	if (page_len > 8) {
334 		*read_speed = GET16(&p[8]);
335 	}
336 	if (page_len > 18) {
337 		*write_speed = GET16(&p[18]);
338 	}
339 	if (page_len < 28) {
340 		printf("MMC-2\n");
341 		return;
342 	} else {
343 		printf("MMC-3\n");
344 	}
345 
346 	*write_speed = GET16(&p[28]);
347 
348 	if (page_len < 30) {
349 		return;
350 	}
351 
352 	/* retrieve speed list */
353 	n = GET16(&p[30]);
354 	n = min(n, (sizeof (p) - 32) / 4);
355 
356 	get_write_speeds(&p[32], n, speeds, n_speeds, speeds_mem);
357 
358 	if (*speeds != NULL) {
359 		*write_speed = max(*write_speed, (*speeds)[0].val);
360 	}
361 }
362 
363 boolean_t
get_disc_info(int fd,disc_info_t * di)364 get_disc_info(int fd, disc_info_t *di)
365 {
366 	struct uscsi_cmd scmd;
367 	char cdb[16];
368 	uint8_t	buf[32];
369 	int bufsize = sizeof (buf);
370 
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;
381 
382 	if ((uscsi(fd, &scmd)) != 0) {
383 		return (B_FALSE);
384 	}
385 
386 	/*
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.
390 	 */
391 	if (GET16(&buf[0]) < 22) {
392 		return (B_FALSE);
393 	}
394 
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;
401 	}
402 
403 	return (B_TRUE);
404 }
405 
406 /*
407  * returns current/maximum format capacity in bytes
408  */
409 boolean_t
read_format_capacity(int fd,uint64_t * capacity)410 read_format_capacity(int fd, uint64_t *capacity)
411 {
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;
418 
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;
429 
430 	if ((uscsi(fd, &scmd)) != 0) {
431 		return (B_FALSE);
432 	}
433 
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;
437 
438 	return (B_TRUE);
439 }
440 
441 boolean_t
get_media_info(int fd,struct dk_minfo * minfop)442 get_media_info(int fd, struct dk_minfo *minfop)
443 {
444 	return (ioctl(fd, DKIOCGMEDIAINFO, minfop) != -1);
445 }
446 
447 /*
448  * given current profile, use the best method for determining
449  * disc capacity (in bytes)
450  */
451 boolean_t
get_disc_capacity_for_profile(int fd,int profile,uint64_t * capacity)452 get_disc_capacity_for_profile(int fd, int profile, uint64_t *capacity)
453 {
454 	struct dk_minfo	mi;
455 	disc_info_t	di;
456 	boolean_t	ret = B_FALSE;
457 
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;
464 		}
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;
473 			}
474 		}
475 	}
476 
477 	return (ret);
478 }
479 
480 boolean_t
read_toc(int fd,int format,int trackno,int buflen,uchar_t * buf)481 read_toc(int fd, int format, int trackno, int buflen, uchar_t *buf)
482 {
483 	struct uscsi_cmd scmd;
484 	char cdb[16];
485 
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;
498 
499 	if ((uscsi(fd, &scmd)) != 0) {
500         	return (B_FALSE);
501 	}
502 
503 	return (B_TRUE);
504 }
505