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