diff options
Diffstat (limited to 'contrib/libcdio/scsi_mmc.c')
-rw-r--r-- | contrib/libcdio/scsi_mmc.c | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/contrib/libcdio/scsi_mmc.c b/contrib/libcdio/scsi_mmc.c new file mode 100644 index 000000000..9b4a456a5 --- /dev/null +++ b/contrib/libcdio/scsi_mmc.c @@ -0,0 +1,589 @@ +/* Common SCSI Multimedia Command (MMC) routines. + + $Id: scsi_mmc.c,v 1.1 2005/01/01 02:43:57 rockyb Exp $ + + Copyright (C) 2004 Rocky Bernstein <rocky@panix.com> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <cdio/cdio.h> +#include <cdio/logging.h> +#include <cdio/scsi_mmc.h> +#include "cdio_private.h" + +#ifdef HAVE_STRING_H +#include <string.h> +#endif + +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif + +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif + +/*! + On input a MODE_SENSE command was issued and we have the results + in p. We interpret this and return a bit mask set according to the + capabilities. + */ +void +scsi_mmc_get_drive_cap_buf(const uint8_t *p, + /*out*/ cdio_drive_read_cap_t *p_read_cap, + /*out*/ cdio_drive_write_cap_t *p_write_cap, + /*out*/ cdio_drive_misc_cap_t *p_misc_cap) +{ + /* Reader */ + if (p[2] & 0x01) *p_read_cap |= CDIO_DRIVE_CAP_READ_CD_R; + if (p[2] & 0x02) *p_read_cap |= CDIO_DRIVE_CAP_READ_CD_RW; + if (p[2] & 0x08) *p_read_cap |= CDIO_DRIVE_CAP_READ_DVD_ROM; + if (p[4] & 0x01) *p_read_cap |= CDIO_DRIVE_CAP_READ_AUDIO; + if (p[5] & 0x01) *p_read_cap |= CDIO_DRIVE_CAP_READ_CD_DA; + if (p[5] & 0x10) *p_read_cap |= CDIO_DRIVE_CAP_READ_C2_ERRS; + + /* Writer */ + if (p[3] & 0x01) *p_write_cap |= CDIO_DRIVE_CAP_WRITE_CD_R; + if (p[3] & 0x02) *p_write_cap |= CDIO_DRIVE_CAP_WRITE_CD_RW; + if (p[3] & 0x10) *p_write_cap |= CDIO_DRIVE_CAP_WRITE_DVD_R; + if (p[3] & 0x20) *p_write_cap |= CDIO_DRIVE_CAP_WRITE_DVD_RAM; + if (p[4] & 0x80) *p_misc_cap |= CDIO_DRIVE_CAP_WRITE_BURN_PROOF; + + /* Misc */ + if (p[4] & 0x40) *p_misc_cap |= CDIO_DRIVE_CAP_MISC_MULTI_SESSION; + if (p[6] & 0x01) *p_misc_cap |= CDIO_DRIVE_CAP_MISC_LOCK; + if (p[6] & 0x08) *p_misc_cap |= CDIO_DRIVE_CAP_MISC_EJECT; + if (p[6] >> 5 != 0) + *p_misc_cap |= CDIO_DRIVE_CAP_MISC_CLOSE_TRAY; +} + +/*! + Return the number of length in bytes of the Command Descriptor + buffer (CDB) for a given SCSI MMC command. The length will be + either 6, 10, or 12. +*/ +uint8_t +scsi_mmc_get_cmd_len(uint8_t scsi_cmd) +{ + static const uint8_t scsi_cdblen[8] = {6, 10, 10, 12, 12, 12, 10, 10}; + return scsi_cdblen[((scsi_cmd >> 5) & 7)]; +} + +/*! + Run a SCSI MMC command. + + cdio CD structure set by cdio_open(). + i_timeout time in milliseconds we will wait for the command + to complete. If this value is -1, use the default + time-out value. + buf Buffer for data, both sending and receiving + len Size of buffer + e_direction direction the transfer is to go + cdb CDB bytes. All values that are needed should be set on + input. We'll figure out what the right CDB length should be. + + We return 0 if command completed successfully and 1 if not. + */ +int +scsi_mmc_run_cmd( const CdIo *p_cdio, unsigned int i_timeout_ms, + const scsi_mmc_cdb_t *p_cdb, + scsi_mmc_direction_t e_direction, unsigned int i_buf, + /*in/out*/ void *p_buf ) +{ + if (p_cdio && p_cdio->op.run_scsi_mmc_cmd) { + return p_cdio->op.run_scsi_mmc_cmd(p_cdio->env, i_timeout_ms, + scsi_mmc_get_cmd_len(p_cdb->field[0]), + p_cdb, e_direction, i_buf, p_buf); + } else + return 1; +} + +#define DEFAULT_TIMEOUT_MS 6000 + +/*! + * Eject using SCSI MMC commands. Return 0 if successful. + */ +int +scsi_mmc_eject_media( const CdIo *cdio ) +{ + int i_status; + scsi_mmc_cdb_t cdb = {{0, }}; + uint8_t buf[1]; + scsi_mmc_run_cmd_fn_t run_scsi_mmc_cmd; + + if ( ! cdio || ! cdio->op.run_scsi_mmc_cmd ) + return -2; + + run_scsi_mmc_cmd = cdio->op.run_scsi_mmc_cmd; + + CDIO_MMC_SET_COMMAND(cdb.field, CDIO_MMC_GPCMD_ALLOW_MEDIUM_REMOVAL); + + i_status = run_scsi_mmc_cmd (cdio->env, DEFAULT_TIMEOUT_MS, + scsi_mmc_get_cmd_len(cdb.field[0]), &cdb, + SCSI_MMC_DATA_WRITE, 0, &buf); + if (0 != i_status) + return i_status; + + CDIO_MMC_SET_COMMAND(cdb.field, CDIO_MMC_GPCMD_START_STOP); + cdb.field[4] = 1; + i_status = run_scsi_mmc_cmd (cdio->env, DEFAULT_TIMEOUT_MS, + scsi_mmc_get_cmd_len(cdb.field[0]), &cdb, + SCSI_MMC_DATA_WRITE, 0, &buf); + if (0 != i_status) + return i_status; + + CDIO_MMC_SET_COMMAND(cdb.field, CDIO_MMC_GPCMD_START_STOP); + cdb.field[4] = 2; /* eject */ + + return run_scsi_mmc_cmd (cdio->env, DEFAULT_TIMEOUT_MS, + scsi_mmc_get_cmd_len(cdb.field[0]), &cdb, + SCSI_MMC_DATA_WRITE, 0, &buf); + +} + +/*! Packet driver to read mode2 sectors. + Can read only up to 25 blocks. +*/ +int +scsi_mmc_read_sectors ( const CdIo *cdio, void *p_buf, lba_t lba, + int sector_type, unsigned int nblocks ) +{ + scsi_mmc_cdb_t cdb = {{0, }}; + + scsi_mmc_run_cmd_fn_t run_scsi_mmc_cmd; + + if ( ! cdio || ! cdio->op.run_scsi_mmc_cmd ) + return -2; + + run_scsi_mmc_cmd = cdio->op.run_scsi_mmc_cmd; + + CDIO_MMC_SET_COMMAND(cdb.field, CDIO_MMC_GPCMD_READ_CD); + CDIO_MMC_SET_READ_TYPE (cdb.field, sector_type); + CDIO_MMC_SET_READ_LBA (cdb.field, lba); + CDIO_MMC_SET_READ_LENGTH24(cdb.field, nblocks); + CDIO_MMC_SET_MAIN_CHANNEL_SELECTION_BITS(cdb.field, + CDIO_MMC_MCSB_ALL_HEADERS); + + return run_scsi_mmc_cmd (cdio->env, DEFAULT_TIMEOUT_MS, + scsi_mmc_get_cmd_len(cdb.field[0]), &cdb, + SCSI_MMC_DATA_READ, + CDIO_CD_FRAMESIZE_RAW * nblocks, + p_buf); +} + +int +scsi_mmc_set_blocksize_private ( const void *p_env, + const scsi_mmc_run_cmd_fn_t run_scsi_mmc_cmd, + unsigned int bsize) +{ + scsi_mmc_cdb_t cdb = {{0, }}; + + struct + { + uint8_t reserved1; + uint8_t medium; + uint8_t reserved2; + uint8_t block_desc_length; + uint8_t density; + uint8_t number_of_blocks_hi; + uint8_t number_of_blocks_med; + uint8_t number_of_blocks_lo; + uint8_t reserved3; + uint8_t block_length_hi; + uint8_t block_length_med; + uint8_t block_length_lo; + } mh; + + if ( ! p_env || ! run_scsi_mmc_cmd ) + return -2; + + memset (&mh, 0, sizeof (mh)); + mh.block_desc_length = 0x08; + mh.block_length_hi = (bsize >> 16) & 0xff; + mh.block_length_med = (bsize >> 8) & 0xff; + mh.block_length_lo = (bsize >> 0) & 0xff; + + CDIO_MMC_SET_COMMAND(cdb.field, CDIO_MMC_GPCMD_MODE_SELECT_6); + + cdb.field[1] = 1 << 4; + cdb.field[4] = 12; + + return run_scsi_mmc_cmd (p_env, DEFAULT_TIMEOUT_MS, + scsi_mmc_get_cmd_len(cdb.field[0]), &cdb, + SCSI_MMC_DATA_WRITE, sizeof(mh), &mh); +} + +int +scsi_mmc_set_blocksize ( const CdIo *cdio, unsigned int bsize) +{ + if ( ! cdio ) return -2; + return + scsi_mmc_set_blocksize_private (cdio->env, cdio->op.run_scsi_mmc_cmd, + bsize); +} + + +/*! + Return the the kind of drive capabilities of device. + */ +void +scsi_mmc_get_drive_cap_private (const void *p_env, + const scsi_mmc_run_cmd_fn_t run_scsi_mmc_cmd, + /*out*/ cdio_drive_read_cap_t *p_read_cap, + /*out*/ cdio_drive_write_cap_t *p_write_cap, + /*out*/ cdio_drive_misc_cap_t *p_misc_cap) +{ + /* Largest buffer size we use. */ +#define BUF_MAX 2048 + uint8_t buf[BUF_MAX] = { 0, }; + + scsi_mmc_cdb_t cdb = {{0, }}; + int i_status; + uint16_t i_data = BUF_MAX; + + if ( ! p_env || ! run_scsi_mmc_cmd ) + return; + + CDIO_MMC_SET_COMMAND(cdb.field, CDIO_MMC_GPCMD_MODE_SENSE_10); + cdb.field[1] = 0x0; + cdb.field[2] = CDIO_MMC_ALL_PAGES; + + retry: + CDIO_MMC_SET_READ_LENGTH16(cdb.field, 8); + + /* In the first run we run MODE SENSE 10 we are trying to get the + length of the data features. */ + i_status = run_scsi_mmc_cmd (p_env, DEFAULT_TIMEOUT_MS, + scsi_mmc_get_cmd_len(cdb.field[0]), + &cdb, SCSI_MMC_DATA_READ, + sizeof(buf), &buf); + if (0 == i_status) { + uint16_t i_data_try = (uint16_t) CDIO_MMC_GET_LEN16(buf); + if (i_data_try < BUF_MAX) i_data = i_data_try; + } + + /* Now try getting all features with length set above, possibly + truncated or the default length if we couldn't get the proper + length. */ + CDIO_MMC_SET_READ_LENGTH16(cdb.field, i_data); + + i_status = run_scsi_mmc_cmd (p_env, DEFAULT_TIMEOUT_MS, + scsi_mmc_get_cmd_len(cdb.field[0]), + &cdb, SCSI_MMC_DATA_READ, + sizeof(buf), &buf); + + if (0 != i_status && CDIO_MMC_CAPABILITIES_PAGE != cdb.field[2]) { + cdb.field[2] = CDIO_MMC_CAPABILITIES_PAGE; + goto retry; + } + + if (0 == i_status) { + uint8_t *p; + uint8_t *p_max = buf + 256; + + *p_read_cap = 0; + *p_write_cap = 0; + *p_misc_cap = 0; + + /* set to first sense mask, and then walk through the masks */ + p = buf + 8; + while( (p < &(buf[2+i_data])) && (p < p_max) ) { + uint8_t which_page; + + which_page = p[0] & 0x3F; + switch( which_page ) + { + case CDIO_MMC_AUDIO_CTL_PAGE: + case CDIO_MMC_R_W_ERROR_PAGE: + case CDIO_MMC_CDR_PARMS_PAGE: + /* Don't handle these yet. */ + break; + case CDIO_MMC_CAPABILITIES_PAGE: + scsi_mmc_get_drive_cap_buf(p, p_read_cap, p_write_cap, p_misc_cap); + break; + default: ; + } + p += (p[1] + 2); + } + } else { + cdio_info("%s: %s\n", "error in MODE_SELECT", strerror(errno)); + *p_read_cap = CDIO_DRIVE_CAP_ERROR; + *p_write_cap = CDIO_DRIVE_CAP_ERROR; + *p_misc_cap = CDIO_DRIVE_CAP_ERROR; + } + return; +} + +void +scsi_mmc_get_drive_cap (const CdIo *p_cdio, + /*out*/ cdio_drive_read_cap_t *p_read_cap, + /*out*/ cdio_drive_write_cap_t *p_write_cap, + /*out*/ cdio_drive_misc_cap_t *p_misc_cap) +{ + if ( ! p_cdio ) return; + scsi_mmc_get_drive_cap_private (p_cdio->env, + p_cdio->op.run_scsi_mmc_cmd, + p_read_cap, p_write_cap, p_misc_cap); +} + +void +scsi_mmc_get_drive_cap_generic (const void *p_user_data, + /*out*/ cdio_drive_read_cap_t *p_read_cap, + /*out*/ cdio_drive_write_cap_t *p_write_cap, + /*out*/ cdio_drive_misc_cap_t *p_misc_cap) +{ + const generic_img_private_t *p_env = p_user_data; + scsi_mmc_get_drive_cap( p_env->cdio, + p_read_cap, p_write_cap, p_misc_cap ); +} + + +/*! + Get the DVD type associated with cd object. +*/ +discmode_t +scsi_mmc_get_dvd_struct_physical_private ( void *p_env, const + scsi_mmc_run_cmd_fn_t run_scsi_mmc_cmd, + cdio_dvd_struct_t *s) +{ + scsi_mmc_cdb_t cdb = {{0, }}; + unsigned char buf[4 + 4 * 20], *base; + int i_status; + uint8_t layer_num = s->physical.layer_num; + + cdio_dvd_layer_t *layer; + + if ( ! p_env || ! run_scsi_mmc_cmd ) + return -2; + + if (layer_num >= CDIO_DVD_MAX_LAYERS) + return -EINVAL; + + CDIO_MMC_SET_COMMAND(cdb.field, CDIO_MMC_GPCMD_READ_DVD_STRUCTURE); + cdb.field[6] = layer_num; + cdb.field[7] = CDIO_DVD_STRUCT_PHYSICAL; + cdb.field[9] = sizeof(buf) & 0xff; + + i_status = run_scsi_mmc_cmd(p_env, DEFAULT_TIMEOUT_MS, + scsi_mmc_get_cmd_len(cdb.field[0]), + &cdb, SCSI_MMC_DATA_READ, + sizeof(buf), &buf); + if (0 != i_status) + return CDIO_DISC_MODE_ERROR; + + base = &buf[4]; + layer = &s->physical.layer[layer_num]; + + /* + * place the data... really ugly, but at least we won't have to + * worry about endianess in userspace. + */ + memset(layer, 0, sizeof(*layer)); + layer->book_version = base[0] & 0xf; + layer->book_type = base[0] >> 4; + layer->min_rate = base[1] & 0xf; + layer->disc_size = base[1] >> 4; + layer->layer_type = base[2] & 0xf; + layer->track_path = (base[2] >> 4) & 1; + layer->nlayers = (base[2] >> 5) & 3; + layer->track_density = base[3] & 0xf; + layer->linear_density = base[3] >> 4; + layer->start_sector = base[5] << 16 | base[6] << 8 | base[7]; + layer->end_sector = base[9] << 16 | base[10] << 8 | base[11]; + layer->end_sector_l0 = base[13] << 16 | base[14] << 8 | base[15]; + layer->bca = base[16] >> 7; + + return 0; +} + + +/*! + Get the DVD type associated with cd object. +*/ +discmode_t +scsi_mmc_get_dvd_struct_physical ( const CdIo *p_cdio, cdio_dvd_struct_t *s) +{ + if ( ! p_cdio ) return -2; + return + scsi_mmc_get_dvd_struct_physical_private (p_cdio->env, + p_cdio->op.run_scsi_mmc_cmd, + s); +} + +/*! + Get the CD-ROM hardware info via a SCSI MMC INQUIRY command. + False is returned if we had an error getting the information. +*/ +bool +scsi_mmc_get_hwinfo ( const CdIo *p_cdio, + /*out*/ cdio_hwinfo_t *hw_info ) +{ + int i_status; /* Result of SCSI MMC command */ + char buf[36] = { 0, }; /* Place to hold returned data */ + scsi_mmc_cdb_t cdb = {{0, }}; /* Command Descriptor Block */ + + CDIO_MMC_SET_COMMAND(cdb.field, CDIO_MMC_GPCMD_INQUIRY); + cdb.field[4] = sizeof(buf); + + if (! p_cdio || ! hw_info ) return false; + + i_status = scsi_mmc_run_cmd(p_cdio, DEFAULT_TIMEOUT_MS, + &cdb, SCSI_MMC_DATA_READ, + sizeof(buf), &buf); + if (i_status == 0) { + + memcpy(hw_info->psz_vendor, + buf + 8, + sizeof(hw_info->psz_vendor)-1); + hw_info->psz_vendor[sizeof(hw_info->psz_vendor)-1] = '\0'; + memcpy(hw_info->psz_model, + buf + 8 + CDIO_MMC_HW_VENDOR_LEN, + sizeof(hw_info->psz_model)-1); + hw_info->psz_model[sizeof(hw_info->psz_model)-1] = '\0'; + memcpy(hw_info->psz_revision, + buf + 8 + CDIO_MMC_HW_VENDOR_LEN + CDIO_MMC_HW_MODEL_LEN, + sizeof(hw_info->psz_revision)-1); + hw_info->psz_revision[sizeof(hw_info->psz_revision)-1] = '\0'; + return true; + } + return false; +} + +/*! + Return the media catalog number MCN. + + Note: string is malloc'd so caller should free() then returned + string when done with it. + + */ +char * +scsi_mmc_get_mcn_private ( void *p_env, + const scsi_mmc_run_cmd_fn_t run_scsi_mmc_cmd + ) +{ + scsi_mmc_cdb_t cdb = {{0, }}; + char buf[28] = { 0, }; + int i_status; + + if ( ! p_env || ! run_scsi_mmc_cmd ) + return NULL; + + CDIO_MMC_SET_COMMAND(cdb.field, CDIO_MMC_GPCMD_READ_SUBCHANNEL); + cdb.field[1] = 0x0; + cdb.field[2] = 0x40; + cdb.field[3] = CDIO_SUBCHANNEL_MEDIA_CATALOG; + CDIO_MMC_SET_READ_LENGTH16(cdb.field, sizeof(buf)); + + i_status = run_scsi_mmc_cmd(p_env, DEFAULT_TIMEOUT_MS, + scsi_mmc_get_cmd_len(cdb.field[0]), + &cdb, SCSI_MMC_DATA_READ, + sizeof(buf), buf); + if(i_status == 0) { + return strdup(&buf[9]); + } + return NULL; +} + +char * +scsi_mmc_get_mcn ( const CdIo *p_cdio ) +{ + if ( ! p_cdio ) return NULL; + return scsi_mmc_get_mcn_private (p_cdio->env, + p_cdio->op.run_scsi_mmc_cmd ); +} + +char * +scsi_mmc_get_mcn_generic (const void *p_user_data) +{ + const generic_img_private_t *p_env = p_user_data; + return scsi_mmc_get_mcn( p_env->cdio ); +} + +/* + Read cdtext information for a CdIo object . + + return true on success, false on error or CD-Text information does + not exist. +*/ +bool +scsi_mmc_init_cdtext_private ( void *p_user_data, + const scsi_mmc_run_cmd_fn_t run_scsi_mmc_cmd, + set_cdtext_field_fn_t set_cdtext_field_fn + ) +{ + + generic_img_private_t *p_env = p_user_data; + scsi_mmc_cdb_t cdb = {{0, }}; + unsigned char wdata[5000] = { 0, }; + int i_status, i_errno; + + if ( ! p_env || ! run_scsi_mmc_cmd || p_env->b_cdtext_error ) + return false; + + /* Operation code */ + CDIO_MMC_SET_COMMAND(cdb.field, CDIO_MMC_GPCMD_READ_TOC); + + cdb.field[1] = CDIO_CDROM_MSF; + /* Format */ + cdb.field[2] = CDIO_MMC_READTOC_FMT_CDTEXT; + + /* Setup to read header, to get length of data */ + CDIO_MMC_SET_READ_LENGTH16(cdb.field, 4); + + errno = 0; + +/* Set read timeout 3 minues. */ +#define READ_TIMEOUT 3*60*1000 + + /* We may need to give CD-Text a little more time to complete. */ + /* First off, just try and read the size */ + i_status = run_scsi_mmc_cmd (p_env, READ_TIMEOUT, + scsi_mmc_get_cmd_len(cdb.field[0]), + &cdb, SCSI_MMC_DATA_READ, + 4, &wdata); + + if (i_status != 0) { + cdio_info ("CD-Text read failed for header: %s\n", strerror(errno)); + i_errno = errno; + p_env->b_cdtext_error = true; + return false; + } else { + /* Now read the CD-Text data */ + int i_cdtext = CDIO_MMC_GET_LEN16(wdata); + + if (i_cdtext > sizeof(wdata)) i_cdtext = sizeof(wdata); + + CDIO_MMC_SET_READ_LENGTH16(cdb.field, i_cdtext); + i_status = run_scsi_mmc_cmd (p_env, READ_TIMEOUT, + scsi_mmc_get_cmd_len(cdb.field[0]), + &cdb, SCSI_MMC_DATA_READ, + i_cdtext, &wdata); + if (i_status != 0) { + cdio_info ("CD-Text read for text failed: %s\n", strerror(errno)); + i_errno = errno; + p_env->b_cdtext_error = true; + return false; + } + p_env->b_cdtext_init = true; + return cdtext_data_init(p_env, p_env->i_first_track, wdata, + set_cdtext_field_fn); + } +} + |