⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 optcd.c

📁 cdrom device drive for linux.
💻 C
📖 第 1 页 / 共 4 页
字号:
}

/* 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 + -