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