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

📄 mcd.c

📁 cdrom device drive for linux.
💻 C
📖 第 1 页 / 共 3 页
字号:
/*
	linux/kernel/blk_drv/mcd.c - Mitsumi CDROM driver

	Copyright (C) 1992  Martin Harriss
	Portions Copyright (C) 2001 Red Hat

	martin@bdsi.com (no longer valid - where are you now, Martin?)

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

	HISTORY

	0.1	First attempt - internal use only
	0.2	Cleaned up delays and use of timer - alpha release
	0.3	Audio support added
	0.3.1 Changes for mitsumi CRMC LU005S march version
		   (stud11@cc4.kuleuven.ac.be)
        0.3.2 bug fixes to the ioctls and merged with ALPHA0.99-pl12
		   (Jon Tombs <jon@robots.ox.ac.uk>)
        0.3.3 Added more #defines and mcd_setup()
   		   (Jon Tombs <jon@gtex02.us.es>)

	October 1993 Bernd Huebner and Ruediger Helsch, Unifix Software GmbH,
	Braunschweig, Germany: rework to speed up data read operation.
	Also enabled definition of irq and address from bootstrap, using the
	environment.
	November 93 added code for FX001 S,D (single & double speed).
	February 94 added code for broken M 5/6 series of 16-bit single speed.


        0.4   
        Added support for loadable MODULEs, so mcd can now also be loaded by 
        insmod and removed by rmmod during runtime.
        Werner Zimmermann (zimmerma@rz.fht-esslingen.de), Mar. 26, 95

	0.5
	I added code for FX001 D to drop from double speed to single speed 
	when encountering errors... this helps with some "problematic" CD's
	that are supposedly "OUT OF TOLERANCE" (but are really shitty presses!)
	severely scratched, or possibly slightly warped! I have noticed that
	the Mitsumi 2x/4x drives are just less tolerant and the firmware is 
	not smart enough to drop speed,	so let's just kludge it with software!
	****** THE 4X SPEED MITSUMI DRIVES HAVE THE SAME PROBLEM!!!!!! ******
	Anyone want to "DONATE" one to me?! ;) I hear sometimes they are
	even WORSE! ;)
	** HINT... HINT... TAKE NOTES MITSUMI This could save some hassles with
	certain "large" CD's that have data on the outside edge in your 
	DOS DRIVERS .... Accuracy counts... speed is secondary ;)
	17 June 95 Modifications By Andrew J. Kroll <ag784@freenet.buffalo.edu>
	07 July 1995 Modifications by Andrew J. Kroll

	Bjorn Ekwall <bj0rn@blox.se> added unregister_blkdev to mcd_init()

	Michael K. Johnson <johnsonm@redhat.com> added retries on open
	for slow drives which take a while to recognize that they contain
	a CD.

	November 1997 -- ported to the Uniform CD-ROM driver by Erik Andersen.
	March    1999 -- made io base and irq CONFIG_ options (Tigran Aivazian).
	
	November 1999 -- Make kernel-parameter implementation work with 2.3.x 
	                 Removed init_module & cleanup_module in favor of 
			 module_init & module_exit.
			 Torben Mathiasen <tmm@image.dk>
		
	September 2001 - Reformatted and cleaned up the code
			 Alan Cox <alan@redhat.com>			 
*/

#include <linux/module.h>

#include <linux/errno.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/timer.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/cdrom.h>
#include <linux/ioport.h>
#include <linux/string.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/config.h>

/* #define REALLY_SLOW_IO  */
#include <asm/system.h>
#include <asm/io.h>
#include <asm/uaccess.h>

#define MAJOR_NR MITSUMI_CDROM_MAJOR
#include <linux/blk.h>

#define mcd_port mcd		/* for compatible parameter passing with "insmod" */
#include "mcd.h"

static int mcd_blocksizes[1];


/* I added A flag to drop to 1x speed if too many errors 0 = 1X ; 1 = 2X */
static int mcdDouble;

/* How many sectors to hold at 1x speed counter */
static int mcd1xhold;

/* Is the drive connected properly and responding?? */
static int mcdPresent;

#define QUICK_LOOP_DELAY udelay(45)	/* use udelay */
#define QUICK_LOOP_COUNT 20

#define CURRENT_VALID \
(!QUEUE_EMPTY && MAJOR(CURRENT -> rq_dev) == MAJOR_NR && CURRENT -> cmd == READ \
&& CURRENT -> sector != -1)

