1N/A/***************************************************************************
1N/A *
1N/A * cdutils.c : CD/DVD utilities
1N/A *
1N/A * Copyright (c) 2006, 2011, Oracle and/or its affiliates. All rights reserved.
1N/A *
1N/A * This file is licensed under either the Academic Free License
1N/A * version 2.1 or The GNU General Public License version 2.
1N/A *
1N/A **************************************************************************/
1N/A
1N/A
1N/A#ifdef HAVE_CONFIG_H
1N/A# include <config.h>
1N/A#endif
1N/A
1N/A#include <stdio.h>
1N/A#include <sys/types.h>
1N/A#include <sys/scsi/impl/uscsi.h>
1N/A#include <string.h>
1N/A#include <strings.h>
1N/A#include <unistd.h>
1N/A#include <stdlib.h>
1N/A#include <errno.h>
1N/A#include <fcntl.h>
1N/A#include <sys/dkio.h>
1N/A#include <libintl.h>
1N/A
1N/A#include <logger.h>
1N/A
1N/A#include "cdutils.h"
1N/A
1N/A#define RQLEN 32
1N/A#define SENSE_KEY(rqbuf) (rqbuf[2]) /* scsi error category */
1N/A#define ASC(rqbuf) (rqbuf[12]) /* additional sense code */
1N/A#define ASCQ(rqbuf) (rqbuf[13]) /* ASC qualifier */
1N/A
1N/A#define GET16(a) (((a)[0] << 8) | (a)[1])
1N/A#define GET32(a) (((a)[0] << 24) | ((a)[1] << 16) | ((a)[2] << 8) | (a)[3])
1N/A
1N/A#define CD_USCSI_TIMEOUT 60
1N/A
1N/Avoid
1N/Auscsi_cmd_init(struct uscsi_cmd *scmd, char *cdb, int cdblen)
1N/A{
1N/A bzero(scmd, sizeof (*scmd));
1N/A bzero(cdb, cdblen);
1N/A scmd->uscsi_cdb = cdb;
1N/A}
1N/A
1N/Aint
1N/Auscsi(int fd, struct uscsi_cmd *scmd)
1N/A{
1N/A char rqbuf[RQLEN];
1N/A int ret;
1N/A int i, retries, total_retries;
1N/A int max_retries = 20;
1N/A
1N/A scmd->uscsi_flags |= USCSI_RQENABLE;
1N/A scmd->uscsi_rqlen = RQLEN;
1N/A scmd->uscsi_rqbuf = rqbuf;
1N/A
1N/A for (retries = 0; retries < max_retries; retries++) {
1N/A scmd->uscsi_status = 0;
1N/A memset(rqbuf, 0, RQLEN);
1N/A
1N/A ret = ioctl(fd, USCSICMD, scmd);
1N/A
1N/A if ((ret == 0) && (scmd->uscsi_status == 2)) {
1N/A ret = -1;
1N/A errno = EIO;
1N/A }
1N/A if ((ret < 0) && (scmd->uscsi_status == 2)) {
1N/A /*
1N/A * The drive is not ready to recieve commands but
1N/A * may be in the process of becoming ready.
1N/A * sleep for a short time then retry command.
1N/A * SENSE/ASC = 2/4 : not ready
1N/A * ASCQ = 0 Not Reportable.
1N/A * ASCQ = 1 Becoming ready.
1N/A * ASCQ = 4 FORMAT in progress.
1N/A * ASCQ = 7 Operation in progress.
1N/A */
1N/A if ((SENSE_KEY(rqbuf) == 2) && (ASC(rqbuf) == 4) &&
1N/A ((ASCQ(rqbuf) == 0) || (ASCQ(rqbuf) == 1) ||
1N/A (ASCQ(rqbuf) == 4)) || (ASCQ(rqbuf) == 7)) {
1N/A total_retries++;
1N/A sleep(1);
1N/A continue;
1N/A }
1N/A
1N/A /*
1N/A * Device is not ready to transmit or a device reset
1N/A * has occurred. wait for a short period of time then
1N/A * retry the command.
1N/A */
1N/A if ((SENSE_KEY(rqbuf) == 6) && ((ASC(rqbuf) == 0x28) ||
1N/A (ASC(rqbuf) == 0x29))) {
1N/A sleep(1);
1N/A total_retries++;
1N/A continue;
1N/A }
1N/A /*
1N/A * Blank Sense, we don't know what the error is or if
1N/A * the command succeeded, Hope for the best. Some
1N/A * drives return blank sense periodically and will
1N/A * fail if this is removed.
1N/A */
1N/A if ((SENSE_KEY(rqbuf) == 0) && (ASC(rqbuf) == 0) &&
1N/A (ASCQ(rqbuf) == 0)) {
1N/A ret = 0;
1N/A break;
1N/A }
1N/A
1N/A HAL_DEBUG (("cmd: 0x%02x ret:%i status:%02x "
1N/A " sense: %02x ASC: %02x ASCQ:%02x\n",
1N/A (uchar_t)scmd->uscsi_cdb[0], ret,
1N/A scmd->uscsi_status,
1N/A (uchar_t)SENSE_KEY(rqbuf),
1N/A (uchar_t)ASC(rqbuf), (uchar_t)ASCQ(rqbuf)));
1N/A }
1N/A
1N/A break;
1N/A }
1N/A
1N/A if (retries) {
1N/A HAL_DEBUG (("total retries: %d\n", total_retries));
1N/A }
1N/A
1N/A return (ret);
1N/A}
1N/A
1N/Aint
1N/Amode_sense(int fd, uchar_t pc, int dbd, int page_len, uchar_t *buffer)
1N/A{
1N/A struct uscsi_cmd scmd;
1N/A char cdb[16];
1N/A
1N/A uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
1N/A scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
1N/A scmd.uscsi_buflen = page_len;
1N/A scmd.uscsi_bufaddr = (char *)buffer;
1N/A scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
1N/A scmd.uscsi_cdblen = 0xa;
1N/A scmd.uscsi_cdb[0] = 0x5a; /* MODE SENSE 10 */
1N/A if (dbd) {
1N/A scmd.uscsi_cdb[1] = 0x8; /* no block descriptors */
1N/A }
1N/A scmd.uscsi_cdb[2] = pc;
1N/A scmd.uscsi_cdb[7] = (page_len >> 8) & 0xff;
1N/A scmd.uscsi_cdb[8] = page_len & 0xff;
1N/A
1N/A return (uscsi(fd, &scmd) == 0);
1N/A}
1N/A
1N/A/*
1N/A * will get the mode page only i.e. will strip off the header.
1N/A */
1N/Aint
1N/Aget_mode_page(int fd, int page_no, int pc, int buf_len, uchar_t *buffer, int *plen)
1N/A{
1N/A int ret;
1N/A uchar_t byte2;
1N/A uchar_t buf[256];
1N/A uint_t header_len, page_len, copy_cnt;
1N/A
1N/A byte2 = (uchar_t)(((pc << 6) & 0xC0) | (page_no & 0x3f));
1N/A
1N/A /* Ask 254 bytes only to make our IDE driver happy */
1N/A if ((ret = mode_sense(fd, byte2, 1, 254, buf)) == 0) {
1N/A return (0);
1N/A }
1N/A
1N/A header_len = 8 + GET16(&buf[6]);
1N/A page_len = buf[header_len + 1] + 2;
1N/A
1N/A copy_cnt = (page_len > buf_len) ? buf_len : page_len;
1N/A (void) memcpy(buffer, &buf[header_len], copy_cnt);
1N/A
1N/A if (plen) {
1N/A *plen = page_len;
1N/A }
1N/A
1N/A return (1);
1N/A}
1N/A
1N/A/* Get information about the Logical Unit's capabilities */
1N/Aint
1N/Aget_configuration(int fd, uint16_t feature, int bufsize, uchar_t *buf)
1N/A{
1N/A struct uscsi_cmd scmd;
1N/A char cdb[16];
1N/A
1N/A uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
1N/A scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
1N/A scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
1N/A scmd.uscsi_cdb[0] = 0x46; /* GET CONFIGURATION */
1N/A scmd.uscsi_cdb[1] = 0x2; /* request type */
1N/A scmd.uscsi_cdb[2] = (feature >> 8) & 0xff; /* starting feature # */
1N/A scmd.uscsi_cdb[3] = feature & 0xff;
1N/A scmd.uscsi_cdb[7] = (bufsize >> 8) & 0xff; /* allocation length */
1N/A scmd.uscsi_cdb[8] = bufsize & 0xff;
1N/A scmd.uscsi_cdblen = 10;
1N/A scmd.uscsi_bufaddr = (char *)buf;
1N/A scmd.uscsi_buflen = bufsize;
1N/A
1N/A return (uscsi(fd, &scmd) == 0);
1N/A}
1N/A
1N/Aboolean_t
1N/Aget_current_profile(int fd, int *profile)
1N/A{
1N/A size_t i;
1N/A uchar_t smallbuf[8];
1N/A size_t buflen;
1N/A uchar_t *bufp;
1N/A int ret = B_FALSE;
1N/A
1N/A /*
1N/A * first determine amount of memory needed to hold all profiles.
1N/A * The first four bytes of smallbuf concatenated tell us the
1N/A * number of bytes of memory we need but do not take themselves
1N/A * into account. Therefore, add four to allocate that number
1N/A * of bytes.
1N/A */
1N/A if (get_configuration(fd, 0, 8, &smallbuf[0])) {
1N/A buflen = GET32(smallbuf) + 4;
1N/A bufp = (uchar_t *)malloc(buflen);
1N/A
1N/A /* now get all profiles */
1N/A if (get_configuration(fd, 0, buflen, bufp)) {
1N/A *profile = GET16(&bufp[6]);
1N/A ret = B_TRUE;
1N/A }
1N/A free(bufp);
1N/A }
1N/A
1N/A return (ret);
1N/A}
1N/A
1N/Avoid
1N/Awalk_profiles(int fd, int (*f)(void *, int, boolean_t), void *arg)
1N/A{
1N/A size_t i;
1N/A uint16_t profile, current_profile;
1N/A uchar_t smallbuf[8];
1N/A size_t buflen;
1N/A uchar_t *bufp;
1N/A int ret;
1N/A
1N/A /*
1N/A * first determine amount of memory needed to hold all profiles.
1N/A * The first four bytes of smallbuf concatenated tell us the
1N/A * number of bytes of memory we need but do not take themselves
1N/A * into account. Therefore, add four to allocate that number
1N/A * of bytes.
1N/A */
1N/A if (get_configuration(fd, 0, 8, &smallbuf[0])) {
1N/A buflen = GET32(smallbuf) + 4;
1N/A bufp = (uchar_t *)malloc(buflen);
1N/A
1N/A /* now get all profiles */
1N/A if (get_configuration(fd, 0, buflen, bufp)) {
1N/A current_profile = GET16(&bufp[6]);
1N/A for (i = 8 + 4; i < buflen; i += 4) {
1N/A profile = GET16(&bufp[i]);
1N/A ret = f(arg, profile, (profile == current_profile));
1N/A if (ret == CDUTIL_WALK_STOP) {
1N/A break;
1N/A }
1N/A }
1N/A }
1N/A
1N/A free(bufp);
1N/A }
1N/A}
1N/A
1N/A/* retrieve speed list from the Write Speed Performance Descriptor Blocks
1N/A */
1N/Avoid
1N/Aget_write_speeds(uchar_t *page, int n, intlist_t **speeds, int *n_speeds, intlist_t **speeds_mem)
1N/A{
1N/A uchar_t *p = page + 2;
1N/A int i;
1N/A intlist_t **nextp;
1N/A intlist_t *current;
1N/A boolean_t skip;
1N/A
1N/A *n_speeds = 0;
1N/A *speeds = NULL;
1N/A *speeds_mem = (intlist_t *)calloc(n, sizeof (intlist_t));
1N/A if (*speeds_mem == NULL) {
1N/A return;
1N/A }
1N/A
1N/A for (i = 0; i < n; i++, p += 4) {
1N/A current = &(*speeds_mem)[i];
1N/A current->val = GET16(p);
1N/A
1N/A /* keep the list sorted */
1N/A skip = B_FALSE;
1N/A for (nextp = speeds; *nextp != NULL; nextp = &((*nextp)->next)) {
1N/A if (current->val == (*nextp)->val) {
1N/A skip = B_TRUE; /* skip duplicates */
1N/A break;
1N/A } else if (current->val > (*nextp)->val) {
1N/A break;
1N/A }
1N/A }
1N/A if (!skip) {
1N/A current->next = *nextp;
1N/A *nextp = current;
1N/A *n_speeds++;
1N/A }
1N/A }
1N/A}
1N/A
1N/Avoid
1N/Aget_read_write_speeds(int fd, int *read_speed, int *write_speed,
1N/A intlist_t **speeds, int *n_speeds, intlist_t **speeds_mem)
1N/A{
1N/A int page_len;
1N/A uchar_t p[254];
1N/A int n; /* number of write speed performance descriptor blocks */
1N/A
1N/A *read_speed = *write_speed = 0;
1N/A *speeds = *speeds_mem = NULL;
1N/A
1N/A if (!get_mode_page(fd, 0x2A, 0, sizeof (p), p, &page_len)) {
1N/A return;
1N/A }
1N/A
1N/A if (page_len > 8) {
1N/A *read_speed = GET16(&p[8]);
1N/A }
1N/A if (page_len > 18) {
1N/A *write_speed = GET16(&p[18]);
1N/A }
1N/A if (page_len < 28) {
1N/A printf("MMC-2\n");
1N/A return;
1N/A } else {
1N/A printf("MMC-3\n");
1N/A }
1N/A
1N/A *write_speed = GET16(&p[28]);
1N/A
1N/A if (page_len < 30) {
1N/A return;
1N/A }
1N/A
1N/A /* retrieve speed list */
1N/A n = GET16(&p[30]);
1N/A n = min(n, (sizeof (p) - 32) / 4);
1N/A
1N/A get_write_speeds(&p[32], n, speeds, n_speeds, speeds_mem);
1N/A
1N/A if (*speeds != NULL) {
1N/A *write_speed = max(*write_speed, (*speeds)[0].val);
1N/A }
1N/A}
1N/A
1N/Aboolean_t
1N/Aget_disc_info(int fd, disc_info_t *di)
1N/A{
1N/A struct uscsi_cmd scmd;
1N/A char cdb[16];
1N/A uint8_t buf[32];
1N/A int bufsize = sizeof (buf);
1N/A
1N/A bzero(buf, bufsize);
1N/A uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
1N/A scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
1N/A scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
1N/A scmd.uscsi_cdb[0] = 0x51; /* READ DISC INFORMATION */
1N/A scmd.uscsi_cdb[7] = (bufsize >> 8) & 0xff; /* allocation length */
1N/A scmd.uscsi_cdb[8] = bufsize & 0xff;
1N/A scmd.uscsi_cdblen = 10;
1N/A scmd.uscsi_bufaddr = (char *)buf;
1N/A scmd.uscsi_buflen = bufsize;
1N/A
1N/A if ((uscsi(fd, &scmd)) != 0) {
1N/A return (B_FALSE);
1N/A }
1N/A
1N/A /*
1N/A * According to MMC-5 6.22.3.2, the Disc Information Length should be
1N/A * 32+8*(Number of OPC Tables). Some devices, like U3 sticks, return 0.
1N/A * Yet some drives can return less than 32. We only need the first 22.
1N/A */
1N/A if (GET16(&buf[0]) < 22) {
1N/A return (B_FALSE);
1N/A }
1N/A
1N/A di->disc_status = buf[2] & 0x03;
1N/A di->erasable = buf[2] & 0x10;
1N/A if ((buf[21] != 0) && (buf[21] != 0xff)) {
1N/A di->capacity = ((buf[21] * 60) + buf[22]) * 75;
1N/A } else {
1N/A di->capacity = 0;
1N/A }
1N/A
1N/A return (B_TRUE);
1N/A}
1N/A
1N/A/*
1N/A * returns current/maximum format capacity in bytes
1N/A */
1N/Aboolean_t
1N/Aread_format_capacity(int fd, uint64_t *capacity)
1N/A{
1N/A struct uscsi_cmd scmd;
1N/A char cdb[16];
1N/A uint8_t buf[32];
1N/A int bufsize = sizeof (buf);
1N/A uint32_t num_blocks;
1N/A uint32_t block_len;
1N/A
1N/A bzero(buf, bufsize);
1N/A uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
1N/A scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
1N/A scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
1N/A scmd.uscsi_cdb[0] = 0x23; /* READ FORMAT CAPACITIRES */
1N/A scmd.uscsi_cdb[7] = (bufsize >> 8) & 0xff; /* allocation length */
1N/A scmd.uscsi_cdb[8] = bufsize & 0xff;
1N/A scmd.uscsi_cdblen = 12;
1N/A scmd.uscsi_bufaddr = (char *)buf;
1N/A scmd.uscsi_buflen = bufsize;
1N/A
1N/A if ((uscsi(fd, &scmd)) != 0) {
1N/A return (B_FALSE);
1N/A }
1N/A
1N/A num_blocks = (uint32_t)(buf[4] << 24) + (buf[5] << 16) + (buf[6] << 8) + buf[7];
1N/A block_len = (uint32_t)(buf[9] << 16) + (buf[10] << 8) + buf[11];
1N/A *capacity = (uint64_t)num_blocks * block_len;
1N/A
1N/A return (B_TRUE);
1N/A}
1N/A
1N/Aboolean_t
1N/Aget_media_info(int fd, struct dk_minfo *minfop)
1N/A{
1N/A return (ioctl(fd, DKIOCGMEDIAINFO, minfop) != -1);
1N/A}
1N/A
1N/A/*
1N/A * given current profile, use the best method for determining
1N/A * disc capacity (in bytes)
1N/A */
1N/Aboolean_t
1N/Aget_disc_capacity_for_profile(int fd, int profile, uint64_t *capacity)
1N/A{
1N/A struct dk_minfo mi;
1N/A disc_info_t di;
1N/A boolean_t ret = B_FALSE;
1N/A
1N/A switch (profile) {
1N/A case 0x08: /* CD-ROM */
1N/A case 0x10: /* DVD-ROM */
1N/A if (get_media_info(fd, &mi) && (mi.dki_capacity > 1)) {
1N/A *capacity = mi.dki_capacity * mi.dki_lbsize;
1N/A ret = B_TRUE;
1N/A }
1N/A break;
1N/A default:
1N/A if (read_format_capacity(fd, capacity) && (*capacity > 0)) {
1N/A ret = B_TRUE;
1N/A } else if (get_disc_info(fd, &di) && (di.capacity > 0)) {
1N/A if (get_media_info(fd, &mi)) {
1N/A *capacity = di.capacity * mi.dki_lbsize;
1N/A ret = B_TRUE;
1N/A }
1N/A }
1N/A }
1N/A
1N/A return (ret);
1N/A}
1N/A
1N/Aboolean_t
1N/Aread_toc(int fd, int format, int trackno, int buflen, uchar_t *buf)
1N/A{
1N/A struct uscsi_cmd scmd;
1N/A char cdb[16];
1N/A
1N/A bzero(buf, buflen);
1N/A uscsi_cmd_init(&scmd, cdb, sizeof (cdb));
1N/A scmd.uscsi_flags = USCSI_READ|USCSI_SILENT;
1N/A scmd.uscsi_timeout = CD_USCSI_TIMEOUT;
1N/A scmd.uscsi_cdb[0] = 0x43 /* READ_TOC_CMD */;
1N/A scmd.uscsi_cdb[2] = format & 0xf;
1N/A scmd.uscsi_cdb[6] = trackno;
1N/A scmd.uscsi_cdb[8] = buflen & 0xff;
1N/A scmd.uscsi_cdb[7] = (buflen >> 8) & 0xff;
1N/A scmd.uscsi_cdblen = 10;
1N/A scmd.uscsi_bufaddr = (char *)buf;
1N/A scmd.uscsi_buflen = buflen;
1N/A
1N/A if ((uscsi(fd, &scmd)) != 0) {
1N/A return (B_FALSE);
1N/A }
1N/A
1N/A return (B_TRUE);
1N/A}