mirror of
https://github.com/torvalds/linux.git
synced 2024-12-23 03:11:46 +00:00
2107 lines
50 KiB
C
2107 lines
50 KiB
C
|
/* 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. */
|
|||
|
inline static 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 struct timer_list delay_timer = TIMER_INITIALIZER(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. */
|
|||
|
inline static 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. */
|
|||
|
inline static 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 */
|
|||
|
inline static int stdt_flags(void)
|
|||
|
{
|
|||
|
return inb(STATUS_PORT) & FL_STDT;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Fetch status that has previously been waited for. <0 means not available */
|
|||
|
inline static 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. */
|
|||
|
inline static 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 */
|
|||
|
inline static 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 */
|
|||
|
inline static 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. */
|
|||
|
inline static 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 */
|
|||
|
inline static 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 */
|
|||
|
inline static 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) */
|
|||
|
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
|
|||
|
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;
|
|||
|
|
|||
|
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 */
|
|||
|
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");
|
|||
|
sprintf(optcd_disk->devfs_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);
|