#define MFL_STATUSorDATA (MFL_STATUS | MFL_DATA)
#define MCD_BUF_SIZ 16
static volatile int mcd_transfer_is_active;
static char mcd_buf[2048 * MCD_BUF_SIZ];	/* buffer for block size conversion */
static volatile int mcd_buf_bn[MCD_BUF_SIZ], mcd_next_bn;
static volatile int mcd_buf_in, mcd_buf_out = -1;
static volatile int mcd_error;
static int mcd_open_count;
enum mcd_state_e {
	MCD_S_IDLE,		/* 0 */
	MCD_S_START,		/* 1 */
	MCD_S_MODE,		/* 2 */
	MCD_S_READ,		/* 3 */
	MCD_S_DATA,		/* 4 */
	MCD_S_STOP,		/* 5 */
	MCD_S_STOPPING		/* 6 */
};
static volatile enum mcd_state_e mcd_state = MCD_S_IDLE;
static int mcd_mode = -1;
static int MCMD_DATA_READ = MCMD_PLAY_READ;

#define READ_TIMEOUT 3000

int mitsumi_bug_93_wait;

static short mcd_port = CONFIG_MCD_BASE;	/* used as "mcd" by "insmod" */
static int mcd_irq = CONFIG_MCD_IRQ;	/* must directly follow mcd_port */
MODULE_PARM(mcd, "1-2i");

static int McdTimeout, McdTries;
static DECLARE_WAIT_QUEUE_HEAD(mcd_waitq);

static struct mcd_DiskInfo DiskInfo;
static struct mcd_Toc Toc[MAX_TRACKS];
static struct mcd_Play_msf mcd_Play;

static int audioStatus;
static char mcdDiskChanged;
static char tocUpToDate;
static char mcdVersion;

static void mcd_transfer(void);
static void mcd_poll(unsigned long dummy);
static void mcd_invalidate_buffers(void);
static void hsg2msf(long hsg, struct msf *msf);
static void bin2bcd(unsigned char *p);
static int bcd2bin(unsigned char bcd);
static int mcdStatus(void);
static void sendMcdCmd(int cmd, struct mcd_Play_msf *params);
static int getMcdStatus(int timeout);
static int GetQChannelInfo(struct mcd_Toc *qp);
static int updateToc(void);
static int GetDiskInfo(void);
static int GetToc(void);
static int getValue(unsigned char *result);
static int mcd_open(struct cdrom_device_info *cdi, int purpose);
static void mcd_release(struct cdrom_device_info *cdi);
static int mcd_media_changed(struct cdrom_device_info *cdi, int disc_nr);
static int mcd_tray_move(struct cdrom_device_info *cdi, int position);
int mcd_audio_ioctl(struct cdrom_device_info *cdi, unsigned int cmd,
		    void *arg);
int mcd_drive_status(struct cdrom_device_info *cdi, int slot_nr);

struct block_device_operations mcd_bdops =
{
	owner:			THIS_MODULE,
	open:			cdrom_open,
	release:		cdrom_release,
	ioctl:			cdrom_ioctl,
	check_media_change:	cdrom_media_changed,
};

static struct timer_list mcd_timer;

static struct cdrom_device_ops mcd_dops = {
	open:mcd_open,
	release:mcd_release,
	drive_status:mcd_drive_status,
	media_changed:mcd_media_changed,
	tray_move:mcd_tray_move,
	audio_ioctl:mcd_audio_ioctl,
	capability:CDC_OPEN_TRAY | CDC_MEDIA_CHANGED |
	    CDC_PLAY_AUDIO | CDC_DRIVE_STATUS,
};

static struct cdrom_device_info mcd_info = {
	ops:&mcd_dops,
	speed:2,
	capacity:1,
	name:"mcd",
};

#ifndef MODULE
static int __init mcd_setup(char *str)
{
	int ints[9];

	(void) get_options(str, ARRAY_SIZE(ints), ints);

	if (ints[0] > 0)
		mcd_port = ints[1];
	if (ints[0] > 1)
		mcd_irq = ints[2];
	if (ints[0] > 2)
		mitsumi_bug_93_wait = ints[3];

	return 1;
}

__setup("mcd=", mcd_setup);

#endif				/* MODULE */

static int mcd_media_changed(struct cdrom_device_info *cdi, int disc_nr)
{
	return 0;
}


