2005-04-17 02:20:36 +04:00
/* 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 0213 9 , 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 .4 a 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 ) ;
2006-12-07 07:37:06 +03:00
vsnprintf ( s , sizeof ( s ) , fmt , args ) ;
2005-04-17 02:20:36 +04:00
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. */
2005-07-27 22:46:09 +04:00
static inline int flag_low ( int flag , unsigned long timeout )
2005-04-17 02:20:36 +04:00
{
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 ) ;
2005-09-10 00:10:40 +04:00
static DEFINE_TIMER ( delay_timer , sleep_timer , 0 , 0 ) ;
2005-04-17 02:20:36 +04:00
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 . */
2005-07-27 22:46:09 +04:00
static inline int get_exec_status ( int busy_waiting )
2005-04-17 02:20:36 +04:00
{
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 . */
2005-07-27 22:46:09 +04:00
static inline int get_data ( int short_timeout )
2005-04-17 02:20:36 +04:00
{
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 */
2005-07-27 22:46:09 +04:00
static inline int stdt_flags ( void )
2005-04-17 02:20:36 +04:00
{
return inb ( STATUS_PORT ) & FL_STDT ;
}
/* Fetch status that has previously been waited for. <0 means not available */
2005-07-27 22:46:09 +04:00
static inline int fetch_status ( void )
2005-04-17 02:20:36 +04:00
{
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. */
2005-07-27 22:46:09 +04:00
static inline void fetch_data ( char * buf , int n )
2005-04-17 02:20:36 +04:00
{
insb ( DATA_PORT , buf , n ) ;
DEBUG ( ( DEBUG_DRIVE_IF , " fetched 0x%x bytes " , n ) ) ;
}
/* Flush status and data fifos */
2005-07-27 22:46:09 +04:00
static inline void flush_data ( void )
2005-04-17 02:20:36 +04:00
{
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 */
2005-07-27 22:46:09 +04:00
static inline int exec_cmd ( int cmd )
2005-04-17 02:20:36 +04:00
{
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 . */
2005-07-27 22:46:09 +04:00
static inline int exec_read_cmd ( int cmd , struct cdrom_msf * params )
2005-04-17 02:20:36 +04:00
{
int ack = send_cmd ( cmd ) ;
if ( ack < 0 )
return ack ;
return send_params ( params ) ;
}
/* Send a seek command with parameters and wait for response */
2005-07-27 22:46:09 +04:00
static inline int exec_seek_cmd ( int cmd , struct cdrom_msf * params )
2005-04-17 02:20:36 +04:00
{
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 */
2005-07-27 22:46:09 +04:00
static inline int exec_long_cmd ( int cmd , struct cdrom_msf * params )
2005-04-17 02:20:36 +04:00
{
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) */
2005-07-27 22:46:09 +04:00
static inline void single_bin2bcd ( u_char * p )
2005-04-17 02:20:36 +04:00
{
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 */
2005-07-27 22:46:09 +04:00
static inline u_char bcd2bin ( u_char bcd )
2005-04-17 02:20:36 +04:00
{
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 ;
2005-07-27 22:46:09 +04:00
static inline void opt_invalidate_buffers ( void )
2005-04-17 02:20:36 +04:00
{
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 ) ;