|
|
|
/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */
|
|
|
|
|
|
|
|
/* Copyright (c) 2004 - 2006 Derek Foreman, Ben Jansens
|
|
|
|
Copyright (c) 2006 - 2014 Thomas Schmitt <scdbackup@gmx.net>
|
|
|
|
Provided under GPL version 2 or later.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* scsi primary commands */
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "../config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
/* ts A61008 */
|
|
|
|
/* #include <a ssert.h> */
|
|
|
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
|
|
|
#include "libburn.h"
|
|
|
|
#include "transport.h"
|
|
|
|
#include "spc.h"
|
|
|
|
#include "mmc.h"
|
|
|
|
#include "sbc.h"
|
|
|
|
#include "drive.h"
|
|
|
|
#include "debug.h"
|
|
|
|
#include "options.h"
|
|
|
|
#include "init.h"
|
|
|
|
#include "util.h"
|
|
|
|
|
|
|
|
#include "libdax_msgs.h"
|
|
|
|
extern struct libdax_msgs *libdax_messenger;
|
|
|
|
|
|
|
|
/* ts A91111 :
|
|
|
|
whether to log SCSI commands:
|
|
|
|
bit0= log in /tmp/libburn_sg_command_log
|
|
|
|
bit1= log to stderr
|
|
|
|
bit2= flush every line
|
|
|
|
*/
|
|
|
|
extern int burn_sg_log_scsi;
|
|
|
|
|
|
|
|
|
|
|
|
/* spc command set */
|
|
|
|
/* ts A70519 : allocation length byte 3+4 was 0,255 */
|
|
|
|
static unsigned char SPC_INQUIRY[] = { 0x12, 0, 0, 0, 36, 0 };
|
|
|
|
|
|
|
|
/*static char SPC_TEST[]={0,0,0,0,0,0};*/
|
|
|
|
static unsigned char SPC_PREVENT[] = { 0x1e, 0, 0, 0, 1, 0 };
|
|
|
|
static unsigned char SPC_ALLOW[] = { 0x1e, 0, 0, 0, 0, 0 };
|
|
|
|
static unsigned char SPC_MODE_SENSE[] = { 0x5a, 0, 0, 0, 0, 0, 0, 16, 0, 0 };
|
|
|
|
static unsigned char SPC_MODE_SELECT[] = { 0x55, 16, 0, 0, 0, 0, 0, 0, 0, 0 };
|
|
|
|
static unsigned char SPC_REQUEST_SENSE[] = { 0x03, 0, 0, 0, 18, 0 };
|
|
|
|
static unsigned char SPC_TEST_UNIT_READY[] = { 0x00, 0, 0, 0, 0, 0 };
|
|
|
|
|
|
|
|
|
|
|
|
/* ts A70519 : An initializer for the abstract SCSI command structure */
|
|
|
|
int scsi_init_command(struct command *c, unsigned char *opcode, int oplen)
|
|
|
|
{
|
|
|
|
if (oplen > 16)
|
|
|
|
return 0;
|
|
|
|
memset(c, 0, sizeof(struct command));
|
|
|
|
memcpy(c->opcode, opcode, oplen);
|
|
|
|
c->oplen = oplen;
|
|
|
|
c->dir = NO_TRANSFER;
|
|
|
|
c->dxfer_len = -1;
|
|
|
|
memset(c->sense, 0, sizeof(c->sense));
|
|
|
|
c->error = 0;
|
|
|
|
c->retry = 0;
|
|
|
|
c->page = NULL;
|
|
|
|
c->timeout = Libburn_scsi_default_timeouT;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ts B00728 */
|
|
|
|
int spc_decode_sense(unsigned char *sense, int senselen,
|
|
|
|
int *key, int *asc, int *ascq)
|
|
|
|
{
|
|
|
|
*key = *asc = *ascq = 0;
|
|
|
|
if ((sense[0] & 0x7f) == 0x72 || (sense[0] & 0x7f) == 0x73) {
|
|
|
|
if (senselen <= 0 || senselen > 1)
|
|
|
|
*key = sense[1] & 0x0f;
|
|
|
|
if (senselen <= 0 || senselen > 2)
|
|
|
|
*asc = sense[2];
|
|
|
|
if (senselen <= 0 || senselen > 3)
|
|
|
|
*ascq = sense[3];
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (senselen <= 0 || senselen > 2)
|
|
|
|
*key = sense[2] & 0x0f;
|
|
|
|
if (senselen <= 0 || senselen > 12)
|
|
|
|
*asc = sense[12];
|
|
|
|
if (senselen <= 0 || senselen > 13)
|
|
|
|
*ascq = sense[13];
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int spc_test_unit_ready_r(struct burn_drive *d, int *key, int *asc, int *ascq,
|
|
|
|
int *progress)
|
|
|
|
{
|
|
|
|
struct command *c;
|
|
|
|
|
|
|
|
c = &(d->casual_command);
|
|
|
|
if (mmc_function_spy(d, "test_unit_ready") <= 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
scsi_init_command(c, SPC_TEST_UNIT_READY,sizeof(SPC_TEST_UNIT_READY));
|
|
|
|
c->retry = 0;
|
|
|
|
c->dir = NO_TRANSFER;
|
|
|
|
d->issue_command(d, c);
|
|
|
|
*key = *asc = *ascq = 0;
|
|
|
|
*progress = -1;
|
|
|
|
if (c->error) {
|
|
|
|
spc_decode_sense(c->sense, 0, key, asc, ascq);
|
|
|
|
if (c->sense[0] == 0x70 &&
|
|
|
|
((c->sense[2] & 0x0f) == 0 || (c->sense[2] & 0x0f) == 2) &&
|
|
|
|
(c->sense[15] & 0x80))
|
|
|
|
*progress = (c->sense[16] << 8) + c->sense[17];
|
|
|
|
return (key == 0);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int spc_test_unit_ready(struct burn_drive *d)
|
|
|
|
{
|
|
|
|
int key, asc, ascq, progress;
|
|
|
|
|
|
|
|
return spc_test_unit_ready_r(d, &key, &asc, &ascq, &progress);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ts A70315 */
|
|
|
|
/** @param flag bit0=do not wait 0.1 seconds before first test unit ready
|
|
|
|
bit1=do not issue success message
|
|
|
|
*/
|
|
|
|
/** Wait until the drive state becomes clear or until max_usec elapsed */
|
|
|
|
int spc_wait_unit_attention(struct burn_drive *d, int max_sec, char *cmd_text,
|
|
|
|
int flag)
|
|
|
|
{
|
|
|
|
int i, ret = 1, key = 0, asc = 0, ascq = 0, clueless_start = 0;
|
|
|
|
static double tests_per_second = 2.0;
|
|
|
|
int sleep_usecs, loop_limit, clueless_timeout, progress;
|
|
|
|
char *msg = NULL;
|
|
|
|
unsigned char sense[14];
|
|
|
|
|
|
|
|
BURN_ALLOC_MEM(msg, char, 320);
|
|
|
|
clueless_timeout = 5 * tests_per_second + 1;
|
|
|
|
loop_limit = max_sec * tests_per_second + 1;
|
|
|
|
sleep_usecs = 1000000 / tests_per_second;
|
|
|
|
|
|
|
|
if (!(flag & 1))
|
|
|
|
usleep(sleep_usecs);
|
|
|
|
|
|
|
|
for(i = !(flag & 1); i < loop_limit; i++) {
|
|
|
|
ret = spc_test_unit_ready_r(d, &key, &asc, &ascq, &progress);
|
|
|
|
if (ret > 0) /* ready */
|
|
|
|
break;
|
|
|
|
if (key!=0x2 || asc!=0x4) {
|
|
|
|
if (key == 0x2 && asc == 0x3A) {
|
|
|
|
ret = 1; /* medium not present = ok */
|
|
|
|
/* <<<
|
|
|
|
ts A70912 :
|
|
|
|
My LG GSA-4082B on asynchronous load:
|
|
|
|
first it reports no media 2,3A,00,
|
|
|
|
then it reports not ready 2,04,00,
|
|
|
|
further media inquiry retrieves wrong data
|
|
|
|
|
|
|
|
if(i<=100)
|
|
|
|
goto slumber;
|
|
|
|
*/
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (key == 0x6 && asc == 0x28 && ascq == 0x00)
|
|
|
|
/* media change notice = try again */
|
|
|
|
goto slumber;
|
|
|
|
|
|
|
|
handle_error:;
|
|
|
|
/* ts A90213 */
|
|
|
|
sprintf(msg,
|
|
|
|
"Asynchronous SCSI error on %s: ", cmd_text);
|
|
|
|
sense[0] = 0x70; /* Fixed format sense data */
|
|
|
|
sense[2] = key;
|
|
|
|
sense[12] = asc;
|
|
|
|
sense[13] = ascq;
|
|
|
|
scsi_error_msg(d, sense, 14, msg + strlen(msg),
|
|
|
|
&key, &asc, &ascq);
|
|
|
|
libdax_msgs_submit(libdax_messenger, d->global_index,
|
|
|
|
0x0002014d,
|
|
|
|
LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
|
|
|
|
msg, 0, 0);
|
|
|
|
d->cancel = 1;
|
|
|
|
break;
|
|
|
|
} else if (ascq == 0x00) { /* CAUSE NOT REPORTABLE */
|
|
|
|
/* Might be a clueless system adapter */
|
|
|
|
if (clueless_start == 0)
|
|
|
|
clueless_start = i;
|
|
|
|
if (i - clueless_start > clueless_timeout) {
|
|
|
|
libdax_msgs_submit(libdax_messenger,
|
|
|
|
d->global_index,
|
|
|
|
0x00000002,
|
|
|
|
LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_HIGH,
|
|
|
|
"Ended clueless NOT READY cycle",
|
|
|
|
0, 0);
|
|
|
|
ret = 1; /* medium not present = ok */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else if (ascq == 0x02 || ascq == 0x03)
|
|
|
|
goto handle_error;
|
|
|
|
|
|
|
|
slumber:;
|
|
|
|
usleep(sleep_usecs);
|
|
|
|
}
|
|
|
|
if (ret <= 0 || !(flag & 2)) {
|
|
|
|
sprintf(msg, "Async %s %s after %d.%d seconds",
|
|
|
|
cmd_text, (ret > 0 ? "succeeded" : "failed"),
|
|
|
|
i / 10, i % 10);
|
|
|
|
libdax_msgs_submit(libdax_messenger, d->global_index,
|
|
|
|
0x00020150, LIBDAX_MSGS_SEV_DEBUG,
|
|
|
|
LIBDAX_MSGS_PRIO_LOW, msg, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i < max_sec * 10)
|
|
|
|
{ret = (ret > 0); goto ex;}
|
|
|
|
|
|
|
|
sprintf(msg, "Timeout (%d s) with asynchronous SCSI command %s\n",
|
|
|
|
max_sec, cmd_text);
|
|
|
|
libdax_msgs_submit(libdax_messenger, d->global_index, 0x0002014f,
|
|
|
|
LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH, msg, 0, 0);
|
|
|
|
ret = 0;
|
|
|
|
ex:;
|
|
|
|
BURN_FREE_MEM(msg);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void spc_request_sense(struct burn_drive *d, struct buffer *buf)
|
|
|
|
{
|
|
|
|
struct command *c;
|
|
|
|
|
|
|
|
c = &(d->casual_command);
|
|
|
|
if (mmc_function_spy(d, "request_sense") <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
scsi_init_command(c, SPC_REQUEST_SENSE, sizeof(SPC_REQUEST_SENSE));
|
|
|
|
c->retry = 0;
|
|
|
|
c->dxfer_len= c->opcode[4];
|
|
|
|
c->retry = 0;
|
|
|
|
c->page = buf;
|
|
|
|
c->page->sectors = 0;
|
|
|
|
c->page->bytes = 0;
|
|
|
|
c->dir = FROM_DRIVE;
|
|
|
|
d->issue_command(d, c);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int spc_report_async_error(struct burn_drive *d,
|
|
|
|
int key, int asc, int ascq, int flag)
|
|
|
|
{
|
|
|
|
char *msg = NULL;
|
|
|
|
unsigned char sense[14];
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
BURN_ALLOC_MEM(msg, char, BURN_DRIVE_ADR_LEN + 160);
|
|
|
|
|
|
|
|
sprintf(msg, "Asynchronous SCSI error : ");
|
|
|
|
sense[0] = 0x70; /* Fixed format sense data */
|
|
|
|
sense[2] = key;
|
|
|
|
sense[12] = asc;
|
|
|
|
sense[13] = ascq;
|
|
|
|
scsi_error_msg(d, sense, 14, msg + strlen(msg), &key, &asc, &ascq);
|
|
|
|
libdax_msgs_submit(libdax_messenger, d->global_index,
|
|
|
|
0x000201a5,
|
|
|
|
LIBDAX_MSGS_SEV_FAILURE, LIBDAX_MSGS_PRIO_HIGH,
|
|
|
|
msg, 0, 0);
|
|
|
|
ret = 1;
|
|
|
|
ex:;
|
|
|
|
BURN_FREE_MEM(msg);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* @return -3 = other error reported
|
|
|
|
-2 = drive is ready ,
|
|
|
|
-1 = not ready, but no progress reported ,
|
|
|
|
>= 0 progress indication between 0 and 65535
|
|
|
|
*/
|
|
|
|
int spc_get_erase_progress(struct burn_drive *d)
|
|
|
|
{
|
|
|
|
struct buffer *b = NULL;
|
|
|
|
int ret, key, asc, ascq, progress;
|
|
|
|
|
|
|
|
if (mmc_function_spy(d, "get_erase_progress") <= 0)
|
|
|
|
{ret = 0; goto ex;}
|
|
|
|
|
|
|
|
/* ts B20104 :
|
|
|
|
TEST UNIT READY seems to be more reliable than REQUEST SENSE.
|
|
|
|
Nevertheless growisofs still uses the latter as fallback.
|
|
|
|
*/
|
|
|
|
ret = spc_test_unit_ready_r(d, &key, &asc, &ascq, &progress);
|
|
|
|
if (ret > 0)
|
|
|
|
{ret = -2; goto ex;}
|
|
|
|
|
|
|
|
/* Check key, asc, ascq for errors other than "not yet ready" */
|
|
|
|
if (key != 0 &&
|
|
|
|
(key != 0x2 || asc != 0x04 || ascq == 0x02 || ascq ==0x03)) {
|
|
|
|
spc_report_async_error(d, key, asc, ascq, 0);
|
|
|
|
ret= -3; goto ex;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (progress >= 0)
|
|
|
|
{ret = progress; goto ex;}
|
|
|
|
|
|
|
|
/* Fallback to request sense */
|
|
|
|
BURN_ALLOC_MEM(b, struct buffer, 1);
|
|
|
|
spc_request_sense(d, b);
|
|
|
|
|
|
|
|
/* Checking the preconditions as of SPC-3 4.5.2.4.4 and 4.5.3 */
|
|
|
|
ret = -1;
|
|
|
|
if (b->data[0] == 0x70 &&
|
|
|
|
((b->data[2] & 0x0f) == 0 || (b->data[2] & 0x0f) == 2) &&
|
|
|
|
(b->data[15] & 0x80))
|
|
|
|
ret = (b->data[16] << 8) | b->data[17];
|
|
|
|
ex:;
|
|
|
|
BURN_FREE_MEM(b);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void spc_inquiry(struct burn_drive *d)
|
|
|
|
{
|
|
|
|
struct buffer *buf = NULL;
|
|
|
|
struct burn_scsi_inquiry_data *id;
|
|
|
|
struct command *c = NULL;
|
|
|
|
|
|
|
|
if (mmc_function_spy(d, "inquiry") <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
BURN_ALLOC_MEM_VOID(buf, struct buffer, 1);
|
|
|
|
BURN_ALLOC_MEM_VOID(c, struct command, 1);
|
|
|
|
scsi_init_command(c, SPC_INQUIRY, sizeof(SPC_INQUIRY));
|
|
|
|
c->dxfer_len = (c->opcode[3] << 8) | c->opcode[4];
|
|
|
|
c->retry = 1;
|
|
|
|
c->page = buf;
|
|
|
|
c->page->bytes = 0;
|
|
|
|
c->page->sectors = 0;
|
|
|
|
c->dir = FROM_DRIVE;
|
|
|
|
d->issue_command(d, c);
|
|
|
|
id = (struct burn_scsi_inquiry_data *)d->idata;
|
|
|
|
id->peripheral = 0x7f; /* SPC-3: incabable undefined peripheral type */
|
|
|
|
id->version = 0; /* SPC-3: no claim for conformance */
|
|
|
|
memset(id->vendor, 0, 9);
|
|
|
|
memset(id->product, 0, 17);
|
|
|
|
memset(id->revision, 0, 5);
|
|
|
|
if (c->error) {
|
|
|
|
id->valid = -1;
|
|
|
|
goto ex;
|
|
|
|
}
|
|
|
|
id->peripheral = ((char *) c->page->data)[0];
|
|
|
|
id->version = ((char *) c->page->data)[2];
|
|
|
|
memcpy(id->vendor, c->page->data + 8, 8);
|
|
|
|
memcpy(id->product, c->page->data + 16, 16);
|
|
|
|
memcpy(id->revision, c->page->data + 32, 4);
|
|
|
|
id->valid = 1;
|
|
|
|
ex:;
|
|
|
|
BURN_FREE_MEM(buf);
|
|
|
|
BURN_FREE_MEM(c);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void spc_prevent(struct burn_drive *d)
|
|
|
|
{
|
|
|
|
struct command *c;
|
|
|
|
|
|
|
|
c = &(d->casual_command);
|
|
|
|
if (mmc_function_spy(d, "prevent") <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
scsi_init_command(c, SPC_PREVENT, sizeof(SPC_PREVENT));
|
|
|
|
c->retry = 1;
|
|
|
|
c->dir = NO_TRANSFER;
|
|
|
|
d->issue_command(d, c);
|
|
|
|
|
|
|
|
#ifdef Libburn_pioneer_dvr_216d_get_evenT
|
|
|
|
mmc_get_event(d);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
void spc_allow(struct burn_drive *d)
|
|
|
|
{
|
|
|
|
struct command *c;
|
|
|
|
|
|
|
|
c = &(d->casual_command);
|
|
|
|
if (mmc_function_spy(d, "allow") <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
scsi_init_command(c, SPC_ALLOW, sizeof(SPC_ALLOW));
|
|
|
|
c->retry = 1;
|
|
|
|
c->dir = NO_TRANSFER;
|
|
|
|
d->issue_command(d, c);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
ts A70518 - A90603 : Do not call with *alloc_len < 10
|
|
|
|
*/
|
|
|
|
/** @param flag bit0= do only inquire alloc_len
|
|
|
|
@return 1=ok , <=0 error ,
|
|
|
|
2=Block Descriptor Length > 0, retry with flag bit1
|
|
|
|
*/
|
|
|
|
static int spc_sense_caps_al(struct burn_drive *d, int *alloc_len, int flag)
|
|
|
|
{
|
|
|
|
struct buffer *buf = NULL;
|
|
|
|
struct scsi_mode_data *m;
|
|
|
|
int page_length, num_write_speeds = 0, i, speed, ret;
|
|
|
|
int old_alloc_len, was_error = 0, block_descr_len;
|
|
|
|
unsigned char *page;
|
|
|
|
struct command *c = NULL;
|
|
|
|
struct burn_speed_descriptor *sd;
|
|
|
|
char *msg = NULL;
|
|
|
|
struct burn_feature_descr *feature_descr;
|
|
|
|
|
|
|
|
/* ts A61225 : 1 = report about post-MMC-1 speed descriptors */
|
|
|
|
static int speed_debug = 0;
|
|
|
|
|
|
|
|
if (*alloc_len < 10)
|
|
|
|
{ret = 0; goto ex;}
|
|
|
|
|
|
|
|
BURN_ALLOC_MEM(msg, char, BURN_DRIVE_ADR_LEN + 160);
|
|
|
|
BURN_ALLOC_MEM(buf, struct buffer, 1);
|
|
|
|
BURN_ALLOC_MEM(c, struct command, 1);
|
|
|
|
|
|
|
|
/* ts A90602 : Clearing mdata before command execution */
|
|
|
|
m = d->mdata;
|
|
|
|
m->p2a_valid = 0;
|
|
|
|
burn_mdata_free_subs(m);
|
|
|
|
|
|
|
|
memset(buf, 0, sizeof(struct buffer));
|
|
|
|
scsi_init_command(c, SPC_MODE_SENSE, sizeof(SPC_MODE_SENSE));
|
|
|
|
c->dxfer_len = *alloc_len;
|
|
|
|
c->opcode[7] = (c->dxfer_len >> 8) & 0xff;
|
|
|
|
c->opcode[8] = c->dxfer_len & 0xff;
|
|
|
|
c->retry = 1;
|
|
|
|
c->opcode[2] = 0x2A;
|
|
|
|
c->page = buf;
|
|
|
|
c->page->bytes = 0;
|
|
|
|
c->page->sectors = 0;
|
|
|
|
c->dir = FROM_DRIVE;
|
|
|
|
d->issue_command(d, c);
|
|
|
|
if (c->error) {
|
|
|
|
memset(buf, 0, sizeof(struct buffer));
|
|
|
|
m->p2a_valid = -1;
|
|
|
|
was_error = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* ts B11103 : qemu SCSI CD-ROM has Block Descriptor Length > 0.
|
|
|
|
The descriptors come between header and page.
|
|
|
|
*/
|
|
|
|
block_descr_len = c->page->data[6] * 256 + c->page->data[7];
|
|
|
|
|
|
|
|
if (block_descr_len + 8 + 2 > *alloc_len) {
|
|
|
|
if (block_descr_len + 8 + 2 > BUFFER_SIZE || !(flag & 1)) {
|
|
|
|
m->p2a_valid = -1;
|
|
|
|
sprintf(msg,
|
|
|
|
"MODE SENSE page 2A with oversized Block Descriptors: %s : %d",
|
|
|
|
d->devname, block_descr_len);
|
|
|
|
libdax_msgs_submit(libdax_messenger, d->global_index,
|
|
|
|
0x0002016e, LIBDAX_MSGS_SEV_DEBUG,
|
|
|
|
LIBDAX_MSGS_PRIO_LOW, msg, 0, 0);
|
|
|
|
{ret = 0; goto ex;}
|
|
|
|
|
|
|
|
}
|
|
|
|
*alloc_len = block_descr_len + 10;
|
|
|
|
{ret = 2; goto ex;}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip over Mode Data Header and block descriptors */
|
|
|
|
page = c->page->data + 8 + block_descr_len;
|
|
|
|
|
|
|
|
/* ts A61225 :
|
|
|
|
Although MODE SENSE indeed belongs to SPC, the returned code page
|
|
|
|
2Ah is part of MMC-1 to MMC-3. In MMC-1 5.2.3.4. it has 22 bytes,
|
|
|
|
in MMC-3 6.3.11 there are at least 28 bytes plus a variable length
|
|
|
|
set of speed descriptors. In MMC-5 E.11 it is declared "legacy".
|
|
|
|
ts B11031 :
|
|
|
|
qemu emulates an ATAPI DVD-ROM, which delivers only a page length
|
|
|
|
of 18. This is now tolerated.
|
|
|
|
*/
|
|
|
|
/* ts A90603 :
|
|
|
|
SPC-1 8.3.3 enumerates mode page format bytes from 0 to n and
|
|
|
|
defines Page Length as (n-1).
|
|
|
|
*/
|
|
|
|
page_length = page[1];
|
|
|
|
old_alloc_len = *alloc_len;
|
|
|
|
*alloc_len = page_length + 10 + block_descr_len;
|
|
|
|
if (flag & 1)
|
|
|
|
{ret = !was_error; goto ex;}
|
|
|
|
if (page_length + 10 > old_alloc_len)
|
|
|
|
page_length = old_alloc_len - 10;
|
|
|
|
|
|
|
|
/* ts A90602 : page_length N asserts page[N+1]. (see SPC-1 8.3.3) */
|
|
|
|
/* ts B11031 : qemu drive has a page_length of 18 */
|
|
|
|
if (page_length < 18) {
|
|
|
|
m->p2a_valid = -1;
|
|
|
|
sprintf(msg, "MODE SENSE page 2A too short: %s : %d",
|
|
|
|
d->devname, page_length);
|
|
|
|
libdax_msgs_submit(libdax_messenger, d->global_index,
|
|
|
|
0x0002016e, LIBDAX_MSGS_SEV_DEBUG,
|
|
|
|
LIBDAX_MSGS_PRIO_LOW, msg, 0, 0);
|
|
|
|
{ret = 0; goto ex;}
|
|
|
|
}
|
|
|
|
|
|
|
|
m->buffer_size = page[12] * 256 + page[13];
|
|
|
|
m->dvdram_read = page[2] & 32;
|
|
|
|
m->dvdram_write = page[3] & 32;
|
|
|
|
m->dvdr_read = page[2] & 16;
|
|
|
|
m->dvdr_write = page[3] & 16;
|
|
|
|
m->dvdrom_read = page[2] & 8;
|
|
|
|
m->simulate = page[3] & 4;
|
|
|
|
m->cdrw_read = page[2] & 2;
|
|
|
|
m->cdrw_write = page[3] & 2;
|
|
|
|
m->cdr_read = page[2] & 1;
|
|
|
|
m->cdr_write = page[3] & 1;
|
|
|
|
|
|
|
|
m->c2_pointers = page[5] & 16;
|
|
|
|
m->underrun_proof = page[4] & 128;
|
|
|
|
|
|
|
|
/* ts A61021 : these fields are marked obsolete in MMC 3 */
|
|
|
|
m->max_read_speed = page[8] * 256 + page[9];
|
|
|
|
m->cur_read_speed = page[14] * 256 + page[15];
|
|
|
|
|
|
|
|
m->max_write_speed = m->cur_write_speed = 0;
|
|
|
|
if (page_length >= 18) /* note: page length is page size - 2 */
|
|
|
|
m->max_write_speed = page[18] * 256 + page[19];
|
|
|
|
if (page_length >= 20)
|
|
|
|
m->cur_write_speed = page[20] * 256 + page[21];
|
|
|
|
|
|
|
|
/* ts A61021 : New field to be set by atip (or following MMC-3 info) */
|
|
|
|
m->min_write_speed = m->max_write_speed;
|
|
|
|
|
|
|
|
/* ts A61225 : for ACh GET PERFORMANCE, Type 03h */
|
|
|
|
m->min_end_lba = 0x7fffffff;
|
|
|
|
m->max_end_lba = 0;
|
|
|
|
|
|
|
|
if (!was_error)
|
|
|
|
m->p2a_valid = 1;
|
|
|
|
|
|
|
|
/* ts A61225 : end of MMC-1 , begin of MMC-3 */
|
|
|
|
if (page_length < 30) /* no write speed descriptors ? */
|
|
|
|
goto try_mmc_get_performance;
|
|
|
|
|
|
|
|
m->cur_write_speed = page[28] * 256 + page[29];
|
|
|
|
|
|
|
|
if (speed_debug)
|
|
|
|
fprintf(stderr, "LIBBURN_DEBUG: cur_write_speed = %d\n",
|
|
|
|
m->cur_write_speed);
|
|
|
|
|
|
|
|
num_write_speeds = page[30] * 256 + page[31];
|
|
|
|
m->max_write_speed = m->min_write_speed = m->cur_write_speed;
|
|
|
|
|
|
|
|
if (32 + 4 * num_write_speeds > page_length + 2) {
|
|
|
|
sprintf(msg, "Malformed capabilities page 2Ah received (len=%d, #speeds=%d)", page_length, num_write_speeds);
|
|
|
|
libdax_msgs_submit(libdax_messenger, d->global_index,
|
|
|
|
0x0002013c,
|
|
|
|
LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH,
|
|
|
|
msg, 0, 0);
|
|
|
|
{ret = 0; goto ex;}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < num_write_speeds; i++) {
|
|
|
|
speed = page[32 + 4 * i + 2] * 256 + page[32 + 4 * i + 3];
|
|
|
|
|
|
|
|
if (speed_debug)
|
|
|
|
fprintf(stderr,
|
|
|
|
"LIBBURN_DEBUG: write speed #%d = %d kB/s (rc %d)\n",
|
|
|
|
i, speed, page[32 + 4 * i + 1] & 7);
|
|
|
|
|
|
|
|
/* ts A61226 */
|
|
|
|
ret = burn_speed_descriptor_new(&(d->mdata->speed_descriptors),
|
|
|
|
NULL, d->mdata->speed_descriptors, 0);
|
|
|
|
if (ret > 0) {
|
|
|
|
sd = d->mdata->speed_descriptors;
|
|
|
|
sd->source = 1;
|
|
|
|
if (d->current_profile > 0) {
|
|
|
|
sd->profile_loaded = d->current_profile;
|
|
|
|
strcpy(sd->profile_name,
|
|
|
|
d->current_profile_text);
|
|
|
|
}
|
|
|
|
sd->wrc = (( page[32 + 4 * i + 1] & 7 ) == 1 );
|
|
|
|
sd->write_speed = speed;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (speed > m->max_write_speed)
|
|
|
|
m->max_write_speed = speed;
|
|
|
|
if (speed < m->min_write_speed)
|
|
|
|
m->min_write_speed = speed;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (speed_debug)
|
|
|
|
fprintf(stderr,
|
|
|
|
"LIBBURN_DEBUG: 5Ah,2Ah min_write_speed = %d , max_write_speed = %d\n",
|
|
|
|
m->min_write_speed, m->max_write_speed);
|
|
|
|
|
|
|
|
try_mmc_get_performance:;
|
|
|
|
/* ts B40107 : Feature 0x107 announces availability of GET PERFORMANCE
|
|
|
|
Its WSPD bit announces Type 3.
|
|
|
|
Try this even if the feature is not current.
|
|
|
|
*/
|
|
|
|
ret = burn_drive_has_feature(d, 0x107, &feature_descr, 0);
|
|
|
|
if (ret > 0) {
|
|
|
|
if (feature_descr->data_lenght > 0) {
|
|
|
|
if (feature_descr->data[0] & 2) { /* WSPD */
|
|
|
|
ret = mmc_get_write_performance(d);
|
|
|
|
if (ret > 0 && speed_debug)
|
|
|
|
fprintf(stderr,
|
|
|
|
"LIBBURN_DEBUG: ACh min_write_speed = %d , max_write_speed = %d\n",
|
|
|
|
m->min_write_speed,
|
|
|
|
m->max_write_speed);
|
|
|
|
}
|
|
|
|
/* Get read performance */
|
|
|
|
mmc_get_performance(d, 0x00, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ret = !was_error;
|
|
|
|
ex:
|
|
|
|
BURN_FREE_MEM(msg);
|
|
|
|
BURN_FREE_MEM(buf);
|
|
|
|
BURN_FREE_MEM(c);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void spc_sense_caps(struct burn_drive *d)
|
|
|
|
{
|
|
|
|
int alloc_len, start_len = 30, minimum_len = 28, ret;
|
|
|
|
|
|
|
|
mmc_start_if_needed(d, 1);
|
|
|
|
if (mmc_function_spy(d, "sense_caps") <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
mmc_get_configuration(d);
|
|
|
|
|
|
|
|
/* first command execution to learn Allocation Length */
|
|
|
|
alloc_len = start_len;
|
|
|
|
ret = spc_sense_caps_al(d, &alloc_len, 1);
|
|
|
|
if (ret == 2) {
|
|
|
|
/* ts B40205: Unexpectedly found Block Descriptors.
|
|
|
|
Repeat with new alloc_len.
|
|
|
|
*/
|
|
|
|
ret = spc_sense_caps_al(d, &alloc_len, 1);
|
|
|
|
if (ret == 2)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* ts B11103:
|
|
|
|
qemu ATAPI DVD-ROM delivers only 28.
|
|
|
|
SanDisk Cruzer U3 memory stick throws error on alloc_len < 30.
|
|
|
|
MMC-1 prescribes that 30 are available. qemu tolerates 30.
|
|
|
|
*/
|
|
|
|
if (alloc_len >= minimum_len && ret > 0)
|
|
|
|
/* second execution with announced length */
|
|
|
|
spc_sense_caps_al(d, &alloc_len, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void spc_sense_error_params(struct burn_drive *d)
|
|
|
|
{
|
|
|
|
struct buffer *buf = NULL;
|
|
|
|
struct scsi_mode_data *m;
|
|
|
|
int alloc_len = 12 ;
|
|
|
|
unsigned char *page;
|
|
|
|
struct command *c = NULL;
|
|
|
|
|
|
|
|
mmc_start_if_needed(d, 1);
|
|
|
|
if (mmc_function_spy(d, "sense_error_params") <= 0)
|
|
|
|
goto ex;
|
|
|
|
|
|
|
|
BURN_ALLOC_MEM_VOID(buf, struct buffer, 1);
|
|
|
|
BURN_ALLOC_MEM_VOID(c, struct command, 1);
|
|
|
|
|
|
|
|
scsi_init_command(c, SPC_MODE_SENSE, sizeof(SPC_MODE_SENSE));
|
|
|
|
c->dxfer_len = alloc_len;
|
|
|
|
c->opcode[7] = (c->dxfer_len >> 8) & 0xff;
|
|
|
|
c->opcode[8] = c->dxfer_len & 0xff;
|
|
|
|
c->retry = 1;
|
|
|
|
c->opcode[2] = 0x01;
|
|
|
|
c->page = buf;
|
|
|
|
c->page->bytes = 0;
|
|
|
|
c->page->sectors = 0;
|
|
|
|
c->dir = FROM_DRIVE;
|
|
|
|
d->issue_command(d, c);
|
|
|
|
m = d->mdata;
|
|
|
|
page = c->page->data + 8;
|
|
|
|
d->params.retries = page[3];
|
|
|
|
m->retry_page_length = page[1];
|
|
|
|
m->retry_page_valid = 1;
|
|
|
|
ex:;
|
|
|
|
BURN_FREE_MEM(buf);
|
|
|
|
BURN_FREE_MEM(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
void spc_select_error_params(struct burn_drive *d,
|
|
|
|
const struct burn_read_opts *o)
|
|
|
|
{
|
|
|
|
|