/*
 * Do a 'get status' command and get the result.  Only use from the top half
 * because it calls 'getMcdStatus' which sleeps.
 */

static int statusCmd(void)
{
	int st = -1, retry;

	for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++) {
		/* send get-status cmd */
		outb(MCMD_GET_STATUS, MCDPORT(0));

		st = getMcdStatus(MCD_STATUS_DELAY);
		if (st != -1)
			break;
	}

	return st;
}


/*
 * Send a 'Play' command and get the status.  Use only from the top half.
 */

static int mcdPlay(struct mcd_Play_msf *arg)
{
	int retry, st = -1;

	for (retry = 0; retry < MCD_RETRY_ATTEMPTS; retry++) {
		sendMcdCmd(MCMD_PLAY_READ, arg);
		st = getMcdStatus(2 * MCD_STATUS_DELAY);
		if (st != -1)
			break;
	}

	return st;
}


static int mcd_tray_move(struct cdrom_device_info *cdi, int position)
{
	int i;
	if (position) {
		/*  Eject */
		/* all drives can at least stop! */
		if (audioStatus == CDROM_AUDIO_PLAY) {
			outb(MCMD_STOP, MCDPORT(0));
			i = getMcdStatus(MCD_STATUS_DELAY);
		}

		audioStatus = CDROM_AUDIO_NO_STATUS;

		outb(MCMD_EJECT, MCDPORT(0));
		/*
		 * the status (i) shows failure on all but the FX drives.
		 * But nothing we can do about that in software!
		 * So just read the status and forget it. - Jon.
		 */
		i = getMcdStatus(MCD_STATUS_DELAY);
		return 0;
	} else
		return -EINVAL;
}

long msf2hsg(struct msf *mp)
{
	return bcd2bin(mp->frame) + bcd2bin(mp->sec) * 75 + bcd2bin(mp->min) * 4500 - 150;
}


