| /*	linux/drivers/cdrom/optcd.c - Optics Storage 8000 AT CDROM driver | 
 | 	$Id: optcd.c,v 1.11 1997/01/26 07:13:00 davem Exp $ | 
 |  | 
 | 	Copyright (C) 1995 Leo Spiekman (spiekman@dutette.et.tudelft.nl) | 
 |  | 
 |  | 
 | 	Based on Aztech CD268 CDROM driver by Werner Zimmermann and preworks | 
 | 	by Eberhard Moenkeberg (emoenke@gwdg.de).  | 
 |  | 
 | 	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. | 
 | */ | 
 |  | 
 | /*	Revision history | 
 |  | 
 |  | 
 | 	14-5-95		v0.0	Plays sound tracks. No reading of data CDs yet. | 
 | 				Detection of disk change doesn't work. | 
 | 	21-5-95		v0.1	First ALPHA version. CD can be mounted. The | 
 | 				device major nr is borrowed from the Aztech | 
 | 				driver. Speed is around 240 kb/s, as measured | 
 | 				with "time dd if=/dev/cdrom of=/dev/null \ | 
 | 				bs=2048 count=4096". | 
 | 	24-6-95		v0.2	Reworked the #defines for the command codes | 
 | 				and the like, as well as the structure of | 
 | 				the hardware communication protocol, to | 
 | 				reflect the "official" documentation, kindly | 
 | 				supplied by C.K. Tan, Optics Storage Pte. Ltd. | 
 | 				Also tidied up the state machine somewhat. | 
 | 	28-6-95		v0.3	Removed the ISP-16 interface code, as this | 
 | 				should go into its own driver. The driver now | 
 | 				has its own major nr. | 
 | 				Disk change detection now seems to work, too. | 
 | 				This version became part of the standard | 
 | 				kernel as of version 1.3.7 | 
 | 	24-9-95		v0.4	Re-inserted ISP-16 interface code which I | 
 | 				copied from sjcd.c, with a few changes. | 
 | 				Updated README.optcd. Submitted for | 
 | 				inclusion in 1.3.21 | 
 | 	29-9-95		v0.4a	Fixed bug that prevented compilation as module | 
 | 	25-10-95	v0.5	Started multisession code. Implementation | 
 | 				copied from Werner Zimmermann, who copied it | 
 | 				from Heiko Schlittermann's mcdx. | 
 | 	17-1-96		v0.6	Multisession works; some cleanup too. | 
 | 	18-4-96		v0.7	Increased some timing constants; | 
 | 				thanks to Luke McFarlane. Also tidied up some | 
 | 				printk behaviour. ISP16 initialization | 
 | 				is now handled by a separate driver. | 
 | 				 | 
 | 	09-11-99 	  	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> | 
 | */ | 
 |  | 
 | /* Includes */ | 
 |  | 
 |  | 
 | #include <linux/module.h> | 
 | #include <linux/mm.h> | 
 | #include <linux/ioport.h> | 
 | #include <linux/init.h> | 
 |  | 
 | #include <asm/io.h> | 
 | #include <linux/blkdev.h> | 
 |  | 
 | #include <linux/cdrom.h> | 
 | #include "optcd.h" | 
 |  | 
 | #include <asm/uaccess.h> | 
 |  | 
 | #define MAJOR_NR OPTICS_CDROM_MAJOR | 
 | #define QUEUE (opt_queue) | 
 | #define CURRENT elv_next_request(opt_queue) | 
 |  | 
 |  | 
 | /* Debug support */ | 
 |  | 
 |  | 
 | /* Don't forget to add new debug flags here. */ | 
 | #if DEBUG_DRIVE_IF | DEBUG_VFS | DEBUG_CONV | DEBUG_TOC | \ | 
 |     DEBUG_BUFFERS | DEBUG_REQUEST | DEBUG_STATE | DEBUG_MULTIS | 
 | #define DEBUG(x) debug x | 
 | static void debug(int debug_this, const char* fmt, ...) | 
 | { | 
 | 	char s[1024]; | 
 | 	va_list args; | 
 |  | 
 | 	if (!debug_this) | 
 | 		return; | 
 |  | 
 | 	va_start(args, fmt); | 
 | 	vsprintf(s, fmt, args); | 
 | 	printk(KERN_DEBUG "optcd: %s\n", s); | 
 | 	va_end(args); | 
 | } | 
 | #else | 
 | #define DEBUG(x) | 
 | #endif | 
 |  | 
 |  | 
 | /* Drive hardware/firmware characteristics | 
 |    Identifiers in accordance with Optics Storage documentation */ | 
 |  | 
 |  | 
 | #define optcd_port optcd			/* Needed for the modutils. */ | 
 | static short optcd_port = OPTCD_PORTBASE;	/* I/O base of drive. */ | 
 | module_param(optcd_port, short, 0); | 
 | /* Drive registers, read */ | 
 | #define DATA_PORT	optcd_port	/* Read data/status */ | 
 | #define STATUS_PORT	optcd_port+1	/* Indicate data/status availability */ | 
 |  | 
 | /* Drive registers, write */ | 
 | #define COMIN_PORT	optcd_port	/* For passing command/parameter */ | 
 | #define RESET_PORT	optcd_port+1	/* Write anything and wait 0.5 sec */ | 
 | #define HCON_PORT	optcd_port+2	/* Host Xfer Configuration */ | 
 |  | 
 |  | 
 | /* Command completion/status read from DATA register */ | 
 | #define ST_DRVERR		0x80 | 
 | #define ST_DOOR_OPEN		0x40 | 
 | #define ST_MIXEDMODE_DISK	0x20 | 
 | #define ST_MODE_BITS		0x1c | 
 | #define ST_M_STOP		0x00 | 
 | #define ST_M_READ		0x04 | 
 | #define ST_M_AUDIO		0x04 | 
 | #define ST_M_PAUSE		0x08 | 
 | #define ST_M_INITIAL		0x0c | 
 | #define ST_M_ERROR		0x10 | 
 | #define ST_M_OTHERS		0x14 | 
 | #define	ST_MODE2TRACK		0x02 | 
 | #define	ST_DSK_CHG		0x01 | 
 | #define ST_L_LOCK		0x01 | 
 | #define ST_CMD_OK		0x00 | 
 | #define ST_OP_OK		0x01 | 
 | #define ST_PA_OK		0x02 | 
 | #define ST_OP_ERROR		0x05 | 
 | #define ST_PA_ERROR		0x06 | 
 |  | 
 |  | 
 | /* Error codes (appear as command completion code from DATA register) */ | 
 | /* Player related errors */ | 
 | #define ERR_ILLCMD	0x11	/* Illegal command to player module */ | 
 | #define ERR_ILLPARM	0x12	/* Illegal parameter to player module */ | 
 | #define ERR_SLEDGE	0x13 | 
 | #define ERR_FOCUS	0x14 | 
 | #define ERR_MOTOR	0x15 | 
 | #define ERR_RADIAL	0x16 | 
 | #define ERR_PLL		0x17	/* PLL lock error */ | 
 | #define ERR_SUB_TIM	0x18	/* Subcode timeout error */ | 
 | #define ERR_SUB_NF	0x19	/* Subcode not found error */ | 
 | #define ERR_TRAY	0x1a | 
 | #define ERR_TOC		0x1b	/* Table of Contents read error */ | 
 | #define ERR_JUMP	0x1c | 
 | /* Data errors */ | 
 | #define ERR_MODE	0x21 | 
 | #define ERR_FORM	0x22 | 
 | #define ERR_HEADADDR	0x23	/* Header Address not found */ | 
 | #define ERR_CRC		0x24 | 
 | #define ERR_ECC		0x25	/* Uncorrectable ECC error */ | 
 | #define ERR_CRC_UNC	0x26	/* CRC error and uncorrectable error */ | 
 | #define ERR_ILLBSYNC	0x27	/* Illegal block sync error */ | 
 | #define ERR_VDST	0x28	/* VDST not found */ | 
 | /* Timeout errors */ | 
 | #define ERR_READ_TIM	0x31	/* Read timeout error */ | 
 | #define ERR_DEC_STP	0x32	/* Decoder stopped */ | 
 | #define ERR_DEC_TIM	0x33	/* Decoder interrupt timeout error */ | 
 | /* Function abort codes */ | 
 | #define ERR_KEY		0x41	/* Key -Detected abort */ | 
 | #define ERR_READ_FINISH	0x42	/* Read Finish */ | 
 | /* Second Byte diagnostic codes */ | 
 | #define ERR_NOBSYNC	0x01	/* No block sync */ | 
 | #define ERR_SHORTB	0x02	/* Short block */ | 
 | #define ERR_LONGB	0x03	/* Long block */ | 
 | #define ERR_SHORTDSP	0x04	/* Short DSP word */ | 
 | #define ERR_LONGDSP	0x05	/* Long DSP word */ | 
 |  | 
 |  | 
 | /* Status availability flags read from STATUS register */ | 
 | #define FL_EJECT	0x20 | 
 | #define FL_WAIT		0x10	/* active low */ | 
 | #define FL_EOP		0x08	/* active low */ | 
 | #define FL_STEN		0x04	/* Status available when low */ | 
 | #define FL_DTEN		0x02	/* Data available when low */ | 
 | #define FL_DRQ		0x01	/* active low */ | 
 | #define FL_RESET	0xde	/* These bits are high after a reset */ | 
 | #define FL_STDT		(FL_STEN|FL_DTEN) | 
 |  | 
 |  | 
 | /* Transfer mode, written to HCON register */ | 
 | #define HCON_DTS	0x08 | 
 | #define HCON_SDRQB	0x04 | 
 | #define HCON_LOHI	0x02 | 
 | #define HCON_DMA16	0x01 | 
 |  | 
 |  | 
 | /* Drive command set, written to COMIN register */ | 
 | /* Quick response commands */ | 
 | #define COMDRVST	0x20	/* Drive Status Read */ | 
 | #define COMERRST	0x21	/* Error Status Read */ | 
 | #define COMIOCTLISTAT	0x22	/* Status Read; reset disk changed bit */ | 
 | #define COMINITSINGLE	0x28	/* Initialize Single Speed */ | 
 | #define COMINITDOUBLE	0x29	/* Initialize Double Speed */ | 
 | #define COMUNLOCK	0x30	/* Unlock */ | 
 | #define COMLOCK		0x31	/* Lock */ | 
 | #define COMLOCKST	0x32	/* Lock/Unlock Status */ | 
 | #define COMVERSION	0x40	/* Get Firmware Revision */ | 
 | #define COMVOIDREADMODE	0x50	/* Void Data Read Mode */ | 
 | /* Read commands */ | 
 | #define COMFETCH	0x60	/* Prefetch Data */ | 
 | #define COMREAD		0x61	/* Read */ | 
 | #define COMREADRAW	0x62	/* Read Raw Data */ | 
 | #define COMREADALL	0x63	/* Read All 2646 Bytes */ | 
 | /* Player control commands */ | 
 | #define COMLEADIN	0x70	/* Seek To Lead-in */ | 
 | #define COMSEEK		0x71	/* Seek */ | 
 | #define COMPAUSEON	0x80	/* Pause On */ | 
 | #define COMPAUSEOFF	0x81	/* Pause Off */ | 
 | #define COMSTOP		0x82	/* Stop */ | 
 | #define COMOPEN		0x90	/* Open Tray Door */ | 
 | #define COMCLOSE	0x91	/* Close Tray Door */ | 
 | #define COMPLAY		0xa0	/* Audio Play */ | 
 | #define COMPLAY_TNO	0xa2	/* Audio Play By Track Number */ | 
 | #define COMSUBQ		0xb0	/* Read Sub-q Code */ | 
 | #define COMLOCATION	0xb1	/* Read Head Position */ | 
 | /* Audio control commands */ | 
 | #define COMCHCTRL	0xc0	/* Audio Channel Control */ | 
 | /* Miscellaneous (test) commands */ | 
 | #define COMDRVTEST	0xd0	/* Write Test Bytes */ | 
 | #define COMTEST		0xd1	/* Diagnostic Test */ | 
 |  | 
 | /* Low level drive interface. Only here we do actual I/O | 
 |    Waiting for status / data available */ | 
 |  | 
 |  | 
 | /* Busy wait until FLAG goes low. Return 0 on timeout. */ | 
 | static inline int flag_low(int flag, unsigned long timeout) | 
 | { | 
 | 	int flag_high; | 
 | 	unsigned long count = 0; | 
 |  | 
 | 	while ((flag_high = (inb(STATUS_PORT) & flag))) | 
 | 		if (++count >= timeout) | 
 | 			break; | 
 |  | 
 | 	DEBUG((DEBUG_DRIVE_IF, "flag_low 0x%x count %ld%s", | 
 | 		flag, count, flag_high ? " timeout" : "")); | 
 | 	return !flag_high; | 
 | } | 
 |  | 
 |  | 
 | /* Timed waiting for status or data */ | 
 | static int sleep_timeout;	/* max # of ticks to sleep */ | 
 | static DECLARE_WAIT_QUEUE_HEAD(waitq); | 
 | static void sleep_timer(unsigned long data); | 
 | static DEFINE_TIMER(delay_timer, sleep_timer, 0, 0); | 
 | static DEFINE_SPINLOCK(optcd_lock); | 
 | static struct request_queue *opt_queue; | 
 |  | 
 | /* Timer routine: wake up when desired flag goes low, | 
 |    or when timeout expires. */ | 
 | static void sleep_timer(unsigned long data) | 
 | { | 
 | 	int flags = inb(STATUS_PORT) & FL_STDT; | 
 |  | 
 | 	if (flags == FL_STDT && --sleep_timeout > 0) { | 
 | 		mod_timer(&delay_timer, jiffies + HZ/100); /* multi-statement macro */ | 
 | 	} else | 
 | 		wake_up(&waitq); | 
 | } | 
 |  | 
 |  | 
 | /* Sleep until FLAG goes low. Return 0 on timeout or wrong flag low. */ | 
 | static int sleep_flag_low(int flag, unsigned long timeout) | 
 | { | 
 | 	int flag_high; | 
 |  | 
 | 	DEBUG((DEBUG_DRIVE_IF, "sleep_flag_low")); | 
 |  | 
 | 	sleep_timeout = timeout; | 
 | 	flag_high = inb(STATUS_PORT) & flag; | 
 | 	if (flag_high && sleep_timeout > 0) { | 
 | 		mod_timer(&delay_timer, jiffies + HZ/100); | 
 | 		sleep_on(&waitq); | 
 | 		flag_high = inb(STATUS_PORT) & flag; | 
 | 	} | 
 |  | 
 | 	DEBUG((DEBUG_DRIVE_IF, "flag 0x%x count %ld%s", | 
 | 		flag, timeout, flag_high ? " timeout" : "")); | 
 | 	return !flag_high; | 
 | } | 
 |  | 
 | /* Low level drive interface. Only here we do actual I/O | 
 |    Sending commands and parameters */ | 
 |  | 
 |  | 
 | /* Errors in the command protocol */ | 
 | #define ERR_IF_CMD_TIMEOUT	0x100 | 
 | #define ERR_IF_ERR_TIMEOUT	0x101 | 
 | #define ERR_IF_RESP_TIMEOUT	0x102 | 
 | #define ERR_IF_DATA_TIMEOUT	0x103 | 
 | #define ERR_IF_NOSTAT		0x104 | 
 |  | 
 |  | 
 | /* Send command code. Return <0 indicates error */ | 
 | static int send_cmd(int cmd) | 
 | { | 
 | 	unsigned char ack; | 
 |  | 
 | 	DEBUG((DEBUG_DRIVE_IF, "sending command 0x%02x\n", cmd)); | 
 |  | 
 | 	outb(HCON_DTS, HCON_PORT);	/* Enable Suspend Data Transfer */ | 
 | 	outb(cmd, COMIN_PORT);		/* Send command code */ | 
 | 	if (!flag_low(FL_STEN, BUSY_TIMEOUT))	/* Wait for status */ | 
 | 		return -ERR_IF_CMD_TIMEOUT; | 
 | 	ack = inb(DATA_PORT);		/* read command acknowledge */ | 
 | 	outb(HCON_SDRQB, HCON_PORT);	/* Disable Suspend Data Transfer */ | 
 | 	return ack==ST_OP_OK ? 0 : -ack; | 
 | } | 
 |  | 
 |  | 
 | /* Send command parameters. Return <0 indicates error */ | 
 | static int send_params(struct cdrom_msf *params) | 
 | { | 
 | 	unsigned char ack; | 
 |  | 
 | 	DEBUG((DEBUG_DRIVE_IF, "sending parameters" | 
 | 		" %02x:%02x:%02x" | 
 | 		" %02x:%02x:%02x", | 
 | 		params->cdmsf_min0, | 
 | 		params->cdmsf_sec0, | 
 | 		params->cdmsf_frame0, | 
 | 		params->cdmsf_min1, | 
 | 		params->cdmsf_sec1, | 
 | 		params->cdmsf_frame1)); | 
 |  | 
 | 	outb(params->cdmsf_min0, COMIN_PORT); | 
 | 	outb(params->cdmsf_sec0, COMIN_PORT); | 
 | 	outb(params->cdmsf_frame0, COMIN_PORT); | 
 | 	outb(params->cdmsf_min1, COMIN_PORT); | 
 | 	outb(params->cdmsf_sec1, COMIN_PORT); | 
 | 	outb(params->cdmsf_frame1, COMIN_PORT); | 
 | 	if (!flag_low(FL_STEN, BUSY_TIMEOUT))	/* Wait for status */ | 
 | 		return -ERR_IF_CMD_TIMEOUT; | 
 | 	ack = inb(DATA_PORT);		/* read command acknowledge */ | 
 | 	return ack==ST_PA_OK ? 0 : -ack; | 
 | } | 
 |  | 
 |  | 
 | /* Send parameters for SEEK command. Return <0 indicates error */ | 
 | static int send_seek_params(struct cdrom_msf *params) | 
 | { | 
 | 	unsigned char ack; | 
 |  | 
 | 	DEBUG((DEBUG_DRIVE_IF, "sending seek parameters" | 
 | 		" %02x:%02x:%02x", | 
 | 		params->cdmsf_min0, | 
 | 		params->cdmsf_sec0, | 
 | 		params->cdmsf_frame0)); | 
 |  | 
 | 	outb(params->cdmsf_min0, COMIN_PORT); | 
 | 	outb(params->cdmsf_sec0, COMIN_PORT); | 
 | 	outb(params->cdmsf_frame0, COMIN_PORT); | 
 | 	if (!flag_low(FL_STEN, BUSY_TIMEOUT))	/* Wait for status */ | 
 | 		return -ERR_IF_CMD_TIMEOUT; | 
 | 	ack = inb(DATA_PORT);		/* read command acknowledge */ | 
 | 	return ack==ST_PA_OK ? 0 : -ack; | 
 | } | 
 |  | 
 |  | 
 | /* Wait for command execution status. Choice between busy waiting | 
 |    and sleeping. Return value <0 indicates timeout. */ | 
 | static inline int get_exec_status(int busy_waiting) | 
 | { | 
 | 	unsigned char exec_status; | 
 |  | 
 | 	if (busy_waiting | 
 | 	    ? !flag_low(FL_STEN, BUSY_TIMEOUT) | 
 | 	    : !sleep_flag_low(FL_STEN, SLEEP_TIMEOUT)) | 
 | 		return -ERR_IF_CMD_TIMEOUT; | 
 |  | 
 | 	exec_status = inb(DATA_PORT); | 
 | 	DEBUG((DEBUG_DRIVE_IF, "returned exec status 0x%02x", exec_status)); | 
 | 	return exec_status; | 
 | } | 
 |  | 
 |  | 
 | /* Wait busy for extra byte of data that a command returns. | 
 |    Return value <0 indicates timeout. */ | 
 | static inline int get_data(int short_timeout) | 
 | { | 
 | 	unsigned char data; | 
 |  | 
 | 	if (!flag_low(FL_STEN, short_timeout ? FAST_TIMEOUT : BUSY_TIMEOUT)) | 
 | 		return -ERR_IF_DATA_TIMEOUT; | 
 |  | 
 | 	data = inb(DATA_PORT); | 
 | 	DEBUG((DEBUG_DRIVE_IF, "returned data 0x%02x", data)); | 
 | 	return data; | 
 | } | 
 |  | 
 |  | 
 | /* Returns 0 if failed */ | 
 | static int reset_drive(void) | 
 | { | 
 | 	unsigned long count = 0; | 
 | 	int flags; | 
 |  | 
 | 	DEBUG((DEBUG_DRIVE_IF, "reset drive")); | 
 |  | 
 | 	outb(0, RESET_PORT); | 
 | 	while (++count < RESET_WAIT) | 
 | 		inb(DATA_PORT); | 
 |  | 
 | 	count = 0; | 
 | 	while ((flags = (inb(STATUS_PORT) & FL_RESET)) != FL_RESET) | 
 | 		if (++count >= BUSY_TIMEOUT) | 
 | 			break; | 
 |  | 
 | 	DEBUG((DEBUG_DRIVE_IF, "reset %s", | 
 | 		flags == FL_RESET ? "succeeded" : "failed")); | 
 |  | 
 | 	if (flags != FL_RESET) | 
 | 		return 0;		/* Reset failed */ | 
 | 	outb(HCON_SDRQB, HCON_PORT);	/* Disable Suspend Data Transfer */ | 
 | 	return 1;			/* Reset succeeded */ | 
 | } | 
 |  | 
 |  | 
 | /* Facilities for asynchronous operation */ | 
 |  | 
 | /* Read status/data availability flags FL_STEN and FL_DTEN */ | 
 | static inline int stdt_flags(void) | 
 | { | 
 | 	return inb(STATUS_PORT) & FL_STDT; | 
 | } | 
 |  | 
 |  | 
 | /* Fetch status that has previously been waited for. <0 means not available */ | 
 | static inline int fetch_status(void) | 
 | { | 
 | 	unsigned char status; | 
 |  | 
 | 	if (inb(STATUS_PORT) & FL_STEN) | 
 | 		return -ERR_IF_NOSTAT; | 
 |  | 
 | 	status = inb(DATA_PORT); | 
 | 	DEBUG((DEBUG_DRIVE_IF, "fetched exec status 0x%02x", status)); | 
 | 	return status; | 
 | } | 
 |  | 
 |  | 
 | /* Fetch data that has previously been waited for. */ | 
 | static inline void fetch_data(char *buf, int n) | 
 | { | 
 | 	insb(DATA_PORT, buf, n); | 
 | 	DEBUG((DEBUG_DRIVE_IF, "fetched 0x%x bytes", n)); | 
 | } | 
 |  | 
 |  | 
 | /* Flush status and data fifos */ | 
 | static inline void flush_data(void) | 
 | { | 
 | 	while ((inb(STATUS_PORT) & FL_STDT) != FL_STDT) | 
 | 		inb(DATA_PORT); | 
 | 	DEBUG((DEBUG_DRIVE_IF, "flushed fifos")); | 
 | } | 
 |  | 
 | /* Command protocol */ | 
 |  | 
 |  | 
 | /* Send a simple command and wait for response. Command codes < COMFETCH | 
 |    are quick response commands */ | 
 | static inline int exec_cmd(int cmd) | 
 | { | 
 | 	int ack = send_cmd(cmd); | 
 | 	if (ack < 0) | 
 | 		return ack; | 
 | 	return get_exec_status(cmd < COMFETCH); | 
 | } | 
 |  | 
 |  | 
 | /* Send a command with parameters. Don't wait for the response, | 
 |  * which consists of data blocks read from the CD. */ | 
 | static inline int exec_read_cmd(int cmd, struct cdrom_msf *params) | 
 | { | 
 | 	int ack = send_cmd(cmd); | 
 | 	if (ack < 0) | 
 | 		return ack; | 
 | 	return send_params(params); | 
 | } | 
 |  | 
 |  | 
 | /* Send a seek command with parameters and wait for response */ | 
 | static inline int exec_seek_cmd(int cmd, struct cdrom_msf *params) | 
 | { | 
 | 	int ack = send_cmd(cmd); | 
 | 	if (ack < 0) | 
 | 		return ack; | 
 | 	ack = send_seek_params(params); | 
 | 	if (ack < 0) | 
 | 		return ack; | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | /* Send a command with parameters and wait for response */ | 
 | static inline int exec_long_cmd(int cmd, struct cdrom_msf *params) | 
 | { | 
 | 	int ack = exec_read_cmd(cmd, params); | 
 | 	if (ack < 0) | 
 | 		return ack; | 
 | 	return get_exec_status(0); | 
 | } | 
 |  | 
 | /* Address conversion routines */ | 
 |  | 
 |  | 
 | /* Binary to BCD (2 digits) */ | 
 | static inline 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 */ | 
 | static inline 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 | 
 | static 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 Documentation/cdrom/optcd\n"); | 
 |  | 
 | 	DEBUG((DEBUG_TOC, "exiting update_toc")); | 
 |  | 
 | 	toc_uptodate = 1; | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* Request handling */ | 
 |  | 
 | static int current_valid(void) | 
 | { | 
 |         return CURRENT && | 
 | 		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; | 
 |  | 
 | static inline 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 */ | 
 | 	S_READ,		/* 2 */ | 
 | 	S_DATA,		/* 3 */ | 
 | 	S_STOP,		/* 4 */ | 
 | 	S_STOPPING	/* 5 */ | 
 | }; | 
 |  | 
 | static volatile enum state_e state = S_IDLE; | 
 | #if DEBUG_STATE | 
 | static volatile enum state_e state_old = S_STOP; | 
 | static volatile int flags_old = 0; | 
 | static volatile long state_n = 0; | 
 | #endif | 
 |  | 
 |  | 
 | /* Used as mutex to keep do_optcd_request (and other processes calling | 
 |    ioctl) out while some process is inside a VFS call. | 
 |    Reverse is accomplished by checking if state = S_IDLE upon entry | 
 |    of opt_ioctl and opt_media_change. */ | 
 | static int in_vfs = 0; | 
 |  | 
 |  | 
 | static volatile int transfer_is_active = 0; | 
 | static volatile int error = 0;	/* %% do something with this?? */ | 
 | static int tries;		/* ibid?? */ | 
 | static int timeout = 0; | 
 |  | 
 | static void poll(unsigned long data); | 
 | static struct timer_list req_timer = {.function = poll}; | 
 |  | 
 |  | 
 | static void poll(unsigned long data) | 
 | { | 
 | 	static volatile int read_count = 1; | 
 | 	int flags; | 
 | 	int loop_again = 1; | 
 | 	int status = 0; | 
 | 	int skip = 0; | 
 |  | 
 | 	if (error) { | 
 | 		printk(KERN_ERR "optcd: I/O error 0x%02x\n", error); | 
 | 		opt_invalidate_buffers(); | 
 | 		if (!tries--) { | 
 | 			printk(KERN_ERR "optcd: read block %d failed;" | 
 | 				" Giving up\n", next_bn); | 
 | 			if (transfer_is_active) | 
 | 				loop_again = 0; | 
 | 			if (current_valid()) | 
 | 				end_request(CURRENT, 0); | 
 | 			tries = 5; | 
 | 		} | 
 | 		error = 0; | 
 | 		state = S_STOP; | 
 | 	} | 
 |  | 
 | 	while (loop_again) | 
 | 	{ | 
 | 		loop_again = 0; /* each case must flip this back to 1 if we want | 
 | 		                 to come back up here */ | 
 |  | 
 | #if DEBUG_STATE | 
 | 		if (state == state_old) | 
 | 			state_n++; | 
 | 		else { | 
 | 			state_old = state; | 
 | 			if (++state_n > 1) | 
 | 				printk(KERN_DEBUG "optcd: %ld times " | 
 | 					"in previous state\n", state_n); | 
 | 			printk(KERN_DEBUG "optcd: state %d\n", state); | 
 | 			state_n = 0; | 
 | 		} | 
 | #endif | 
 |  | 
 | 		switch (state) { | 
 | 		case S_IDLE: | 
 | 			return; | 
 | 		case S_START: | 
 | 			if (in_vfs) | 
 | 				break; | 
 | 			if (send_cmd(COMDRVST)) { | 
 | 				state = S_IDLE; | 
 | 				while (current_valid()) | 
 | 					end_request(CURRENT, 0); | 
 | 				return; | 
 | 			} | 
 | 			state = S_READ; | 
 | 			timeout = READ_TIMEOUT; | 
 | 			break; | 
 | 		case S_READ: { | 
 | 			struct cdrom_msf msf; | 
 | 			if (!skip) { | 
 | 				status = fetch_status(); | 
 | 				if (status < 0) | 
 | 					break; | 
 | 				if (status & ST_DSK_CHG) { | 
 | 					toc_uptodate = 0; | 
 | 					opt_invalidate_buffers(); | 
 | 				} | 
 | 			} | 
 | 			skip = 0; | 
 | 			if ((status & ST_DOOR_OPEN) || (status & ST_DRVERR)) { | 
 | 				toc_uptodate = 0; | 
 | 				opt_invalidate_buffers(); | 
 | 				printk(KERN_WARNING "optcd: %s\n", | 
 | 					(status & ST_DOOR_OPEN) | 
 | 					? "door open" | 
 | 					: "disk removed"); | 
 | 				state = S_IDLE; | 
 | 				while (current_valid()) | 
 | 					end_request(CURRENT, 0); | 
 | 				return; | 
 | 			} | 
 | 			if (!current_valid()) { | 
 | 				state = S_STOP; | 
 | 				loop_again = 1; | 
 | 				break; | 
 | 			} | 
 | 			next_bn = CURRENT -> sector / 4; | 
 | 			lba2msf(next_bn, &msf); | 
 | 			read_count = N_BUFS; | 
 | 			msf.cdmsf_frame1 = read_count; /* Not BCD! */ | 
 |  | 
 | 			DEBUG((DEBUG_REQUEST, "reading %x:%x.%x %x:%x.%x", | 
 | 				msf.cdmsf_min0, | 
 | 				msf.cdmsf_sec0, | 
 | 				msf.cdmsf_frame0, | 
 | 				msf.cdmsf_min1, | 
 | 				msf.cdmsf_sec1, | 
 | 				msf.cdmsf_frame1)); | 
 | 			DEBUG((DEBUG_REQUEST, "next_bn:%d buf_in:%d" | 
 | 				" buf_out:%d buf_bn:%d", | 
 | 				next_bn, | 
 | 				buf_in, | 
 | 				buf_out, | 
 | 				buf_bn[buf_in])); | 
 |  | 
 | 			exec_read_cmd(COMREAD, &msf); | 
 | 			state = S_DATA; | 
 | 			timeout = READ_TIMEOUT; | 
 | 			break; | 
 | 		} | 
 | 		case S_DATA: | 
 | 			flags = stdt_flags() & (FL_STEN|FL_DTEN); | 
 |  | 
 | #if DEBUG_STATE | 
 | 			if (flags != flags_old) { | 
 | 				flags_old = flags; | 
 | 				printk(KERN_DEBUG "optcd: flags:%x\n", flags); | 
 | 			} | 
 | 			if (flags == FL_STEN) | 
 | 				printk(KERN_DEBUG "timeout cnt: %d\n", timeout); | 
 | #endif | 
 |  | 
 | 			switch (flags) { | 
 | 			case FL_DTEN:		/* only STEN low */ | 
 | 				if (!tries--) { | 
 | 					printk(KERN_ERR | 
 | 						"optcd: read block %d failed; " | 
 | 						"Giving up\n", next_bn); | 
 | 					if (transfer_is_active) { | 
 | 						tries = 0; | 
 | 						break; | 
 | 					} | 
 | 					if (current_valid()) | 
 | 						end_request(CURRENT, 0); | 
 | 					tries = 5; | 
 | 				} | 
 | 				state = S_START; | 
 | 				timeout = READ_TIMEOUT; | 
 | 				loop_again = 1; | 
 | 			case (FL_STEN|FL_DTEN):	 /* both high */ | 
 | 				break; | 
 | 			default:	/* DTEN low */ | 
 | 				tries = 5; | 
 | 				if (!current_valid() && buf_in == buf_out) { | 
 | 					state = S_STOP; | 
 | 					loop_again = 1; | 
 | 					break; | 
 | 				} | 
 | 				if (read_count<=0) | 
 | 					printk(KERN_WARNING | 
 | 						"optcd: warning - try to read" | 
 | 						" 0 frames\n"); | 
 | 				while (read_count) { | 
 | 					buf_bn[buf_in] = NOBUF; | 
 | 					if (!flag_low(FL_DTEN, BUSY_TIMEOUT)) { | 
 | 					/* should be no waiting here!?? */ | 
 | 						printk(KERN_ERR | 
 | 						   "read_count:%d " | 
 | 						   "CURRENT->nr_sectors:%ld " | 
 | 						   "buf_in:%d\n", | 
 | 							read_count, | 
 | 							CURRENT->nr_sectors, | 
 | 							buf_in); | 
 | 						printk(KERN_ERR | 
 | 							"transfer active: %x\n", | 
 | 							transfer_is_active); | 
 | 						read_count = 0; | 
 | 						state = S_STOP; | 
 | 						loop_again = 1; | 
 | 						end_request(CURRENT, 0); | 
 | 						break; | 
 | 					} | 
 | 					fetch_data(buf+ | 
 | 					    CD_FRAMESIZE*buf_in, | 
 | 					    CD_FRAMESIZE); | 
 | 					read_count--; | 
 |  | 
 | 					DEBUG((DEBUG_REQUEST, | 
 | 						"S_DATA; ---I've read data- " | 
 | 						"read_count: %d", | 
 | 						read_count)); | 
 | 					DEBUG((DEBUG_REQUEST, | 
 | 						"next_bn:%d  buf_in:%d " | 
 | 						"buf_out:%d  buf_bn:%d", | 
 | 						next_bn, | 
 | 						buf_in, | 
 | 						buf_out, | 
 | 						buf_bn[buf_in])); | 
 |  | 
 | 					buf_bn[buf_in] = next_bn++; | 
 | 					if (buf_out == NOBUF) | 
 | 						buf_out = buf_in; | 
 | 					buf_in = buf_in + 1 == | 
 | 						N_BUFS ? 0 : buf_in + 1; | 
 | 				} | 
 | 				if (!transfer_is_active) { | 
 | 					while (current_valid()) { | 
 | 						transfer(); | 
 | 						if (CURRENT -> nr_sectors == 0) | 
 | 							end_request(CURRENT, 1); | 
 | 						else | 
 | 							break; | 
 | 					} | 
 | 				} | 
 |  | 
 | 				if (current_valid() | 
 | 				    && (CURRENT -> sector / 4 < next_bn || | 
 | 				    CURRENT -> sector / 4 > | 
 | 				     next_bn + N_BUFS)) { | 
 | 					state = S_STOP; | 
 | 					loop_again = 1; | 
 | 					break; | 
 | 				} | 
 | 				timeout = READ_TIMEOUT; | 
 | 				if (read_count == 0) { | 
 | 					state = S_STOP; | 
 | 					loop_again = 1; | 
 | 					break; | 
 | 				} | 
 | 			} | 
 | 			break; | 
 | 		case S_STOP: | 
 | 			if (read_count != 0) | 
 | 				printk(KERN_ERR | 
 | 					"optcd: discard data=%x frames\n", | 
 | 					read_count); | 
 | 			flush_data(); | 
 | 			if (send_cmd(COMDRVST)) { | 
 | 				state = S_IDLE; | 
 | 				while (current_valid()) | 
 | 					end_request(CURRENT, 0); | 
 | 				return; | 
 | 			} | 
 | 			state = S_STOPPING; | 
 | 			timeout = STOP_TIMEOUT; | 
 | 			break; | 
 | 		case S_STOPPING: | 
 | 			status = fetch_status(); | 
 | 			if (status < 0 && timeout) | 
 | 					break; | 
 | 			if ((status >= 0) && (status & ST_DSK_CHG)) { | 
 | 				toc_uptodate = 0; | 
 | 				opt_invalidate_buffers(); | 
 | 			} | 
 | 			if (current_valid()) { | 
 | 				if (status >= 0) { | 
 | 					state = S_READ; | 
 | 					loop_again = 1; | 
 | 					skip = 1; | 
 | 					break; | 
 | 				} else { | 
 | 					state = S_START; | 
 | 					timeout = 1; | 
 | 				} | 
 | 			} else { | 
 | 				state = S_IDLE; | 
 | 				return; | 
 | 			} | 
 | 			break; | 
 | 		default: | 
 | 			printk(KERN_ERR "optcd: invalid state %d\n", state); | 
 | 			return; | 
 | 		} /* case */ | 
 | 	} /* while */ | 
 |  | 
 | 	if (!timeout--) { | 
 | 		printk(KERN_ERR "optcd: timeout in state %d\n", state); | 
 | 		state = S_STOP; | 
 | 		if (exec_cmd(COMSTOP) < 0) { | 
 | 			state = S_IDLE; | 
 | 			while (current_valid()) | 
 | 				end_request(CURRENT, 0); | 
 | 			return; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	mod_timer(&req_timer, jiffies + HZ/100); | 
 | } | 
 |  | 
 |  | 
 | static void do_optcd_request(request_queue_t * q) | 
 | { | 
 | 	DEBUG((DEBUG_REQUEST, "do_optcd_request(%ld+%ld)", | 
 | 	       CURRENT -> sector, CURRENT -> nr_sectors)); | 
 |  | 
 | 	if (disk_info.audio) { | 
 | 		printk(KERN_WARNING "optcd: tried to mount an Audio CD\n"); | 
 | 		end_request(CURRENT, 0); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	transfer_is_active = 1; | 
 | 	while (current_valid()) { | 
 | 		transfer();	/* First try to transfer block from buffers */ | 
 | 		if (CURRENT -> nr_sectors == 0) { | 
 | 			end_request(CURRENT, 1); | 
 | 		} else {	/* Want to read a block not in buffer */ | 
 | 			buf_out = NOBUF; | 
 | 			if (state == S_IDLE) { | 
 | 				/* %% Should this block the request queue?? */ | 
 | 				if (update_toc() < 0) { | 
 | 					while (current_valid()) | 
 | 						end_request(CURRENT, 0); | 
 | 					break; | 
 | 				} | 
 | 				/* Start state machine */ | 
 | 				state = S_START; | 
 | 				timeout = READ_TIMEOUT; | 
 | 				tries = 5; | 
 | 				/* %% why not start right away?? */ | 
 | 				mod_timer(&req_timer, jiffies + HZ/100); | 
 | 			} | 
 | 			break; | 
 | 		} | 
 | 	} | 
 | 	transfer_is_active = 0; | 
 |  | 
 | 	DEBUG((DEBUG_REQUEST, "next_bn:%d  buf_in:%d buf_out:%d  buf_bn:%d", | 
 | 	       next_bn, buf_in, buf_out, buf_bn[buf_in])); | 
 | 	DEBUG((DEBUG_REQUEST, "do_optcd_request ends")); | 
 | } | 
 |  | 
 | /* IOCTLs */ | 
 |  | 
 |  | 
 | static char auto_eject = 0; | 
 |  | 
 | static int cdrompause(void) | 
 | { | 
 | 	int status; | 
 |  | 
 | 	if (audio_status != CDROM_AUDIO_PLAY) | 
 | 		return -EINVAL; | 
 |  | 
 | 	status = exec_cmd(COMPAUSEON); | 
 | 	if (status < 0) { | 
 | 		DEBUG((DEBUG_VFS, "exec_cmd COMPAUSEON: %02x", -status)); | 
 | 		return -EIO; | 
 | 	} | 
 | 	audio_status = CDROM_AUDIO_PAUSED; | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | static int cdromresume(void) | 
 | { | 
 | 	int status; | 
 |  | 
 | 	if (audio_status != CDROM_AUDIO_PAUSED) | 
 | 		return -EINVAL; | 
 |  | 
 | 	status = exec_cmd(COMPAUSEOFF); | 
 | 	if (status < 0) { | 
 | 		DEBUG((DEBUG_VFS, "exec_cmd COMPAUSEOFF: %02x", -status)); | 
 | 		audio_status = CDROM_AUDIO_ERROR; | 
 | 		return -EIO; | 
 | 	} | 
 | 	audio_status = CDROM_AUDIO_PLAY; | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | static int cdromplaymsf(void __user *arg) | 
 | { | 
 | 	int status; | 
 | 	struct cdrom_msf msf; | 
 |  | 
 | 	if (copy_from_user(&msf, arg, sizeof msf)) | 
 | 		return -EFAULT; | 
 |  | 
 | 	bin2bcd(&msf); | 
 | 	status = exec_long_cmd(COMPLAY, &msf); | 
 | 	if (status < 0) { | 
 | 		DEBUG((DEBUG_VFS, "exec_long_cmd COMPLAY: %02x", -status)); | 
 | 		audio_status = CDROM_AUDIO_ERROR; | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 | 	audio_status = CDROM_AUDIO_PLAY; | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | static int cdromplaytrkind(void __user *arg) | 
 | { | 
 | 	int status; | 
 | 	struct cdrom_ti ti; | 
 | 	struct cdrom_msf msf; | 
 |  | 
 | 	if (copy_from_user(&ti, arg, sizeof ti)) | 
 | 		return -EFAULT; | 
 |  | 
 | 	if (ti.cdti_trk0 < disk_info.first | 
 | 	    || ti.cdti_trk0 > disk_info.last | 
 | 	    || ti.cdti_trk1 < ti.cdti_trk0) | 
 | 		return -EINVAL; | 
 | 	if (ti.cdti_trk1 > disk_info.last) | 
 | 		ti.cdti_trk1 = disk_info.last; | 
 |  | 
 | 	msf.cdmsf_min0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.minute; | 
 | 	msf.cdmsf_sec0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.second; | 
 | 	msf.cdmsf_frame0 = toc[ti.cdti_trk0].cdsc_absaddr.msf.frame; | 
 | 	msf.cdmsf_min1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.minute; | 
 | 	msf.cdmsf_sec1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.second; | 
 | 	msf.cdmsf_frame1 = toc[ti.cdti_trk1 + 1].cdsc_absaddr.msf.frame; | 
 |  | 
 | 	DEBUG((DEBUG_VFS, "play %02d:%02d.%02d to %02d:%02d.%02d", | 
 | 		msf.cdmsf_min0, | 
 | 		msf.cdmsf_sec0, | 
 | 		msf.cdmsf_frame0, | 
 | 		msf.cdmsf_min1, | 
 | 		msf.cdmsf_sec1, | 
 | 		msf.cdmsf_frame1)); | 
 |  | 
 | 	bin2bcd(&msf); | 
 | 	status = exec_long_cmd(COMPLAY, &msf); | 
 | 	if (status < 0) { | 
 | 		DEBUG((DEBUG_VFS, "exec_long_cmd COMPLAY: %02x", -status)); | 
 | 		audio_status = CDROM_AUDIO_ERROR; | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 | 	audio_status = CDROM_AUDIO_PLAY; | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | static int cdromreadtochdr(void __user *arg) | 
 | { | 
 | 	struct cdrom_tochdr tochdr; | 
 |  | 
 | 	tochdr.cdth_trk0 = disk_info.first; | 
 | 	tochdr.cdth_trk1 = disk_info.last; | 
 |  | 
 | 	return copy_to_user(arg, &tochdr, sizeof tochdr) ? -EFAULT : 0; | 
 | } | 
 |  | 
 |  | 
 | static int cdromreadtocentry(void __user *arg) | 
 | { | 
 | 	struct cdrom_tocentry entry; | 
 | 	struct cdrom_subchnl *tocptr; | 
 |  | 
 | 	if (copy_from_user(&entry, arg, sizeof entry)) | 
 | 		return -EFAULT; | 
 |  | 
 | 	if (entry.cdte_track == CDROM_LEADOUT) | 
 | 		tocptr = &toc[disk_info.last + 1]; | 
 | 	else if (entry.cdte_track > disk_info.last | 
 | 		|| entry.cdte_track < disk_info.first) | 
 | 		return -EINVAL; | 
 | 	else | 
 | 		tocptr = &toc[entry.cdte_track]; | 
 |  | 
 | 	entry.cdte_adr = tocptr->cdsc_adr; | 
 | 	entry.cdte_ctrl = tocptr->cdsc_ctrl; | 
 | 	entry.cdte_addr.msf.minute = tocptr->cdsc_absaddr.msf.minute; | 
 | 	entry.cdte_addr.msf.second = tocptr->cdsc_absaddr.msf.second; | 
 | 	entry.cdte_addr.msf.frame = tocptr->cdsc_absaddr.msf.frame; | 
 | 	/* %% What should go into entry.cdte_datamode? */ | 
 |  | 
 | 	if (entry.cdte_format == CDROM_LBA) | 
 | 		msf2lba(&entry.cdte_addr); | 
 | 	else if (entry.cdte_format != CDROM_MSF) | 
 | 		return -EINVAL; | 
 |  | 
 | 	return copy_to_user(arg, &entry, sizeof entry) ? -EFAULT : 0; | 
 | } | 
 |  | 
 |  | 
 | static int cdromvolctrl(void __user *arg) | 
 | { | 
 | 	int status; | 
 | 	struct cdrom_volctrl volctrl; | 
 | 	struct cdrom_msf msf; | 
 |  | 
 | 	if (copy_from_user(&volctrl, arg, sizeof volctrl)) | 
 | 		return -EFAULT; | 
 |  | 
 | 	msf.cdmsf_min0 = 0x10; | 
 | 	msf.cdmsf_sec0 = 0x32; | 
 | 	msf.cdmsf_frame0 = volctrl.channel0; | 
 | 	msf.cdmsf_min1 = volctrl.channel1; | 
 | 	msf.cdmsf_sec1 = volctrl.channel2; | 
 | 	msf.cdmsf_frame1 = volctrl.channel3; | 
 |  | 
 | 	status = exec_long_cmd(COMCHCTRL, &msf); | 
 | 	if (status < 0) { | 
 | 		DEBUG((DEBUG_VFS, "exec_long_cmd COMCHCTRL: %02x", -status)); | 
 | 		return -EIO; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | static int cdromsubchnl(void __user *arg) | 
 | { | 
 | 	int status; | 
 | 	struct cdrom_subchnl subchnl; | 
 |  | 
 | 	if (copy_from_user(&subchnl, arg, sizeof subchnl)) | 
 | 		return -EFAULT; | 
 |  | 
 | 	if (subchnl.cdsc_format != CDROM_LBA | 
 | 	    && subchnl.cdsc_format != CDROM_MSF) | 
 | 		return -EINVAL; | 
 |  | 
 | 	status = get_q_channel(&subchnl); | 
 | 	if (status < 0) { | 
 | 		DEBUG((DEBUG_VFS, "get_q_channel: %02x", -status)); | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 | 	if (copy_to_user(arg, &subchnl, sizeof subchnl)) | 
 | 		return -EFAULT; | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | static struct gendisk *optcd_disk; | 
 |  | 
 |  | 
 | static int cdromread(void __user *arg, int blocksize, int cmd) | 
 | { | 
 | 	int status; | 
 | 	struct cdrom_msf msf; | 
 |  | 
 | 	if (copy_from_user(&msf, arg, sizeof msf)) | 
 | 		return -EFAULT; | 
 |  | 
 | 	bin2bcd(&msf); | 
 | 	msf.cdmsf_min1 = 0; | 
 | 	msf.cdmsf_sec1 = 0; | 
 | 	msf.cdmsf_frame1 = 1;	/* read only one frame */ | 
 | 	status = exec_read_cmd(cmd, &msf); | 
 |  | 
 | 	DEBUG((DEBUG_VFS, "read cmd status 0x%x", status)); | 
 |  | 
 | 	if (!sleep_flag_low(FL_DTEN, SLEEP_TIMEOUT)) | 
 | 		return -EIO; | 
 |  | 
 | 	fetch_data(optcd_disk->private_data, blocksize); | 
 |  | 
 | 	if (copy_to_user(arg, optcd_disk->private_data, blocksize)) | 
 | 		return -EFAULT; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | static int cdromseek(void __user *arg) | 
 | { | 
 | 	int status; | 
 | 	struct cdrom_msf msf; | 
 |  | 
 | 	if (copy_from_user(&msf, arg, sizeof msf)) | 
 | 		return -EFAULT; | 
 |  | 
 | 	bin2bcd(&msf); | 
 | 	status = exec_seek_cmd(COMSEEK, &msf); | 
 |  | 
 | 	DEBUG((DEBUG_VFS, "COMSEEK status 0x%x", status)); | 
 |  | 
 | 	if (status < 0) | 
 | 		return -EIO; | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | #ifdef MULTISESSION | 
 | static int cdrommultisession(void __user *arg) | 
 | { | 
 | 	struct cdrom_multisession ms; | 
 |  | 
 | 	if (copy_from_user(&ms, arg, sizeof ms)) | 
 | 		return -EFAULT; | 
 |  | 
 | 	ms.addr.msf.minute = disk_info.last_session.minute; | 
 | 	ms.addr.msf.second = disk_info.last_session.second; | 
 | 	ms.addr.msf.frame = disk_info.last_session.frame; | 
 |  | 
 | 	if (ms.addr_format != CDROM_LBA | 
 | 	   && ms.addr_format != CDROM_MSF) | 
 | 		return -EINVAL; | 
 | 	if (ms.addr_format == CDROM_LBA) | 
 | 		msf2lba(&ms.addr); | 
 |  | 
 | 	ms.xa_flag = disk_info.xa; | 
 |  | 
 |   	if (copy_to_user(arg, &ms, sizeof(struct cdrom_multisession))) | 
 | 		return -EFAULT; | 
 |  | 
 | #if DEBUG_MULTIS | 
 |  	if (ms.addr_format == CDROM_MSF) | 
 |                	printk(KERN_DEBUG | 
 | 			"optcd: multisession xa:%d, msf:%02d:%02d.%02d\n", | 
 | 			ms.xa_flag, | 
 | 			ms.addr.msf.minute, | 
 | 			ms.addr.msf.second, | 
 | 			ms.addr.msf.frame); | 
 | 	else | 
 | 		printk(KERN_DEBUG | 
 | 		    "optcd: multisession %d, lba:0x%08x [%02d:%02d.%02d])\n", | 
 | 			ms.xa_flag, | 
 | 			ms.addr.lba, | 
 | 			disk_info.last_session.minute, | 
 | 			disk_info.last_session.second, | 
 | 			disk_info.last_session.frame); | 
 | #endif /* DEBUG_MULTIS */ | 
 |  | 
 | 	return 0; | 
 | } | 
 | #endif /* MULTISESSION */ | 
 |  | 
 |  | 
 | static int cdromreset(void) | 
 | { | 
 | 	if (state != S_IDLE) { | 
 | 		error = 1; | 
 | 		tries = 0; | 
 | 	} | 
 |  | 
 | 	toc_uptodate = 0; | 
 | 	disk_changed = 1; | 
 | 	opt_invalidate_buffers(); | 
 | 	audio_status = CDROM_AUDIO_NO_STATUS; | 
 |  | 
 | 	if (!reset_drive()) | 
 | 		return -EIO; | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* VFS calls */ | 
 |  | 
 |  | 
 | static int opt_ioctl(struct inode *ip, struct file *fp, | 
 |                      unsigned int cmd, unsigned long arg) | 
 | { | 
 | 	int status, err, retval = 0; | 
 | 	void __user *argp = (void __user *)arg; | 
 |  | 
 | 	DEBUG((DEBUG_VFS, "starting opt_ioctl")); | 
 |  | 
 | 	if (!ip) | 
 | 		return -EINVAL; | 
 |  | 
 | 	if (cmd == CDROMRESET) | 
 | 		return cdromreset(); | 
 |  | 
 | 	/* is do_optcd_request or another ioctl busy? */ | 
 | 	if (state != S_IDLE || in_vfs) | 
 | 		return -EBUSY; | 
 |  | 
 | 	in_vfs = 1; | 
 |  | 
 | 	status = drive_status(); | 
 | 	if (status < 0) { | 
 | 		DEBUG((DEBUG_VFS, "drive_status: %02x", -status)); | 
 | 		in_vfs = 0; | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 | 	if (status & ST_DOOR_OPEN) | 
 | 		switch (cmd) {	/* Actions that can be taken with door open */ | 
 | 		case CDROMCLOSETRAY: | 
 | 			/* We do this before trying to read the toc. */ | 
 | 			err = exec_cmd(COMCLOSE); | 
 | 			if (err < 0) { | 
 | 				DEBUG((DEBUG_VFS, | 
 | 				       "exec_cmd COMCLOSE: %02x", -err)); | 
 | 				in_vfs = 0; | 
 | 				return -EIO; | 
 | 			} | 
 | 			break; | 
 | 		default:	in_vfs = 0; | 
 | 				return -EBUSY; | 
 | 		} | 
 |  | 
 | 	err = update_toc(); | 
 | 	if (err < 0) { | 
 | 		DEBUG((DEBUG_VFS, "update_toc: %02x", -err)); | 
 | 		in_vfs = 0; | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 | 	DEBUG((DEBUG_VFS, "ioctl cmd 0x%x", cmd)); | 
 |  | 
 | 	switch (cmd) { | 
 | 	case CDROMPAUSE:	retval = cdrompause(); break; | 
 | 	case CDROMRESUME:	retval = cdromresume(); break; | 
 | 	case CDROMPLAYMSF:	retval = cdromplaymsf(argp); break; | 
 | 	case CDROMPLAYTRKIND:	retval = cdromplaytrkind(argp); break; | 
 | 	case CDROMREADTOCHDR:	retval = cdromreadtochdr(argp); break; | 
 | 	case CDROMREADTOCENTRY:	retval = cdromreadtocentry(argp); break; | 
 |  | 
 | 	case CDROMSTOP:		err = exec_cmd(COMSTOP); | 
 | 				if (err < 0) { | 
 | 					DEBUG((DEBUG_VFS, | 
 | 						"exec_cmd COMSTOP: %02x", | 
 | 						-err)); | 
 | 					retval = -EIO; | 
 | 				} else | 
 | 					audio_status = CDROM_AUDIO_NO_STATUS; | 
 | 				break; | 
 | 	case CDROMSTART:	break;	/* This is a no-op */ | 
 | 	case CDROMEJECT:	err = exec_cmd(COMUNLOCK); | 
 | 				if (err < 0) { | 
 | 					DEBUG((DEBUG_VFS, | 
 | 						"exec_cmd COMUNLOCK: %02x", | 
 | 						-err)); | 
 | 					retval = -EIO; | 
 | 					break; | 
 | 				} | 
 | 				err = exec_cmd(COMOPEN); | 
 | 				if (err < 0) { | 
 | 					DEBUG((DEBUG_VFS, | 
 | 						"exec_cmd COMOPEN: %02x", | 
 | 						-err)); | 
 | 					retval = -EIO; | 
 | 				} | 
 | 				break; | 
 |  | 
 | 	case CDROMVOLCTRL:	retval = cdromvolctrl(argp); break; | 
 | 	case CDROMSUBCHNL:	retval = cdromsubchnl(argp); break; | 
 |  | 
 | 	/* The drive detects the mode and automatically delivers the | 
 | 	   correct 2048 bytes, so we don't need these IOCTLs */ | 
 | 	case CDROMREADMODE2:	retval = -EINVAL; break; | 
 | 	case CDROMREADMODE1:	retval = -EINVAL; break; | 
 |  | 
 | 	/* Drive doesn't support reading audio */ | 
 | 	case CDROMREADAUDIO:	retval = -EINVAL; break; | 
 |  | 
 | 	case CDROMEJECT_SW:	auto_eject = (char) arg; | 
 | 				break; | 
 |  | 
 | #ifdef MULTISESSION | 
 | 	case CDROMMULTISESSION:	retval = cdrommultisession(argp); break; | 
 | #endif | 
 |  | 
 | 	case CDROM_GET_MCN:	retval = -EINVAL; break; /* not implemented */ | 
 | 	case CDROMVOLREAD:	retval = -EINVAL; break; /* not implemented */ | 
 |  | 
 | 	case CDROMREADRAW: | 
 | 			/* this drive delivers 2340 bytes in raw mode */ | 
 | 			retval = cdromread(argp, CD_FRAMESIZE_RAW1, COMREADRAW); | 
 | 			break; | 
 | 	case CDROMREADCOOKED: | 
 | 			retval = cdromread(argp, CD_FRAMESIZE, COMREAD); | 
 | 			break; | 
 | 	case CDROMREADALL: | 
 | 			retval = cdromread(argp, CD_FRAMESIZE_RAWER, COMREADALL); | 
 | 			break; | 
 |  | 
 | 	case CDROMSEEK:		retval = cdromseek(argp); break; | 
 | 	case CDROMPLAYBLK:	retval = -EINVAL; break; /* not implemented */ | 
 | 	case CDROMCLOSETRAY:	break;	/* The action was taken earlier */ | 
 | 	default:		retval = -EINVAL; | 
 | 	} | 
 | 	in_vfs = 0; | 
 | 	return retval; | 
 | } | 
 |  | 
 |  | 
 | static int open_count = 0; | 
 |  | 
 | /* Open device special file; check that a disk is in. */ | 
 | static int opt_open(struct inode *ip, struct file *fp) | 
 | { | 
 | 	DEBUG((DEBUG_VFS, "starting opt_open")); | 
 |  | 
 | 	if (!open_count && state == S_IDLE) { | 
 | 		int status; | 
 | 		char *buf; | 
 |  | 
 | 		buf = kmalloc(CD_FRAMESIZE_RAWER, GFP_KERNEL); | 
 | 		if (!buf) { | 
 | 			printk(KERN_INFO "optcd: cannot allocate read buffer\n"); | 
 | 			return -ENOMEM; | 
 | 		} | 
 | 		optcd_disk->private_data = buf;		/* save read buffer */ | 
 |  | 
 | 		toc_uptodate = 0; | 
 | 		opt_invalidate_buffers(); | 
 |  | 
 | 		status = exec_cmd(COMCLOSE);	/* close door */ | 
 | 		if (status < 0) { | 
 | 			DEBUG((DEBUG_VFS, "exec_cmd COMCLOSE: %02x", -status)); | 
 | 		} | 
 |  | 
 | 		status = drive_status(); | 
 | 		if (status < 0) { | 
 | 			DEBUG((DEBUG_VFS, "drive_status: %02x", -status)); | 
 | 			goto err_out; | 
 | 		} | 
 | 		DEBUG((DEBUG_VFS, "status: %02x", status)); | 
 | 		if ((status & ST_DOOR_OPEN) || (status & ST_DRVERR)) { | 
 | 			printk(KERN_INFO "optcd: no disk or door open\n"); | 
 | 			goto err_out; | 
 | 		} | 
 | 		status = exec_cmd(COMLOCK);		/* Lock door */ | 
 | 		if (status < 0) { | 
 | 			DEBUG((DEBUG_VFS, "exec_cmd COMLOCK: %02x", -status)); | 
 | 		} | 
 | 		status = update_toc();	/* Read table of contents */ | 
 | 		if (status < 0)	{ | 
 | 			DEBUG((DEBUG_VFS, "update_toc: %02x", -status)); | 
 | 	 		status = exec_cmd(COMUNLOCK);	/* Unlock door */ | 
 | 			if (status < 0) { | 
 | 				DEBUG((DEBUG_VFS, | 
 | 				       "exec_cmd COMUNLOCK: %02x", -status)); | 
 | 			} | 
 | 			goto err_out; | 
 | 		} | 
 | 		open_count++; | 
 | 	} | 
 |  | 
 | 	DEBUG((DEBUG_VFS, "exiting opt_open")); | 
 |  | 
 | 	return 0; | 
 |  | 
 | err_out: | 
 | 	return -EIO; | 
 | } | 
 |  | 
 |  | 
 | /* Release device special file; flush all blocks from the buffer cache */ | 
 | static int opt_release(struct inode *ip, struct file *fp) | 
 | { | 
 | 	int status; | 
 |  | 
 | 	DEBUG((DEBUG_VFS, "executing opt_release")); | 
 | 	DEBUG((DEBUG_VFS, "inode: %p, device: %s, file: %p\n", | 
 | 		ip, ip->i_bdev->bd_disk->disk_name, fp)); | 
 |  | 
 | 	if (!--open_count) { | 
 | 		toc_uptodate = 0; | 
 | 		opt_invalidate_buffers(); | 
 | 	 	status = exec_cmd(COMUNLOCK);	/* Unlock door */ | 
 | 		if (status < 0) { | 
 | 			DEBUG((DEBUG_VFS, "exec_cmd COMUNLOCK: %02x", -status)); | 
 | 		} | 
 | 		if (auto_eject) { | 
 | 			status = exec_cmd(COMOPEN); | 
 | 			DEBUG((DEBUG_VFS, "exec_cmd COMOPEN: %02x", -status)); | 
 | 		} | 
 | 		kfree(optcd_disk->private_data); | 
 | 		del_timer(&delay_timer); | 
 | 		del_timer(&req_timer); | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | /* Check if disk has been changed */ | 
 | static int opt_media_change(struct gendisk *disk) | 
 | { | 
 | 	DEBUG((DEBUG_VFS, "executing opt_media_change")); | 
 | 	DEBUG((DEBUG_VFS, "dev: %s; disk_changed = %d\n", | 
 | 			disk->disk_name, disk_changed)); | 
 |  | 
 | 	if (disk_changed) { | 
 | 		disk_changed = 0; | 
 | 		return 1; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* Driver initialisation */ | 
 |  | 
 |  | 
 | /* Returns 1 if a drive is detected with a version string | 
 |    starting with "DOLPHIN". Otherwise 0. */ | 
 | static int __init version_ok(void) | 
 | { | 
 | 	char devname[100]; | 
 | 	int count, i, ch, status; | 
 |  | 
 | 	status = exec_cmd(COMVERSION); | 
 | 	if (status < 0) { | 
 | 		DEBUG((DEBUG_VFS, "exec_cmd COMVERSION: %02x", -status)); | 
 | 		return 0; | 
 | 	} | 
 | 	if ((count = get_data(1)) < 0) { | 
 | 		DEBUG((DEBUG_VFS, "get_data(1): %02x", -count)); | 
 | 		return 0; | 
 | 	} | 
 | 	for (i = 0, ch = -1; count > 0; count--) { | 
 | 		if ((ch = get_data(1)) < 0) { | 
 | 			DEBUG((DEBUG_VFS, "get_data(1): %02x", -ch)); | 
 | 			break; | 
 | 		} | 
 | 		if (i < 99) | 
 | 			devname[i++] = ch; | 
 | 	} | 
 | 	devname[i] = '\0'; | 
 | 	if (ch < 0) | 
 | 		return 0; | 
 |  | 
 | 	printk(KERN_INFO "optcd: Device %s detected\n", devname); | 
 | 	return ((devname[0] == 'D') | 
 | 	     && (devname[1] == 'O') | 
 | 	     && (devname[2] == 'L') | 
 | 	     && (devname[3] == 'P') | 
 | 	     && (devname[4] == 'H') | 
 | 	     && (devname[5] == 'I') | 
 | 	     && (devname[6] == 'N')); | 
 | } | 
 |  | 
 |  | 
 | static struct block_device_operations opt_fops = { | 
 | 	.owner		= THIS_MODULE, | 
 | 	.open		= opt_open, | 
 | 	.release	= opt_release, | 
 | 	.ioctl		= opt_ioctl, | 
 | 	.media_changed	= opt_media_change, | 
 | }; | 
 |  | 
 | #ifndef MODULE | 
 | /* Get kernel parameter when used as a kernel driver */ | 
 | static int optcd_setup(char *str) | 
 | { | 
 | 	int ints[4]; | 
 | 	(void)get_options(str, ARRAY_SIZE(ints), ints); | 
 | 	 | 
 | 	if (ints[0] > 0) | 
 | 		optcd_port = ints[1]; | 
 |  | 
 |  	return 1; | 
 | } | 
 |  | 
 | __setup("optcd=", optcd_setup); | 
 |  | 
 | #endif /* MODULE */ | 
 |  | 
 | /* Test for presence of drive and initialize it. Called at boot time | 
 |    or during module initialisation. */ | 
 | static int __init optcd_init(void) | 
 | { | 
 | 	int status; | 
 |  | 
 | 	if (optcd_port <= 0) { | 
 | 		printk(KERN_INFO | 
 | 			"optcd: no Optics Storage CDROM Initialization\n"); | 
 | 		return -EIO; | 
 | 	} | 
 | 	optcd_disk = alloc_disk(1); | 
 | 	if (!optcd_disk) { | 
 | 		printk(KERN_ERR "optcd: can't allocate disk\n"); | 
 | 		return -ENOMEM; | 
 | 	} | 
 | 	optcd_disk->major = MAJOR_NR; | 
 | 	optcd_disk->first_minor = 0; | 
 | 	optcd_disk->fops = &opt_fops; | 
 | 	sprintf(optcd_disk->disk_name, "optcd"); | 
 |  | 
 | 	if (!request_region(optcd_port, 4, "optcd")) { | 
 | 		printk(KERN_ERR "optcd: conflict, I/O port 0x%x already used\n", | 
 | 			optcd_port); | 
 | 		put_disk(optcd_disk); | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 | 	if (!reset_drive()) { | 
 | 		printk(KERN_ERR "optcd: drive at 0x%x not ready\n", optcd_port); | 
 | 		release_region(optcd_port, 4); | 
 | 		put_disk(optcd_disk); | 
 | 		return -EIO; | 
 | 	} | 
 | 	if (!version_ok()) { | 
 | 		printk(KERN_ERR "optcd: unknown drive detected; aborting\n"); | 
 | 		release_region(optcd_port, 4); | 
 | 		put_disk(optcd_disk); | 
 | 		return -EIO; | 
 | 	} | 
 | 	status = exec_cmd(COMINITDOUBLE); | 
 | 	if (status < 0) { | 
 | 		printk(KERN_ERR "optcd: cannot init double speed mode\n"); | 
 | 		release_region(optcd_port, 4); | 
 | 		DEBUG((DEBUG_VFS, "exec_cmd COMINITDOUBLE: %02x", -status)); | 
 | 		put_disk(optcd_disk); | 
 | 		return -EIO; | 
 | 	} | 
 | 	if (register_blkdev(MAJOR_NR, "optcd")) { | 
 | 		release_region(optcd_port, 4); | 
 | 		put_disk(optcd_disk); | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 |  | 
 | 	opt_queue = blk_init_queue(do_optcd_request, &optcd_lock); | 
 | 	if (!opt_queue) { | 
 | 		unregister_blkdev(MAJOR_NR, "optcd"); | 
 | 		release_region(optcd_port, 4); | 
 | 		put_disk(optcd_disk); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	blk_queue_hardsect_size(opt_queue, 2048); | 
 | 	optcd_disk->queue = opt_queue; | 
 | 	add_disk(optcd_disk); | 
 |  | 
 | 	printk(KERN_INFO "optcd: DOLPHIN 8000 AT CDROM at 0x%x\n", optcd_port); | 
 | 	return 0; | 
 | } | 
 |  | 
 |  | 
 | static void __exit optcd_exit(void) | 
 | { | 
 | 	del_gendisk(optcd_disk); | 
 | 	put_disk(optcd_disk); | 
 | 	if (unregister_blkdev(MAJOR_NR, "optcd") == -EINVAL) { | 
 | 		printk(KERN_ERR "optcd: what's that: can't unregister\n"); | 
 | 		return; | 
 | 	} | 
 | 	blk_cleanup_queue(opt_queue); | 
 | 	release_region(optcd_port, 4); | 
 | 	printk(KERN_INFO "optcd: module released.\n"); | 
 | } | 
 |  | 
 | module_init(optcd_init); | 
 | module_exit(optcd_exit); | 
 |  | 
 | MODULE_LICENSE("GPL"); | 
 | MODULE_ALIAS_BLOCKDEV_MAJOR(OPTICS_CDROM_MAJOR); |