/* * The Mitsumi CDROM interface * Copyright (C) 1995 1996 Heiko Schlittermann * VERSION: 2.14(hs) * * ... anyway, I'm back again, thanks to Marcin, he adopted * large portions of my code (at least the parts containing * my main thoughts ...) * ****************** H E L P ********************************* * If you ever plan to update your CD ROM drive and perhaps * want to sell or simply give away your Mitsumi FX-001[DS] * -- Please -- * mail me (heiko@lotte.sax.de). When my last drive goes * ballistic no more driver support will be available from me! ************************************************************* * * 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; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * * Thanks to * The Linux Community at all and ... * Martin Harriss (he wrote the first Mitsumi Driver) * Eberhard Moenkeberg (he gave me much support and the initial kick) * Bernd Huebner, Ruediger Helsch (Unifix-Software GmbH, they * improved the original driver) * Jon Tombs, Bjorn Ekwall (module support) * Daniel v. Mosnenck (he sent me the Technical and Programming Reference) * Gerd Knorr (he lent me his PhotoCD) * Nils Faerber and Roger E. Wolff (extensively tested the LU portion) * Andreas Kies (testing the mysterious hang-ups) * Heiko Eissfeldt (VERIFY_READ/WRITE) * Marcin Dalecki (improved performance, shortened code) * ... somebody forgotten? * */ #if RCS static const char *mcdx_c_version = "$Id: mcdx.c,v 1.21 1997/01/26 07:12:59 davem Exp $"; #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAJOR_NR MITSUMI_X_CDROM_MAJOR #include /* for compatible parameter passing with "insmod" */ #define mcdx_drive_map mcdx #include #ifndef HZ #error HZ not defined #endif #define xwarn(fmt, args...) printk(KERN_WARNING MCDX " " fmt, ## args) #if !MCDX_QUIET #define xinfo(fmt, args...) printk(KERN_INFO MCDX " " fmt, ## args) #else #define xinfo(fmt, args...) { ; } #endif #if MCDX_DEBUG #define xtrace(lvl, fmt, args...) \ { if (lvl > 0) \ { printk(KERN_DEBUG MCDX ":: " fmt, ## args); } } #define xdebug(fmt, args...) printk(KERN_DEBUG MCDX ":: " fmt, ## args) #else #define xtrace(lvl, fmt, args...) { ; } #define xdebug(fmt, args...) { ; } #endif /* CONSTANTS *******************************************************/ /* Following are the number of sectors we _request_ from the drive every time an access outside the already requested range is done. The _direct_ size is the number of sectors we're allowed to skip directly (performing a read instead of requesting the new sector needed */ const int REQUEST_SIZE = 800; /* should be less then 255 * 4 */ const int DIRECT_SIZE = 400; /* should be less then REQUEST_SIZE */ const unsigned long ACLOSE_INHIBIT = 800; /* 1/100 s of autoclose inhibit */ enum drivemodes { TOC, DATA, RAW, COOKED }; enum datamodes { MODE0, MODE1, MODE2 }; enum resetmodes { SOFT, HARD }; const int SINGLE = 0x01; /* single speed drive (FX001S, LU) */ const int DOUBLE = 0x02; /* double speed drive (FX001D, ..? */ const int DOOR = 0x04; /* door locking capability */ const int MULTI = 0x08; /* multi session capability */ const unsigned char READ1X = 0xc0; const unsigned char READ2X = 0xc1; /* DECLARATIONS ****************************************************/ struct s_subqcode { unsigned char control; unsigned char tno; unsigned char index; struct cdrom_msf0 tt; struct cdrom_msf0 dt; }; struct s_diskinfo { unsigned int n_first; unsigned int n_last; struct cdrom_msf0 msf_leadout; struct cdrom_msf0 msf_first; }; struct s_multi { unsigned char multi; struct cdrom_msf0 msf_last; }; struct s_version { unsigned char code; unsigned char ver; }; /* Per drive/controller stuff **************************************/ struct s_drive_stuff { /* waitqueues */ struct wait_queue *busyq; struct wait_queue *lockq; struct wait_queue *sleepq; /* flags */ volatile int introk; /* status of last irq operation */ volatile int busy; /* drive performs an operation */ volatile int lock; /* exclusive usage */ int eject_sw; /* 1 - eject on last close (default 0) */ int autoclose; /* 1 - close the door on open (default 1) */ /* cd infos */ struct s_diskinfo di; struct s_multi multi; struct s_subqcode* toc; /* first entry of the toc array */ struct s_subqcode start; struct s_subqcode stop; int xa; /* 1 if xa disk */ int audio; /* 1 if audio disk */ int audiostatus; /* `buffer' control */ volatile int valid; /* pending, ..., values are valid */ volatile int pending; /* next sector to be read */ volatile int low_border; /* first sector not to be skipped direct */ volatile int high_border; /* first sector `out of area' */ #ifdef AK2 volatile int int_err; #endif /* AK2 */ /* adds and odds */ void* wreg_data; /* w data */ void* wreg_reset; /* w hardware reset */ void* wreg_hcon; /* w hardware conf */ void* wreg_chn; /* w channel */ void* rreg_data; /* r data */ void* rreg_status; /* r status */ int irq; /* irq used by this drive */ int minor; /* minor number of this drive */ int present; /* drive present and its capabilities */ unsigned char readcmd; /* read cmd depends on single/double speed */ unsigned char playcmd; /* play should always be single speed */ unsigned int xxx; /* set if changed, reset while open */ unsigned int yyy; /* set if changed, reset by media_changed */ unsigned long ejected; /* time we called the eject function */ int users; /* keeps track of open/close */ int lastsector; /* last block accessible */ int status; /* last operation's error / status */ int readerrs; /* # of blocks read w/o error */ }; /* Prototypes ******************************************************/ /* The following prototypes are already declared elsewhere. They are repeated here to show what's going on. And to sense, if they're changed elsewhere. */ /* declared in blk.h */ int mcdx_init(void); void do_mcdx_request(void); int check_mcdx_media_change(kdev_t); /* already declared in init/main */ void mcdx_setup(char *, int *); /* Indirect exported functions. These functions are exported by their addresses, such as mcdx_open and mcdx_close in the structure fops. */ /* ??? exported by the mcdx_sigaction struct */ static void mcdx_intr(int, void *, struct pt_regs*); /* exported by file_ops */ static int mcdx_open(struct inode*, struct file*); static int mcdx_close(struct inode*, struct file*); static int mcdx_ioctl(struct inode*, struct file*, unsigned int, unsigned long); /* misc internal support functions */ static void log2msf(unsigned int, struct cdrom_msf0*); static unsigned int msf2log(const struct cdrom_msf0*); static unsigned int uint2bcd(unsigned int); static unsigned int bcd2uint(unsigned char); static char *port(int*); static int irq(int*); static void mcdx_delay(struct s_drive_stuff*, long jifs); static int mcdx_transfer(struct s_drive_stuff*, char* buf, int sector, int nr_sectors); static int mcdx_xfer(struct s_drive_stuff*, char* buf, int sector, int nr_sectors); static int mcdx_config(struct s_drive_stuff*, int); static int mcdx_closedoor(struct s_drive_stuff*, int); static int mcdx_requestversion(struct s_drive_stuff*, struct s_version*, int); static int mcdx_lockdoor(struct s_drive_stuff*, int, int); static int mcdx_stop(struct s_drive_stuff*, int); static int mcdx_hold(struct s_drive_stuff*, int); static int mcdx_reset(struct s_drive_stuff*, enum resetmodes, int); static int mcdx_eject(struct s_drive_stuff*, int); static int mcdx_setdrivemode(struct s_drive_stuff*, enum drivemodes, int); static int mcdx_setdatamode(struct s_drive_stuff*, enum datamodes, int); static int mcdx_requestsubqcode(struct s_drive_stuff*, struct s_subqcode*, int); static int mcdx_requestmultidiskinfo(struct s_drive_stuff*, struct s_multi*, int); static int mcdx_requesttocdata(struct s_drive_stuff*, struct s_diskinfo*, int); static int mcdx_getstatus(struct s_drive_stuff*, int); static int mcdx_getval(struct s_drive_stuff*, int to, int delay, char*); static int mcdx_talk(struct s_drive_stuff*, const unsigned char* cmd, size_t, void *buffer, size_t size, unsigned int timeout, int); static int mcdx_readtoc(struct s_drive_stuff*); static int mcdx_playtrk(struct s_drive_stuff*, const struct cdrom_ti*); static int mcdx_playmsf(struct s_drive_stuff*, const struct cdrom_msf*); static int mcdx_setattentuator(struct s_drive_stuff*, struct cdrom_volctrl*, int); /* static variables ************************************************/ static int mcdx_blocksizes[MCDX_NDRIVES]; static int mcdx_drive_map[][2] = MCDX_DRIVEMAP; static struct s_drive_stuff* mcdx_stuffp[MCDX_NDRIVES]; static struct s_drive_stuff* mcdx_irq_map[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; MODULE_PARM(mcdx, "1-4i"); static struct file_operations mcdx_fops = { NULL, /* lseek - use kernel default */ block_read, /* read - general block-dev read */ block_write, /* write - general block-dev write */ NULL, /* no readdir */ NULL, /* no poll */ mcdx_ioctl, /* ioctl() */ NULL, /* no mmap */ mcdx_open, /* open() */ mcdx_close, /* close() */ NULL, /* fsync */ NULL, /* fasync */ check_mcdx_media_change, /* media_change */ NULL /* revalidate */ }; /* KERNEL INTERFACE FUNCTIONS **************************************/ static int mcdx_ioctl( struct inode* ip, struct file* fp, unsigned int cmd, unsigned long arg) { struct s_drive_stuff *stuffp = mcdx_stuffp[MINOR(ip->i_rdev)]; if (!stuffp->present) return -ENXIO; if (!ip) return -EINVAL; switch (cmd) { case CDROMSTART: { xtrace(IOCTL, "ioctl() START\n"); return 0; } case CDROMSTOP: { xtrace(IOCTL, "ioctl() STOP\n"); stuffp->audiostatus = CDROM_AUDIO_INVALID; if (-1 == mcdx_stop(stuffp, 1)) return -EIO; return 0; } case CDROMPLAYTRKIND: { int ans; struct cdrom_ti ti; xtrace(IOCTL, "ioctl() PLAYTRKIND\n"); if ((ans = verify_area(VERIFY_READ, (void*) arg, sizeof(ti)))) return ans; copy_from_user(&ti, (void*) arg, sizeof(ti)); if ((ti.cdti_trk0 < stuffp->di.n_first) || (ti.cdti_trk0 > stuffp->di.n_last) || (ti.cdti_trk1 < stuffp->di.n_first)) return -EINVAL; if (ti.cdti_trk1 > stuffp->di.n_last) ti.cdti_trk1 = stuffp->di.n_last; xtrace(PLAYTRK, "ioctl() track %d to %d\n", ti.cdti_trk0, ti.cdti_trk1); return mcdx_playtrk(stuffp, &ti); } case CDROMPLAYMSF: { int ans; struct cdrom_msf msf; xtrace(IOCTL, "ioctl() PLAYMSF\n"); if ((stuffp->audiostatus == CDROM_AUDIO_PLAY) && (-1 == mcdx_hold(stuffp, 1))) return -EIO; if ((ans = verify_area( VERIFY_READ, (void*) arg, sizeof(struct cdrom_msf)))) return ans; copy_from_user(&msf, (void*) arg, sizeof msf); msf.cdmsf_min0 = uint2bcd(msf.cdmsf_min0); msf.cdmsf_sec0 = uint2bcd(msf.cdmsf_sec0); msf.cdmsf_frame0 = uint2bcd(msf.cdmsf_frame0); msf.cdmsf_min1 = uint2bcd(msf.cdmsf_min1); msf.cdmsf_sec1 = uint2bcd(msf.cdmsf_sec1); msf.cdmsf_frame1 = uint2bcd(msf.cdmsf_frame1); stuffp->stop.dt.minute = msf.cdmsf_min1; stuffp->stop.dt.second = msf.cdmsf_sec1; stuffp->stop.dt.frame = msf.cdmsf_frame1; return mcdx_playmsf(stuffp, &msf); } case CDROMRESUME: { xtrace(IOCTL, "ioctl() RESUME\n"); return mcdx_playtrk(stuffp, NULL); } case CDROMREADTOCENTRY: { struct cdrom_tocentry entry; struct s_subqcode *tp = NULL; int ans; xtrace(IOCTL, "ioctl() READTOCENTRY\n"); if (-1 == mcdx_readtoc(stuffp)) return -1; if ((ans = verify_area(VERIFY_WRITE, (void *) arg, sizeof(entry)))) return ans; copy_from_user(&entry, (void *) arg, sizeof(entry)); if (entry.cdte_track == CDROM_LEADOUT) tp = &stuffp->toc[stuffp->di.n_last - stuffp->di.n_first + 1]; else if (entry.cdte_track > stuffp->di.n_last || entry.cdte_track < stuffp->di.n_first) return -EINVAL; else tp = &stuffp->toc[entry.cdte_track - stuffp->di.n_first]; if (NULL == tp) xwarn("FATAL.\n"); entry.cdte_adr = tp->control; entry.cdte_ctrl = tp->control >> 4; if (entry.cdte_format == CDROM_MSF) { entry.cdte_addr.msf.minute = bcd2uint(tp->dt.minute); entry.cdte_addr.msf.second = bcd2uint(tp->dt.second); entry.cdte_addr.msf.frame = bcd2uint(tp->dt.frame); } else if (entry.cdte_format == CDROM_LBA) entry.cdte_addr.lba = msf2log(&tp->dt); else return -EINVAL; copy_to_user((void*) arg, &entry, sizeof(entry)); return 0; } case CDROMSUBCHNL: { int ans; struct cdrom_subchnl sub; struct s_subqcode q; xtrace(IOCTL, "ioctl() SUBCHNL\n"); if ((ans = verify_area(VERIFY_WRITE, (void*) arg, sizeof(sub)))) return ans; copy_from_user(&sub, (void*) arg, sizeof(sub)); if (-1 == mcdx_requestsubqcode(stuffp, &q, 2)) return -EIO; xtrace(SUBCHNL, "audiostatus: %x\n", stuffp->audiostatus); sub.cdsc_audiostatus = stuffp->audiostatus; sub.cdsc_adr = q.control; sub.cdsc_ctrl = q.control >> 4; sub.cdsc_trk = bcd2uint(q.tno); sub.cdsc_ind = bcd2uint(q.index); xtrace(SUBCHNL, "trk %d, ind %d\n", sub.cdsc_trk, sub.cdsc_ind); if (sub.cdsc_format == CDROM_LBA) { sub.cdsc_absaddr.lba = msf2log(&q.dt); sub.cdsc_reladdr.lba = msf2log(&q.tt); xtrace(SUBCHNL, "lba: abs %d, rel %d\n", sub.cdsc_absaddr.lba, sub.cdsc_reladdr.lba); } else if (sub.cdsc_format == CDROM_MSF) { sub.cdsc_absaddr.msf.minute = bcd2uint(q.dt.minute); sub.cdsc_absaddr.msf.second = bcd2uint(q.dt.second); sub.cdsc_absaddr.msf.frame = bcd2uint(q.dt.frame); sub.cdsc_reladdr.msf.minute = bcd2uint(q.tt.minute); sub.cdsc_reladdr.msf.second = bcd2uint(q.tt.second); sub.cdsc_reladdr.msf.frame = bcd2uint(q.tt.frame); xtrace(SUBCHNL, "msf: abs %02d:%02d:%02d, rel %02d:%02d:%02d\n", sub.cdsc_absaddr.msf.minute, sub.cdsc_absaddr.msf.second, sub.cdsc_absaddr.msf.frame, sub.cdsc_reladdr.msf.minute, sub.cdsc_reladdr.msf.second, sub.cdsc_reladdr.msf.frame); } else return -EINVAL; copy_to_user((void*) arg, &sub, sizeof(sub)); return 0; } case CDROMREADTOCHDR: { struct cdrom_tochdr toc; int ans; xtrace(IOCTL, "ioctl() READTOCHDR\n"); if ((ans = verify_area(VERIFY_WRITE, (void*) arg, sizeof toc))) return ans; toc.cdth_trk0 = stuffp->di.n_first; toc.cdth_trk1 = stuffp->di.n_last; copy_to_user((void*) arg, &toc, sizeof toc); xtrace(TOCHDR, "ioctl() track0 = %d, track1 = %d\n", stuffp->di.n_first, stuffp->di.n_last); return 0; } case CDROMPAUSE: { xtrace(IOCTL, "ioctl() PAUSE\n"); if (stuffp->audiostatus != CDROM_AUDIO_PLAY) return -EINVAL; if (-1 == mcdx_stop(stuffp, 1)) return -EIO; stuffp->audiostatus = CDROM_AUDIO_PAUSED; if (-1 == mcdx_requestsubqcode(stuffp, &stuffp->start, 1)) return -EIO; return 0; } case CDROMMULTISESSION: { int ans; struct cdrom_multisession ms; xtrace(IOCTL, "ioctl() MULTISESSION\n"); if (0 != (ans = verify_area(VERIFY_WRITE, (void*) arg, sizeof(struct cdrom_multisession)))) return ans; copy_from_user(&ms, (void*) arg, sizeof(struct cdrom_multisession)); if (ms.addr_format == CDROM_MSF) { ms.addr.msf.minute = bcd2uint(stuffp->multi.msf_last.minute); ms.addr.msf.second = bcd2uint(stuffp->multi.msf_last.second); ms.addr.msf.frame = bcd2uint(stuffp->multi.msf_last.frame); } else if (ms.addr_format == CDROM_LBA) ms.addr.lba = msf2log(&stuffp->multi.msf_last); else return -EINVAL; ms.xa_flag = !!stuffp->multi.multi; copy_to_user((void*) arg, &ms, sizeof(struct cdrom_multisession)); if (ms.addr_format == CDROM_MSF) { xtrace(MS, "ioctl() (%d, %02x:%02x.%02x [%02x:%02x.%02x])\n", ms.xa_flag, ms.addr.msf.minute, ms.addr.msf.second, ms.addr.msf.frame, stuffp->multi.msf_last.minute, stuffp->multi.msf_last.second, stuffp->multi.msf_last.frame); } else { xtrace(MS, "ioctl() (%d, 0x%08x [%02x:%02x.%02x])\n", ms.xa_flag, ms.addr.lba, stuffp->multi.msf_last.minute, stuffp->multi.msf_last.second, stuffp->multi.msf_last.frame); } return 0; } case CDROMEJECT: { xtrace(IOCTL, "ioctl() EJECT\n"); if (stuffp->users > 1) return -EBUSY; if (-1 == mcdx_eject(stuffp, 1)) return -EIO; return 0; } case CDROMEJECT_SW: { stuffp->eject_sw = arg; return 0; } case CDROMVOLCTRL: { int ans; struct cdrom_volctrl volctrl; xtrace(IOCTL, "ioctl() VOLCTRL\n"); if ((ans = verify_area( VERIFY_READ, (void*) arg, sizeof(volctrl)))) return ans; copy_from_user(&volctrl, (char *) arg, sizeof(volctrl)); #if 0 /* not tested! */ /* adjust for the weirdness of workman (md) */ /* can't test it (hs) */ volctrl.channel2 = volctrl.channel1; volctrl.channel1 = volctrl.channel3 = 0x00; #endif return mcdx_setattentuator(stuffp, &volctrl, 2); } default: xwarn("ioctl(): unknown request 0x%04x\n", cmd); return -EINVAL; } } void do_mcdx_request() { int dev; struct s_drive_stuff *stuffp; again: if (CURRENT == NULL) { xtrace(REQUEST, "end_request(0): CURRENT == NULL\n"); return; } if (CURRENT->rq_status == RQ_INACTIVE) { xtrace(REQUEST, "end_request(0): rq_status == RQ_INACTIVE\n"); return; } INIT_REQUEST; dev = MINOR(CURRENT->rq_dev); stuffp = mcdx_stuffp[dev]; if ((dev < 0) || (dev >= MCDX_NDRIVES) || !stuffp || (!stuffp->present)) { xwarn("do_request(): bad device: %s\n", kdevname(CURRENT->rq_dev)); xtrace(REQUEST, "end_request(0): bad device\n"); end_request(0); return; } if (stuffp->audio) { xwarn("do_request() attempt to read from audio cd\n"); xtrace(REQUEST, "end_request(0): read from audio\n"); end_request(0); return; } xtrace(REQUEST, "do_request() (%lu + %lu)\n", CURRENT->sector, CURRENT->nr_sectors); switch (CURRENT->cmd) { case WRITE: xwarn("do_request(): attempt to write to cd!!\n"); xtrace(REQUEST, "end_request(0): write\n"); end_request(0); return; case READ: stuffp->status = 0; while (CURRENT->nr_sectors) { int i; i = mcdx_transfer(stuffp, CURRENT->buffer, CURRENT->sector, CURRENT->nr_sectors); if (i == -1) { end_request(0); goto again; } CURRENT->sector += i; CURRENT->nr_sectors -= i; CURRENT->buffer += (i * 512); } end_request(1); goto again; xtrace(REQUEST, "end_request(1)\n"); end_request(1); break; default: panic(MCDX "do_request: unknown command.\n"); break; } goto again; } static int mcdx_open(struct inode *ip, struct file *fp) /* actions done on open: * 1) get the drives status * 2) set the stuffp.readcmd if a CD is in. * (return no error if no CD is found, since ioctl() * needs an opened device */ { struct s_drive_stuff *stuffp; xtrace(OPENCLOSE, "open()\n"); stuffp = mcdx_stuffp[MINOR(ip->i_rdev)]; if (!stuffp->present) return -ENXIO; /* Make the modules looking used ... (thanx bjorn). * But we shouldn't forget to decrement the module counter * on error return */ MOD_INC_USE_COUNT; #if 0 /* We don't allow multiple users of a drive. In case of data CDs * they'll be used by mounting, which ensures anyway exclusive * usage. In case of audio CDs it's meaningless to try playing to * different tracks at once! (md) * - Hey, what about cat /dev/cdrom? Why shouldn't it called by * more then one process at any time? (hs) */ if (stuffp->users) { MOD_DEC_USE_COUNT; return -EBUSY; } #endif /* this is only done to test if the drive talks with us */ if (-1 == mcdx_getstatus(stuffp, 1)) { MOD_DEC_USE_COUNT; return -EIO; } /* close the door, * This should be explained ... * - If the door is open and its last close is too recent the * autoclose wouldn't probably be what we want. * - If we didn't try to close the door yet, close it and go on. * - If we autoclosed the door and couldn't succeed in find a valid * CD we shouldn't try autoclose any longer (until a valid CD is * in.) */ if (inb((unsigned int) stuffp->rreg_status) & MCDX_RBIT_DOOR) { if (jiffies - stuffp->ejected < ACLOSE_INHIBIT) { MOD_DEC_USE_COUNT; return -EIO; } if (stuffp->autoclose) mcdx_closedoor(stuffp, 1); else { MOD_DEC_USE_COUNT; return -EIO; } } /* if the media changed we will have to do a little more */ if (stuffp->xxx) { xtrace(OPENCLOSE, "open() media changed\n"); /* but wait - the time of media change will be set at the * very last of this block - it seems, some of the following * talk() will detect a media change ... (I think, config() * is the reason. */ stuffp->audiostatus = CDROM_AUDIO_INVALID; stuffp->readcmd = 0; /* get the multisession information */ xtrace(OPENCLOSE, "open() Request multisession info\n"); if (-1 == mcdx_requestmultidiskinfo( stuffp, &stuffp->multi, 6)) { xinfo("No multidiskinfo\n"); stuffp->autoclose = 0; /* MOD_DEC_USE_COUNT; stuffp->xxx = 0; return -EIO; */ } else { /* we succeeded, so on next open(2) we could try auto close * again */ stuffp->autoclose = 1; #if !MCDX_QUIET if (stuffp->multi.multi > 2) xinfo("open() unknown multisession value (%d)\n", stuffp->multi.multi); #endif /* multisession ? */ if (!stuffp->multi.multi) stuffp->multi.msf_last.second = 2; xtrace(OPENCLOSE, "open() MS: %d, last @ %02x:%02x.%02x\n", stuffp->multi.multi, stuffp->multi.msf_last.minute, stuffp->multi.msf_last.second, stuffp->multi.msf_last.frame); { ; } /* got multisession information */ /* request the disks table of contents (aka diskinfo) */ if (-1 == mcdx_requesttocdata(stuffp, &stuffp->di, 1)) { stuffp->lastsector = -1; } else { stuffp->lastsector = (CD_FRAMESIZE / 512) * msf2log(&stuffp->di.msf_leadout) - 1; xtrace(OPENCLOSE, "open() start %d (%02x:%02x.%02x) %d\n", stuffp->di.n_first, stuffp->di.msf_first.minute, stuffp->di.msf_first.second, stuffp->di.msf_first.frame, msf2log(&stuffp->di.msf_first)); xtrace(OPENCLOSE, "open() last %d (%02x:%02x.%02x) %d\n", stuffp->di.n_last, stuffp->di.msf_leadout.minute, stuffp->di.msf_leadout.second, stuffp->di.msf_leadout.frame, msf2log(&stuffp->di.msf_leadout)); } if (stuffp->toc) { xtrace(MALLOC, "open() free old toc @ %p\n", stuffp->toc); kfree(stuffp->toc); stuffp->toc = NULL; } xtrace(OPENCLOSE, "open() init irq generation\n"); if (-1 == mcdx_config(stuffp, 1)) { MOD_DEC_USE_COUNT; return -EIO; } #if FALLBACK /* Set the read speed */ xwarn("AAA %x AAA\n", stuffp->readcmd); if (stuffp->readerrs) stuffp->readcmd = READ1X; else stuffp->readcmd = stuffp->present | SINGLE ? READ1X : READ2X; xwarn("XXX %x XXX\n", stuffp->readcmd); #else stuffp->readcmd = stuffp->present | SINGLE ? READ1X : READ2X; #endif /* try to get the first sector, iff any ... */ if (stuffp->lastsector >= 0) { char buf[512]; int ans; int tries; stuffp->xa = 0; stuffp->audio = 0; for (tries = 6; tries; tries--) { stuffp->introk = 1; xtrace(OPENCLOSE, "open() try as %s\n", stuffp->xa ? "XA" : "normal"); /* set data mode */ if (-1 == (ans = mcdx_setdatamode(stuffp, stuffp->xa ? MODE2 : MODE1, 1))) { /* MOD_DEC_USE_COUNT, return -EIO; */ stuffp->xa = 0; break; } if ((stuffp->audio = e_audio(ans))) break; while (0 == (ans = mcdx_transfer(stuffp, buf, 0, 1))) ; if (ans == 1) break; stuffp->xa = !stuffp->xa; } /* if (!tries) MOD_DEC_USE_COUNT, return -EIO; */ } /* xa disks will be read in raw mode, others not */ if (-1 == mcdx_setdrivemode(stuffp, stuffp->xa ? RAW : COOKED, 1)) { MOD_DEC_USE_COUNT; return -EIO; } if (stuffp->audio) { xinfo("open() audio disk found\n"); } else if (stuffp->lastsector >= 0) { xinfo("open() %s%s disk found\n", stuffp->xa ? "XA / " : "", stuffp->multi.multi ? "Multi Session" : "Single Session"); } /* stuffp->xxx = 0; */ } /* lock the door if not already done */ if (0 == stuffp->users && (-1 == mcdx_lockdoor(stuffp, 1, 1))) { MOD_DEC_USE_COUNT; return -EIO; } } stuffp->xxx = 0; stuffp->users++; return 0; } static int mcdx_close(struct inode *ip, struct file *fp) { struct s_drive_stuff *stuffp; xtrace(OPENCLOSE, "close()\n"); stuffp = mcdx_stuffp[MINOR(ip->i_rdev)]; if (0 == --stuffp->users) { sync_dev(ip->i_rdev); /* needed for r/o device? */ /* invalidate_inodes(ip->i_rdev); */ invalidate_buffers(ip->i_rdev); #if !MCDX_QUIET if (-1 == mcdx_lockdoor(stuffp, 0, 3)) xinfo("close() Cannot unlock the door\n"); #else mcdx_lockdoor(stuffp, 0, 3); #endif /* eject if wished */ if (stuffp->eject_sw) mcdx_eject(stuffp, 1); } MOD_DEC_USE_COUNT; return 0; } int check_mcdx_media_change(kdev_t full_dev) /* Return: 1 if media changed since last call to this function 0 otherwise */ { struct s_drive_stuff *stuffp; xinfo("check_mcdx_media_change called for device %s\n", kdevname(full_dev)); stuffp = mcdx_stuffp[MINOR(full_dev)]; mcdx_getstatus(stuffp, 1); if (stuffp->yyy == 0) return 0; stuffp->yyy = 0; return 1; } __initfunc(void mcdx_setup(char *str, int *pi)) { if (pi[0] > 0) mcdx_drive_map[0][0] = pi[1]; if (pi[0] > 1) mcdx_drive_map[0][1] = pi[2]; } /* DIRTY PART ******************************************************/ static void mcdx_delay(struct s_drive_stuff *stuff, long jifs) /* This routine is used for sleeping. * A jifs value <0 means NO sleeping, * =0 means minimal sleeping (let the kernel * run for other processes) * >0 means at least sleep for that amount. * May be we could use a simple count loop w/ jumps to itself, but * I wanna make this independent of cpu speed. [1 jiffy is 1/HZ] sec */ { unsigned long tout = jiffies + jifs; if (jifs < 0) return; /* If loaded during kernel boot no *_sleep_on is * allowed! */ if (current->pid == 0) { while (jiffies < tout) { current->timeout = jiffies; schedule(); } } else { current->timeout = tout; xtrace(SLEEP, "*** delay: sleepq\n"); interruptible_sleep_on(&stuff->sleepq); xtrace(SLEEP, "delay awoken\n"); if (current->signal & ~current->blocked) { xtrace(SLEEP, "got signal\n"); } } } static void mcdx_intr(int irq, void *dev_id, struct pt_regs* regs) { struct s_drive_stuff *stuffp; unsigned char b; stuffp = mcdx_irq_map[irq]; if (stuffp == NULL ) { xwarn("mcdx: no device for intr %d\n", irq); return; } #ifdef AK2 if ( !stuffp->busy && stuffp->pending ) stuffp->int_err = 1; #endif /* AK2 */ /* get the interrupt status */ b = inb((unsigned int) stuffp->rreg_status); stuffp->introk = ~b & MCDX_RBIT_DTEN; /* NOTE: We only should get interrupts if the data we * requested are ready to transfer. * But the drive seems to generate ``asynchronous'' interrupts * on several error conditions too. (Despite the err int enable * setting during initialisation) */ /* if not ok, read the next byte as the drives status */ if (!stuffp->introk) { xtrace(IRQ, "intr() irq %d hw status 0x%02x\n", irq, b); if (~b & MCDX_RBIT_STEN) { xinfo( "intr() irq %d status 0x%02x\n", irq, inb((unsigned int) stuffp->rreg_data)); } else { xinfo( "intr() irq %d ambiguous hw status\n", irq); } } else { xtrace(IRQ, "irq() irq %d ok, status %02x\n", irq, b); } stuffp->busy = 0; wake_up_interruptible(&stuffp->busyq); } static int mcdx_talk ( struct s_drive_stuff *stuffp, const unsigned char *cmd, size_t cmdlen, void *buffer, size_t size, unsigned int timeout, int tries) /* Send a command to the drive, wait for the result. * returns -1 on timeout, drive status otherwise * If buffer is not zero, the result (length size) is stored there. * If buffer is zero the size should be the number of bytes to read * from the drive. These bytes are discarded. */ { int st; char c; int discard; /* Somebody wants the data read? */ if ((discard = (buffer == NULL))) buffer = &c; while (stuffp->lock) { xtrace(SLEEP, "*** talk: lockq\n"); interruptible_sleep_on(&stuffp->lockq); xtrace(SLEEP, "talk: awoken\n"); } stuffp->lock = 1; /* An operation other then reading data destroys the * data already requested and remembered in stuffp->request, ... */ stuffp->valid = 0; #if MCDX_DEBUG & TALK { unsigned char i; xtrace(TALK, "talk() %d / %d tries, res.size %d, command 0x%02x", tries, timeout, size, (unsigned char) cmd[0]); for (i = 1; i < cmdlen; i++) xtrace(TALK, " 0x%02x", cmd[i]); xtrace(TALK, "\n"); } #endif /* give up if all tries are done (bad) or if the status * st != -1 (good) */ for (st = -1; st == -1 && tries; tries--) { char *bp = (char*) buffer; size_t sz = size; outsb((unsigned int) stuffp->wreg_data, cmd, cmdlen); xtrace(TALK, "talk() command sent\n"); /* get the status byte */ if (-1 == mcdx_getval(stuffp, timeout, 0, bp)) { xinfo("talk() %02x timed out (status), %d tr%s left\n", cmd[0], tries - 1, tries == 2 ? "y" : "ies"); continue; } st = *bp; sz--; if (!discard) bp++; xtrace(TALK, "talk() got status 0x%02x\n", st); /* command error? */ if (e_cmderr(st)) { xwarn("command error cmd = %02x %s \n", cmd[0], cmdlen > 1 ? "..." : ""); st = -1; continue; } /* audio status? */ if (stuffp->audiostatus == CDROM_AUDIO_INVALID) stuffp->audiostatus = e_audiobusy(st) ? CDROM_AUDIO_PLAY : CDROM_AUDIO_NO_STATUS; else if (stuffp->audiostatus == CDROM_AUDIO_PLAY && e_audiobusy(st) == 0) stuffp->audiostatus = CDROM_AUDIO_COMPLETED; /* media change? */ if (e_changed(st)) { xinfo("talk() media changed\n"); stuffp->xxx = stuffp->yyy = 1; } /* now actually get the data */ while (sz--) { if (-1 == mcdx_getval(stuffp, timeout, 0, bp)) { xinfo("talk() %02x timed out (data), %d tr%s left\n", cmd[0], tries - 1, tries == 2 ? "y" : "ies"); st = -1; break; } if (!discard) bp++; xtrace(TALK, "talk() got 0x%02x\n", *(bp - 1)); } } #if !MCDX_QUIET if (!tries && st == -1) xinfo("talk() giving up\n"); #endif stuffp->lock = 0; wake_up_interruptible(&stuffp->lockq); xtrace(TALK, "talk() done with 0x%02x\n", st); return st; } /* MODULE STUFF ***********************************************************/ #ifdef MODULE EXPORT_NO_SYMBOLS; int init_module(void) { int i; int drives = 0; mcdx_init(); for (i = 0; i < MCDX_NDRIVES; i++) { if (mcdx_stuffp[i]) { xtrace(INIT, "init_module() drive %d stuff @ %p\n", i, mcdx_stuffp[i]); drives++; } } if (!drives) return -EIO; return 0; } void cleanup_module(void) { int i; xinfo("cleanup_module called\n"); for (i = 0; i < MCDX_NDRIVES; i++) { struct s_drive_stuff *stuffp; stuffp = mcdx_stuffp[i]; if (!stuffp) continue; release_region((unsigned long) stuffp->wreg_data, MCDX_IO_SIZE); free_irq(stuffp->irq, NULL); if (stuffp->toc) { xtrace(MALLOC, "cleanup_module() free toc @ %p\n", stuffp->toc); kfree(stuffp->toc); } xtrace(MALLOC, "cleanup_module() free stuffp @ %p\n", stuffp); mcdx_stuffp[i] = NULL; kfree(stuffp); } if (unregister_blkdev(MAJOR_NR, DEVICE_NAME) != 0) { xwarn("cleanup() unregister_blkdev() failed\n"); } #if !MCDX_QUIET else xinfo("cleanup() succeeded\n"); #endif } #endif MODULE /* Support functions ************************************************/ __initfunc(int mcdx_init(void)) { int drive; #ifdef MODULE xwarn("Version 2.14(hs) for " UTS_RELEASE "\n"); #else xwarn("Version 2.14(hs) \n"); #endif xwarn("$Id: mcdx.c,v 1.21 1997/01/26 07:12:59 davem Exp $\n"); /* zero the pointer array */ for (drive = 0; drive < MCDX_NDRIVES; drive++) mcdx_stuffp[drive] = NULL; /* do the initialisation */ for (drive = 0; drive < MCDX_NDRIVES; drive++) { struct s_version version; struct s_drive_stuff* stuffp; int size; mcdx_blocksizes[drive] = 0; size = sizeof(*stuffp); xtrace(INIT, "init() try drive %d\n", drive); xtrace(INIT, "kmalloc space for stuffpt's\n"); xtrace(MALLOC, "init() malloc %d bytes\n", size); if (!(stuffp = kmalloc(size, GFP_KERNEL))) { xwarn("init() malloc failed\n"); break; } xtrace(INIT, "init() got %d bytes for drive stuff @ %p\n", sizeof(*stuffp), stuffp); /* set default values */ memset(stuffp, 0, sizeof(*stuffp)); stuffp->autoclose = 1; /* close the door on open(2) */ stuffp->present = 0; /* this should be 0 already */ stuffp->toc = NULL; /* this should be NULL already */ /* setup our irq and i/o addresses */ stuffp->irq = irq(mcdx_drive_map[drive]); stuffp->wreg_data = stuffp->rreg_data = port(mcdx_drive_map[drive]); stuffp->wreg_reset = stuffp->rreg_status = stuffp->wreg_data + 1; stuffp->wreg_hcon = stuffp->wreg_reset + 1; stuffp->wreg_chn = stuffp->wreg_hcon + 1; /* check if i/o addresses are available */ if (0 != check_region((unsigned int) stuffp->wreg_data, MCDX_IO_SIZE)) { xwarn("0x%3p,%d: " "Init failed. I/O ports (0x%3p..0x%3p) already in use.\n", stuffp->wreg_data, stuffp->irq, stuffp->wreg_data, stuffp->wreg_data + MCDX_IO_SIZE - 1); xtrace(MALLOC, "init() free stuffp @ %p\n", stuffp); kfree(stuffp); xtrace(INIT, "init() continue at next drive\n"); continue; /* next drive */ } xtrace(INIT, "init() i/o port is available at 0x%3p\n", stuffp->wreg_data); xtrace(INIT, "init() hardware reset\n"); mcdx_reset(stuffp, HARD, 1); xtrace(INIT, "init() get version\n"); if (-1 == mcdx_requestversion(stuffp, &version, 4)) { /* failed, next drive */ xwarn("%s=0x%3p,%d: Init failed. Can't get version.\n", MCDX, stuffp->wreg_data, stuffp->irq); xtrace(MALLOC, "init() free stuffp @ %p\n", stuffp); kfree(stuffp); xtrace(INIT, "init() continue at next drive\n"); continue; } switch (version.code) { case 'D': stuffp->readcmd = READ2X; stuffp->present = DOUBLE | DOOR | MULTI; break; case 'F': stuffp->readcmd = READ1X; stuffp->present = SINGLE | DOOR | MULTI; break; case 'M': stuffp->readcmd = READ1X; stuffp->present = SINGLE; break; default: stuffp->present = 0; break; } stuffp->playcmd = READ1X; if (!stuffp->present) { xwarn("%s=0x%3p,%d: Init failed. No Mitsumi CD-ROM?.\n", MCDX, stuffp->wreg_data, stuffp->irq); kfree(stuffp); continue; /* next drive */ } xtrace(INIT, "init() register blkdev\n"); if (register_blkdev(MAJOR_NR, DEVICE_NAME, &mcdx_fops) != 0) { xwarn("%s=0x%3p,%d: Init failed. Can't get major %d.\n", MCDX, stuffp->wreg_data, stuffp->irq, MAJOR_NR); kfree(stuffp); continue; /* next drive */ } blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; read_ahead[MAJOR_NR] = READ_AHEAD; blksize_size[MAJOR_NR] = mcdx_blocksizes; xtrace(INIT, "init() subscribe irq and i/o\n"); mcdx_irq_map[stuffp->irq] = stuffp; if (request_irq(stuffp->irq, mcdx_intr, SA_INTERRUPT, DEVICE_NAME, NULL)) { xwarn("%s=0x%3p,%d: Init failed. Can't get irq (%d).\n", MCDX, stuffp->wreg_data, stuffp->irq, stuffp->irq); stuffp->irq = 0; kfree(stuffp); continue; } request_region((unsigned int) stuffp->wreg_data, MCDX_IO_SIZE, DEVICE_NAME); xtrace(INIT, "init() get garbage\n"); { int i; mcdx_delay(stuffp, HZ/2); for (i = 100; i; i--) (void) inb((unsigned int) stuffp->rreg_status); } #if WE_KNOW_WHY outb(0x50, (unsigned int) stuffp->wreg_chn); /* irq 11 -> channel register */ #endif xtrace(INIT, "init() set non dma but irq mode\n"); mcdx_config(stuffp, 1); stuffp->minor = drive; xwarn("(%s) installed at 0x%3p, irq %d." " (Firmware version %c %x)\n", DEVICE_NAME, stuffp->wreg_data, stuffp->irq, version.code, version.ver); mcdx_stuffp[drive] = stuffp; xtrace(INIT, "init() mcdx_stuffp[%d] = %p\n", drive, stuffp); } return 0; } static int mcdx_transfer(struct s_drive_stuff *stuffp, char *p, int sector, int nr_sectors) /* This seems to do the actually transfer. But it does more. It keeps track of errors occurred and will (if possible) fall back to single speed on error. Return: -1 on timeout or other error else status byte (as in stuff->st) */ { int ans; ans = mcdx_xfer(stuffp, p, sector, nr_sectors); return ans; #if FALLBACK if (-1 == ans) stuffp->readerrs++; else return ans; if (stuffp->readerrs && stuffp->readcmd == READ1X) { xwarn("XXX Already reading 1x -- no chance\n"); return -1; } xwarn("XXX Fallback to 1x\n"); stuffp->readcmd = READ1X; return mcdx_transfer(stuffp, p, sector, nr_sectors); #endif } static int mcdx_xfer(struct s_drive_stuff *stuffp, char *p, int sector, int nr_sectors) /* This does actually the transfer from the drive. Return: -1 on timeout or other error else status byte (as in stuff->st) */ { int border; int done = 0; if (stuffp->audio) { xwarn("Attempt to read from audio CD.\n"); return -1; } if (!stuffp->readcmd) { xinfo("Can't transfer from missing disk.\n"); return -1; } while (stuffp->lock) { interruptible_sleep_on(&stuffp->lockq); } if (stuffp->valid && (sector >= stuffp->pending) && (sector < stuffp->low_border)) { /* All (or at least a part of the sectors requested) seems * to be already requested, so we don't need to bother the * drive with new requests ... * Wait for the drive become idle, but first * check for possible occurred errors --- the drive * seems to report them asynchronously */ border = stuffp->high_border < (border = sector + nr_sectors) ? stuffp->high_border : border; stuffp->lock = current->pid; do { current->timeout = jiffies + 5 * HZ; while (stuffp->busy) { interruptible_sleep_on(&stuffp->busyq); if (!stuffp->introk) { xtrace(XFER, "error via interrupt\n"); } else if (current->timeout == 0) { xtrace(XFER, "timeout\n"); } else if (current->signal & ~current->blocked) { xtrace(XFER, "signal\n"); } else continue; stuffp->lock = 0; stuffp->busy = 0; stuffp->valid = 0; wake_up_interruptible(&stuffp->lockq); xtrace(XFER, "transfer() done (-1)\n"); return -1; } /* check if we need to set the busy flag (as we * expect an interrupt */ stuffp->busy = (3 == (stuffp->pending & 3)); /* Test if it's the first sector of a block, * there we have to skip some bytes as we read raw data */ if (stuffp->xa && (0 == (stuffp->pending & 3))) { const int HEAD = CD_FRAMESIZE_RAW - CD_XA_TAIL - CD_FRAMESIZE; insb((unsigned int) stuffp->rreg_data, p, HEAD); } /* now actually read the data */ insb((unsigned int) stuffp->rreg_data, p, 512); /* test if it's the last sector of a block, * if so, we have to handle XA special */ if ((3 == (stuffp->pending & 3)) && stuffp->xa) { char dummy[CD_XA_TAIL]; insb((unsigned int) stuffp->rreg_data, &dummy[0], CD_XA_TAIL); } if (stuffp->pending == sector) { p += 512; done++; sector++; } } while (++(stuffp->pending) < border); stuffp->lock = 0; wake_up_interruptible(&stuffp->lockq); } else { /* The requested sector(s) is/are out of the * already requested range, so we have to bother the drive * with a new request. */ static unsigned char cmd[] = { 0, 0, 0, 0, 0, 0, 0 }; cmd[0] = stuffp->readcmd; /* The numbers held in ->pending, ..., should be valid */ stuffp->valid = 1; stuffp->pending = sector & ~3; /* do some sanity checks */ if (stuffp->pending > stuffp->lastsector) { xwarn("transfer() sector %d from nirvana requested.\n", stuffp->pending); stuffp->status = MCDX_ST_EOM; stuffp->valid = 0; xtrace(XFER, "transfer() done (-1)\n"); return -1; } if ((stuffp->low_border = stuffp->pending + DIRECT_SIZE) > stuffp->lastsector + 1) { xtrace(XFER, "cut low_border\n"); stuffp->low_border = stuffp->lastsector + 1; } if ((stuffp->high_border = stuffp->pending + REQUEST_SIZE) > stuffp->lastsector + 1) { xtrace(XFER, "cut high_border\n"); stuffp->high_border = stuffp->lastsector + 1; } { /* Convert the sector to be requested to MSF format */ struct cdrom_msf0 pending; log2msf(stuffp->pending / 4, &pending); cmd[1] = pending.minute; cmd[2] = pending.second; cmd[3] = pending.frame; } cmd[6] = (unsigned char) ((stuffp->high_border - stuffp->pending) / 4); xtrace(XFER, "[%2d]\n", cmd[6]); stuffp->busy = 1; /* Now really issue the request command */ outsb((unsigned int) stuffp->wreg_data, cmd, sizeof cmd); } #ifdef AK2 if ( stuffp->int_err ) { stuffp->valid = 0; stuffp->int_err = 0; return -1; } #endif /* AK2 */ stuffp->low_border = (stuffp->low_border += done) < stuffp->high_border ? stuffp->low_border : stuffp->high_border; return done; } /* Access to elements of the mcdx_drive_map members */ static char* port(int *ip) { return (char*) ip[0]; } static int irq(int *ip) { return ip[1]; } /* Misc number converters */ static unsigned int bcd2uint(unsigned char c) { return (c >> 4) * 10 + (c & 0x0f); } static unsigned int uint2bcd(unsigned int ival) { return ((ival / 10) << 4) | (ival % 10); } static void log2msf(unsigned int l, struct cdrom_msf0* pmsf) { l += CD_BLOCK_OFFSET; pmsf->minute = uint2bcd(l / 4500), l %= 4500; pmsf->second = uint2bcd(l / 75); pmsf->frame = uint2bcd(l % 75); } static unsigned int msf2log(const struct cdrom_msf0* pmsf) { return bcd2uint(pmsf->frame) + bcd2uint(pmsf->second) * 75 + bcd2uint(pmsf->minute) * 4500 - CD_BLOCK_OFFSET; } int mcdx_readtoc(struct s_drive_stuff* stuffp) /* Read the toc entries from the CD, * Return: -1 on failure, else 0 */ { if (stuffp->toc) { xtrace(READTOC, "ioctl() toc already read\n"); return 0; } xtrace(READTOC, "ioctl() readtoc for %d tracks\n", stuffp->di.n_last - stuffp->di.n_first + 1); if (-1 == mcdx_hold(stuffp, 1)) return -1; xtrace(READTOC, "ioctl() tocmode\n"); if (-1 == mcdx_setdrivemode(stuffp, TOC, 1)) return -EIO; /* all seems to be ok so far ... malloc */ { int size; size = sizeof(struct s_subqcode) * (stuffp->di.n_last - stuffp->di.n_first + 2); xtrace(MALLOC, "ioctl() malloc %d bytes\n", size); stuffp->toc = kmalloc(size, GFP_KERNEL); if (!stuffp->toc) { xwarn("Cannot malloc %d bytes for toc\n", size); mcdx_setdrivemode(stuffp, DATA, 1); return -EIO; } } /* now read actually the index */ { int trk; int retries; for (trk = 0; trk < (stuffp->di.n_last - stuffp->di.n_first + 1); trk++) stuffp->toc[trk].index = 0; for (retries = 300; retries; retries--) { /* why 300? */ struct s_subqcode q; unsigned int idx; if (-1 == mcdx_requestsubqcode(stuffp, &q, 1)) { mcdx_setdrivemode(stuffp, DATA, 1); return -EIO; } idx = bcd2uint(q.index); if ((idx > 0) && (idx <= stuffp->di.n_last) && (q.tno == 0) && (stuffp->toc[idx - stuffp->di.n_first].index == 0)) { stuffp->toc[idx - stuffp->di.n_first] = q; xtrace(READTOC, "ioctl() toc idx %d (trk %d)\n", idx, trk); trk--; } if (trk == 0) break; } memset(&stuffp->toc[stuffp->di.n_last - stuffp->di.n_first + 1], 0, sizeof(stuffp->toc[0])); stuffp->toc[stuffp->di.n_last - stuffp->di.n_first + 1].dt = stuffp->di.msf_leadout; } /* unset toc mode */ xtrace(READTOC, "ioctl() undo toc mode\n"); if (-1 == mcdx_setdrivemode(stuffp, DATA, 2)) return -EIO; #if MCDX_DEBUG && READTOC { int trk; for (trk = 0; trk < (stuffp->di.n_last - stuffp->di.n_first + 2); trk++) xtrace(READTOC, "ioctl() %d readtoc %02x %02x %02x" " %02x:%02x.%02x %02x:%02x.%02x\n", trk + stuffp->di.n_first, stuffp->toc[trk].control, stuffp->toc[trk].tno, stuffp->toc[trk].index, stuffp->toc[trk].tt.minute, stuffp->toc[trk].tt.second, stuffp->toc[trk].tt.frame, stuffp->toc[trk].dt.minute, stuffp->toc[trk].dt.second, stuffp->toc[trk].dt.frame); } #endif return 0; } static int mcdx_playmsf(struct s_drive_stuff* stuffp, const struct cdrom_msf* msf) { unsigned char cmd[7] = { 0, 0, 0, 0, 0, 0, 0 }; if (!stuffp->readcmd) { xinfo("Can't play from missing disk.\n"); return -1; } cmd[0] = stuffp->playcmd; cmd[1] = msf->cdmsf_min0; cmd[2] = msf->cdmsf_sec0; cmd[3] = msf->cdmsf_frame0; cmd[4] = msf->cdmsf_min1; cmd[5] = msf->cdmsf_sec1; cmd[6] = msf->cdmsf_frame1; xtrace(PLAYMSF, "ioctl(): play %x " "%02x:%02x:%02x -- %02x:%02x:%02x\n", cmd[0], cmd[1], cmd[2], cmd[3], cmd[4], cmd[5], cmd[6]); outsb((unsigned int) stuffp->wreg_data, cmd, sizeof cmd); if (-1 == mcdx_getval(stuffp, 3 * HZ, 0, NULL)) { xwarn("playmsf() timeout\n"); return -1; } stuffp->audiostatus = CDROM_AUDIO_PLAY; return 0; } static int mcdx_playtrk(struct s_drive_stuff* stuffp, const struct cdrom_ti* ti) { struct s_subqcode* p; struct cdrom_msf msf; if (-1 == mcdx_readtoc(stuffp)) return -1; if (ti) p = &stuffp->toc[ti->cdti_trk0 - stuffp->di.n_first]; else p = &stuffp->start; msf.cdmsf_min0 = p->dt.minute; msf.cdmsf_sec0 = p->dt.second; msf.cdmsf_frame0 = p->dt.frame; if (ti) { p = &stuffp->toc[ti->cdti_trk1 - stuffp->di.n_first + 1]; stuffp->stop = *p; } else p = &stuffp->stop; msf.cdmsf_min1 = p->dt.minute; msf.cdmsf_sec1 = p->dt.second; msf.cdmsf_frame1 = p->dt.frame; return mcdx_playmsf(stuffp, &msf); } /* Drive functions ************************************************/ static int mcdx_closedoor(struct s_drive_stuff *stuffp, int tries) { if (stuffp->present & DOOR) return mcdx_talk(stuffp, "\xf8", 1, NULL, 1, 5 * HZ, tries); else return 0; } static int mcdx_stop(struct s_drive_stuff *stuffp, int tries) { return mcdx_talk(stuffp, "\xf0", 1, NULL, 1, 2 * HZ, tries); } static int mcdx_hold(struct s_drive_stuff *stuffp, int tries) { return mcdx_talk(stuffp, "\x70", 1, NULL, 1, 2 * HZ, tries); } static int mcdx_eject(struct s_drive_stuff *stuffp, int tries) { if (stuffp->present & DOOR) { stuffp->ejected = jiffies; return mcdx_talk(stuffp, "\xf6", 1, NULL, 1, 5 * HZ, tries); } else return 0; } static int mcdx_requestsubqcode(struct s_drive_stuff *stuffp, struct s_subqcode *sub, int tries) { char buf[11]; int ans; if (-1 == (ans = mcdx_talk( stuffp, "\x20", 1, buf, sizeof(buf), 2 * HZ, tries))) return -1; sub->control = buf[1]; sub->tno = buf[2]; sub->index = buf[3]; sub->tt.minute = buf[4]; sub->tt.second = buf[5]; sub->tt.frame = buf[6]; sub->dt.minute = buf[8]; sub->dt.second = buf[9]; sub->dt.frame = buf[10]; return ans; } static int mcdx_requestmultidiskinfo(struct s_drive_stuff *stuffp, struct s_multi *multi, int tries) { char buf[5]; int ans; if (stuffp->present & MULTI) { ans = mcdx_talk(stuffp, "\x11", 1, buf, sizeof(buf), 2 * HZ, tries); multi->multi = buf[1]; multi->msf_last.minute = buf[2]; multi->msf_last.second = buf[3]; multi->msf_last.frame = buf[4]; return ans; } else { multi->multi = 0; return 0; } } static int mcdx_requesttocdata(struct s_drive_stuff *stuffp, struct s_diskinfo *info, int tries) { char buf[9]; int ans; ans = mcdx_talk(stuffp, "\x10", 1, buf, sizeof(buf), 2 * HZ, tries); if (ans == -1) { info->n_first = 0; info->n_last = 0; } else { info->n_first = bcd2uint(buf[1]); info->n_last = bcd2uint(buf[2]); info->msf_leadout.minute = buf[3]; info->msf_leadout.second = buf[4]; info->msf_leadout.frame = buf[5]; info->msf_first.minute = buf[6]; info->msf_first.second = buf[7]; info->msf_first.frame = buf[8]; } return ans; } static int mcdx_setdrivemode(struct s_drive_stuff *stuffp, enum drivemodes mode, int tries) { char cmd[2]; int ans; xtrace(HW, "setdrivemode() %d\n", mode); if (-1 == (ans = mcdx_talk(stuffp, "\xc2", 1, cmd, sizeof(cmd), 5 * HZ, tries))) return -1; switch (mode) { case TOC: cmd[1] |= 0x04; break; case DATA: cmd[1] &= ~0x04; break; case RAW: cmd[1] |= 0x40; break; case COOKED: cmd[1] &= ~0x40; break; default: break; } cmd[0] = 0x50; return mcdx_talk(stuffp, cmd, 2, NULL, 1, 5 * HZ, tries); } static int mcdx_setdatamode(struct s_drive_stuff *stuffp, enum datamodes mode, int tries) { unsigned char cmd[2] = { 0xa0 }; xtrace(HW, "setdatamode() %d\n", mode); switch (mode) { case MODE0: cmd[1] = 0x00; break; case MODE1: cmd[1] = 0x01; break; case MODE2: cmd[1] = 0x02; break; default: return -EINVAL; } return mcdx_talk(stuffp, cmd, 2, NULL, 1, 5 * HZ, tries); } static int mcdx_config(struct s_drive_stuff *stuffp, int tries) { char cmd[4]; xtrace(HW, "config()\n"); cmd[0] = 0x90; cmd[1] = 0x10; /* irq enable */ cmd[2] = 0x05; /* pre, err irq enable */ if (-1 == mcdx_talk(stuffp, cmd, 3, NULL, 1, 1 * HZ, tries)) return -1; cmd[1] = 0x02; /* dma select */ cmd[2] = 0x00; /* no dma */ return mcdx_talk(stuffp, cmd, 3, NULL, 1, 1 * HZ, tries); } static int mcdx_requestversion(struct s_drive_stuff *stuffp, struct s_version *ver, int tries) { char buf[3]; int ans; if (-1 == (ans = mcdx_talk(stuffp, "\xdc", 1, buf, sizeof(buf), 2 * HZ, tries))) return ans; ver->code = buf[1]; ver->ver = buf[2]; return ans; } static int mcdx_reset(struct s_drive_stuff *stuffp, enum resetmodes mode, int tries) { if (mode == HARD) { outb(0, (unsigned int) stuffp->wreg_chn); /* no dma, no irq -> hardware */ outb(0, (unsigned int) stuffp->wreg_reset); /* hw reset */ return 0; } else return mcdx_talk(stuffp, "\x60", 1, NULL, 1, 5 * HZ, tries); } static int mcdx_lockdoor(struct s_drive_stuff *stuffp, int lock, int tries) { char cmd[2] = { 0xfe }; if (stuffp->present & DOOR) { cmd[1] = lock ? 0x01 : 0x00; return mcdx_talk(stuffp, cmd, sizeof(cmd), NULL, 1, 5 * HZ, tries); } else return 0; } static int mcdx_getstatus(struct s_drive_stuff *stuffp, int tries) { return mcdx_talk(stuffp, "\x40", 1, NULL, 1, 5 * HZ, tries); } static int mcdx_getval(struct s_drive_stuff *stuffp, int to, int delay, char* buf) { unsigned long timeout = to + jiffies; char c; if (!buf) buf = &c; while (inb((unsigned int) stuffp->rreg_status) & MCDX_RBIT_STEN) { if (jiffies > timeout) return -1; mcdx_delay(stuffp, delay); } *buf = (unsigned char) inb((unsigned int) stuffp->rreg_data) & 0xff; return 0; } static int mcdx_setattentuator( struct s_drive_stuff* stuffp, struct cdrom_volctrl* vol, int tries) { char cmd[5]; cmd[0] = 0xae; cmd[1] = vol->channel0; cmd[2] = 0; cmd[3] = vol->channel1; cmd[4] = 0; return mcdx_talk(stuffp, cmd, sizeof(cmd), NULL, 5, 200, tries); } /* ex:set ts=4 sw=4 ai si: */