int mcd_audio_ioctl(struct cdrom_device_info *cdi, unsigned int cmd,
		    void *arg)
{
	int i, st;
	struct mcd_Toc qInfo;
	struct cdrom_ti *ti;
	struct cdrom_tochdr *tocHdr;
	struct cdrom_msf *msf;
	struct cdrom_subchnl *subchnl;
	struct cdrom_tocentry *entry;
	struct mcd_Toc *tocPtr;
	struct cdrom_volctrl *volctrl;

	st = statusCmd();
	if (st < 0)
		return -EIO;

	if (!tocUpToDate) {
		i = updateToc();
		if (i < 0)
			return i;	/* error reading TOC */
	}

	switch (cmd) {
	case CDROMSTART:	/* Spin up the drive */
		/* Don't think we can do this.  Even if we could,
		 * I think the drive times out and stops after a while
		 * anyway.  For now, ignore it.
		 */

		return 0;

	case CDROMSTOP:	/* Spin down the drive */
		outb(MCMD_STOP, MCDPORT(0));
		i = getMcdStatus(MCD_STATUS_DELAY);

		/* should we do anything if it fails? */

		audioStatus = CDROM_AUDIO_NO_STATUS;
		return 0;

	case CDROMPAUSE:	/* Pause the drive */
		if (audioStatus != CDROM_AUDIO_PLAY)
			return -EINVAL;

		outb(MCMD_STOP, MCDPORT(0));
		i = getMcdStatus(MCD_STATUS_DELAY);

		if (GetQChannelInfo(&qInfo) < 0) {
			/* didn't get q channel info */

			audioStatus = CDROM_AUDIO_NO_STATUS;
			return 0;
		}

		mcd_Play.start = qInfo.diskTime;	/* remember restart point */

		audioStatus = CDROM_AUDIO_PAUSED;
		return 0;

	case CDROMRESUME:	/* Play it again, Sam */
		if (audioStatus != CDROM_AUDIO_PAUSED)
			return -EINVAL;

		/* restart the drive at the saved position. */

		i = mcdPlay(&mcd_Play);
		if (i < 0) {
			audioStatus = CDROM_AUDIO_ERROR;
			return -EIO;
		}

		audioStatus = CDROM_AUDIO_PLAY;
		return 0;

	case CDROMPLAYTRKIND:	/* Play a track.  This currently ignores index. */

		ti = (struct cdrom_ti *) arg;

		if (ti->cdti_trk0 < DiskInfo.first
		    || ti->cdti_trk0 > DiskInfo.last
		    || ti->cdti_trk1 < ti->cdti_trk0) {
			return -EINVAL;
		}

		if (ti->cdti_trk1 > DiskInfo.last)
			ti->cdti_trk1 = DiskInfo.last;

		mcd_Play.start = Toc[ti->cdti_trk0].diskTime;
		mcd_Play.end = Toc[ti->cdti_trk1 + 1].diskTime;

#ifdef MCD_DEBUG
		printk("play: %02x:%02x.%02x to %02x:%02x.%02x\n",
		       mcd_Play.start.min, mcd_Play.start.sec,
		       mcd_Play.start.frame, mcd_Play.end.min,
		       mcd_Play.end.sec, mcd_Play.end.frame);
#endif

		i = mcdPlay(&mcd_Play);
		if (i < 0) {
			audioStatus = CDROM_AUDIO_ERROR;
			return -EIO;
		}

		audioStatus = CDROM_AUDIO_PLAY;
		return 0;

	case CDROMPLAYMSF:	/* Play starting at the given MSF address. */

		if (audioStatus == CDROM_AUDIO_PLAY) {
			outb(MCMD_STOP, MCDPORT(0));
			i = getMcdStatus(MCD_STATUS_DELAY);
			audioStatus = CDROM_AUDIO_NO_STATUS;
		}

		msf = (struct cdrom_msf *) arg;

		/* convert to bcd */

		bin2bcd(&msf->cdmsf_min0);
		bin2bcd(&msf->cdmsf_sec0);
		bin2bcd(&msf->cdmsf_frame0);
		bin2bcd(&msf->cdmsf_min1);
		bin2bcd(&msf->cdmsf_sec1);
		bin2bcd(&msf->cdmsf_frame1);

		mcd_Play.start.min = msf->cdmsf_min0;
		mcd_Play.start.sec = msf->cdmsf_sec0;
		mcd_Play.start.frame = msf->cdmsf_frame0;
		mcd_Play.end.min = msf->cdmsf_min1;
		mcd_Play.end.sec = msf->cdmsf_sec1;
		mcd_Play.end.frame = msf->cdmsf_frame1;

#ifdef MCD_DEBUG
		printk("play: %02x:%02x.%02x to %02x:%02x.%02x\n",
		       mcd_Play.start.min, mcd_Play.start.sec,
		       mcd_Play.start.frame, mcd_Play.end.min,
		       mcd_Play.end.sec, mcd_Play.end.frame);
#endif

		i = mcdPlay(&mcd_Play);
		if (i < 0) {
			audioStatus = CDROM_AUDIO_ERROR;
			return -EIO;
		}

		audioStatus = CDROM_AUDIO_PLAY;
		return 0;

	case CDROMREADTOCHDR:	/* Read the table of contents header */
		tocHdr = (struct cdrom_tochdr *) arg;
		tocHdr->cdth_trk0 = DiskInfo.first;
		tocHdr->cdth_trk1 = DiskInfo.last;
		return 0;

	case CDROMREADTOCENTRY:	/* Read an entry in the table of contents */
		entry = (struct cdrom_tocentry *) arg;
		if (entry->cdte_track == CDROM_LEADOUT)
			tocPtr = &Toc[DiskInfo.last - DiskInfo.first + 1];

		else if (entry->cdte_track > DiskInfo.last
			 || entry->cdte_track < DiskInfo.first)
			return -EINVAL;

		else
			tocPtr = &Toc[entry->cdte_track];

		entry->cdte_adr = tocPtr->ctrl_addr;
		entry->cdte_ctrl = tocPtr->ctrl_addr >> 4;

		if (entry->cdte_format == CDROM_LBA)
			entry->cdte_addr.lba = msf2hsg(&tocPtr->diskTime);

		else if (entry->cdte_format == CDROM_MSF) {
			entry->cdte_addr.msf.minute =
			    bcd2bin(tocPtr->diskTime.min);
			entry->cdte_addr.msf.second =
			    bcd2bin(tocPtr->diskTime.sec);
			entry->cdte_addr.msf.frame =
			    bcd2bin(tocPtr->diskTime.frame);
		}

		else
			return -EINVAL;

		return 0;

	case CDROMSUBCHNL:	/* Get subchannel info */

		subchnl = (struct cdrom_subchnl *) arg;
		if (GetQChannelInfo(&qInfo) < 0)
			return -EIO;

		subchnl->cdsc_audiostatus = audioStatus;

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -