📄 optcd.c
字号:
}
/* Address conversion routines */
/* Binary to BCD (2 digits) */
inline static void single_bin2bcd(u_char *p)
{
DEBUG((DEBUG_CONV, "bin2bcd %02d", *p));
*p = (*p % 10) | ((*p / 10) << 4);
}
/* Convert entire msf struct */
static void bin2bcd(struct cdrom_msf *msf)
{
single_bin2bcd(&msf->cdmsf_min0);
single_bin2bcd(&msf->cdmsf_sec0);
single_bin2bcd(&msf->cdmsf_frame0);
single_bin2bcd(&msf->cdmsf_min1);
single_bin2bcd(&msf->cdmsf_sec1);
single_bin2bcd(&msf->cdmsf_frame1);
}
/* Linear block address to minute, second, frame form */
#define CD_FPM (CD_SECS * CD_FRAMES) /* frames per minute */
static void lba2msf(int lba, struct cdrom_msf *msf)
{
DEBUG((DEBUG_CONV, "lba2msf %d", lba));
lba += CD_MSF_OFFSET;
msf->cdmsf_min0 = lba / CD_FPM; lba %= CD_FPM;
msf->cdmsf_sec0 = lba / CD_FRAMES;
msf->cdmsf_frame0 = lba % CD_FRAMES;
msf->cdmsf_min1 = 0;
msf->cdmsf_sec1 = 0;
msf->cdmsf_frame1 = 0;
bin2bcd(msf);
}
/* Two BCD digits to binary */
inline static u_char bcd2bin(u_char bcd)
{
DEBUG((DEBUG_CONV, "bcd2bin %x%02x", bcd));
return (bcd >> 4) * 10 + (bcd & 0x0f);
}
static void msf2lba(union cdrom_addr *addr)
{
addr->lba = addr->msf.minute * CD_FPM
+ addr->msf.second * CD_FRAMES
+ addr->msf.frame - CD_MSF_OFFSET;
}
/* Minute, second, frame address BCD to binary or to linear address,
depending on MODE */
static void msf_bcd2bin(union cdrom_addr *addr)
{
addr->msf.minute = bcd2bin(addr->msf.minute);
addr->msf.second = bcd2bin(addr->msf.second);
addr->msf.frame = bcd2bin(addr->msf.frame);
}
/* High level drive commands */
static int audio_status = CDROM_AUDIO_NO_STATUS;
static char toc_uptodate = 0;
static char disk_changed = 1;
/* Get drive status, flagging completion of audio play and disk changes. */
static int drive_status(void)
{
int status;
status = exec_cmd(COMIOCTLISTAT);
DEBUG((DEBUG_DRIVE_IF, "IOCTLISTAT: %03x", status));
if (status < 0)
return status;
if (status == 0xff) /* No status available */
return -ERR_IF_NOSTAT;
if (((status & ST_MODE_BITS) != ST_M_AUDIO) &&
(audio_status == CDROM_AUDIO_PLAY)) {
audio_status = CDROM_AUDIO_COMPLETED;
}
if (status & ST_DSK_CHG) {
toc_uptodate = 0;
disk_changed = 1;
audio_status = CDROM_AUDIO_NO_STATUS;
}
return status;
}
/* Read the current Q-channel info. Also used for reading the
table of contents. qp->cdsc_format must be set on entry to
indicate the desired address format */
static int get_q_channel(struct cdrom_subchnl *qp)
{
int status, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10;
status = drive_status();
if (status < 0)
return status;
qp->cdsc_audiostatus = audio_status;
status = exec_cmd(COMSUBQ);
if (status < 0)
return status;
d1 = get_data(0);
if (d1 < 0)
return d1;
qp->cdsc_adr = d1;
qp->cdsc_ctrl = d1 >> 4;
d2 = get_data(0);
if (d2 < 0)
return d2;
qp->cdsc_trk = bcd2bin(d2);
d3 = get_data(0);
if (d3 < 0)
return d3;
qp->cdsc_ind = bcd2bin(d3);
d4 = get_data(0);
if (d4 < 0)
return d4;
qp->cdsc_reladdr.msf.minute = d4;
d5 = get_data(0);
if (d5 < 0)
return d5;
qp->cdsc_reladdr.msf.second = d5;
d6 = get_data(0);
if (d6 < 0)
return d6;
qp->cdsc_reladdr.msf.frame = d6;
d7 = get_data(0);
if (d7 < 0)
return d7;
/* byte not used */
d8 = get_data(0);
if (d8 < 0)
return d8;
qp->cdsc_absaddr.msf.minute = d8;
d9 = get_data(0);
if (d9 < 0)
return d9;
qp->cdsc_absaddr.msf.second = d9;
d10 = get_data(0);
if (d10 < 0)
return d10;
qp->cdsc_absaddr.msf.frame = d10;
DEBUG((DEBUG_TOC, "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
d1, d2, d3, d4, d5, d6, d7, d8, d9, d10));
msf_bcd2bin(&qp->cdsc_absaddr);
msf_bcd2bin(&qp->cdsc_reladdr);
if (qp->cdsc_format == CDROM_LBA) {
msf2lba(&qp->cdsc_absaddr);
msf2lba(&qp->cdsc_reladdr);
}
return 0;
}
/* Table of contents handling */
/* Errors in table of contents */
#define ERR_TOC_MISSINGINFO 0x120
#define ERR_TOC_MISSINGENTRY 0x121
struct cdrom_disk_info {
unsigned char first;
unsigned char last;
struct cdrom_msf0 disk_length;
struct cdrom_msf0 first_track;
/* Multisession info: */
unsigned char next;
struct cdrom_msf0 next_session;
struct cdrom_msf0 last_session;
unsigned char multi;
unsigned char xa;
unsigned char audio;
};
static struct cdrom_disk_info disk_info;
#define MAX_TRACKS 111
static struct cdrom_subchnl toc[MAX_TRACKS];
#define QINFO_FIRSTTRACK 100 /* bcd2bin(0xa0) */
#define QINFO_LASTTRACK 101 /* bcd2bin(0xa1) */
#define QINFO_DISKLENGTH 102 /* bcd2bin(0xa2) */
#define QINFO_NEXTSESSION 110 /* bcd2bin(0xb0) */
#define I_FIRSTTRACK 0x01
#define I_LASTTRACK 0x02
#define I_DISKLENGTH 0x04
#define I_NEXTSESSION 0x08
#define I_ALL (I_FIRSTTRACK | I_LASTTRACK | I_DISKLENGTH)
#if DEBUG_TOC
void toc_debug_info(int i)
{
printk(KERN_DEBUG "#%3d ctl %1x, adr %1x, track %2d index %3d"
" %2d:%02d.%02d %2d:%02d.%02d\n",
i, toc[i].cdsc_ctrl, toc[i].cdsc_adr,
toc[i].cdsc_trk, toc[i].cdsc_ind,
toc[i].cdsc_reladdr.msf.minute,
toc[i].cdsc_reladdr.msf.second,
toc[i].cdsc_reladdr.msf.frame,
toc[i].cdsc_absaddr.msf.minute,
toc[i].cdsc_absaddr.msf.second,
toc[i].cdsc_absaddr.msf.frame);
}
#endif
static int read_toc(void)
{
int status, limit, count;
unsigned char got_info = 0;
struct cdrom_subchnl q_info;
#if DEBUG_TOC
int i;
#endif
DEBUG((DEBUG_TOC, "starting read_toc"));
count = 0;
for (limit = 60; limit > 0; limit--) {
int index;
q_info.cdsc_format = CDROM_MSF;
status = get_q_channel(&q_info);
if (status < 0)
return status;
index = q_info.cdsc_ind;
if (index > 0 && index < MAX_TRACKS
&& q_info.cdsc_trk == 0 && toc[index].cdsc_ind == 0) {
toc[index] = q_info;
DEBUG((DEBUG_TOC, "got %d", index));
if (index < 100)
count++;
switch (q_info.cdsc_ind) {
case QINFO_FIRSTTRACK:
got_info |= I_FIRSTTRACK;
break;
case QINFO_LASTTRACK:
got_info |= I_LASTTRACK;
break;
case QINFO_DISKLENGTH:
got_info |= I_DISKLENGTH;
break;
case QINFO_NEXTSESSION:
got_info |= I_NEXTSESSION;
break;
}
}
if ((got_info & I_ALL) == I_ALL
&& toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute + count
>= toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute + 1)
break;
}
/* Construct disk_info from TOC */
if (disk_info.first == 0) {
disk_info.first = toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute;
disk_info.first_track.minute =
toc[disk_info.first].cdsc_absaddr.msf.minute;
disk_info.first_track.second =
toc[disk_info.first].cdsc_absaddr.msf.second;
disk_info.first_track.frame =
toc[disk_info.first].cdsc_absaddr.msf.frame;
}
disk_info.last = toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute;
disk_info.disk_length.minute =
toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.minute;
disk_info.disk_length.second =
toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.second-2;
disk_info.disk_length.frame =
toc[QINFO_DISKLENGTH].cdsc_absaddr.msf.frame;
disk_info.next_session.minute =
toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.minute;
disk_info.next_session.second =
toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.second;
disk_info.next_session.frame =
toc[QINFO_NEXTSESSION].cdsc_reladdr.msf.frame;
disk_info.next = toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute;
disk_info.last_session.minute =
toc[disk_info.next].cdsc_absaddr.msf.minute;
disk_info.last_session.second =
toc[disk_info.next].cdsc_absaddr.msf.second;
disk_info.last_session.frame =
toc[disk_info.next].cdsc_absaddr.msf.frame;
toc[disk_info.last + 1].cdsc_absaddr.msf.minute =
disk_info.disk_length.minute;
toc[disk_info.last + 1].cdsc_absaddr.msf.second =
disk_info.disk_length.second;
toc[disk_info.last + 1].cdsc_absaddr.msf.frame =
disk_info.disk_length.frame;
#if DEBUG_TOC
for (i = 1; i <= disk_info.last + 1; i++)
toc_debug_info(i);
toc_debug_info(QINFO_FIRSTTRACK);
toc_debug_info(QINFO_LASTTRACK);
toc_debug_info(QINFO_DISKLENGTH);
toc_debug_info(QINFO_NEXTSESSION);
#endif
DEBUG((DEBUG_TOC, "exiting read_toc, got_info %x, count %d",
got_info, count));
if ((got_info & I_ALL) != I_ALL
|| toc[QINFO_FIRSTTRACK].cdsc_absaddr.msf.minute + count
< toc[QINFO_LASTTRACK].cdsc_absaddr.msf.minute + 1)
return -ERR_TOC_MISSINGINFO;
return 0;
}
#ifdef MULTISESSION
static int get_multi_disk_info(void)
{
int sessions, status;
struct cdrom_msf multi_index;
for (sessions = 2; sessions < 10 /* %%for now */; sessions++) {
int count;
for (count = 100; count < MAX_TRACKS; count++)
toc[count].cdsc_ind = 0;
multi_index.cdmsf_min0 = disk_info.next_session.minute;
multi_index.cdmsf_sec0 = disk_info.next_session.second;
multi_index.cdmsf_frame0 = disk_info.next_session.frame;
if (multi_index.cdmsf_sec0 >= 20)
multi_index.cdmsf_sec0 -= 20;
else {
multi_index.cdmsf_sec0 += 40;
multi_index.cdmsf_min0--;
}
DEBUG((DEBUG_MULTIS, "Try %d: %2d:%02d.%02d", sessions,
multi_index.cdmsf_min0,
multi_index.cdmsf_sec0,
multi_index.cdmsf_frame0));
bin2bcd(&multi_index);
multi_index.cdmsf_min1 = 0;
multi_index.cdmsf_sec1 = 0;
multi_index.cdmsf_frame1 = 1;
status = exec_read_cmd(COMREAD, &multi_index);
if (status < 0) {
DEBUG((DEBUG_TOC, "exec_read_cmd COMREAD: %02x",
-status));
break;
}
status = sleep_flag_low(FL_DTEN, MULTI_SEEK_TIMEOUT) ?
0 : -ERR_TOC_MISSINGINFO;
flush_data();
if (status < 0) {
DEBUG((DEBUG_TOC, "sleep_flag_low: %02x", -status));
break;
}
status = read_toc();
if (status < 0) {
DEBUG((DEBUG_TOC, "read_toc: %02x", -status));
break;
}
disk_info.multi = 1;
}
exec_cmd(COMSTOP);
if (status < 0)
return -EIO;
return 0;
}
#endif /* MULTISESSION */
static int update_toc(void)
{
int status, count;
if (toc_uptodate)
return 0;
DEBUG((DEBUG_TOC, "starting update_toc"));
disk_info.first = 0;
for (count = 0; count < MAX_TRACKS; count++)
toc[count].cdsc_ind = 0;
status = exec_cmd(COMLEADIN);
if (status < 0)
return -EIO;
status = read_toc();
if (status < 0) {
DEBUG((DEBUG_TOC, "read_toc: %02x", -status));
return -EIO;
}
/* Audio disk detection. Look at first track. */
disk_info.audio =
(toc[disk_info.first].cdsc_ctrl & CDROM_DATA_TRACK) ? 0 : 1;
/* XA detection */
disk_info.xa = drive_status() & ST_MODE2TRACK;
/* Multisession detection: if we want this, define MULTISESSION */
disk_info.multi = 0;
#ifdef MULTISESSION
if (disk_info.xa)
get_multi_disk_info(); /* Here disk_info.multi is set */
#endif /* MULTISESSION */
if (disk_info.multi)
printk(KERN_WARNING "optcd: Multisession support experimental, "
"see linux/Documentation/cdrom/optcd\n");
DEBUG((DEBUG_TOC, "exiting update_toc"));
toc_uptodate = 1;
return 0;
}
/* Request handling */
#define CURRENT_VALID \
(!QUEUE_EMPTY && MAJOR(CURRENT -> rq_dev) == MAJOR_NR \
&& CURRENT -> cmd == READ && CURRENT -> sector != -1)
/* Buffers for block size conversion. */
#define NOBUF -1
static char buf[CD_FRAMESIZE * N_BUFS];
static volatile int buf_bn[N_BUFS], next_bn;
static volatile int buf_in = 0, buf_out = NOBUF;
inline static void opt_invalidate_buffers(void)
{
int i;
DEBUG((DEBUG_BUFFERS, "executing opt_invalidate_buffers"));
for (i = 0; i < N_BUFS; i++)
buf_bn[i] = NOBUF;
buf_out = NOBUF;
}
/* Take care of the different block sizes between cdrom and Linux.
When Linux gets variable block sizes this will probably go away. */
static void transfer(void)
{
#if DEBUG_BUFFERS | DEBUG_REQUEST
printk(KERN_DEBUG "optcd: executing transfer\n");
#endif
if (!CURRENT_VALID)
return;
while (CURRENT -> nr_sectors) {
int bn = CURRENT -> sector / 4;
int i, offs, nr_sectors;
for (i = 0; i < N_BUFS && buf_bn[i] != bn; ++i);
DEBUG((DEBUG_REQUEST, "found %d", i));
if (i >= N_BUFS) {
buf_out = NOBUF;
break;
}
offs = (i * 4 + (CURRENT -> sector & 3)) * 512;
nr_sectors = 4 - (CURRENT -> sector & 3);
if (buf_out != i) {
buf_out = i;
if (buf_bn[i] != bn) {
buf_out = NOBUF;
continue;
}
}
if (nr_sectors > CURRENT -> nr_sectors)
nr_sectors = CURRENT -> nr_sectors;
memcpy(CURRENT -> buffer, buf + offs, nr_sectors * 512);
CURRENT -> nr_sectors -= nr_sectors;
CURRENT -> sector += nr_sectors;
CURRENT -> buffer += nr_sectors * 512;
}
}
/* State machine for reading disk blocks */
enum state_e {
S_IDLE, /* 0 */
S_START, /* 1 */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -