You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1910 lines
48 KiB

16 years ago
/* -*- 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.
*/
16 years ago
/* scsi primary commands */
#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif
16 years ago
#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> */
16 years ago
#include <stdlib.h>
#include "libburn.h"
16 years ago
#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"
16 years ago
#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;
16 years ago
/* spc command set */
/* ts A70519 : allocation length byte 3+4 was 0,255 */
static unsigned char SPC_INQUIRY[] = { 0x12, 0, 0, 0, 36, 0 };
16 years ago
/*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 };
16 years ago
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)
16 years ago
{
struct command *c;
16 years ago
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;
16 years ago
}
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;
}
16 years ago
void spc_request_sense(struct burn_drive *d, struct buffer *buf)
{
struct command *c;
16 years ago
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);
16 years ago
}
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
*/
16 years ago
int spc_get_erase_progress(struct burn_drive *d)
{
struct buffer *b = NULL;
int ret, key, asc, ascq, progress;
16 years ago
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;
16 years ago
}
void spc_inquiry(struct burn_drive *d)
{
struct buffer *buf = NULL;
struct burn_scsi_inquiry_data *id;
struct command *c = NULL;
16 years ago
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);
16 years ago
id->valid = 1;
ex:;
BURN_FREE_MEM(buf);
BURN_FREE_MEM(c);
16 years ago
return;
}
void spc_prevent(struct burn_drive *d)
{
struct command *c;
16 years ago
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
16 years ago
}
void spc_allow(struct burn_drive *d)
{
struct command *c;
16 years ago
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);
16 years ago
}
/*
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)
16 years ago
{
struct buffer *buf = NULL;
16 years ago
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;
16 years ago
unsigned char *page;
struct command *c = NULL;
struct burn_speed_descriptor *sd;
char *msg = NULL;
struct burn_feature_descr *feature_descr;
16 years ago
/* 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;
}
16 years ago
/* 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;
16 years ago
/* 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;}
}
16 years ago
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 */
16 years ago
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);
16 years ago
}
16 years ago
void spc_sense_error_params(struct burn_drive *d)
{
struct buffer *buf = NULL;
16 years ago
struct scsi_mode_data *m;
int alloc_len = 12 ;
16 years ago
unsigned char *page;
struct command *c = NULL;
16 years ago
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);
16 years ago
m = d->mdata;
page = c->page->data + 8;
16 years ago
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);
16 years ago
}
void spc_select_error_params(struct burn_drive *d,
const struct burn_read_opts *o)
{