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.
5232 lines
146 KiB
5232 lines
146 KiB
/* -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 8; -*- */ |
|
|
|
/* Copyright (c) 2004 - 2006 Derek Foreman, Ben Jansens |
|
Copyright (c) 2006 - 2012 Thomas Schmitt <scdbackup@gmx.net> |
|
Provided under GPL version 2 or later. |
|
*/ |
|
|
|
#ifdef HAVE_CONFIG_H |
|
#include "../config.h" |
|
#endif |
|
|
|
|
|
/* ts A61009 */ |
|
/* #include <a ssert.h> */ |
|
|
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <unistd.h> |
|
#include <stdlib.h> |
|
#include <sys/time.h> |
|
#include <pthread.h> |
|
#include <ctype.h> |
|
|
|
#include "error.h" |
|
#include "sector.h" |
|
#include "libburn.h" |
|
#include "transport.h" |
|
#include "mmc.h" |
|
#include "spc.h" |
|
#include "drive.h" |
|
#include "debug.h" |
|
#include "toc.h" |
|
#include "structure.h" |
|
#include "options.h" |
|
#include "util.h" |
|
#include "init.h" |
|
|
|
|
|
/* ts A70223 : in init.c */ |
|
extern int burn_support_untested_profiles; |
|
|
|
static int mmc_get_configuration_al(struct burn_drive *d, int *alloc_len); |
|
|
|
|
|
#ifdef Libburn_log_in_and_out_streaM |
|
/* <<< ts A61031 */ |
|
#include <sys/types.h> |
|
#include <sys/stat.h> |
|
#include <fcntl.h> |
|
#endif /* Libburn_log_in_and_out_streaM */ |
|
|
|
|
|
/* ts A61005 */ |
|
#include "libdax_msgs.h" |
|
extern struct libdax_msgs *libdax_messenger; |
|
|
|
|
|
/* ts A61219 : Based on knowlege from dvd+rw-tools-7.0 and mmc5r03c.pdf */ |
|
#define Libburn_support_dvd_plus_rW 1 |
|
|
|
/* ts A61229 */ |
|
#define Libburn_support_dvd_minusrw_overW 1 |
|
|
|
/* ts A70112 */ |
|
/* ts A80410 : applies to BD-RE too */ |
|
#define Libburn_support_dvd_raM 1 |
|
|
|
/* ts A70129 */ |
|
#define Libburn_support_dvd_r_seQ 1 |
|
|
|
/* ts A70306 */ |
|
#define Libburn_support_dvd_plus_R 1 |
|
|
|
/* ts A70509 : handling 0x41 as read-only type */ |
|
#define Libburn_support_bd_r_readonlY 1 |
|
|
|
/* ts A81208 */ |
|
#define Libburn_support_bd_plus_r_srM 1 |
|
|
|
|
|
/* ts A80410 : <<< Dangerous experiment: Pretend that DVD-RAM is BD-RE |
|
# define Libburn_dvd_ram_as_bd_rE yes |
|
*/ |
|
/* ts A80509 : <<< Experiment: pretend that DVD-ROM and CD-ROM are other media |
|
like BD-ROM (0x40), BD-R seq (0x41), BD-R random (0x42) |
|
# define Libburn_rom_as_profilE 0x40 |
|
*/ |
|
|
|
|
|
/* ts A80425 : Prevents command FORMAT UNIT for DVD-RAM or BD-RE. |
|
Useful only to test the selection of format descriptors without |
|
actually formatting the media. |
|
# define Libburn_do_not_format_dvd_ram_or_bd_rE 1 |
|
*/ |
|
|
|
|
|
/* ts A90603 : Simulate the command restrictions of an old MMC-1 drive |
|
# define Libisofs_simulate_old_mmc1_drivE 1 |
|
*/ |
|
|
|
|
|
/* DVD/BD progress report: |
|
ts A61219 : It seems to work with a used (i.e. thoroughly formatted) DVD+RW. |
|
Error messages of class DEBUG appear because of inability to |
|
read TOC or track info. Nevertheless, the written images verify. |
|
ts A61220 : Burned to a virgin DVD+RW by help of new mmc_format_unit() |
|
(did not test wether it would work without). Burned to a |
|
not completely formatted DVD+RW. (Had worked before without |
|
mmc_format_unit() but i did not exceed the formatted range |
|
as reported by dvd+rw-mediainfo.) |
|
ts A61221 : Speed setting now works for both of my drives. The according |
|
functions in dvd+rw-tools are a bit intimidating to the reader. |
|
I hope it is possible to leave much of this to the drive. |
|
And if it fails ... well, it's only speed setting. :)) |
|
ts A61229 : Burned to several DVD-RW formatted to mode Restricted Overwrite |
|
by dvd+rw-format. Needs Libburn_support_dvd_minusrw_overW. |
|
ts A61230 : Other than growisofs, libburn does not send a mode page 5 for |
|
such DVD-RW (which the MMC-5 standard does deprecate) and it |
|
really seems to work without such a page. |
|
ts A70101 : Formatted DVD-RW media. Success is varying with media, but |
|
dvd+rw-format does not do better with the same media. |
|
ts A70112 : Support for writing to DVD-RAM. |
|
ts A70130 : Burned a first non-multi sequential DVD-RW. Feature 0021h |
|
Incremental Recording vanishes after that and media thus gets |
|
not recognized as suitable any more. |
|
After a run with -multi another disc still offers 0021h . |
|
dvd+rw-mediainfo shows two tracks. The second, an afio archive |
|
is readable by afio. Third and forth veryfy too. Suddenly |
|
dvd+rw-mediainfo sees lba 0 with track 2. But #2 still verifies |
|
if one knows its address. |
|
ts A70203 : DVD-RW need to get blanked fully. Then feature 0021h persists. |
|
Meanwhile Incremental streaming is supported like CD TAO: |
|
with unpredicted size, multi-track, multi-session. |
|
ts A70205 : Beginning to implement DVD-R[W] DAO : single track and session, |
|
size prediction mandatory. |
|
ts A70208 : Finally made tests with DVD-R. Worked exactly as new DVD-RW. |
|
ts A70306 : Implemented DVD+R (always -multi for now) |
|
ts A70330 : Allowed finalizing of DVD+R. |
|
ts A80228 : Made DVD+R/DL support official after nightmorph reported success |
|
in http://libburnia-project.org/ticket/13 |
|
ts A80416 : drive->do_stream_recording brings DVD-RAM to full nominal |
|
writing speed at cost of no defect management. |
|
ts A80416 : Giulio Orsero reports success with BD-RE writing. With |
|
drive->do_stream_recording it does full nominal speed. |
|
ts A80506 : Giulio Orsero reports success with BD-RE formatting. |
|
BD-RE is now an officially supported profile. |
|
ts A81209 : The first two sessions have been written to BD-R SRM |
|
(auto formatted without Defect Management). |
|
ts A90107 : BD-R is now supported media type |
|
*/ |
|
|
|
/* ts A70519 : With MMC commands of data direction FROM_DRIVE: |
|
Made struct command.dxfer_len equal to Allocation Length |
|
of MMC commands. Made sure that not more bytes are allowed |
|
for transfer than there are available. |
|
*/ |
|
|
|
|
|
/* ts A70711 Trying to keep writing from clogging the SCSI driver due to |
|
full buffer at burner drive: 0=waiting disabled, 1=enabled |
|
These are only defaults which can be overwritten by |
|
burn_drive_set_buffer_waiting() |
|
*/ |
|
#define Libburn_wait_for_buffer_freE 0 |
|
#define Libburn_wait_for_buffer_min_useC 10000 |
|
#define Libburn_wait_for_buffer_max_useC 100000 |
|
#define Libburn_wait_for_buffer_tio_seC 120 |
|
#define Libburn_wait_for_buffer_min_perC 65 |
|
#define Libburn_wait_for_buffer_max_perC 95 |
|
|
|
/* ts B31107 The minimum values to be applied if maximum read speed is |
|
requested. Some drives tell only the currently set speed and |
|
thus cannot be made faster by using the highest told value. |
|
(The fractions get added or subtracted to yield an integer |
|
number on the safe side of the intended limit.) |
|
*/ |
|
#define Libburn_cd_max_read_speeD (52 * 150) |
|
#define Libburn_dvd_max_read_speeD (24 * 1385) |
|
#define Libburn_bd_max_read_speeD (20 * 4495.625 + 0.5) |
|
|
|
/* ts B31114 The maximum values for minimum speed |
|
*/ |
|
#define Libburn_cd_min_read_speeD ( 1 * 150) |
|
#define Libburn_dvd_min_read_speeD ( 1 * 1385) |
|
#define Libburn_bd_min_read_speeD ( 1 * 4495.625 - 0.625) |
|
|
|
|
|
static unsigned char MMC_GET_MSINFO[] = |
|
{ 0x43, 0, 1, 0, 0, 0, 0, 16, 0, 0 }; |
|
static unsigned char MMC_GET_TOC[] = { 0x43, 2, 2, 0, 0, 0, 0, 16, 0, 0 }; |
|
static unsigned char MMC_GET_TOC_FMT0[] = { 0x43, 0, 0, 0, 0, 0, 0, 16, 0, 0 }; |
|
static unsigned char MMC_GET_ATIP[] = { 0x43, 2, 4, 0, 0, 0, 0, 16, 0, 0 }; |
|
static unsigned char MMC_GET_LEADTEXT[] = { 0x43, 2, 5, 0, 0, 0, 0, 4, 0, 0 }; |
|
static unsigned char MMC_GET_DISC_INFO[] = |
|
{ 0x51, 0, 0, 0, 0, 0, 0, 16, 0, 0 }; |
|
static unsigned char MMC_READ_CD[] = { 0xBE, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
|
static unsigned char MMC_BLANK[] = { 0xA1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
|
static unsigned char MMC_SEND_OPC[] = { 0x54, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
|
static unsigned char MMC_SET_SPEED[] = |
|
{ 0xBB, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
|
static unsigned char MMC_WRITE_12[] = |
|
{ 0xAA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
|
static unsigned char MMC_WRITE_10[] = { 0x2A, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
|
|
|
/* ts A61201 : inserted 0, before 16, */ |
|
static unsigned char MMC_GET_CONFIGURATION[] = |
|
{ 0x46, 0, 0, 0, 0, 0, 0, 16, 0, 0 }; |
|
|
|
static unsigned char MMC_SYNC_CACHE[] = { 0x35, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
|
static unsigned char MMC_GET_EVENT[] = { 0x4A, 1, 0, 0, 0x7e, 0, 0, 0, 8, 0 }; |
|
static unsigned char MMC_CLOSE[] = { 0x5B, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
|
static unsigned char MMC_TRACK_INFO[] = { 0x52, 0, 0, 0, 0, 0, 0, 16, 0, 0 }; |
|
|
|
static unsigned char MMC_SEND_CUE_SHEET[] = |
|
{ 0x5D, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
|
|
|
/* ts A61023 : get size and free space of drive buffer */ |
|
static unsigned char MMC_READ_BUFFER_CAPACITY[] = |
|
{ 0x5C, 0, 0, 0, 0, 0, 0, 16, 0, 0 }; |
|
|
|
/* ts A61219 : format DVD+RW (and various others) */ |
|
static unsigned char MMC_FORMAT_UNIT[] = { 0x04, 0x11, 0, 0, 0, 0 }; |
|
|
|
/* ts A61221 : |
|
To set speed for DVD media (0xBB is for CD but works on my LG GSA drive) */ |
|
static unsigned char MMC_SET_STREAMING[] = |
|
{ 0xB6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
|
|
|
/* ts A61225 : |
|
To obtain write speed descriptors (command can do other things too) */ |
|
static unsigned char MMC_GET_PERFORMANCE[] = |
|
{ 0xAC, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
|
|
|
/* ts A70108 : To obtain info about drive and media formatting opportunities */ |
|
static unsigned char MMC_READ_FORMAT_CAPACITIES[] = |
|
{ 0x23, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
|
|
|
/* ts A70205 : To describe the layout of a DVD-R[W] DAO session */ |
|
static unsigned char MMC_RESERVE_TRACK[] = |
|
{ 0x53, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
|
|
|
/* ts A70812 : Read data sectors (for types with 2048 bytes/sector only) */ |
|
static unsigned char MMC_READ_10[] = |
|
{ 0x28, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
|
|
|
/* ts A81210 : Determine the upper limit of readable data size */ |
|
static unsigned char MMC_READ_CAPACITY[] = |
|
{ 0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
|
|
|
/* ts A90903 : Obtain media type specific information. E.g. manufacturer. |
|
*/ |
|
static unsigned char MMC_READ_DISC_STRUCTURE[] = |
|
{ 0xAD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
|
|
|
/* ts B21125 : An alternatvie to BEh READ CD |
|
*/ |
|
static unsigned char MMC_READ_CD_MSF[] = |
|
{ 0xB9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; |
|
|
|
static int mmc_function_spy_do_tell = 0; |
|
|
|
int mmc_function_spy(struct burn_drive *d, char * text) |
|
{ |
|
if (mmc_function_spy_do_tell) |
|
fprintf(stderr,"libburn: experimental: mmc_function_spy: %s\n", |
|
text); |
|
if (d == NULL) |
|
return 1; |
|
if (d->drive_role != 1) { |
|
char msg[4096]; |
|
|
|
sprintf(msg, "Emulated drive caught in SCSI adapter \"%s\"", |
|
text); |
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x0002014c, |
|
LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH, |
|
msg, 0, 0); |
|
d->cancel = 1; |
|
return 0; |
|
} |
|
return 1; |
|
} |
|
|
|
int mmc_function_spy_ctrl(int do_tell) |
|
{ |
|
mmc_function_spy_do_tell= !!do_tell; |
|
return 1; |
|
} |
|
|
|
|
|
/* ts A70201 */ |
|
int mmc_four_char_to_int(unsigned char *data) |
|
{ |
|
return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; |
|
} |
|
|
|
|
|
/* ts A70201 */ |
|
int mmc_int_to_four_char(unsigned char *data, int num) |
|
{ |
|
data[0] = (num >> 24) & 0xff; |
|
data[1] = (num >> 16) & 0xff; |
|
data[2] = (num >> 8) & 0xff; |
|
data[3] = num & 0xff; |
|
return 1; |
|
} |
|
|
|
|
|
static int mmc_start_for_bit0 = 0; |
|
|
|
/* @param flag bit0= the calling function should need no START UNIT. |
|
(Handling depends on mmc_start_for_bit0) |
|
*/ |
|
int mmc_start_if_needed(struct burn_drive *d, int flag) |
|
{ |
|
if (!d->is_stopped) |
|
return 2; |
|
if ((flag & 1) && !mmc_start_for_bit0) |
|
return 2; |
|
d->start_unit(d); |
|
d->is_stopped = 0; |
|
return 1; |
|
} |
|
|
|
|
|
int mmc_send_cue_sheet(struct burn_drive *d, struct cue_sheet *s) |
|
{ |
|
struct buffer *buf = NULL; |
|
struct command *c; |
|
|
|
c = &(d->casual_command); |
|
mmc_start_if_needed(d, 0); |
|
if (mmc_function_spy(d, "mmc_send_cue_sheet") <= 0) |
|
return 0; |
|
BURN_ALLOC_MEM_VOID(buf, struct buffer, 1); |
|
scsi_init_command(c, MMC_SEND_CUE_SHEET, sizeof(MMC_SEND_CUE_SHEET)); |
|
c->retry = 1; |
|
c->page = buf; |
|
c->page->bytes = s->count * 8; |
|
c->page->sectors = 0; |
|
c->opcode[6] = (c->page->bytes >> 16) & 0xFF; |
|
c->opcode[7] = (c->page->bytes >> 8) & 0xFF; |
|
c->opcode[8] = c->page->bytes & 0xFF; |
|
c->dir = TO_DRIVE; |
|
memcpy(c->page->data, s->data, c->page->bytes); |
|
d->issue_command(d, c); |
|
ex:; |
|
BURN_FREE_MEM(buf); |
|
if (c->error) { |
|
d->cancel = 1; |
|
scsi_notify_error(d, c, c->sense, 18, 2); |
|
} |
|
return !c->error; |
|
} |
|
|
|
|
|
/* ts A70205 : Announce size of a DVD-R[W] DAO session. |
|
@param size The size in bytes to be announced to the drive. |
|
It will get rounded up to align to 32 KiB. |
|
*/ |
|
int mmc_reserve_track(struct burn_drive *d, off_t size) |
|
{ |
|
struct command *c; |
|
int lba; |
|
char msg[80]; |
|
|
|
c = &(d->casual_command); |
|
mmc_start_if_needed(d, 0); |
|
if (mmc_function_spy(d, "mmc_reserve_track") <= 0) |
|
return 0; |
|
|
|
scsi_init_command(c, MMC_RESERVE_TRACK, sizeof(MMC_RESERVE_TRACK)); |
|
c->retry = 1; |
|
|
|
lba = size / 2048; |
|
if (size % 2048) |
|
lba++; |
|
mmc_int_to_four_char(c->opcode+5, lba); |
|
|
|
sprintf(msg, "reserving track of %d blocks", lba); |
|
libdax_msgs_submit(libdax_messenger, -1, 0x00000002, |
|
LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_ZERO, |
|
msg, 0, 0); |
|
|
|
c->page = NULL; |
|
c->dir = NO_TRANSFER; |
|
c->timeout = Libburn_mmc_reserve_timeouT; |
|
d->issue_command(d, c); |
|
if (c->error) { |
|
d->cancel = 1; |
|
scsi_notify_error(d, c, c->sense, 18, 2); |
|
} |
|
return !c->error; |
|
} |
|
|
|
|
|
/* ts A70201 : |
|
Common track info fetcher for mmc_get_nwa() and mmc_fake_toc() |
|
*/ |
|
int mmc_read_track_info(struct burn_drive *d, int trackno, struct buffer *buf, |
|
int alloc_len) |
|
{ |
|
struct command *c; |
|
|
|
c = &(d->casual_command); |
|
mmc_start_if_needed(d, 1); |
|
if (mmc_function_spy(d, "mmc_read_track_info") <= 0) |
|
return 0; |
|
|
|
scsi_init_command(c, MMC_TRACK_INFO, sizeof(MMC_TRACK_INFO)); |
|
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[1] = 1; |
|
if(trackno<=0) { |
|
if (d->current_profile == 0x1a || d->current_profile == 0x13 || |
|
d->current_profile == 0x12 || d->current_profile == 0x42 || |
|
d->current_profile == 0x43) |
|
/* DVD+RW , DVD-RW restricted overwrite , DVD-RAM |
|
BD-R random recording, BD-RE */ |
|
trackno = 1; |
|
else if (d->current_profile == 0x10 || |
|
d->current_profile == 0x11 || |
|
d->current_profile == 0x14 || |
|
d->current_profile == 0x15 || |
|
d->current_profile == 0x40 || |
|
d->current_profile == 0x41) |
|
/* DVD-ROM , DVD-R[W] Sequential , |
|
BD-ROM , BD-R sequential */ |
|
trackno = d->last_track_no; |
|
else /* mmc5r03c.pdf: valid only for CD, DVD+R, DVD+R DL */ |
|
trackno = 0xFF; |
|
} |
|
mmc_int_to_four_char(c->opcode + 2, trackno); |
|
c->page = buf; |
|
memset(buf->data, 0, BUFFER_SIZE); |
|
c->dir = FROM_DRIVE; |
|
d->issue_command(d, c); |
|
if (c->error) |
|
return 0; |
|
return 1; |
|
} |
|
|
|
|
|
/* ts A61110 : added parameters trackno, lba, nwa. Redefined return value. |
|
@return 1=nwa is valid , 0=nwa is not valid , -1=error */ |
|
/* ts A70201 : outsourced 52h READ TRACK INFO command */ |
|
int mmc_get_nwa(struct burn_drive *d, int trackno, int *lba, int *nwa) |
|
{ |
|
struct buffer *buf = NULL; |
|
int ret, num, alloc_len = 20, err; |
|
unsigned char *data; |
|
char *msg = NULL; |
|
|
|
if (trackno <= 0) |
|
d->next_track_damaged = 0; |
|
mmc_start_if_needed(d, 1); |
|
if (mmc_function_spy(d, "mmc_get_nwa") <= 0) |
|
{ret = -1; goto ex;} |
|
|
|
/* ts B00327 : Avoid to inquire unsuitable media states */ |
|
if (d->status != BURN_DISC_BLANK && d->status != BURN_DISC_APPENDABLE) |
|
{ret = 0; goto ex;} |
|
|
|
BURN_ALLOC_MEM(buf, struct buffer, 1); |
|
ret = mmc_read_track_info(d, trackno, buf, alloc_len); |
|
if (ret <= 0) |
|
goto ex; |
|
data = buf->data; |
|
*lba = mmc_four_char_to_int(data + 8); |
|
*nwa = mmc_four_char_to_int(data + 12); |
|
num = mmc_four_char_to_int(data + 16); |
|
|
|
/* Pioneer BD-RW BDR-205 and LITE-ON LTR-48125S return -150 as *nwa |
|
of blank media */ |
|
if (*nwa < *lba && d->status == BURN_DISC_BLANK) |
|
*nwa = *lba; |
|
|
|
#ifdef Libburn_pioneer_dvr_216d_load_mode5 |
|
/* >>> memorize track mode : data[6] & 0xf */; |
|
#endif |
|
|
|
{ static int fake_damage = 0; /* bit0= damage on , bit1= NWA_V off */ |
|
|
|
if (fake_damage & 1) |
|
data[5] |= 32; /* Damage bit */ |
|
if (fake_damage & 2) |
|
data[7] &= ~1; |
|
|
|
} |
|
|
|
BURN_ALLOC_MEM(msg, char, 160); |
|
if (trackno > 0) |
|
sprintf(msg, "Track number %d: ", trackno); |
|
else |
|
sprintf(msg, "Upcomming track: "); |
|
if (d->current_profile == 0x1a || d->current_profile == 0x13 || |
|
d->current_profile == 0x12 || d->current_profile == 0x43) { |
|
/* overwriteable */ |
|
*lba = *nwa = num = 0; |
|
|
|
} else if (data[5] & 32) { /* ts B10534 : MMC-5 6.27.3.7 Damage Bit */ |
|
if (!(data[7] & 1)) { /* NWA_V is set to zero */ |
|
/* "not closed due to an incomplete write" */ |
|
strcat(msg, "Damaged, not closed and not writable"); |
|
err= 0x00020185; |
|
} else { |
|
/* "may be recorded further in an incremental manner"*/ |
|
strcat(msg, "Damaged and not closed"); |
|
err= 0x00020186; |
|
} |
|
libdax_msgs_submit(libdax_messenger, d->global_index, err, |
|
LIBDAX_MSGS_SEV_WARNING, LIBDAX_MSGS_PRIO_HIGH, |
|
msg, 0, 0); |
|
if (trackno <= 0) |
|
d->next_track_damaged |= ((!(data[7] & 1)) << 1) | 1; |
|
{ret = 0; goto ex;} |
|
|
|
} else if (!(data[7] & 1)) { |
|
/* ts A61106 : MMC-1 Table 142 : NWA_V = NWA Valid Flag */ |
|
strcat(msg, "No Next-Writable-Address"); |
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x00020184, |
|
LIBDAX_MSGS_SEV_WARNING, LIBDAX_MSGS_PRIO_HIGH, |
|
msg, 0, 0); |
|
if (trackno <= 0) |
|
d->next_track_damaged |= 2; |
|
{ret = 0; goto ex;} |
|
|
|
} |
|
if (num > 0) { |
|
burn_drive_set_media_capacity_remaining(d, |
|
((off_t) num) * ((off_t) 2048)); |
|
d->media_lba_limit = *nwa + num; |
|
} else |
|
d->media_lba_limit = 0; |
|
|
|
/* |
|
fprintf(stderr, "LIBBURN_DEBUG: media_lba_limit= %d\n", |
|
d->media_lba_limit); |
|
*/ |
|
|
|
ret = 1; |
|
ex: |
|
BURN_FREE_MEM(buf); |
|
BURN_FREE_MEM(msg); |
|
return ret; |
|
} |
|
|
|
/* ts A61009 : function is obviously unused. */ |
|
/* void mmc_close_disc(struct burn_drive *d, struct burn_write_opts *o) */ |
|
void mmc_close_disc(struct burn_write_opts *o) |
|
{ |
|
struct burn_drive *d = o->drive; |
|
|
|
if (mmc_function_spy(d, "mmc_close_disc") <= 0) |
|
return; |
|
|
|
libdax_msgs_submit(libdax_messenger, -1, 0x00000002, |
|
LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_ZERO, |
|
"HOW THAT ? mmc_close_disc() was called", 0, 0); |
|
|
|
/* ts A61009 : made impossible by removing redundant parameter d */ |
|
/* a ssert(o->drive == d); */ |
|
|
|
o->multi = 0; |
|
spc_select_write_params(d, NULL, 0, o); |
|
mmc_close(d, 1, 0); |
|
} |
|
|
|
/* ts A61009 : function is obviously unused. */ |
|
/* void mmc_close_session(struct burn_drive *d, struct burn_write_opts *o) */ |
|
void mmc_close_session(struct burn_write_opts *o) |
|
{ |
|
struct burn_drive *d = o->drive; |
|
|
|
if (mmc_function_spy(d, "mmc_close_session") <= 0) |
|
return; |
|
|
|
libdax_msgs_submit(libdax_messenger, -1, 0x00000002, |
|
LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_ZERO, |
|
"HOW THAT ? mmc_close_session() was called", 0, 0); |
|
|
|
/* ts A61009 : made impossible by removing redundant parameter d */ |
|
/* a ssert(o->drive == d); */ |
|
|
|
o->multi = 3; |
|
spc_select_write_params(d, NULL, 0, o); |
|
mmc_close(d, 1, 0); |
|
} |
|
|
|
/* ts A70227 : extended meaning of session to address all possible values |
|
of 5Bh CLOSE TRACK SESSION to address any Close Function. |
|
@param session contains the two high bits of Close Function |
|
@param track if not 0: sets the lowest bit of Close Function |
|
*/ |
|
void mmc_close(struct burn_drive *d, int session, int track) |
|
{ |
|
struct command *c; |
|
char msg[256]; |
|
int key, asc, ascq; |
|
|
|
c = &(d->casual_command); |
|
if (mmc_function_spy(d, "mmc_close") <= 0) |
|
return; |
|
|
|
scsi_init_command(c, MMC_CLOSE, sizeof(MMC_CLOSE)); |
|
c->retry = 1; |
|
|
|
c->opcode[1] |= 1; /* ts A70918 : Immed */ |
|
|
|
/* (ts A61030 : shifted !!session rather than or-ing plain session ) */ |
|
c->opcode[2] = ((session & 3) << 1) | !!track; |
|
c->opcode[4] = track >> 8; |
|
c->opcode[5] = track & 0xFF; |
|
c->page = NULL; |
|
c->dir = NO_TRANSFER; |
|
c->timeout = Libburn_mmc_close_timeouT; |
|
d->issue_command(d, c); |
|
|
|
/* ts A70918 : Immed : wait for drive to complete command */ |
|
if (c->error) { |
|
sprintf(msg, "Failed to close %s (%d)", |
|
session > 1 ? "disc" : session > 0 ? "session" : "track", |
|
((session & 3) << 1) | !!track); |
|
sprintf(msg + strlen(msg), ". SCSI error : "); |
|
scsi_error_msg(d, c->sense, 14, msg + strlen(msg), |
|
&key, &asc, &ascq); |
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x0002017e, |
|
LIBDAX_MSGS_SEV_FAILURE, LIBDAX_MSGS_PRIO_HIGH, |
|
msg, 0, 0); |
|
d->cancel = 1; |
|
return; |
|
} |
|
if (spc_wait_unit_attention(d, 3600, "CLOSE TRACK SESSION", 0) <= 0) |
|
d->cancel = 1; |
|
} |
|
|
|
void mmc_get_event(struct burn_drive *d) |
|
{ |
|
struct buffer *buf = NULL; |
|
struct command *c; |
|
int alloc_len = 8, len, evt_code, loops = 0; |
|
unsigned char *evt; |
|
|
|
c = &(d->casual_command); |
|
BURN_ALLOC_MEM_VOID(buf, struct buffer, 1); |
|
if (mmc_function_spy(d, "mmc_get_event") <= 0) |
|
goto ex; |
|
|
|
again:; |
|
scsi_init_command(c, MMC_GET_EVENT, sizeof(MMC_GET_EVENT)); |
|
c->dxfer_len = 8; |
|
|
|
/* >>> have a burn_drive element for Notification Class */; |
|
c->opcode[4] = 0x7e; |
|
|
|
c->opcode[7] = (c->dxfer_len >> 8) & 0xff; |
|
c->opcode[8] = c->dxfer_len & 0xff; |
|
c->retry = 1; |
|
c->page = buf; |
|
c->page->bytes = 0; |
|
c->page->sectors = 0; |
|
c->dir = FROM_DRIVE; |
|
d->issue_command(d, c); |
|
if (c->error) |
|
goto ex; |
|
|
|
evt = c->page->data; |
|
len = ((evt[0] << 8) | evt[1]) + 2; |
|
if (len < 8) |
|
goto ex; |
|
|
|
/* >>> memorize evt[3] in burn_drive element for Notification Class */; |
|
if (evt[3] == 0) /* No event */ |
|
goto ex; |
|
|
|
evt_code = evt[4] & 0xf; |
|
if (evt_code == 0) /* No change */ |
|
goto ex; |
|
|
|
switch (evt[2] & 7) { |
|
case 0: /* no events supported */ |
|
goto ex; |
|
case 1: /* Operational change */ |
|
if (((evt[6] << 8) | evt[7])) { |
|
alloc_len = 8; |
|
mmc_get_configuration_al(d, &alloc_len); |
|
} |
|
break; |
|
case 2: /* Power Management */ |
|
if (evt[5] >= 2) |
|
d->start_unit(d); |
|
break; |
|
case 3: /* External request */ |
|
|
|
/* >>> report about external request */; |
|
|
|
break; |
|
case 4: /* Media */ |
|
if (evt_code == 2) { |
|
d->start_unit(d); |
|
alloc_len = 8; |
|
mmc_get_configuration_al(d, &alloc_len); |
|
} |
|
break; |
|
case 5: /* Multiple Host Events */ |
|
|
|
/* >>> report about foreign host interference */; |
|
|
|
break; |
|
|
|
case 6: /* Device busy */ |
|
if (evt_code == 1 && evt[5]) { |
|
|
|
/* >>> wait the time announced in evt[6],[7] |
|
as 100ms units */; |
|
} |
|
break; |
|
default: /* reserved */ |
|
break; |
|
} |
|
loops++; |
|
if (loops < 100) |
|
goto again; |
|
ex:; |
|
BURN_FREE_MEM(buf); |
|
} |
|
|
|
|
|
/* ts A70711 |
|
This has become a little monster because of the creative buffer reports of |
|
my LG GSA-4082B : Belated, possibly statistically dampened. But only with |
|
DVD media. With CD it is ok. |
|
*/ |
|
static int mmc_wait_for_buffer_free(struct burn_drive *d, struct buffer *buf) |
|
{ |
|
int usec= 0, need, reported_3s = 0, first_wait = 1; |
|
struct timeval t0,tnow; |
|
struct timezone dummy_tz; |
|
double max_fac, min_fac, waiting; |
|
|
|
/* Enable to get reported waiting activities and total time. |
|
#define Libburn_mmc_wfb_debuG 1 |
|
*/ |
|
#ifdef Libburn_mmc_wfb_debuG |
|
char sleeplist[32768]; |
|
static int buffer_still_invalid = 1; |
|
#endif |
|
|
|
max_fac = ((double) d->wfb_max_percent) / 100.0; |
|
|
|
/* Buffer info from the drive is valid only after writing has begun. |
|
Caring for buffer space makes sense mostly after max_percent of the |
|
buffer was transmitted. */ |
|
if (d->progress.buffered_bytes <= 0 || |
|
d->progress.buffer_capacity <= 0 || |
|
d->progress.buffered_bytes + buf->bytes <= |
|
d->progress.buffer_capacity * max_fac) |
|
return 2; |
|
|
|
#ifdef Libburn_mmc_wfb_debuG |
|
if (buffer_still_invalid) |
|
fprintf(stderr, |
|
"\nLIBBURN_DEBUG: Buffer considered valid now\n"); |
|
buffer_still_invalid = 0; |
|
#endif |
|
|
|
/* The pessimistic counter does not assume any buffer consumption */ |
|
if (d->pessimistic_buffer_free - buf->bytes >= |
|
( 1.0 - max_fac) * d->progress.buffer_capacity) |
|
return 1; |
|
|
|
/* There is need to inquire the buffer fill */ |
|
d->pessimistic_writes++; |
|
min_fac = ((double) d->wfb_min_percent) / 100.0; |
|
gettimeofday(&t0, &dummy_tz); |
|
#ifdef Libburn_mmc_wfb_debuG |
|
sleeplist[0]= 0; |
|
sprintf(sleeplist,"(%d%s %d)", |
|
(int) (d->pessimistic_buffer_free - buf->bytes), |
|
(d->pbf_altered ? "? -" : " -"), |
|
(int) ((1.0 - max_fac) * d->progress.buffer_capacity)); |
|
#endif |
|
|
|
while (1) { |
|
if ((!first_wait) || d->pbf_altered) { |
|
d->pbf_altered = 1; |
|
mmc_read_buffer_capacity(d); |
|
} |
|
#ifdef Libburn_mmc_wfb_debuG |
|
if(strlen(sleeplist) < sizeof(sleeplist) - 80) |
|
sprintf(sleeplist+strlen(sleeplist)," (%d%s %d)", |
|
(int) (d->pessimistic_buffer_free - buf->bytes), |
|
(d->pbf_altered ? "? -" : " -"), |
|
(int) ((1.0 - min_fac) * d->progress.buffer_capacity)); |
|
#endif |
|
gettimeofday(&tnow,&dummy_tz); |
|
waiting = (tnow.tv_sec - t0.tv_sec) + |
|
((double) (tnow.tv_usec - t0.tv_usec)) / 1.0e6; |
|
if (d->pessimistic_buffer_free - buf->bytes >= |
|
(1.0 - min_fac) * d->progress.buffer_capacity) { |
|
#ifdef Libburn_mmc_wfb_debuG |
|
if(strlen(sleeplist) >= sizeof(sleeplist) - 80) |
|
strcat(sleeplist," ..."); |
|
sprintf(sleeplist+strlen(sleeplist)," -> %d [%.6f]", |
|
(int) ( |
|
d->pessimistic_buffer_free - buf->bytes - |
|
(1.0 - min_fac) * d->progress.buffer_capacity |
|
), waiting); |
|
fprintf(stderr, |
|
"\nLIBBURN_DEBUG: sleeplist= %s\n",sleeplist); |
|
#endif |
|
return 1; |
|
} |
|
|
|
/* Waiting is needed */ |
|
if (waiting >= 3 && !reported_3s) { |
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x0002013d, |
|
LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_LOW, |
|
"Waiting for free buffer takes more than 3 seconds", |
|
0,0); |
|
reported_3s = 1; |
|
} else if (d->wfb_timeout_sec > 0 && |
|
waiting > d->wfb_timeout_sec) { |
|
d->wait_for_buffer_free = 0; |
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x0002013d, |
|
LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH, |
|
"Timeout with waiting for free buffer. Now disabled.", |
|
0,0); |
|
break; |
|
} |
|
|
|
need = (1.0 - min_fac) * d->progress.buffer_capacity + |
|
buf->bytes - d->pessimistic_buffer_free; |
|
usec = 0; |
|
if (d->nominal_write_speed > 0) |
|
usec = ((double) need) / 1000.0 / |
|
((double) d->nominal_write_speed) * 1.0e6; |
|
else |
|
usec = d->wfb_min_usec * 2; |
|
|
|
/* >>> learn about buffer progress and adjust usec */ |
|
|
|
if (usec < (int) d->wfb_min_usec) |
|
usec = d->wfb_min_usec; |
|
else if (usec > (int) d->wfb_max_usec) |
|
usec = d->wfb_max_usec; |
|
usleep(usec); |
|
if (d->waited_usec < 0xf0000000) |
|
d->waited_usec += usec; |
|
d->waited_tries++; |
|
if(first_wait) |
|
d->waited_writes++; |
|
#ifdef Libburn_mmc_wfb_debuG |
|
if(strlen(sleeplist) < sizeof(sleeplist) - 80) |
|
sprintf(sleeplist+strlen(sleeplist)," %d", usec); |
|
#endif |
|
first_wait = 0; |
|
} |
|
return 0; |
|
} |
|
|
|
|
|
void mmc_write_12(struct burn_drive *d, int start, struct buffer *buf) |
|
{ |
|
struct command *c; |
|
int len; |
|
|
|
c = &(d->casual_command); |
|
mmc_start_if_needed(d, 0); |
|
if (mmc_function_spy(d, "mmc_write_12") <= 0) |
|
return; |
|
|
|
len = buf->sectors; |
|
|
|
scsi_init_command(c, MMC_WRITE_12, sizeof(MMC_WRITE_12)); |
|
c->retry = 1; |
|
mmc_int_to_four_char(c->opcode + 2, start); |
|
mmc_int_to_four_char(c->opcode + 6, len); |
|
c->page = buf; |
|
c->dir = TO_DRIVE; |
|
c->timeout = Libburn_scsi_write_timeouT; |
|
|
|
d->issue_command(d, c); |
|
|
|
/* ts A70711 */ |
|
d->pessimistic_buffer_free -= buf->bytes; |
|
d->pbf_altered = 1; |
|
} |
|
|
|
|
|
#ifdef Libburn_write_time_debuG |
|
|
|
static int print_time(int flag) |
|
{ |
|
static struct timeval prev = {0, 0}; |
|
struct timeval now; |
|
struct timezone tz; |
|
int ret, diff; |
|
|
|
ret = gettimeofday(&now, &tz); |
|
if (ret == -1) |
|
return 0; |
|
if (now.tv_sec - prev.tv_sec < Libburn_scsi_write_timeouT) { |
|
diff = (now.tv_sec - prev.tv_sec) * 1000000 + |
|
((int) (now.tv_usec) - (int) prev.tv_usec); |
|
fprintf(stderr, "\nlibburn_DEBUG: %d.%-6d : %d\n", (int) now.tv_sec, (int) now.tv_usec, diff); |
|
} |
|
memcpy(&prev, &now, sizeof(struct timeval)); |
|
return 1; |
|
} |
|
|
|
#endif /* Libburn_write_time_debuG */ |
|
|
|
|
|
int mmc_write(struct burn_drive *d, int start, struct buffer *buf) |
|
{ |
|
int cancelled; |
|
struct command *c; |
|
int len, key, asc, ascq; |
|
char *msg = NULL; |
|
|
|
#ifdef Libburn_write_time_debuG |
|
extern int burn_sg_log_scsi; |
|
#endif |
|
|
|
/* |
|
fprintf(stderr, "libburn_DEBUG: buffer sectors= %d bytes= %d\n", |
|
buf->sectors, buf->bytes); |
|
*/ |
|
|
|
|
|
c = &(d->casual_command); |
|
|
|
#ifdef Libburn_log_in_and_out_streaM |
|
/* <<< ts A61031 */ |
|
static int tee_fd= -1; |
|
if(tee_fd==-1) |
|
tee_fd= open("/tmp/libburn_sg_written", |
|
O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR); |
|
#endif /* Libburn_log_in_and_out_streaM */ |
|
|
|
mmc_start_if_needed(d, 0); |
|
if (mmc_function_spy(d, "mmc_write") <= 0) |
|
return BE_CANCELLED; |
|
|
|
cancelled = d->cancel; |
|
if (cancelled) |
|
return BE_CANCELLED; |
|
|
|
/* ts A70215 */ |
|
if (d->media_lba_limit > 0 && start >= d->media_lba_limit) { |
|
|
|
msg = calloc(1, 160); |
|
if (msg != NULL) { |
|
sprintf(msg, |
|
"Exceeding range of permissible write addresses (%d >= %d)", |
|
start, d->media_lba_limit); |
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x0002012d, |
|
LIBDAX_MSGS_SEV_FATAL, LIBDAX_MSGS_PRIO_HIGH, |
|
msg, 0, 0); |
|
free(msg); |
|
} |
|
d->cancel = 1; /* No need for mutexing because atomic */ |
|
return BE_CANCELLED; |
|
} |
|
|
|
len = buf->sectors; |
|
|
|
/* ts A61009 : buffer fill problems are to be handled by caller */ |
|
/* a ssert(buf->bytes >= buf->sectors);*/ /* can be == at 0... */ |
|
|
|
/* ts A70711 */ |
|
if(d->wait_for_buffer_free) |
|
mmc_wait_for_buffer_free(d, buf); |
|
|
|
#ifdef Libburn_write_time_debuG |
|
if (burn_sg_log_scsi & 3) |
|
print_time(0); |
|
#endif |
|
|
|
/* ts A80412 */ |
|
if(d->do_stream_recording > 0 && start >= d->stream_recording_start) { |
|
|
|
/* >>> ??? is WRITE12 available ? */ |
|
/* >>> ??? inquire feature 107h Stream Writing bit ? */ |
|
|
|
scsi_init_command(c, MMC_WRITE_12, sizeof(MMC_WRITE_12)); |
|
mmc_int_to_four_char(c->opcode + 2, start); |
|
mmc_int_to_four_char(c->opcode + 6, len); |
|
c->opcode[10] = 1<<7; /* Streaming bit */ |
|
} else { |
|
scsi_init_command(c, MMC_WRITE_10, sizeof(MMC_WRITE_10)); |
|
mmc_int_to_four_char(c->opcode + 2, start); |
|
c->opcode[6] = 0; |
|
c->opcode[7] = (len >> 8) & 0xFF; |
|
c->opcode[8] = len & 0xFF; |
|
} |
|
c->retry = 1; |
|
c->page = buf; |
|
c->dir = TO_DRIVE; |
|
c->timeout = Libburn_scsi_write_timeouT; |
|
|
|
#ifdef Libburn_log_in_and_out_streaM |
|
/* <<< ts A61031 */ |
|
if(tee_fd!=-1) { |
|
write(tee_fd, c->page->data, c->page->bytes); |
|
} |
|
#endif /* Libburn_log_in_and_out_streaM */ |
|
|
|
d->issue_command(d, c); |
|
|
|
/* ts A70711 */ |
|
d->pessimistic_buffer_free -= buf->bytes; |
|
d->pbf_altered = 1; |
|
|
|
/* ts A61112 : react on eventual error condition */ |
|
spc_decode_sense(c->sense, 0, &key, &asc, &ascq); |
|
if (c->error && key != 0) { |
|
int key, asc, ascq; |
|
int err_sev = LIBDAX_MSGS_SEV_FATAL; |
|
|
|
msg = calloc(1, 256); |
|
if (msg != NULL) { |
|
sprintf(msg, "SCSI error on write(%d,%d): ", |
|
start, len); |
|
scsi_error_msg(d, c->sense, 14, msg + strlen(msg), |
|
&key, &asc, &ascq); |
|
} |
|
|
|
/* ts B31023 */ |
|
/* Memorize if on DVD-RW write mode is TAO/Incremental and |
|
error [5 64 00] occurs within the first drive buffer fill. |
|
*/ |
|
if (d->current_profile == 0x14 && d->write_opts != NULL && |
|
(d->progress.buffer_capacity == 0 || |
|
start < (int) d->progress.buffer_capacity / 2048) && |
|
key == 5 && asc == 0x64 && ascq == 0) { |
|
if (d->write_opts->write_type == BURN_WRITE_TAO) { |
|
d->was_feat21h_failure = 1 + (start == 0); |
|
if (d->write_opts->feat21h_fail_sev != 0) |
|
err_sev = |
|
d->write_opts->feat21h_fail_sev; |
|
} |
|
} |
|
|
|
if (msg != NULL) { |
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x0002011d, |
|
err_sev, LIBDAX_MSGS_PRIO_HIGH, |
|
msg, 0, 0); |
|
free(msg); |
|
} |
|
d->cancel = 1; |
|
|
|
return BE_CANCELLED; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
/* ts A70201 : Set up an entry for mmc_fake_toc() */ |
|
int mmc_fake_toc_entry(struct burn_toc_entry *entry, int session_number, |
|
int track_number, |
|
unsigned char *size_data, unsigned char *start_data, |
|
unsigned char *last_adr_data) |
|
{ |
|
int min, sec, frames, num; |
|
|
|
/* mark DVD extensions and Track Info extension as valid */ |
|
entry->extensions_valid |= (1 | 2); |
|
|
|
/* defaults are as of mmc5r03.pdf 6.26.3.2.4 Fabricated TOC */ |
|
entry->session = session_number & 0xff; |
|
entry->session_msb = (session_number >> 8) & 0xff; |
|
entry->adr = 1; |
|
entry->control = 4; |
|
entry->tno = 0; |
|
entry->point = track_number & 0xff; |
|
entry->point_msb = (track_number >> 8) & 0xff; |
|
num = mmc_four_char_to_int(size_data); |
|
entry->track_blocks = num; |
|
burn_lba_to_msf(num, &min, &sec, &frames); |
|
if (min > 255) { |
|
min = 255; |
|
sec = 255; |
|
frames = 255; |
|
} |
|
entry->min = min; |
|
entry->sec = sec; |
|
entry->frame = frames; |
|
entry->zero = 0; |
|
num = mmc_four_char_to_int(start_data); |
|
entry->start_lba = num; |
|
burn_lba_to_msf(num, &min, &sec, &frames); |
|
if (min > 255) { |
|
min = 255; |
|
sec = 255; |
|
frames = 255; |
|
} |
|
entry->pmin = min; |
|
entry->psec = sec; |
|
entry->pframe = frames; |
|
entry->last_recorded_address = mmc_four_char_to_int(last_adr_data); |
|
return 1; |
|
} |
|
|
|
|
|
/* ts A71128 : for DVD-ROM drives which offer no reliable track information */ |
|
static int mmc_read_toc_fmt0_al(struct burn_drive *d, int *alloc_len) |
|
{ |
|
struct burn_track *track; |
|
struct burn_session *session; |
|
struct burn_toc_entry *entry; |
|
struct buffer *buf = NULL; |
|
struct command *c = NULL; |
|
int dlen, i, old_alloc_len, session_number, prev_session = -1, ret; |
|
int lba, size; |
|
unsigned char *tdata, size_data[4], start_data[4], end_data[4]; |
|
|
|
if (*alloc_len < 4) |
|
{ret = 0; goto ex;} |
|
|
|
BURN_ALLOC_MEM(buf, struct buffer, 1); |
|
BURN_ALLOC_MEM(c, struct command, 1); |
|
scsi_init_command(c, MMC_GET_TOC_FMT0, sizeof(MMC_GET_TOC_FMT0)); |
|
c->dxfer_len = *alloc_len; |
|
c->opcode[7] = (c->dxfer_len >> 8) & 0xff; |
|
c->opcode[8] = c->dxfer_len & 0xff; |
|
c->retry = 1; |
|
c->page = buf; |
|
c->page->bytes = 0; |
|
c->page->sectors = 0; |
|
c->dir = FROM_DRIVE; |
|
d->issue_command(d, c); |
|
|
|
if (c->error) { |
|
err_ex:; |
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x0002010d, |
|
LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_HIGH, |
|
"Could not inquire TOC", 0,0); |
|
d->status = BURN_DISC_UNSUITABLE; |
|
d->toc_entries = 0; |
|
/* Prefering memory leaks over fandangos */ |
|
d->toc_entry = calloc(1, sizeof(struct burn_toc_entry)); |
|
{ret = 0; goto ex;} |
|
} |
|
dlen = c->page->data[0] * 256 + c->page->data[1]; |
|
old_alloc_len = *alloc_len; |
|
*alloc_len = dlen + 2; |
|
if (old_alloc_len < 12) |
|
{ret = 1; goto ex;} |
|
if (dlen + 2 > old_alloc_len) |
|
dlen = old_alloc_len - 2; |
|
d->complete_sessions = 1 + c->page->data[3] - c->page->data[2]; |
|
|
|
#ifdef Libburn_disc_with_incomplete_sessioN |
|
/* ts B30112 : number of open sessions */ |
|
d->incomplete_sessions = 0; |
|
#endif |
|
|
|
d->last_track_no = d->complete_sessions; |
|
if (dlen - 2 < (d->last_track_no + 1) * 8) { |
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x00020159, |
|
LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_HIGH, |
|
"TOC Format 0 returns inconsistent data", 0,0); |
|
goto err_ex; |
|
} |
|
|
|
d->toc_entries = d->last_track_no + d->complete_sessions; |
|
if (d->toc_entries < 1) |
|
{ret = 0; goto ex;} |
|
d->toc_entry = calloc(d->toc_entries, sizeof(struct burn_toc_entry)); |
|
if(d->toc_entry == NULL) |
|
{ret = 0; goto ex;} |
|
|
|
d->disc = burn_disc_create(); |
|
if (d->disc == NULL) |
|
{ret = 0; goto ex;} |
|
for (i = 0; i < d->complete_sessions; i++) { |
|
session = burn_session_create(); |
|
if (session == NULL) |
|
{ret = 0; goto ex;} |
|
burn_disc_add_session(d->disc, session, BURN_POS_END); |
|
burn_session_free(session); |
|
} |
|
|
|
|
|
for (i = 0; i < d->last_track_no; i++) { |
|
tdata = c->page->data + 4 + i * 8; |
|
session_number = i + 1; |
|
if (session_number != prev_session && prev_session > 0) { |
|
/* leadout entry previous session */ |
|
entry = &(d->toc_entry[(i - 1) + prev_session]); |
|
lba = mmc_four_char_to_int(start_data) + |
|
mmc_four_char_to_int(size_data); |
|
mmc_int_to_four_char(start_data, lba); |
|
mmc_int_to_four_char(size_data, 0); |
|
mmc_int_to_four_char(end_data, lba - 1); |
|
mmc_fake_toc_entry(entry, prev_session, 0xA2, |
|
size_data, start_data, end_data); |
|
entry->min= entry->sec= entry->frame= 0; |
|
d->disc->session[prev_session - 1]->leadout_entry = |
|
entry; |
|
} |
|
|
|
/* ??? >>> d->media_capacity_remaining , d->media_lba_limit |
|
as of mmc_fake_toc() |
|
*/ |
|
|
|
entry = &(d->toc_entry[i + session_number - 1]); |
|
track = burn_track_create(); |
|
if (track == NULL) |
|
{ret = -1; goto ex;} |
|
burn_session_add_track( |
|
d->disc->session[session_number - 1], |
|
track, BURN_POS_END); |
|
track->entry = entry; |
|
burn_track_free(track); |
|
|
|
memcpy(start_data, tdata + 4, 4); |
|
/* size_data are estimated from next track start */ |
|
memcpy(size_data, tdata + 8 + 4, 4); |
|
mmc_int_to_four_char(end_data, |
|
mmc_four_char_to_int(size_data) - 1); |
|
size = mmc_four_char_to_int(size_data) - |
|
mmc_four_char_to_int(start_data); |
|
mmc_int_to_four_char(size_data, size); |
|
mmc_fake_toc_entry(entry, session_number, i + 1, |
|
size_data, start_data, end_data); |
|
if (prev_session != session_number) |
|
d->disc->session[session_number - 1]->firsttrack = i+1; |
|
d->disc->session[session_number - 1]->lasttrack = i+1; |
|
prev_session = session_number; |
|
} |
|
if (prev_session > 0 && prev_session <= d->disc->sessions) { |
|
/* leadout entry of last session of closed disc */ |
|
tdata = c->page->data + 4 + d->last_track_no * 8; |
|
entry = &(d->toc_entry[(d->last_track_no - 1) + prev_session]); |
|
memcpy(start_data, tdata + 4, 4); |
|
mmc_int_to_four_char(size_data, 0); |
|
mmc_int_to_four_char(end_data, |
|
mmc_four_char_to_int(start_data) - 1); |
|
mmc_fake_toc_entry(entry, prev_session, 0xA2, |
|
size_data, start_data, end_data); |
|
entry->min= entry->sec= entry->frame= 0; |
|
d->disc->session[prev_session - 1]->leadout_entry = entry; |
|
} |
|
ret = 1; |
|
ex:; |
|
BURN_FREE_MEM(buf); |
|
BURN_FREE_MEM(c); |
|
return ret; |
|
} |
|
|
|
|
|
/* ts A71128 : for DVD-ROM drives which offer no reliable track information */ |
|
static int mmc_read_toc_fmt0(struct burn_drive *d) |
|
{ |
|
int alloc_len = 4, ret; |
|
|
|
mmc_start_if_needed(d, 1); |
|
if (mmc_function_spy(d, "mmc_read_toc_fmt0") <= 0) |
|
return -1; |
|
ret = mmc_read_toc_fmt0_al(d, &alloc_len); |
|
if (alloc_len >= 12) |
|
ret = mmc_read_toc_fmt0_al(d, &alloc_len); |
|
return ret; |
|
} |
|
|
|
|
|
/* ts A70131 : compose a disc TOC structure from d->complete_sessions |
|
and 52h READ TRACK INFORMATION */ |
|
int mmc_fake_toc(struct burn_drive *d) |
|
{ |
|
struct burn_track *track; |
|
struct burn_session *session; |
|
struct burn_toc_entry *entry; |
|
struct buffer *buf = NULL; |
|
int i, session_number, prev_session = -1, ret, lba, alloc_len = 34; |
|
unsigned char *tdata, size_data[4], start_data[4], end_data[4]; |
|
char *msg = NULL; |
|
|
|
if (mmc_function_spy(d, "mmc_fake_toc") <= 0) |
|
{ret = -1; goto ex;} |
|
BURN_ALLOC_MEM(buf, struct buffer, 1); |
|
|
|
#ifdef Libburn_disc_with_incomplete_sessioN |
|
|
|
if (d->last_track_no <= 0 || |
|
d->complete_sessions + d->incomplete_sessions <= 0 || |
|
d->status == BURN_DISC_BLANK) |
|
{ret = 2; goto ex;} |
|
|
|
#else |
|
|
|
if (d->last_track_no <= 0 || d->complete_sessions <= 0 || |
|
d->status == BURN_DISC_BLANK) |
|
{ret = 2; goto ex;} |
|
|
|
#endif /* ! Libburn_disc_with_incomplete_sessioN */ |
|
|
|
|
|
if (d->last_track_no > BURN_MMC_FAKE_TOC_MAX_SIZE) { |
|
msg = calloc(1, 160); |
|
if (msg != NULL) { |
|
sprintf(msg, |
|
"Too many logical tracks recorded (%d , max. %d)\n", |
|
d->last_track_no, BURN_MMC_FAKE_TOC_MAX_SIZE); |
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x0002012c, |
|
LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH, |
|
msg, 0,0); |
|
free(msg); |
|
} |
|
{ret = 0; goto ex;} |
|
} |
|
/* ts A71128 : My DVD-ROM drive issues no reliable track info. |
|
One has to try 43h READ TOC/PMA/ATIP Form 0. */ |
|
if ((d->current_profile == 0x10) && d->last_track_no <= 1) { |
|
ret = mmc_read_toc_fmt0(d); |
|
goto ex; |
|
} |
|
d->disc = burn_disc_create(); |
|
if (d->disc == NULL) |
|
{ret = -1; goto ex;} |
|
d->toc_entries = d->last_track_no |
|
+ d->complete_sessions + d->incomplete_sessions; |
|
d->toc_entry = calloc(d->toc_entries, sizeof(struct burn_toc_entry)); |
|
if (d->toc_entry == NULL) |
|
{ret = -1; goto ex;} |
|
memset(d->toc_entry, 0,d->toc_entries * sizeof(struct burn_toc_entry)); |
|
|
|
#ifdef Libburn_disc_with_incomplete_sessioN |
|
|
|
for (i = 0; i < d->complete_sessions + d->incomplete_sessions; i++) { |
|
|
|
#else |
|
|
|
for (i = 0; i < d->complete_sessions; i++) { |
|
|
|
#endif |
|
|
|
session = burn_session_create(); |
|
if (session == NULL) |
|
{ret = -1; goto ex;} |
|
burn_disc_add_session(d->disc, session, BURN_POS_END); |
|
burn_session_free(session); |
|
} |
|
|
|
#ifdef Libburn_disc_with_incomplete_sessioN |
|
d->disc->incomplete_sessions = d->incomplete_sessions; |
|
#endif |
|
|
|
memset(size_data, 0, 4); |
|
memset(start_data, 0, 4); |
|
|
|
|
|
/* Entry Layout : |
|
session 1 track 1 entry 0 |
|
... |
|
session 1 track N entry N-1 |
|
leadout 1 entry N |
|
session 2 track N+1 entry N+1 |
|
... |
|
session 2 track M+1 entry M+1 |
|
leadout 2 entry M+2 |
|
session X track K entry (K-1)+(X-1) |
|
... |
|
session X track i+1 entry i+(X-1) |
|
leadout X entry i+X |
|
*/ |
|
for (i = 0; i < d->last_track_no; i++) { |
|
ret = mmc_read_track_info(d, i+1, buf, alloc_len); |
|
if (ret <= 0) |
|
goto ex; |
|
tdata = buf->data; |
|
session_number = (tdata[33] << 8) | tdata[3]; |
|
if (session_number <= 0) |
|
continue; |
|
|
|
if (session_number != prev_session && prev_session > 0) { |
|
/* leadout entry previous session */ |
|
entry = &(d->toc_entry[(i - 1) + prev_session]); |
|
lba = mmc_four_char_to_int(start_data) + |
|
mmc_four_char_to_int(size_data); |
|
mmc_int_to_four_char(start_data, lba); |
|
mmc_int_to_four_char(size_data, 0); |
|
mmc_int_to_four_char(end_data, lba - 1); |
|
mmc_fake_toc_entry(entry, prev_session, 0xA2, |
|
size_data, start_data, end_data); |
|
entry->min= entry->sec= entry->frame= 0; |
|
d->disc->session[prev_session - 1]->leadout_entry = |
|
entry; |
|
} |
|
|
|
#ifdef Libburn_disc_with_incomplete_sessioN |
|
|
|
if (session_number > d->complete_sessions) { |
|
|
|
#else |
|
|
|
if (session_number > d->disc->sessions) { |
|
|
|
#endif |
|
|
|
if (i == d->last_track_no - 1) { |
|
/* ts A70212 : Last track field Free Blocks */ |
|
burn_drive_set_media_capacity_remaining(d, |
|
((off_t) mmc_four_char_to_int(tdata + 16)) * |
|
((off_t) 2048)); |
|
d->media_lba_limit = 0; |
|
} |
|
|
|
#ifdef Libburn_disc_with_incomplete_sessioN |
|
|
|
if (session_number > d->disc->sessions ) |
|
continue; |
|
|
|
#else |
|
|
|
continue; |
|
|
|
#endif |
|
|
|
} |
|
|
|
entry = &(d->toc_entry[i + session_number - 1]); |
|
track = burn_track_create(); |
|
if (track == NULL) |
|
{ret = -1; goto ex;} |
|
burn_session_add_track( |
|
d->disc->session[session_number - 1], |
|
track, BURN_POS_END); |
|
track->entry = entry; |
|
burn_track_free(track); |
|
|
|
memcpy(size_data, tdata + 24, 4); |
|
memcpy(start_data, tdata + 8, 4); |
|
memcpy(end_data, tdata + 28, 4); |
|
mmc_fake_toc_entry(entry, session_number, i + 1, |
|
size_data, start_data, end_data); |
|
entry->track_status_bits = tdata[5] | (tdata[6] << 8) | |
|
(tdata[7] << 16); |
|
entry->extensions_valid |= 4; |
|
|
|
if (prev_session != session_number) |
|
d->disc->session[session_number - 1]->firsttrack = i+1; |
|
d->disc->session[session_number - 1]->lasttrack = i+1; |
|
prev_session = session_number; |
|
} |
|
|
|
if (prev_session > 0 && prev_session <= d->disc->sessions) { |
|
/* leadout entry of last session of closed disc */ |
|
entry = &(d->toc_entry[(d->last_track_no - 1) + prev_session]); |
|
lba = mmc_four_char_to_int(start_data) + |
|
mmc_four_char_to_int(size_data); |
|
mmc_int_to_four_char(start_data, lba); |
|
mmc_int_to_four_char(size_data, 0); |
|
mmc_int_to_four_char(end_data, lba - 1); |
|
mmc_fake_toc_entry(entry, prev_session, 0xA2, |
|
size_data, start_data, end_data); |
|
entry->min= entry->sec= entry->frame= 0; |
|
d->disc->session[prev_session - 1]->leadout_entry = entry; |
|
} |
|
ret = 1; |
|
ex:; |
|
BURN_FREE_MEM(buf); |
|
return ret; |
|
} |
|
|
|
|
|
static int mmc_read_toc_al(struct burn_drive *d, int *alloc_len) |
|
{ |
|
/* read full toc, all sessions, in m/s/f form, 4k buffer */ |
|
/* ts A70201 : or fake a toc from track information */ |
|
struct burn_track *track; |
|
struct burn_session *session; |
|
struct buffer *buf = NULL; |
|
struct command *c = NULL; |
|
int dlen; |
|
int i, old_alloc_len, t_idx, ret; |
|
unsigned char *tdata; |
|
char *msg = NULL; |
|
|
|
if (*alloc_len < 4) |
|
{ret = 0; goto ex;} |
|
|
|
BURN_ALLOC_MEM(buf, struct buffer, 1); |
|
BURN_ALLOC_MEM(c, struct command, 1); |
|
BURN_ALLOC_MEM(msg, char, 321); |
|
|
|
if (!(d->current_profile == -1 || d->current_is_cd_profile)) { |
|
/* ts A70131 : MMC_GET_TOC uses Response Format 2 |
|
For DVD this fails with 5,24,00 */ |
|
/* mmc_read_toc_fmt0() uses |
|
Response Format 0: mmc5r03.pdf 6.26.3.2 |
|
which does not yield the same result with the same disc |
|
on different drives. |
|
*/ |
|
/* ts A70201 : |
|
This uses the session count from 51h READ DISC INFORMATION |
|
and the track records from 52h READ TRACK INFORMATION. |
|
mmc_read_toc_fmt0() is used as fallback for dull DVD-ROM. |
|
*/ |
|
mmc_fake_toc(d); |
|
|
|
if (d->status == BURN_DISC_UNREADY) |
|
d->status = BURN_DISC_FULL; |
|
{ret = 1; goto ex;} |
|
} |
|
|
|
/* ts A90823: |
|
SanDisk Cruzer U3 memory stick stalls on format 2. |
|
Format 0 seems to be more conservative with read-only drives. |
|
*/ |
|
if (!(d->mdata->cdrw_write || d->current_profile != 0x08)) { |
|
ret = mmc_read_toc_fmt0(d); |
|
goto ex; |
|
} |
|
|
|
scsi_init_command(c, MMC_GET_TOC, sizeof(MMC_GET_TOC)); |
|
c->dxfer_len = *alloc_len; |
|
c->opcode[7] = (c->dxfer_len >> 8) & 0xff; |
|
c->opcode[8] = c->dxfer_len & 0xff; |
|
c->retry = 1; |
|
c->page = buf; |
|
c->page->bytes = 0; |
|
c->page->sectors = 0; |
|
c->dir = FROM_DRIVE; |
|
d->issue_command(d, c); |
|
|
|
if (c->error) { |
|
|
|
/* ts A61020 : this snaps on non-blank DVD media */ |
|
/* ts A61106 : also snaps on CD with unclosed track/session */ |
|
/* Very unsure wether this old measure is ok. |
|
Obviously higher levels do not care about this. |
|
outdated info: DVD+RW burns go on after passing through here. |
|
|
|
d->busy = BURN_DRIVE_IDLE; |
|
*/ |
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x0002010d, |
|
LIBDAX_MSGS_SEV_DEBUG, LIBDAX_MSGS_PRIO_HIGH, |
|
"Could not inquire TOC", 0,0); |
|
d->status = BURN_DISC_UNSUITABLE; |
|
d->toc_entries = 0; |
|
/* Prefering memory leaks over fandangos */ |
|
d->toc_entry = calloc(1, sizeof(struct burn_toc_entry)); |
|
{ret = 0; goto ex;} |
|
} |
|
|
|
dlen = c->page->data[0] * 256 + c->page->data[1]; |
|
old_alloc_len = *alloc_len; |
|
*alloc_len = dlen + 2; |
|
if (old_alloc_len < 15) |
|
{ret = 1; goto ex;} |
|
if (dlen + 2 > old_alloc_len) |
|
dlen = old_alloc_len - 2; |
|
d->toc_entries = (dlen - 2) / 11; |
|
if (d->toc_entries < 1) |
|
{ret = 0; goto ex;} |
|
/* |
|
some drives fail this check. |
|
|
|
ts A61007 : if re-enabled then not via Assert. |
|
a ssert(((dlen - 2) % 11) == 0); |
|
*/ |
|
/* ts A81202: plus number of sessions as reserve for leadout default */ |
|
d->toc_entry = calloc(d->toc_entries + (unsigned char) c->page->data[3], |
|
sizeof(struct burn_toc_entry)); |
|
if(d->toc_entry == NULL) /* ts A70825 */ |
|
{ret = 0; goto ex;} |
|
tdata = c->page->data + 4; |
|
|
|
d->disc = burn_disc_create(); |
|
if (d->disc == NULL) /* ts A70825 */ |
|
{ret = 0; goto ex;} |
|
|
|
for (i = 0; i < c->page->data[3]; i++) { |
|
session = burn_session_create(); |
|
if (session == NULL) /* ts A70825 */ |
|
{ret = 0; goto ex;} |
|
burn_disc_add_session(d->disc, session, BURN_POS_END); |
|
burn_session_free(session); |
|
} |
|
|
|
/* ts A61022 */ |
|
|
|
for (i = 0; i < d->toc_entries; i++, tdata += 11) { |
|
|
|
/* |
|
fprintf(stderr, "libburn_experimental: toc entry #%d : %d %d %d\n",i,tdata[8], tdata[9], tdata[10]); |
|
*/ |
|
|
|
#ifdef Libburn_allow_first_hiddeN |
|
/* ts B00430 : this causes problems because the track has |
|
no entry. One would have to coordinate this |
|
with other parts of libburn. |
|
*/ |
|
if (tdata[3] == 1) { |
|
if (burn_msf_to_lba(tdata[8], tdata[9], tdata[10])) { |
|
d->disc->session[0]->hidefirst = 1; |
|
track = burn_track_create(); |
|
burn_session_add_track(d->disc-> |
|
session[tdata[0] - 1], |
|
track, BURN_POS_END); |
|
burn_track_free(track); |
|
} |
|
} |
|
#endif /* Libburn_allow_first_hiddeN */ |
|
|
|
if (tdata[0] <= 0 || tdata[0] > d->disc->sessions) |
|
tdata[0] = d->disc->sessions; |
|
if (tdata[3] < 100 && tdata[0] > 0) { |
|
track = burn_track_create(); |
|
burn_session_add_track(d->disc->session[tdata[0] - 1], |
|
track, BURN_POS_END); |
|
track->entry = &d->toc_entry[i]; |
|
burn_track_free(track); |
|
} |
|
d->toc_entry[i].session = tdata[0]; |
|
d->toc_entry[i].adr = tdata[1] >> 4; |
|
d->toc_entry[i].control = tdata[1] & 0xF; |
|
d->toc_entry[i].tno = tdata[2]; |
|
d->toc_entry[i].point = tdata[3]; |
|
d->toc_entry[i].min = tdata[4]; |
|
d->toc_entry[i].sec = tdata[5]; |
|
d->toc_entry[i].frame = tdata[6]; |
|
d->toc_entry[i].zero = tdata[7]; |
|
d->toc_entry[i].pmin = tdata[8]; |
|
d->toc_entry[i].psec = tdata[9]; |
|
d->toc_entry[i].pframe = tdata[10]; |
|
if (tdata[3] == 0xA0) |
|
d->disc->session[tdata[0] - 1]->firsttrack = tdata[8]; |
|
if (tdata[3] == 0xA1) |
|
d->disc->session[tdata[0] - 1]->lasttrack = tdata[8]; |
|
if (tdata[3] == 0xA2) |
|
d->disc->session[tdata[0] - 1]->leadout_entry = |
|
&d->toc_entry[i]; |
|
} |
|
|
|
/* ts A70131 : was (d->status != BURN_DISC_BLANK) */ |
|
if (d->status == BURN_DISC_UNREADY) |
|
d->status = BURN_DISC_FULL; |
|
toc_find_modes(d); |
|
|
|
/* ts A81202 ticket 146 : a drive reported a session with no leadout */ |
|
for (i = 0; i < d->disc->sessions; i++) { |
|
if (d->disc->session[i]->leadout_entry != NULL) |
|
continue; |
|
sprintf(msg, "Session %d of %d encountered without leadout", |
|
i + 1, d->disc->sessions); |
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x00020160, |
|
LIBDAX_MSGS_SEV_WARNING, LIBDAX_MSGS_PRIO_HIGH, |
|
msg, 0, 0); |
|
|
|
/* Produce default leadout entry from last track of session |
|
which will thus get its size set to 0 */; |
|
if (d->disc->session[i]->track != NULL && |
|
d->disc->session[i]->tracks > 0) { |
|
t_idx = d->toc_entries++; |
|
memcpy(d->toc_entry + t_idx, |
|
d->disc->session[i]->track[ |
|
d->disc->session[i]->tracks - 1]->entry, |
|
sizeof(struct burn_toc_entry)); |
|
d->toc_entry[t_idx].point = 0xA2; |
|
d->disc->session[i]->leadout_entry = |
|
d->toc_entry + t_idx; |
|
} else { |
|
burn_disc_remove_session(d->disc, d->disc->session[i]); |
|
sprintf(msg, |
|
"Empty session %d deleted. Now %d sessions.", |
|
i + 1, d->disc->sessions); |
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x00020161, |
|
LIBDAX_MSGS_SEV_WARNING, LIBDAX_MSGS_PRIO_HIGH, |
|
msg, 0, 0); |
|
i--; |
|
} |
|
} |
|
|
|
/* A80808 */ |
|
burn_disc_cd_toc_extensions(d, 0); |
|
|
|
ret = 1; |
|
ex:; |
|
BURN_FREE_MEM(msg); |
|
BURN_FREE_MEM(c); |
|
BURN_FREE_MEM(buf); |
|
return ret; |
|
} |
|
|
|
|
|
void mmc_read_toc(struct burn_drive *d) |
|
{ |
|
int alloc_len = 4, ret; |
|
|
|
mmc_start_if_needed(d, 1); |
|
if (mmc_function_spy(d, "mmc_read_toc") <= 0) |
|
return; |
|
|
|
ret = mmc_read_toc_al(d, &alloc_len); |
|
/* |
|
fprintf(stderr, |
|
"LIBBURN_DEBUG: 43h READ TOC alloc_len = %d , ret = %d\n", |
|
alloc_len, ret); |
|
*/ |
|
if (alloc_len >= 15) |
|
ret = mmc_read_toc_al(d, &alloc_len); |
|
if (ret <= 0) |
|
return; |
|
} |
|
|
|
|
|
/* ts A70131 : This tries to get the start of the last complete session */ |
|
/* man mkisofs , option -C : |
|
The first number is the sector number of the first sector in |
|
the last session of the disk that should be appended to. |
|
*/ |
|
int mmc_read_multi_session_c1(struct burn_drive *d, int *trackno, int *start) |
|
{ |
|
struct buffer *buf = NULL; |
|
struct command *c = NULL; |
|
unsigned char *tdata; |
|
int num_sessions, session_no, num_tracks, alloc_len = 12, ret; |
|
struct burn_disc *disc; |
|
struct burn_session **sessions; |
|
struct burn_track **tracks; |
|
struct burn_toc_entry toc_entry; |
|
|
|
BURN_ALLOC_MEM(buf, struct buffer, 1); |
|
BURN_ALLOC_MEM(c, struct command, 1); |
|
mmc_start_if_needed(d, 1); |
|
if (mmc_function_spy(d, "mmc_read_multi_session_c1") <= 0) |
|
{ret = 0; goto ex;} |
|
|
|
/* First try to evaluate the eventually loaded TOC before issueing |
|
a MMC command. This search obtains the first track of the last |
|
complete session which has a track. |
|
*/ |
|
*trackno = 0; |
|
disc = burn_drive_get_disc(d); |
|
if (disc == NULL) |
|
goto inquire_drive; |
|
sessions = burn_disc_get_sessions(disc, &num_sessions); |
|
for (session_no = 0; session_no<num_sessions; session_no++) { |
|
tracks = burn_session_get_tracks(sessions[session_no], |
|
&num_tracks); |
|
if (tracks == NULL || num_tracks <= 0) |
|
continue; |
|
burn_track_get_entry(tracks[0], &toc_entry); |
|
if (toc_entry.extensions_valid & 1) { /* DVD extension valid */ |
|
*start = toc_entry.start_lba; |
|
*trackno = (toc_entry.point_msb << 8)| toc_entry.point; |
|
} else { |
|
*start = burn_msf_to_lba(toc_entry.pmin, |
|
toc_entry.psec, toc_entry.pframe); |
|
*trackno = toc_entry.point; |
|
} |
|
} |
|
burn_disc_free(disc); |
|
if(*trackno > 0) |
|
{ret = 1; goto ex;} |
|
|
|
inquire_drive:; |
|
/* mmc5r03.pdf 6.26.3.3.3 states that with non-CD this would |
|
be a useless fake always starting at track 1, lba 0. |
|
My drives return useful data, though. |
|
MMC-3 states that DVD had no tracks. So maybe this mandatory fake |
|
is a forgotten legacy ? |
|
*/ |
|
scsi_init_command(c, MMC_GET_MSINFO, sizeof(MMC_GET_MSINFO)); |
|
c->dxfer_len = alloc_len; |
|
c->opcode[7]= (c->dxfer_len >> 8) & 0xff; |
|
c->opcode[8]= c->dxfer_len & 0xff; |
|
c->retry = 1; |
|
c->page = buf; |
|
c->page->bytes = 0; |
|
c->page->sectors = 0; |
|
c->dir = FROM_DRIVE; |
|
d->issue_command(d, c); |
|
|
|
if (c->error) |
|
{ret = 0; goto ex;} |
|
|
|
tdata = c->page->data + 4; |
|
*trackno = tdata[2]; |
|
*start = mmc_four_char_to_int(tdata + 4); |
|
ret = 1; |
|
ex:; |
|
BURN_FREE_MEM(buf); |
|
BURN_FREE_MEM(c); |
|
return ret; |
|
} |
|
|
|
|
|
/* ts A61201 */ |
|
char *mmc_obtain_profile_name(int profile_number) |
|
{ |
|
static char *texts[0x53] = {NULL}; |
|
int i, max_pno = 0x53; |
|
|
|
if (texts[0] == NULL) { |
|
for (i = 0; i<max_pno; i++) |
|
texts[i] = ""; |
|
/* mmc5r03c.pdf , Table 89, Spelling: guessed cdrecord style */ |
|
texts[0x01] = "Non-removable disk"; |
|
texts[0x02] = "Removable disk"; |
|
texts[0x03] = "MO erasable"; |
|
texts[0x04] = "Optical write once"; |
|
texts[0x05] = "AS-MO"; |
|
texts[0x08] = "CD-ROM"; |
|
texts[0x09] = "CD-R"; |
|
texts[0x0a] = "CD-RW"; |
|
texts[0x10] = "DVD-ROM"; |
|
texts[0x11] = "DVD-R sequential recording"; |
|
texts[0x12] = "DVD-RAM"; |
|
texts[0x13] = "DVD-RW restricted overwrite"; |
|
texts[0x14] = "DVD-RW sequential recording"; |
|
texts[0x15] = "DVD-R/DL sequential recording"; |
|
texts[0x16] = "DVD-R/DL layer jump recording"; |
|
texts[0x1a] = "DVD+RW"; |
|
texts[0x1b] = "DVD+R"; |
|
texts[0x2a] = "DVD+RW/DL"; |
|
texts[0x2b] = "DVD+R/DL"; |
|
texts[0x40] = "BD-ROM"; |
|
texts[0x41] = "BD-R sequential recording"; |
|
texts[0x42] = "BD-R random recording"; |
|
texts[0x43] = "BD-RE"; |
|
texts[0x50] = "HD-DVD-ROM"; |
|
texts[0x51] = "HD-DVD-R"; |
|
texts[0x52] = "HD-DVD-RAM"; |
|
} |
|
if (profile_number<0 || profile_number>=max_pno) |
|
return ""; |
|
return texts[profile_number]; |
|
} |
|
|
|
|
|
/* ts A90603 : to be used if the drive knows no GET CONFIGURATION |
|
*/ |
|
static int mmc_guess_profile(struct burn_drive *d, int flag) |
|
{ |
|
int cp; |
|
|
|
cp = 0; |
|
if (d->status == BURN_DISC_BLANK || |
|
d->status == BURN_DISC_APPENDABLE) { |
|
cp = 0x09; |
|
} else if (d->status == BURN_DISC_FULL) { |
|
cp = 0x08; |
|
} |
|
if (cp) |
|
if (d->erasable) |
|
cp = 0x0a; |
|
d->current_profile = cp; |
|
if (cp == 0) |
|
return 0; |
|
d->current_is_cd_profile = 1; |
|
d->current_is_supported_profile = 1; |
|
strcpy(d->current_profile_text, mmc_obtain_profile_name(cp)); |
|
return 1; |
|
} |
|
|
|
|
|
static int mmc_read_disc_info_al(struct burn_drive *d, int *alloc_len) |
|
{ |
|
struct buffer *buf = NULL; |
|
unsigned char *data; |
|
struct command *c = NULL; |
|
char *msg = NULL; |
|
/* ts A70131 : had to move mmc_read_toc() to end of function */ |
|
int do_read_toc = 0, disc_status, len, old_alloc_len; |
|
int ret, number_of_sessions = -1; |
|
int key, asc, ascq; |
|
|
|
BURN_ALLOC_MEM(buf, struct buffer, 1); |
|
BURN_ALLOC_MEM(c, struct command, 1); |
|
|
|
/* ts A61020 */ |
|
d->start_lba = d->end_lba = -2000000000; |
|
d->erasable = 0; |
|
d->last_track_no = 1; |
|
|
|
/* ts B10730 */ |
|
d->sent_default_page_05 = 0; |
|
/* ts A70212 - A70215 */ |
|
d->media_capacity_remaining = 0; |
|
d->media_lba_limit = 0; |
|
|
|
/* ts A81210 */ |
|
d->media_read_capacity = 0x7fffffff; |
|
|
|
/* ts A61202 */ |
|
d->toc_entries = 0; |
|
if (d->status == BURN_DISC_EMPTY) |
|
{ret = 1; goto ex;} |
|
|
|
mmc_get_configuration(d); |
|
|
|
scsi_init_command(c, MMC_GET_DISC_INFO, sizeof(MMC_GET_DISC_INFO)); |
|
c->dxfer_len = *alloc_len; |
|
c->opcode[7]= (c->dxfer_len >> 8) & 0xff; |
|
c->opcode[8]= c->dxfer_len & 0xff; |
|
c->retry = 1; |
|
c->page = buf; |
|
c->page->sectors = 0; |
|
c->page->bytes = 0; |
|
c->dir = FROM_DRIVE; |
|
d->issue_command(d, c); |
|
|
|
if (c->error) { |
|
spc_decode_sense(c->sense, 0, &key, &asc, &ascq); |
|
if (key == 5 && asc == 0x20 && ascq == 0) { |
|
/* ts B11031 : qemu -cdrom does not know |
|
051h READ DISC INFORMATION |
|
*/ |
|
ret = mmc_read_toc_fmt0(d); |
|
if (ret > 0) { |
|
|
|
/* >>> ??? anything more to be set ? */; |
|
|
|
mmc_read_capacity(d); |
|
*alloc_len = 0; |
|
goto ex; |
|
} |
|
} |
|
|
|
d->busy = BURN_DRIVE_IDLE; |
|
{ret = 0; goto ex;} |
|
} |
|
|
|
data = c->page->data; |
|
len = (data[0] << 8) | data[1]; |
|
old_alloc_len = *alloc_len; |
|
*alloc_len = len + 2; |
|
if (old_alloc_len < 34) |
|
{ret = 1; goto ex;} |
|
if (*alloc_len < 24) /* data[23] is the last mandatory byte here */ |
|
{ret = 0; goto ex;} |
|
if (len + 2 > old_alloc_len) |
|
len = old_alloc_len - 2; |
|
|
|
d->erasable = !!(data[2] & 16); |
|
|
|
/* ts A90908 */ |
|
d->disc_type = data[8]; |
|
d->disc_info_valid = 1; |
|
d->disc_id = mmc_four_char_to_int(data + 12); |
|
d->disc_info_valid |= (!!(data[7] & 128)) << 1; |
|
if (len + 2 > 31 && (data[7] & 64)) { |
|
memcpy(d->disc_bar_code, data + 24, 8); |
|
d->disc_bar_code[8] = 0; |
|
d->disc_info_valid |= 4; |
|
} |
|
if (len + 2 > 32 && (data[7] & 16)) { |
|
d->disc_app_code = data[32]; |
|
d->disc_info_valid |= 8; |
|
} |
|
if (data[7] & 32) |
|
d->disc_info_valid |= 16; |
|
if (data[2] & 16) |
|
d->disc_info_valid |= 32; |
|
|
|
disc_status = data[2] & 3; |
|
d->state_of_last_session = (data[2] >> 2) & 3; |
|
number_of_sessions = (data[9] << 8) | data[4]; |
|
|
|
if (d->current_profile == 0x10 || d->current_profile == 0x40) { |
|
/* DVD-ROM , BD-ROM */ |
|
disc_status = 2; /* always full and finalized */ |
|
d->erasable = 0; /* never erasable */ |
|
} |
|
|
|
#ifdef Libburn_support_bd_r_readonlY |
|
/* <<< For now: declaring BD-R read-only |
|
*/ |
|
#ifndef Libburn_support_bd_plus_r_srM |
|
if (d->current_profile == 0x41) { |
|
/* BD-R seq as readonly dummy */ |
|
disc_status = 2; /* always full and finalized */ |
|
d->erasable = 0; /* never erasable */ |
|
} |
|
#endif |
|
if (d->current_profile == 0x42) { |
|
/* BD-R rnd */ |
|
disc_status = 2; /* always full and finalized */ |
|
d->erasable = 0; /* never erasable */ |
|
} |
|
#endif /* Libburn_support_bd_r_readonlY */ |
|
|
|
/* MMC-5 6.22.3.1.16: |
|
Last Session Lead-in Start Address bytes 16 to 19 |
|
Last Possible Lead-out Start Address bytes 20 to 23 |
|
MSF for CD, LBA else |
|
*/ |
|
if(d->current_profile == 0x08 || d->current_profile == 0x09 || |
|
d->current_profile == 0x0a) { |
|
d->last_lead_in = |
|
burn_msf_to_lba(data[17], data[18], data[19]); |
|
d->last_lead_out = |
|
burn_msf_to_lba(data[21], data[22], data[23]); |
|
} else { |
|
d->last_lead_in = mmc_four_char_to_int(data + 16); |
|
d->last_lead_out = mmc_four_char_to_int(data + 20); |
|
} |
|
|
|
switch (disc_status) { |
|
case 0: |
|
regard_as_blank:; |
|
d->toc_entries = 0; |
|
|
|
/* |
|
fprintf(stderr, "libburn_experimental: start_lba = %d (%d %d %d) , end_lba = %d (%d %d %d)\n", |
|
d->start_lba, data[17], data[18], data[19], |
|
d->end_lba, data[21], data[22], data[23]); |
|
*/ |
|
|
|
d->status = BURN_DISC_BLANK; |
|
d->start_lba = d->last_lead_in; |
|
d->end_lba = d->last_lead_out; |
|
break; |
|
case 1: |
|
d->status = BURN_DISC_APPENDABLE; |
|
|
|
case 2: |
|
if (disc_status == 2) |
|
d->status = BURN_DISC_FULL; |
|
|
|
/* ts A81210 */ |
|
ret = mmc_read_capacity(d); |
|
/* Freshly formatted, unwritten BD-R pretend to be appendable |
|
but in our model they need to be regarded as blank. |
|
Criterion: BD-R seq, read capacity known and 0, |
|
declared appendable, single empty session |
|
*/ |
|
if (d->current_profile == 0x41 && |
|
d->status == BURN_DISC_APPENDABLE && |
|
ret > 0 && d->media_read_capacity == 0 && |
|
d->state_of_last_session == 0 && number_of_sessions == 1) |
|
goto regard_as_blank; |
|
|
|
if (d->current_profile == 0x41 && |
|
d->status == BURN_DISC_APPENDABLE && |
|
d->state_of_last_session == 1) { |
|
|
|
/* ??? apply this test to other media types ? */ |
|
|
|
libdax_msgs_submit(libdax_messenger, d->global_index, |
|
0x00020169, |
|
LIBDAX_MSGS_SEV_WARNING, LIBDAX_MSGS_PRIO_HIGH, |
|
"Last session on media is still open.", 0, 0); |
|
} |
|
|
|
do_read_toc = 1; |
|
break; |
|
case 3: |
|
/* ts A91009 : DVD-RAM has disc status "others" */ |
|
mmc_read_capacity(d); |
|
break; |
|
} |
|
|
|
/* ts A90603 : An MMC-1 drive might not know the media type yet */ |
|
if (d->current_is_guessed_profile && d->current_profile == 0) |
|
mmc_guess_profile(d, 0); |
|
|
|
if ((d->current_profile != 0 || d->status != BURN_DISC_UNREADY) |
|
&& ! d->current_is_supported_profile) { |
|
if (!d->silent_on_scsi_error) { |
|
msg = calloc(1, 160); |
|
if (msg != NULL) { |
|
sprintf(msg, |
|
"Unsuitable media detected. Profile %4.4Xh %s", |
|
d->current_profile, d->current_profile_text); |
|
libdax_msgs_submit(libdax_messenger, |
|
d->global_index, 0x0002011e, |
|
LIBDAX_MSGS_SEV_SORRY, LIBDAX_MSGS_PRIO_HIGH, |
|
msg, 0,0); |
|
free(msg); |
|
} |
|
} |
|
d->status = BURN_DISC_UNSUITABLE; |
|
{ret = 0; goto ex;} |
|
} |
|
|
|
/* ts A61217 : |
|
growisofs performs OPC if (data[0]<<8)|data[1]<=32 |
|
which indicates no OPC entries are attached to the |
|
reply from the drive. |
|
ts A91104 : |
|
Actually growisofs performs OPC only on DVD-R[W]. |
|
*/ |
|
d->num_opc_tables = 0; |
|
if(((data[0] << 8) | data[1]) > 32) /* i.e. > 34 bytes are available */ |
|
d->num_opc_tables = data[33]; |
|
|
|
/* ts A61219 : mmc5r03c.pdf 6.22.3.1.13 BG Format Status |
|
0=blank (not yet started) |
|
1=started but neither running nor complete |
|
2=in progress |
|
3=completed |
|
*/ |
|
d->bg_format_status = data[7] & 3; |
|
|
|
/* Preliminarily declare blank: |
|
ts A61219 : DVD+RW (is not bg_format_status==0 "blank") |
|
ts A61229 : same for DVD-RW Restricted overwrite |
|
ts A70112 : same for DVD-RAM |
|
*/ |
|
if (d->current_profile == 0x1a || d->current_profile == 0x13 || |
|
d->current_profile == 0x12 || d->current_profile == 0x43) |
|
d->status = BURN_DISC_BLANK; |
|
|
|
#ifdef Libburn_disc_with_incomplete_sessioN |
|
/* ts B30112 : number of open sessions */ |
|
d->incomplete_sessions = 0; |
|
#endif |
|
|
|
if (d->status == BURN_DISC_BLANK) { |
|
d->last_track_no = 1; /* The "incomplete track" */ |
|
d->complete_sessions = 0; |
|
} else { |
|
/* ts A70131 : number of closed sessions */ |
|
d->complete_sessions = number_of_sessions; |
|
/* mmc5r03c.pdf 6.22.3.1.3 State of Last Session: 3=complete */ |
|
if (d->state_of_last_session != 3 && |
|
d->complete_sessions >= 1) { |
|
d->complete_sessions--; |
|
|
|
#ifdef Libburn_disc_with_incomplete_sessioN |
|
d->incomplete_sessions++; |
|
#endif |
|
|
|
} |
|
|
|
/* ts A70129 : mmc5r03c.pdf 6.22.3.1.7 |
|
This includes the "incomplete track" if the disk is |
|
appendable. I.e number of complete tracks + 1. */ |
|
d->last_track_no = (data[11] << 8) | data[6]; |
|
} |
|
if (d->current_profile != 0x0a && d->current_profile != 0x13 && |
|
d->current_profile != 0x14 && d->status != BURN_DISC_FULL) |
|
d->erasable = 0; /* stay in sync with burn_disc_erase() */ |
|
|
|
if (do_read_toc) |
|
mmc_read_toc(d); |
|
ret = 1; |
|
ex: |
|
BURN_FREE_MEM(buf); |
|
BURN_FREE_MEM(c); |
|
return ret; |
|
} |
|
|
|
|
|
void mmc_read_disc_info(struct burn_drive *d) |
|
{ |
|
int alloc_len = 34, ret; |
|
|
|
mmc_start_if_needed(d, 1); |
|
if (mmc_function_spy(d, "mmc_read_disc_info") <= 0) |
|
return; |
|
|
|
ret = mmc_read_disc_info_al(d, &alloc_len); |
|
/* |
|
fprintf(stderr,"LIBBURN_DEBUG: 51h alloc_len = %d , ret = %d\n", |
|
alloc_len, ret); |
|
*/ |
|
if (ret <= 0) |
|
return; |
|
|
|
/* for now there is no need to inquire the variable lenght part */ |
|
} |
|
|
|
|
|
/* @param flag bit= do not allocate text_packs |
|
*/ |
|
static int mmc_get_leadin_text_al(struct burn_drive *d, |
|
unsigned char **text_packs, int *alloc_len, |
|
int flag) |
|
{ |
|
struct buffer *buf = NULL; |
|
struct command *c = NULL; |
|
unsigned char *data; |
|
int ret, data_length; |
|
|
|
*text_packs = NULL; |
|
|
|
BURN_ALLOC_MEM(buf, struct buffer, 1); |
|
BURN_ALLOC_MEM(c, struct command, 1); |
|
|
|
scsi_init_command(c, MMC_GET_LEADTEXT, sizeof(MMC_GET_LEADTEXT)); |
|
c->dxfer_len = *alloc_len; |
|
c->opcode[7]= (c->dxfer_len >> 8) & 0xff; |
|
c->opcode[8]= c->dxfer_len & 0xff; |
|
c->retry = 1; |
|
c->page = buf; |
|
c->page->bytes = 0; |
|
c->page->sectors = 0; |
|
|
|
c->dir = FROM_DRIVE; |
|
d->issue_command(d, c); |
|
if (c->error) |
|
{ret = 0; goto ex;} |
|
|
|
data = c->page->data; |
|
data_length = (data[0] << 8) + data[1]; |
|
*alloc_len = data_length + 2; |
|
if (*alloc_len >= 22 && !(flag & 1)) { |
|
BURN_ALLOC_MEM(*text_packs, unsigned char, *alloc_len - 4); |
|
memcpy(*text_packs, data + 4, *alloc_len - 4); |
|
} |
|
ret = 1; |
|
ex:; |
|
BURN_FREE_MEM(c); |
|
BURN_FREE_MEM(buf); |
|
return ret; |
|
} |
|
|
|
|
|
/* ts B11201 */ |
|
/* Read the CD-TEXT data from the Lead-in of an Audio CD |
|
*/ |
|
int mmc_get_leadin_text(struct burn_drive *d, |
|
unsigned char **text_packs, int *num_packs, int flag) |
|
{ |
|
int alloc_len = 4, ret; |
|
|
|
*num_packs = 0; |
|
if (mmc_function_spy(d, "mmc_get_leadin_text") <= 0) |
|
return -1; |
|
ret = mmc_get_leadin_text_al(d, text_packs, &alloc_len, 1); |
|
if (ret <= 0 || alloc_len < 22) |
|
return (ret > 0 ? 0 : ret); |
|
ret = mmc_get_leadin_text_al(d, text_packs, &alloc_len, 0); |
|
if (ret <= 0 || alloc_len < 22) { |
|
if (*text_packs != NULL) |
|
free(*text_packs); |
|
*text_packs = NULL; |
|
return (ret > 0 ? 0 : ret); |
|
} |
|
*num_packs = (alloc_len - 4) / 18; |
|
return ret; |
|
} |
|
|
|
|
|
void mmc_read_atip(struct burn_drive *d) |
|
{ |
|
struct buffer *buf = NULL; |
|
struct command *c = NULL; |
|
int alloc_len = 28; |
|
|
|
/* ts A61021 */ |
|
unsigned char *data; |
|
/* Speed values from A1: |
|
With 4 cdrecord tells "10" or "8" where MMC-1 says "8". |
|
cdrecord "8" appear on 4xCD-RW and thus seem to be quite invalid. |
|
My CD-R (>=24 speed) tell no A1. |
|
The higher non-MMC-1 values are hearsay. |
|
*/ |
|
/* 0, 2, 4, 6, 10, -, 16, -, */ |
|
static int speed_value[16]= { 0, 353, 706, 1059, 1764, -5, 2824, -7, |
|
4234, 5646, 7056, 8468, -12, -13, -14, -15}; |
|
/* 24, 32, 40, 48, -, -, -, - */ |
|
|
|
BURN_ALLOC_MEM_VOID(buf, struct buffer, 1); |
|
BURN_ALLOC_MEM_VOID(c, struct command, 1); |
|
mmc_start_if_needed(d, 1); |
|
if (mmc_function_spy(d, "mmc_read_atip") <= 0) |
|
goto ex; |
|
|
|
scsi_init_command(c, MMC_GET_ATIP, sizeof(MMC_GET_ATIP)); |
|
c->dxfer_len = alloc_len; |
|
c->opcode[7] = (c->dxfer_len >> 8) & 0xff; |
|
c->opcode[8] = c->dxfer_len & 0xff; |
|
c->retry = 1; |
|
c->page = buf; |
|
c->page->bytes = 0; |
|
c->page->sectors = 0; |
|
|
|
c->dir = FROM_DRIVE; |
|
d->issue_command(d, c); |
|
/* ts B00501 : now caring for error */ |
|
if (c->error) { |
|
d->erasable = 0; |
|
d->start_lba = 0; |
|
d->end_lba = 0; |
|
goto ex; |
|
} |
|
|
|
/* ts A61021 */ |
|
data = c->page->data; |
|
d->erasable = !!(data[6]&64); |
|
d->start_lba = burn_msf_to_lba(data[8],data[9],data[10]); |
|
d->end_lba = burn_msf_to_lba(data[12],data[13],data[14]); |
|
|
|
/* ts B21124 : LITE-ON LTR-48125S returns crap on pressed |
|
audio CD and CD-ROM |
|
*/ |
|
if (d->start_lba >= d->end_lba) { |
|
d->start_lba = 0; |
|
d->end_lba = 0; |
|
} |
|
|
|
if (data[6]&4) { |
|
|