/* * sbpcd.c CD-ROM device driver for the whole family of IDE-style * Kotobuki/Matsushita/Panasonic CR-5xx drives for * SoundBlaster ("Pro" or "16 ASP" or compatible) cards * and for "no-sound" interfaces like Lasermate and the * Panasonic CI-101P. * * NOTE: This is release 1.2. * It works with my SbPro & drive CR-521 V2.11 from 2/92 * and with the new CR-562-B V0.75 on a "naked" Panasonic * CI-101P interface. And vice versa. * * * VERSION HISTORY * * 0.1 initial release, April/May 93, after mcd.c * * 0.2 the "repeat:"-loop in do_sbpcd_request did not check for * end-of-request_queue (resulting in kernel panic). * Flow control seems stable, but throughput is not better. * * 0.3 interrupt locking totally eliminated (maybe "inb" and "outb" * are still locking) - 0.2 made keyboard-type-ahead losses. * check_sbpcd_media_change added (to use by isofs/inode.c) * - but it detects almost nothing. * * 0.4 use MAJOR 25 definitely. * Almost total re-design to support double-speed drives and * "naked" (no sound) interface cards. * Flow control should be exact now (tell me if not). * Don't occupy the SbPro IRQ line (not needed either); will * live together with Hannu Savolainen's sndkit now. * Speeded up data transfer to 150 kB/sec, with help from Kai * Makisara, the "provider" of the "mt" tape utility. * Give "SpinUp" command if necessary. * First steps to support up to 4 drives (but currently only one). * Implemented audio capabilities - workman should work, xcdplayer * gives some problems. * This version is still consuming too much CPU time, and * sleeping still has to be worked on. * During "long" implied seeks, it seems possible that a * ReadStatus command gets ignored. That gives the message * "ResponseStatus timed out" (happens about 6 times here during * a "ls -alR" of the YGGDRASIL LGX-Beta CD). Such a case is * handled without data error, but it should get done better. * * 0.5 Free CPU during waits (again with help from Kai Makisara). * Made it work together with the LILO/kernel setup standard. * Included auto-probing code, as suggested by YGGDRASIL. * Formal redesign to add DDI debugging. * There are still flaws in IOCTL (workman with double speed drive). * * 1.0 Added support for all drive ids (0...3, no longer only 0) * and up to 4 drives on one controller. * Added "#define MANY_SESSION" for "old" multi session CDs. * * 1.1 Do SpinUp for new drives, too. * Revised for clean compile under "old" kernels (pl9). * * 1.2 Found the "workman with double-speed drive" bug: use the driver's * audio_state, not what the drive is reporting with ReadSubQ. * * * * special thanks to Kai Makisara (kai.makisara@vtt.fi) for his fine * elaborated speed-up experiments (and the fabulous results!), for * the "push" towards load-free wait loops, and for the extensive mail * thread which brought additional hints and bug fixes. * * * Copyright (C) 1993, 1994 Eberhard Moenkeberg * or * * The FTP-home of this driver is * ftp.gwdg.de:/pub/linux/cdrom/drivers/sbpcd/. * * If you change this software, you should mail a .diff * file with some description lines to emoenke@gwdg.de. * I want to know about it. * * If you are the editor of a Linux CD, you should * enable sbpcd.c within your boot floppy kernel and * send me one of your CDs for free. * * 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. * * You should have received a copy of the GNU General Public License * (for example /usr/src/linux/COPYING); if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #ifndef PATCHLEVEL #define PATCHLEVEL 9 #endif #include #include #if SBPCD_USE_IRQ #include #endif SBPCD_USE_IRQ #include #include #include #include #include #include #include #if PATCHLEVEL>13 #include #include #else #define DDIOCSDBG 0x9000 #define MATSUSHITA_CDROM_MAJOR 25 #endif #include #include #include #include #define MAJOR_NR MATSUSHITA_CDROM_MAJOR #include "blk.h" #define VERSION "1.2" #define SBPCD_DEBUG #ifndef CONFIG_SBPCD #error "SBPCD: \"make config\" again. Enable Matsushita/Panasonic CDROM support" #endif #ifndef CONFIG_ISO9660_FS #error "SBPCD: \"make config\" again. File system iso9660 is necessary." #endif /* * still testing around... */ #define MANY_SESSION 0 #define CDMKE #undef FUTURE #define WORKMAN 0 /* some testing stuff to make it better */ /*==========================================================================*/ /*==========================================================================*/ /* * auto-probing address list * inspired by Adam J. Richter from Yggdrasil * * still not good enough - can cause a hang. * example: a NE 2000 ehernet card at 300 will cause a hang probing 310. * if that happens, reboot and use the LILO (kernel) command line. * the conflicting possible ethernet card addresses get probed last - to * minimize the hang possibilities. * * The SB Pro addresses get "mirrored" at 0x6xx - to avoid a type error, * the 0x2xx-addresses must get checked before 0x6xx. * * what are other cards' default and range ??? * what about ESCOM PowerSound??? * what about HighScreen??? */ static int autoprobe[] = { CDROM_PORT, SBPRO, /* probe with user's setup first */ 0x230, 1, /* Soundblaster Pro and 16 (default) */ 0x300, 0, /* CI-101P (default), Galaxy (default), Reveal (one default) */ 0x250, 1, /* OmniCD default, Soundblaster Pro and 16 */ 0x260, 1, /* OmniCD */ 0x320, 0, /* Lasermate, CI-101P, Galaxy, Reveal (other default) */ 0x340, 0, /* Lasermate, CI-101P */ 0x360, 0, /* Lasermate, CI-101P */ 0x270, 1, /* Soundblaster 16 */ 0x630, 0, /* "sound card #9" (default) */ 0x650, 0, /* "sound card #9" */ 0x670, 0, /* "sound card #9" */ 0x690, 0, /* "sound card #9" */ 0x330, 0, /* Lasermate, CI-101P */ 0x350, 0, /* Lasermate, CI-101P */ 0x370, 0, /* Lasermate, CI-101P */ 0x290, 1, /* Soundblaster 16 */ 0x310, 0, /* Lasermate, CI-101P */ }; #define NUM_AUTOPROBE (sizeof(autoprobe) / sizeof(int)) /*==========================================================================*/ /* * the forward references: */ static void sbp_read_cmd(void); static int sbp_data(void); /*==========================================================================*/ /* * pattern for printk selection: * * (1<= 128) sbpcd_debug &= ~(1 << (val - 128)); else sbpcd_debug |= (1 << val); } return(0); } /*==========================================================================*/ /*==========================================================================*/ /* * Wait a little while (used for polling the drive). If in initialization, * setting a timeout doesn't work, so just loop for a while. */ static inline void sbp_sleep(u_int jifs) { current->state = TASK_INTERRUPTIBLE; current->timeout = jiffies + jifs; schedule(); } /*==========================================================================*/ /*==========================================================================*/ /* * convert logical_block_address to m-s-f_number (3 bytes only) */ static void lba2msf(int lba, u_char *msf) { lba += CD_BLOCK_OFFSET; msf[0] = lba / (CD_SECS*CD_FRAMES); lba %= CD_SECS*CD_FRAMES; msf[1] = lba / CD_FRAMES; msf[2] = lba % CD_FRAMES; } /*==========================================================================*/ /*==========================================================================*/ /* * convert msf-bin to msf-bcd */ static void bin2bcdx(u_char *p) /* must work only up to 75 or 99 */ { *p=((*p/10)<<4)|(*p%10); } /*==========================================================================*/ static u_int blk2msf(u_int blk) { MSF msf; u_int mm; msf.c[3] = 0; msf.c[2] = (blk + CD_BLOCK_OFFSET) / (CD_SECS * CD_FRAMES); mm = (blk + CD_BLOCK_OFFSET) % (CD_SECS * CD_FRAMES); msf.c[1] = mm / CD_FRAMES; msf.c[0] = mm % CD_FRAMES; return (msf.n); } /*==========================================================================*/ static u_int make16(u_char rh, u_char rl) { return ((rh<<8)|rl); } /*==========================================================================*/ static u_int make32(u_int rh, u_int rl) { return ((rh<<16)|rl); } /*==========================================================================*/ static u_char swap_nibbles(u_char i) { return ((i<<4)|(i>>4)); } /*==========================================================================*/ static u_char byt2bcd(u_char i) { return (((i/10)<<4)+i%10); } /*==========================================================================*/ static u_char bcd2bin(u_char bcd) { return ((bcd>>4)*10+(bcd&0x0F)); } /*==========================================================================*/ static int msf2blk(int msfx) { MSF msf; int i; msf.n=msfx; i=(msf.c[2] * CD_SECS + msf.c[1]) * CD_FRAMES + msf.c[0] - CD_BLOCK_OFFSET; if (i<0) return (0); return (i); } /*==========================================================================*/ /* evaluate xx_ReadError code (still mysterious) */ static int sta2err(int sta) { if (sta<=2) return (sta); if (sta==0x05) return (-4); if (sta==0x06) return (-6); if (sta==0x0d) return (-6); if (sta==0x0e) return (-3); if (sta==0x14) return (-3); if (sta==0x0c) return (-11); if (sta==0x0f) return (-11); if (sta==0x10) return (-11); if (sta>=0x16) return (-12); DS[d].CD_changed=0xFF; if (sta==0x11) return (-15); return (-2); } /*==========================================================================*/ static void clr_cmdbuf(void) { int i; for (i=0;i<7;i++) drvcmd[i]=0; cmd_type=0; } /*==========================================================================*/ static void mark_timeout(void) { timed_out=1; DPRINTF((DBG_TIM,"SBPCD: timer stopped.\n")); } /*==========================================================================*/ static void flush_status(void) { #ifdef CDMKE int i; if (current == task[0]) for (i=maxtim02;i!=0;i--) inb(CDi_status); else { sbp_sleep(150); for (i=maxtim_data;i!=0;i--) inb(CDi_status); } #else timed_out=0; SET_TIMER(mark_timeout,150); do { } while (!timed_out); CLEAR_TIMER; inb(CDi_status); #endif CDMKE } /*==========================================================================*/ static int CDi_stat_loop(void) { int i,j; u_long timeout; if (current == task[0]) for(i=maxtim16;i!=0;i--) { j=inb(CDi_status); if (!(j&s_not_data_ready)) return (j); if (!(j&s_not_result_ready)) return (j); if (!new_drive) if (j&s_attention) return (j); } else for(timeout = jiffies + 1000, i=maxtim_data; timeout > jiffies; ) { for ( ;i!=0;i--) { j=inb(CDi_status); if (!(j&s_not_data_ready)) return (j); if (!(j&s_not_result_ready)) return (j); if (!new_drive) if (j&s_attention) return (j); } sbp_sleep(1); i = 1; } return (-1); } /*==========================================================================*/ static int ResponseInfo(void) { int i,j, st=0; u_long timeout; if (current == task[0]) for (i=0;i1) return (-3); if (!new_drive) { if (f_blk_msf==1) pos=msf2blk(pos); drvcmd[2]=(pos>>16)&0x00FF; drvcmd[3]=(pos>>8)&0x00FF; drvcmd[4]=pos&0x00FF; flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta | f_ResponseStatus | f_obey_p_check | f_bit1; } else { if (f_blk_msf==0) pos=blk2msf(pos); drvcmd[1]=(pos>>16)&0x00FF; drvcmd[2]=(pos>>8)&0x00FF; drvcmd[3]=pos&0x00FF; flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; } drvcmd[0]=0x01; response_count=0; i=cmd_out(); return (i); } /*==========================================================================*/ static int xx_SpinUp(void) { int i; DPRINTF((DBG_SPI,"SBPCD: SpinUp.\n")); DS[d].in_SpinUp = 1; clr_cmdbuf(); if (!new_drive) { drvcmd[0]=0x05; flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; } else { drvcmd[0]=0x02; flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; } response_count=0; i=cmd_out(); DS[d].in_SpinUp = 0; return (i); } /*==========================================================================*/ static int yy_SpinDown(void) { int i; if (!new_drive) return (-3); clr_cmdbuf(); drvcmd[0]=0x06; flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; response_count=0; i=cmd_out(); return (i); } /*==========================================================================*/ static int yy_SetSpeed(u_char speed, u_char x1, u_char x2) { int i; if (!new_drive) return (-3); clr_cmdbuf(); drvcmd[0]=0x09; drvcmd[1]=0x03; drvcmd[2]=speed; drvcmd[3]=x1; drvcmd[4]=x2; flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; response_count=0; i=cmd_out(); return (i); } /*==========================================================================*/ static int xx_SetVolume(void) { int i; u_char channel0,channel1,volume0,volume1; u_char control0,value0,control1,value1; DS[d].diskstate_flags &= ~volume_bit; clr_cmdbuf(); channel0=DS[d].vol_chan0; volume0=DS[d].vol_ctrl0; channel1=control1=DS[d].vol_chan1; volume1=value1=DS[d].vol_ctrl1; control0=value0=0; if (((DS[d].drv_options&sax_a)!=0)&&(DS[d].drv_type>=drv_211)) { if ((volume0!=0)&&(volume1==0)) { volume1=volume0; channel1=channel0; } else if ((volume0==0)&&(volume1!=0)) { volume0=volume1; channel0=channel1; } } if (channel0>1) { channel0=0; volume0=0; } if (channel1>1) { channel1=1; volume1=0; } if (new_drive) { control0=channel0+1; control1=channel1+1; value0=(volume0>volume1)?volume0:volume1; value1=value0; if (volume0==0) control0=0; if (volume1==0) control1=0; drvcmd[0]=0x09; drvcmd[1]=0x05; drvcmd[3]=control0; drvcmd[4]=value0; drvcmd[5]=control1; drvcmd[6]=value1; flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; } else { if (DS[d].drv_type>=drv_300) { control0=volume0&0xFC; value0=volume1&0xFC; if ((volume0!=0)&&(volume0<4)) control0 |= 0x04; if ((volume1!=0)&&(volume1<4)) value0 |= 0x04; if (channel0!=0) control0 |= 0x01; if (channel1==1) value0 |= 0x01; } else { value0=(volume0>volume1)?volume0:volume1; if (DS[d].drv_type=drv_201) { if (volume0==0) control0 |= 0x80; if (volume1==0) control0 |= 0x40; } if (DS[d].drv_type>=drv_211) { if (channel0!=0) control0 |= 0x20; if (channel1!=1) control0 |= 0x10; } } drvcmd[0]=0x84; drvcmd[1]=0x83; drvcmd[4]=control0; drvcmd[5]=value0; flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; } response_count=0; i=cmd_out(); if (i>0) return (i); DS[d].diskstate_flags |= volume_bit; return (0); } /*==========================================================================*/ static int GetStatus(void) { int i; flags_cmd_out=f_getsta|f_ResponseStatus|f_obey_p_check; response_count=0; cmd_type=0; i=cmd_out(); return (i); } /*==========================================================================*/ static int xy_DriveReset(void) { int i; DPRINTF((DBG_RES,"SBPCD: xy_DriveReset called.\n")); if (!new_drive) OUT(CDo_reset,0x00); else { clr_cmdbuf(); drvcmd[0]=0x0A; flags_cmd_out=f_putcmd; response_count=0; i=cmd_out(); } flush_status(); i=GetStatus(); if (i>=0) return -1; if (DS[d].error_byte!=aud_12) return -1; return (0); } /*==========================================================================*/ static int SetSpeed(void) { int i, speed; if (!(DS[d].drv_options&(speed_auto|speed_300|speed_150))) return (0); speed=0x80; if (!(DS[d].drv_options&speed_auto)) { speed |= 0x40; if (!(DS[d].drv_options&speed_300)) speed=0; } i=yy_SetSpeed(speed,0,0); return (i); } /*==========================================================================*/ static int DriveReset(void) { int i; i=xy_DriveReset(); if (i<0) return (-2); do { i=GetStatus(); if ((i<0)&&(i!=-15)) return (-2); /* i!=-15 is from sta2err */ if (!st_caddy_in) break; } while (!st_diskok); DS[d].CD_changed=1; i=SetSpeed(); if (i<0) return (-2); return (0); } /*==========================================================================*/ static int xx_Pause_Resume(int pau_res) { int i; clr_cmdbuf(); if (new_drive) { drvcmd[0]=0x0D; flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; } else { drvcmd[0]=0x8D; flags_cmd_out=f_putcmd|f_respo2|f_getsta|f_ResponseStatus|f_obey_p_check; } if (pau_res!=1) drvcmd[1]=0x80; response_count=0; i=cmd_out(); return (i); } /*==========================================================================*/ #if 000 static int yy_LockDoor(char lock) { int i; if (!new_drive) return (-3); clr_cmdbuf(); drvcmd[0]=0x0C; if (lock==1) drvcmd[1]=0x01; flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; response_count=0; i=cmd_out(); return (i); } #endif 000 /*==========================================================================*/ static int xx_ReadSubQ(void) { int i,j; DS[d].diskstate_flags &= ~subq_bit; clr_cmdbuf(); if (new_drive) { drvcmd[0]=0x87; flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; response_count=11; } else { drvcmd[0]=0x89; drvcmd[1]=0x02; flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; response_count=13; } for (j=0;j<255;j++) { i=cmd_out(); if (i<0) return (i); if (infobuf[0]!=0) break; if (!st_spinning) { DS[d].SubQ_ctl_adr=DS[d].SubQ_trk=DS[d].SubQ_pnt_idx=DS[d].SubQ_whatisthis=0; DS[d].SubQ_run_tot=DS[d].SubQ_run_trk=0; return (0); } } DS[d].SubQ_audio=infobuf[0]; DS[d].SubQ_ctl_adr=swap_nibbles(infobuf[1]); DS[d].SubQ_trk=byt2bcd(infobuf[2]); DS[d].SubQ_pnt_idx=byt2bcd(infobuf[3]); i=4; if (!new_drive) i=5; DS[d].SubQ_run_tot=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); /* msf-bin */ i=7; if (!new_drive) i=9; DS[d].SubQ_run_trk=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); /* msf-bin */ DS[d].SubQ_whatisthis=infobuf[i+3]; DS[d].diskstate_flags |= subq_bit; return (0); } /*==========================================================================*/ static int xx_ModeSense(void) { int i; DS[d].diskstate_flags &= ~frame_size_bit; clr_cmdbuf(); if (new_drive) { drvcmd[0]=0x84; drvcmd[1]=0x00; flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; response_count=5; } else { drvcmd[0]=0x85; drvcmd[1]=0x00; flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; response_count=2; } i=cmd_out(); if (i<0) return (i); i=0; if (new_drive) DS[d].sense_byte=infobuf[i++]; DS[d].frame_size=make16(infobuf[i],infobuf[i+1]); DS[d].diskstate_flags |= frame_size_bit; return (0); } /*==========================================================================*/ #if 0000 static int xx_TellVolume(void) { int i; u_char switches; u_char chan0,vol0,chan1,vol1; DS[d].diskstate_flags &= ~volume_bit; clr_cmdbuf(); if (new_drive) { drvcmd[0]=0x84; drvcmd[1]=0x05; response_count=5; flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; } else { drvcmd[0]=0x85; drvcmd[1]=0x03; response_count=2; flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; } i=cmd_out(); if (i<0) return (i); if (new_drive) { chan0=infobuf[1]&0x0F; vol0=infobuf[2]; chan1=infobuf[3]&0x0F; vol1=infobuf[4]; if (chan0==0) { chan0=1; vol0=0; } if (chan1==0) { chan1=2; vol1=0; } chan0 >>= 1; chan1 >>= 1; } else { chan0=0; chan1=1; vol0=vol1=infobuf[1]; if (DS[d].drv_type>=drv_201) { if (DS[d].drv_type=drv_211) { if ((switches&0x20)!=0) chan0=1; if ((switches&0x10)!=0) chan1=0; } } else { vol0=infobuf[0]; if ((vol0&0x01)!=0) chan0=1; if ((vol1&0x01)==0) chan1=0; vol0 &= 0xFC; vol1 &= 0xFC; if (vol0!=0) vol0 += 3; if (vol1!=0) vol1 += 3; } } } DS[d].vol_chan0=chan0; DS[d].vol_ctrl0=vol0; DS[d].vol_chan1=chan1; DS[d].vol_ctrl1=vol1; DS[d].vol_chan2=2; DS[d].vol_ctrl2=0xFF; DS[d].vol_chan3=3; DS[d].vol_ctrl3=0xFF; DS[d].diskstate_flags |= volume_bit; return (0); } #endif /*==========================================================================*/ static int xx_ReadCapacity(void) { int i; DS[d].diskstate_flags &= ~cd_size_bit; clr_cmdbuf(); if (new_drive) { drvcmd[0]=0x85; flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; } else { drvcmd[0]=0x88; flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; } response_count=5; i=cmd_out(); if (i<0) return (i); DS[d].CDsize_blk=make32(make16(0,infobuf[0]),make16(infobuf[1],infobuf[2])); if (new_drive) DS[d].CDsize_blk=msf2blk(DS[d].CDsize_blk); DS[d].CDsize_frm = (DS[d].CDsize_blk * make16(infobuf[3],infobuf[4])) / CD_FRAMESIZE; DS[d].CDsize_blk += 151; DS[d].diskstate_flags |= cd_size_bit; return (0); } /*==========================================================================*/ static int xx_ReadTocDescr(void) { int i; DS[d].diskstate_flags &= ~toc_bit; clr_cmdbuf(); if (new_drive) { drvcmd[0]=0x8B; flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; } else { drvcmd[0]=0x8B; flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; } response_count=6; i=cmd_out(); if (i<0) return (i); DS[d].xa_byte=infobuf[0]; DS[d].n_first_track=infobuf[1]; DS[d].n_last_track=infobuf[2]; DS[d].size_msf=make32(make16(0,infobuf[3]),make16(infobuf[4],infobuf[5])); DS[d].size_blk=msf2blk(DS[d].size_msf); DS[d].diskstate_flags |= toc_bit; DPRINTF((DBG_TOC,"SBPCD: TocDesc: %02X %02X %02X %08X\n", DS[d].xa_byte,DS[d].n_first_track,DS[d].n_last_track,DS[d].size_msf)); return (0); } /*==========================================================================*/ static int xx_ReadTocEntry(int num) { int i; clr_cmdbuf(); if (new_drive) { drvcmd[0]=0x8C; flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; } else { drvcmd[0]=0x8C; drvcmd[1]=0x02; flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; } drvcmd[2]=num; response_count=8; i=cmd_out(); if (i<0) return (i); DS[d].TocEnt_nixbyte=infobuf[0]; DS[d].TocEnt_ctl_adr=swap_nibbles(infobuf[1]); DS[d].TocEnt_number=infobuf[2]; DS[d].TocEnt_format=infobuf[3]; if (new_drive) i=4; else i=5; DS[d].TocEnt_address=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); DPRINTF((DBG_TOC,"SBPCD: TocEntry: %02X %02X %02X %02X %08X\n", DS[d].TocEnt_nixbyte,DS[d].TocEnt_ctl_adr,DS[d].TocEnt_number, DS[d].TocEnt_format,DS[d].TocEnt_address)); return (0); } /*==========================================================================*/ static int xx_ReadPacket(void) { int i; clr_cmdbuf(); drvcmd[0]=0x8E; drvcmd[1]=response_count; flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; i=cmd_out(); return (i); } /*==========================================================================*/ static int convert_UPC(u_char *p) { int i; p++; if (!new_drive) p[13]=0; for (i=0;i<7;i++) { if (new_drive) DS[d].UPC_buf[i]=swap_nibbles(*p++); else { DS[d].UPC_buf[i]=((*p++)<<4)&0xFF; DS[d].UPC_buf[i] |= *p++; } } DS[d].UPC_buf[6] &= 0xF0; return (0); } /*==========================================================================*/ static int xx_ReadUPC(void) { int i; DS[d].diskstate_flags &= ~upc_bit; clr_cmdbuf(); if (new_drive) { drvcmd[0]=0x88; response_count=8; flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; } else { drvcmd[0]=0x08; response_count=0; flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; } i=cmd_out(); if (i<0) return (i); if (!new_drive) { response_count=16; i=xx_ReadPacket(); if (i<0) return (i); } DS[d].UPC_ctl_adr=0; if (new_drive) i=0; else i=2; if ((infobuf[i]&0x80)!=0) { convert_UPC(&infobuf[i]); DS[d].UPC_ctl_adr &= 0xF0; DS[d].UPC_ctl_adr |= 0x02; } DS[d].diskstate_flags |= upc_bit; return (0); } /*==========================================================================*/ static int yy_CheckMultiSession(void) { int i; DS[d].diskstate_flags &= ~multisession_bit; DS[d].f_multisession=0; clr_cmdbuf(); if (new_drive) { drvcmd[0]=0x8D; response_count=6; flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; i=cmd_out(); if (i<0) return (i); if ((infobuf[0]&0x80)!=0) { DPRINTF((DBG_MUL,"SBPCD: MultiSession CD detected: %02X %02X %02X %02X %02X %02X\n", infobuf[0], infobuf[1], infobuf[2], infobuf[3], infobuf[4], infobuf[5])); DS[d].f_multisession=1; DS[d].lba_multi=msf2blk(make32(make16(0,infobuf[1]), make16(infobuf[2],infobuf[3]))); } } DS[d].diskstate_flags |= multisession_bit; return (0); } /*==========================================================================*/ static void check_datarate(void) { #ifdef CDMKE int i=0; timed_out=0; datarate=0; /* set a timer to make (timed_out!=0) after 1.1 seconds */ DPRINTF((DBG_TIM,"SBPCD: timer started (110).\n")); sti(); /* to avoid possible "printf" bug */ SET_TIMER(mark_timeout,110); do { i=inb(CDi_status); datarate++; #if 00000 if (datarate>0x0FFFFFFF) break; #endif 00000 } while (!timed_out); /* originally looping for 1.1 seconds */ CLEAR_TIMER; DPRINTF((DBG_TIM,"SBPCD: datarate: %d\n", datarate)); if (datarate<65536) datarate=65536; maxtim16=datarate*16; maxtim04=datarate*4; maxtim02=datarate*2; maxtim_8=datarate/32; #if MANY_SESSION maxtim_data=datarate/100; #else maxtim_data=datarate/300; #endif MANY_SESSION DPRINTF((DBG_TIM,"SBPCD: maxtim_8 %d, maxtim_data %d.\n", maxtim_8, maxtim_data)); #endif CDMKE } /*==========================================================================*/ static int check_version(void) { int i, j; /* clear any pending error state */ clr_cmdbuf(); drvcmd[0]=0x82; response_count=9; flags_cmd_out=f_putcmd; cmd_out(); /* read drive version */ clr_cmdbuf(); for (i=0;i<12;i++) infobuf[i]=0; drvcmd[0]=0x83; response_count=12; flags_cmd_out=f_putcmd; i=cmd_out(); if (i<0) DPRINTF((DBG_INI,"SBPCD: cmd_83 returns %d\n",i)); DPRINTF((DBG_INI,"SBPCD: infobuf = \"")); for (i=0;i<12;i++) DPRINTF((DBG_INI,"%c",infobuf[i])); DPRINTF((DBG_INI,"\"\n")); for (i=0;i<4;i++) if (infobuf[i]!=drive_family[i]) break; if (i==4) { DS[d].drive_model[0]=infobuf[i++]; DS[d].drive_model[1]=infobuf[i++]; DS[d].drive_model[2]='-'; DS[d].drive_model[3]='x'; DS[d].drv_type=drv_new; } else { for (i=0;i<8;i++) if (infobuf[i]!=drive_vendor[i]) break; if (i!=8) return (-1); DS[d].drive_model[0]='2'; DS[d].drive_model[1]='x'; DS[d].drive_model[2]='-'; DS[d].drive_model[3]='x'; DS[d].drv_type=drv_old; } for (j=0;j<4;j++) DS[d].firmware_version[j]=infobuf[i+j]; j = (DS[d].firmware_version[0] & 0x0F) * 100 + (DS[d].firmware_version[2] & 0x0F) *10 + (DS[d].firmware_version[3] & 0x0F); if (new_drive) { if (j<100) DS[d].drv_type=drv_099; else DS[d].drv_type=drv_100; } else if (j<200) DS[d].drv_type=drv_199; else if (j<201) DS[d].drv_type=drv_200; else if (j<210) DS[d].drv_type=drv_201; else if (j<211) DS[d].drv_type=drv_210; else if (j<300) DS[d].drv_type=drv_211; else DS[d].drv_type=drv_300; return (0); } /*==========================================================================*/ static int switch_drive(int num) { int i; d=num; i=num; if (sbpro_type) i=(i&0x01)<<1|(i&0x02)>>1; OUT(CDo_enable,i); DPRINTF((DBG_DID,"SBPCD: switch_drive: drive %d activated.\n",DS[d].drv_minor)); return (0); } /*==========================================================================*/ /* * probe for the presence of drives on the selected controller */ static int check_drives(void) { int i, j; char *printk_header=""; DPRINTF((DBG_INI,"SBPCD: check_drives entered.\n")); ndrives=0; for (j=0;j=0) { ndrives++; DS[d].drv_options=drv_pattern[j]; if (!new_drive) DS[d].drv_options&=~(speed_auto|speed_300|speed_150); printk("%sDrive %d: %s%.4s (%.4s)\n", printk_header, DS[d].drv_minor, drive_family, DS[d].drive_model, DS[d].firmware_version); printk_header=" - "; } else DS[d].drv_minor=-1; } if (ndrives==0) return (-1); return (0); } /*==========================================================================*/ #if 000 static void timewait(void) { int i; for (i=0; i<65500; i++); } #endif 000 /*==========================================================================*/ #if FUTURE /* * obtain if requested service disturbs current audio state */ static int obey_audio_state(u_char audio_state, u_char func,u_char subfunc) { switch (audio_state) /* audio status from controller */ { case aud_11: /* "audio play in progress" */ case audx11: switch (func) /* DOS command code */ { case cmd_07: /* input flush */ case cmd_0d: /* open device */ case cmd_0e: /* close device */ case cmd_0c: /* ioctl output */ return (1); case cmd_03: /* ioctl input */ switch (subfunc) /* DOS ioctl input subfunction */ { case cxi_00: case cxi_06: case cxi_09: return (1); default: return (ERROR15); } return (1); default: return (ERROR15); } return (1); case aud_12: /* "audio play paused" */ case audx12: return (1); default: return (2); } } #endif FUTURE /*==========================================================================*/ /* allowed is only * ioctl_o, flush_input, open_device, close_device, * tell_address, tell_volume, tell_capabiliti, * tell_framesize, tell_CD_changed, tell_audio_posi */ static int check_allowed1(u_char func1, u_char func2) { #if 000 if (func1==ioctl_o) return (0); if (func1==read_long) return (-1); if (func1==read_long_prefetch) return (-1); if (func1==seek) return (-1); if (func1==audio_play) return (-1); if (func1==audio_pause) return (-1); if (func1==audio_resume) return (-1); if (func1!=ioctl_i) return (0); if (func2==tell_SubQ_run_tot) return (-1); if (func2==tell_cdsize) return (-1); if (func2==tell_TocDescrip) return (-1); if (func2==tell_TocEntry) return (-1); if (func2==tell_subQ_info) return (-1); if (new_drive) if (func2==tell_SubChanInfo) return (-1); if (func2==tell_UPC) return (-1); #else return (0); #endif 000 } /*==========================================================================*/ static int check_allowed2(u_char func1, u_char func2) { #if 000 if (func1==read_long) return (-1); if (func1==read_long_prefetch) return (-1); if (func1==seek) return (-1); if (func1==audio_play) return (-1); if (func1!=ioctl_o) return (0); if (new_drive) { if (func2==EjectDisk) return (-1); if (func2==CloseTray) return (-1); } #else return (0); #endif 000 } /*==========================================================================*/ static int check_allowed3(u_char func1, u_char func2) { #if 000 if (func1==ioctl_i) { if (func2==tell_address) return (0); if (func2==tell_capabiliti) return (0); if (func2==tell_CD_changed) return (0); if (!new_drive) if (func2==tell_SubChanInfo) return (0); return (-1); } if (func1==ioctl_o) { if (func2==DriveReset) return (0); if (!new_drive) { if (func2==EjectDisk) return (0); if (func2==LockDoor) return (0); if (func2==CloseTray) return (0); } return (-1); } if (func1==flush_input) return (-1); if (func1==read_long) return (-1); if (func1==read_long_prefetch) return (-1); if (func1==seek) return (-1); if (func1==audio_play) return (-1); if (func1==audio_pause) return (-1); if (func1==audio_resume) return (-1); #else return (0); #endif 000 } /*==========================================================================*/ static int seek_pos_audio_end(void) { int i; i=msf2blk(DS[d].pos_audio_end)-1; if (i<0) return (-1); i=xx_Seek(i,0); return (i); } /*==========================================================================*/ static int ReadToC(void) { int i, j; DS[d].diskstate_flags &= ~toc_bit; DS[d].ored_ctl_adr=0; for (j=DS[d].n_first_track;j<=DS[d].n_last_track;j++) { i=xx_ReadTocEntry(j); if (i<0) return (i); DS[d].TocBuffer[j].nixbyte=DS[d].TocEnt_nixbyte; DS[d].TocBuffer[j].ctl_adr=DS[d].TocEnt_ctl_adr; DS[d].TocBuffer[j].number=DS[d].TocEnt_number; DS[d].TocBuffer[j].format=DS[d].TocEnt_format; DS[d].TocBuffer[j].address=DS[d].TocEnt_address; DS[d].ored_ctl_adr |= DS[d].TocEnt_ctl_adr; } /* fake entry for LeadOut Track */ DS[d].TocBuffer[j].nixbyte=0; DS[d].TocBuffer[j].ctl_adr=0; DS[d].TocBuffer[j].number=0; DS[d].TocBuffer[j].format=0; DS[d].TocBuffer[j].address=DS[d].size_msf; DS[d].diskstate_flags |= toc_bit; return (0); } /*==========================================================================*/ static int DiskInfo(void) { int i; i=SetSpeed(); if (i<0) { DPRINTF((DBG_INF,"SBPCD: DiskInfo: first SetSpeed returns %d\n", i)); i=SetSpeed(); if (i<0) { DPRINTF((DBG_INF,"SBPCD: DiskInfo: second SetSpeed returns %d\n", i)); return (i); } } i=xx_ModeSense(); if (i<0) { DPRINTF((DBG_INF,"SBPCD: DiskInfo: first xx_ModeSense returns %d\n", i)); i=xx_ModeSense(); if (i<0) { DPRINTF((DBG_INF,"SBPCD: DiskInfo: second xx_ModeSense returns %d\n", i)); return (i); } return (i); } i=xx_ReadCapacity(); if (i<0) { DPRINTF((DBG_INF,"SBPCD: DiskInfo: first ReadCapacity returns %d\n", i)); i=xx_ReadCapacity(); if (i<0) { DPRINTF((DBG_INF,"SBPCD: DiskInfo: second ReadCapacity returns %d\n", i)); return (i); } return (i); } i=xx_ReadTocDescr(); if (i<0) { DPRINTF((DBG_INF,"SBPCD: DiskInfo: ReadTocDescr returns %d\n", i)); return (i); } i=ReadToC(); if (i<0) { DPRINTF((DBG_INF,"SBPCD: DiskInfo: ReadToC returns %d\n", i)); return (i); } i=yy_CheckMultiSession(); if (i<0) { DPRINTF((DBG_INF,"SBPCD: DiskInfo: yy_CheckMultiSession returns %d\n", i)); return (i); } i=xx_ReadTocEntry(DS[d].n_first_track); if (i<0) { DPRINTF((DBG_INF,"SBPCD: DiskInfo: xx_ReadTocEntry(1) returns %d\n", i)); return (i); } i=xx_ReadUPC(); if (i<0) { DPRINTF((DBG_INF,"SBPCD: DiskInfo: xx_ReadUPC returns %d\n", i)); return (i); } return (0); } /*==========================================================================*/ /* * called always if driver gets entered * returns 0 or ERROR2 or ERROR15 */ static int prepare(u_char func, u_char subfunc) { int i; if (!new_drive) { i=inb(CDi_status); if (i&s_attention) GetStatus(); } else GetStatus(); if (DS[d].CD_changed==0xFF) { #if MANY_SESSION #else DS[d].diskstate_flags=0; #endif MANY_SESSION DS[d].audio_state=0; if (!st_diskok) { i=check_allowed1(func,subfunc); if (i<0) return (-2); } else { i=check_allowed3(func,subfunc); if (i<0) { DS[d].CD_changed=1; return (-15); } } } else { if (!st_diskok) { #if MANY_SESSION #else DS[d].diskstate_flags=0; #endif MANY_SESSION DS[d].audio_state=0; i=check_allowed1(func,subfunc); if (i<0) return (-2); } else { if (st_busy) { if (DS[d].audio_state!=audio_pausing) { i=check_allowed2(func,subfunc); if (i<0) return (-2); } } else { if (DS[d].audio_state==audio_playing) seek_pos_audio_end(); DS[d].audio_state=0; } if (!frame_size_valid) { i=DiskInfo(); if (i<0) { #if MANY_SESSION #else DS[d].diskstate_flags=0; #endif MANY_SESSION DS[d].audio_state=0; i=check_allowed1(func,subfunc); if (i<0) return (-2); } } } } return (0); } /*==========================================================================*/ static int xx_PlayAudioMSF(int pos_audio_start,int pos_audio_end) { int i; if (DS[d].audio_state==audio_playing) return (-EINVAL); clr_cmdbuf(); if (new_drive) { drvcmd[0]=0x0E; flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | f_obey_p_check | f_wait_if_busy; } else { drvcmd[0]=0x0B; flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta | f_ResponseStatus | f_obey_p_check | f_wait_if_busy; } drvcmd[1]=(pos_audio_start>>16)&0x00FF; drvcmd[2]=(pos_audio_start>>8)&0x00FF; drvcmd[3]=pos_audio_start&0x00FF; drvcmd[4]=(pos_audio_end>>16)&0x00FF; drvcmd[5]=(pos_audio_end>>8)&0x00FF; drvcmd[6]=pos_audio_end&0x00FF; response_count=0; i=cmd_out(); return (i); } /*==========================================================================*/ /*==========================================================================*/ /*==========================================================================*/ /*==========================================================================*/ /* * ioctl support, adopted from scsi/sr_ioctl.c and mcd.c */ static int sbpcd_ioctl(struct inode *inode,struct file *file, u_int cmd, u_long arg) { int i, st; DPRINTF((DBG_IOC,"SBPCD: ioctl(%d, 0x%08lX, 0x%08lX)\n", MINOR(inode->i_rdev), cmd, arg)); if (!inode) return (-EINVAL); st=GetStatus(); if (st<0) return (-EIO); if (!toc_valid) { i=DiskInfo(); if (i<0) return (-EIO); /* error reading TOC */ } i=MINOR(inode->i_rdev); if ( (i<0) || (i>=NR_SBPCD) ) { DPRINTF((DBG_INF,"SBPCD: ioctl: bad device: %d\n", i)); return (-ENODEV); /* no such drive */ } switch_drive(i); DPRINTF((DBG_IOC,"SBPCD: ioctl: device %d, request %04X\n",i,cmd)); switch (cmd) /* Sun-compatible */ { case DDIOCSDBG: /* DDI Debug */ if (! suser()) return (-EPERM); i = verify_area(VERIFY_READ, (int *) arg, sizeof(int)); if (i>=0) i=sbpcd_dbg_ioctl(arg,1); return (i); case CDROMPAUSE: /* Pause the drive */ DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMPAUSE entered.\n")); /* pause the drive unit when it is currently in PLAY mode, */ /* or reset the starting and ending locations when in PAUSED mode. */ /* If applicable, at the next stopping point it reaches */ /* the drive will discontinue playing. */ switch (DS[d].audio_state) { case audio_playing: i=xx_Pause_Resume(1); if (i<0) return (-EIO); DS[d].audio_state=audio_pausing; i=xx_ReadSubQ(); if (i<0) return (-EIO); DS[d].pos_audio_start=DS[d].SubQ_run_tot; return (0); case audio_pausing: i=xx_Seek(DS[d].pos_audio_start,1); if (i<0) return (-EIO); return (0); default: return (-EINVAL); } case CDROMRESUME: /* resume paused audio play */ DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMRESUME entered.\n")); /* resume playing audio tracks when a previous PLAY AUDIO call has */ /* been paused with a PAUSE command. */ /* It will resume playing from the location saved in SubQ_run_tot. */ if (DS[d].audio_state!=audio_pausing) return -EINVAL; i=xx_Pause_Resume(3); if (i<0) return (-EIO); DS[d].audio_state=audio_playing; return (0); case CDROMPLAYMSF: DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMPLAYMSF entered.\n")); if (DS[d].audio_state==audio_playing) { i=xx_Pause_Resume(1); if (i<0) return (-EIO); i=xx_ReadSubQ(); if (i<0) return (-EIO); DS[d].pos_audio_start=DS[d].SubQ_run_tot; i=xx_Seek(DS[d].pos_audio_start,1); } st=verify_area(VERIFY_READ, (void *) arg, sizeof(struct cdrom_msf)); if (st) return (st); memcpy_fromfs(&msf, (void *) arg, sizeof(struct cdrom_msf)); /* values come as msf-bin */ DS[d].pos_audio_start = (msf.cdmsf_min0<<16) | (msf.cdmsf_sec0<<8) | msf.cdmsf_frame0; DS[d].pos_audio_end = (msf.cdmsf_min1<<16) | (msf.cdmsf_sec1<<8) | msf.cdmsf_frame1; DPRINTF((DBG_IOX,"SBPCD: ioctl: CDROMPLAYMSF %08X %08X\n", DS[d].pos_audio_start,DS[d].pos_audio_end)); i=xx_PlayAudioMSF(DS[d].pos_audio_start,DS[d].pos_audio_end); DPRINTF((DBG_IOC,"SBPCD: ioctl: xx_PlayAudioMSF returns %d\n",i)); #if 0 if (i<0) return (-EIO); #endif 0 DS[d].audio_state=audio_playing; return (0); case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */ DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMPLAYTRKIND entered.\n")); if (DS[d].audio_state==audio_playing) { DPRINTF((DBG_IOX,"SBPCD: CDROMPLAYTRKIND: already audio_playing.\n")); return (0); return (-EINVAL); } st=verify_area(VERIFY_READ,(void *) arg,sizeof(struct cdrom_ti)); if (st<0) { DPRINTF((DBG_IOX,"SBPCD: CDROMPLAYTRKIND: verify_area error.\n")); return (st); } memcpy_fromfs(&ti,(void *) arg,sizeof(struct cdrom_ti)); DPRINTF((DBG_IOX,"SBPCD: ioctl: trk0: %d, ind0: %d, trk1:%d, ind1:%d\n", ti.cdti_trk0,ti.cdti_ind0,ti.cdti_trk1,ti.cdti_ind1)); if (ti.cdti_trk0DS[d].n_last_track) return (-EINVAL); if (ti.cdti_trk1DS[d].n_last_track) ti.cdti_trk1=DS[d].n_last_track; DS[d].pos_audio_start=DS[d].TocBuffer[ti.cdti_trk0].address; DS[d].pos_audio_end=DS[d].TocBuffer[ti.cdti_trk1+1].address; i=xx_PlayAudioMSF(DS[d].pos_audio_start,DS[d].pos_audio_end); #if 0 if (i<0) return (-EIO); #endif 0 DS[d].audio_state=audio_playing; return (0); case CDROMREADTOCHDR: /* Read the table of contents header */ DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMREADTOCHDR entered.\n")); tochdr.cdth_trk0=DS[d].n_first_track; tochdr.cdth_trk1=DS[d].n_last_track; st=verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct cdrom_tochdr)); if (st) return (st); memcpy_tofs((void *) arg, &tochdr, sizeof(struct cdrom_tochdr)); return (0); case CDROMREADTOCENTRY: /* Read an entry in the table of contents */ DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMREADTOCENTRY entered.\n")); st=verify_area(VERIFY_READ, (void *) arg, sizeof(struct cdrom_tocentry)); if (st) return (st); memcpy_fromfs(&tocentry, (void *) arg, sizeof(struct cdrom_tocentry)); i=tocentry.cdte_track; if (i==CDROM_LEADOUT) i=DS[d].n_last_track+1; else if (iDS[d].n_last_track) return (-EINVAL); tocentry.cdte_adr=DS[d].TocBuffer[i].ctl_adr&0x0F; tocentry.cdte_ctrl=(DS[d].TocBuffer[i].ctl_adr>>4)&0x0F; tocentry.cdte_datamode=DS[d].TocBuffer[i].format; if (tocentry.cdte_format==CDROM_MSF) /* MSF-bin required */ { tocentry.cdte_addr.msf.minute=(DS[d].TocBuffer[i].address>>16)&0x00FF; tocentry.cdte_addr.msf.second=(DS[d].TocBuffer[i].address>>8)&0x00FF; tocentry.cdte_addr.msf.frame=DS[d].TocBuffer[i].address&0x00FF; } else if (tocentry.cdte_format==CDROM_LBA) /* blk required */ tocentry.cdte_addr.lba=msf2blk(DS[d].TocBuffer[i].address); else return (-EINVAL); st=verify_area(VERIFY_WRITE,(void *) arg, sizeof(struct cdrom_tocentry)); if (st) return (st); memcpy_tofs((void *) arg, &tocentry, sizeof(struct cdrom_tocentry)); return (0); case CDROMSTOP: /* Spin down the drive */ DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMSTOP entered.\n")); i=DriveReset(); #if WORKMAN DS[d].CD_changed=0xFF; DS[d].diskstate_flags=0; #endif WORKMAN DPRINTF((DBG_IOC,"SBPCD: ioctl: DriveReset returns %d\n",i)); DS[d].audio_state=0; i=DiskInfo(); return (0); case CDROMSTART: /* Spin up the drive */ DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMSTART entered.\n")); i=xx_SpinUp(); DS[d].audio_state=0; return (0); case CDROMEJECT: DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMEJECT entered.\n")); if (!new_drive) return (0); #if WORKMAN DS[d].CD_changed=0xFF; DS[d].diskstate_flags=0; #endif WORKMAN i=yy_SpinDown(); if (i<0) return (-EIO); DS[d].audio_state=0; return (0); case CDROMVOLCTRL: /* Volume control */ DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMVOLCTRL entered.\n")); st=verify_area(VERIFY_READ,(void *) arg,sizeof(volctrl)); if (st) return (st); memcpy_fromfs(&volctrl,(char *) arg,sizeof(volctrl)); DS[d].vol_chan0=0; DS[d].vol_ctrl0=volctrl.channel0; DS[d].vol_chan1=1; DS[d].vol_ctrl1=volctrl.channel1; i=xx_SetVolume(); return (0); case CDROMSUBCHNL: /* Get subchannel info */ DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMSUBCHNL entered.\n")); if ((st_spinning)||(!subq_valid)) { i=xx_ReadSubQ(); if (i<0) return (-EIO); } st=verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct cdrom_subchnl)); if (st) return (st); memcpy_fromfs(&SC, (void *) arg, sizeof(struct cdrom_subchnl)); #if 0 if (DS[d].SubQ_audio==0x80) DS[d].SubQ_audio=CDROM_AUDIO_NO_STATUS; #endif switch (DS[d].audio_state) { case audio_playing: SC.cdsc_audiostatus=CDROM_AUDIO_PLAY; break; case audio_pausing: SC.cdsc_audiostatus=CDROM_AUDIO_PAUSED; break; default: SC.cdsc_audiostatus=CDROM_AUDIO_NO_STATUS; break; } SC.cdsc_adr=DS[d].SubQ_ctl_adr; SC.cdsc_ctrl=DS[d].SubQ_ctl_adr>>4; SC.cdsc_trk=bcd2bin(DS[d].SubQ_trk); SC.cdsc_ind=bcd2bin(DS[d].SubQ_pnt_idx); if (SC.cdsc_format==CDROM_LBA) { SC.cdsc_absaddr.lba=msf2blk(DS[d].SubQ_run_tot); SC.cdsc_reladdr.lba=msf2blk(DS[d].SubQ_run_trk); } else /* not only if (SC.cdsc_format==CDROM_MSF) */ { SC.cdsc_absaddr.msf.minute=(DS[d].SubQ_run_tot>>16)&0x00FF; SC.cdsc_absaddr.msf.second=(DS[d].SubQ_run_tot>>8)&0x00FF; SC.cdsc_absaddr.msf.frame=DS[d].SubQ_run_tot&0x00FF; SC.cdsc_reladdr.msf.minute=(DS[d].SubQ_run_trk>>16)&0x00FF; SC.cdsc_reladdr.msf.second=(DS[d].SubQ_run_trk>>8)&0x00FF; SC.cdsc_reladdr.msf.frame=DS[d].SubQ_run_trk&0x00FF; } memcpy_tofs((void *) arg, &SC, sizeof(struct cdrom_subchnl)); DPRINTF((DBG_IOC,"SBPCD: CDROMSUBCHNL: %1X %02X %08X %08X %02X %02X %06X %06X\n", SC.cdsc_format,SC.cdsc_audiostatus, SC.cdsc_adr,SC.cdsc_ctrl, SC.cdsc_trk,SC.cdsc_ind, SC.cdsc_absaddr,SC.cdsc_reladdr)); return (0); case CDROMREADMODE2: DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMREADMODE2 requested.\n")); return (-EINVAL); case CDROMREADMODE1: DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMREADMODE1 requested.\n")); return (-EINVAL); default: DPRINTF((DBG_IOX,"SBPCD: ioctl: unknown function request %04X\n", cmd)); return (-EINVAL); } /* end switch(cmd) */ } /*==========================================================================*/ /* * Take care of the different block sizes between cdrom and Linux. * When Linux gets variable block sizes this will probably go away. */ static void sbp_transfer(void) { long offs; while ( (CURRENT->nr_sectors > 0) && (CURRENT->sector/4 >= DS[d].sbp_first_frame) && (CURRENT->sector/4 <= DS[d].sbp_last_frame) ) { offs = (CURRENT->sector - DS[d].sbp_first_frame * 4) * 512; memcpy(CURRENT->buffer, DS[d].sbp_buf + offs, 512); CURRENT->nr_sectors--; CURRENT->sector++; CURRENT->buffer += 512; } } /*==========================================================================*/ /* * We seem to get never an interrupt. */ #if SBPCD_USE_IRQ static void sbpcd_interrupt(int unused) { int st; st = inb(CDi_status) & 0xFF; DPRINTF((DBG_IRQ,"SBPCD: INTERRUPT received - CDi_status=%02X\n", st)); } #endif SBPCD_USE_IRQ /*==========================================================================*/ /* * Called from the timer to check the results of the get-status cmd. */ static int sbp_status(void) { int st; st=ResponseStatus(); if (st<0) { DPRINTF((DBG_INF,"SBPCD: sbp_status: timeout.\n")); return (0); } if (!st_spinning) DPRINTF((DBG_SPI,"SBPCD: motor got off - ignoring.\n")); if (st_check) { DPRINTF((DBG_INF,"SBPCD: st_check detected - retrying.\n")); return (0); } if (!st_door_closed) { DPRINTF((DBG_INF,"SBPCD: door is open - retrying.\n")); return (0); } if (!st_caddy_in) { DPRINTF((DBG_INF,"SBPCD: disk removed - retrying.\n")); return (0); } if (!st_diskok) { DPRINTF((DBG_INF,"SBPCD: !st_diskok detected - retrying.\n")); return (0); } if (st_busy) { DPRINTF((DBG_INF,"SBPCD: st_busy detected - retrying.\n")); return (0); } return (1); } /*==========================================================================*/ /* * I/O request routine, called from Linux kernel. */ static void do_sbpcd_request(void) { u_int block; int dev; u_int nsect; int i, status_tries, data_tries; request_loop: sti(); if ((CURRENT==NULL)||(CURRENT->dev<0)) return; if (CURRENT -> sector == -1) return; dev = MINOR(CURRENT->dev); if ( (dev<0) || (dev>=NR_SBPCD) ) { DPRINTF((DBG_INF,"SBPCD: do_request: bad device: %d\n", dev)); return; } switch_drive(dev); INIT_REQUEST; block = CURRENT->sector; nsect = CURRENT->nr_sectors; if (CURRENT->cmd != READ) { DPRINTF((DBG_INF,"SBPCD: bad cmd %d\n", CURRENT->cmd)); end_request(0); goto request_loop; } DPRINTF((DBG_MUL,"SBPCD: read LBA %d\n", block/4)); sbp_transfer(); /* if we satisfied the request from the buffer, we're done. */ if (CURRENT->nr_sectors == 0) { end_request(1); goto request_loop; } i=prepare(0,0); /* at moment not really a hassle check, but ... */ if (i!=0) DPRINTF((DBG_INF,"SBPCD: \"prepare\" tells error %d -- ignored\n", i)); if (!st_spinning) xx_SpinUp(); for (data_tries=3; data_tries > 0; data_tries--) { for (status_tries=3; status_tries > 0; status_tries--) { flags_cmd_out |= f_respo3; xx_ReadStatus(); if (sbp_status() != 0) break; sbp_sleep(1); /* wait a bit, try again */ } if (status_tries == 0) { DPRINTF((DBG_INF,"SBPCD: sbp_status: failed after 3 tries\n")); break; } sbp_read_cmd(); sbp_sleep(0); if (sbp_data() != 0) { end_request(1); goto request_loop; } } end_request(0); sbp_sleep(10); /* wait a bit, try again */ goto request_loop; } /*==========================================================================*/ /* * build and send the READ command. * Maybe it would be better to "set mode1" before ... */ static void sbp_read_cmd(void) { int i; int block; DS[d].sbp_first_frame=DS[d].sbp_last_frame=-1; /* purge buffer */ block=CURRENT->sector/4; if (new_drive) { #if MANY_SESSION DPRINTF((DBG_MUL,"SBPCD: read MSF %08X\n", blk2msf(block))); if ( (DS[d].f_multisession) && (multisession_valid) ) { DPRINTF((DBG_MUL,"SBPCD: MultiSession: use %08X for %08X (msf)\n", blk2msf(DS[d].lba_multi+block), blk2msf(block))); block=DS[d].lba_multi+block; } #else if ( (block==166) && (DS[d].f_multisession) && (multisession_valid) ) { DPRINTF((DBG_MUL,"SBPCD: MultiSession: use %08X for %08X (msf)\n", blk2msf(DS[d].lba_multi+16), blk2msf(block))); block=DS[d].lba_multi+16; } #endif MANY_SESSION } if (block+SBP_BUFFER_FRAMES <= DS[d].CDsize_frm) DS[d].sbp_read_frames = SBP_BUFFER_FRAMES; else { DS[d].sbp_read_frames=DS[d].CDsize_frm-block; /* avoid reading past end of data */ if (DS[d].sbp_read_frames < 1) { DPRINTF((DBG_INF,"SBPCD: requested frame %d, CD size %d ???\n", block, DS[d].CDsize_frm)); DS[d].sbp_read_frames=1; } } DS[d].sbp_current = 0; flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | f_obey_p_check; if (!new_drive) { if (DS[d].drv_type>=drv_201) { lba2msf(block,&drvcmd[1]); /* msf-bcd format required */ bin2bcdx(&drvcmd[1]); bin2bcdx(&drvcmd[2]); bin2bcdx(&drvcmd[3]); } else { drvcmd[1]=(block>>16)&0x000000ff; drvcmd[2]=(block>>8)&0x000000ff; drvcmd[3]=block&0x000000ff; } drvcmd[4]=0; drvcmd[5]=DS[d].sbp_read_frames; drvcmd[6]=(DS[d].drv_type= 1000) { DPRINTF((DBG_INF,"SBPCD: info: %d waits in %d frames.\n", data_waits, data_tries)); data_waits = data_tries = 0; } } #if SBPCD_DIS_IRQ sti(); #endif SBPCD_DIS_IRQ if (error_flag) /* must have been spurious D_RDY or (ATTN&&!D_RDY) */ { DPRINTF((DBG_INF,"SBPCD: read aborted by drive\n")); i=DriveReset(); /* ugly fix to prevent a hang */ return (0); } if (!new_drive) { #if SBPCD_DIS_IRQ cli(); #endif SBPCD_DIS_IRQ i=maxtim_data; for (timeout=jiffies+100; timeout > jiffies; timeout--) { for ( ;i!=0;i--) { j=inb(CDi_status); if (!(j&s_not_data_ready)) break; if (!(j&s_not_result_ready)) break; if (j&s_attention) break; } if (i != 0 || timeout <= jiffies) break; sbp_sleep(0); i = 1; } if (i==0) { DPRINTF((DBG_INF,"SBPCD: STATUS TIMEOUT AFTER READ")); } if (!(j&s_attention)) { DPRINTF((DBG_INF,"SBPCD: sbp_data: timeout waiting DRV_ATTN - retrying\n")); i=DriveReset(); /* ugly fix to prevent a hang */ #if SBPCD_DIS_IRQ sti(); #endif SBPCD_DIS_IRQ return (0); } #if SBPCD_DIS_IRQ sti(); #endif SBPCD_DIS_IRQ } do { if (!new_drive) xx_ReadStatus(); i=ResponseStatus(); /* builds status_byte, returns orig. status (old) or faked p_success_old (new) */ if (i<0) { DPRINTF((DBG_INF,"SBPCD: xx_ReadStatus error after read: %02X\n", DS[d].status_byte)); return (0); } } while ((!new_drive)&&(!st_check)&&(!(i&p_success_old))); if (st_check) { i=xx_ReadError(); DPRINTF((DBG_INF,"SBPCD: xx_ReadError was necessary after read: %02X\n",i)); return (0); } DS[d].sbp_first_frame = CURRENT -> sector / 4; DS[d].sbp_last_frame = DS[d].sbp_first_frame + DS[d].sbp_read_frames - 1; sbp_transfer(); return (1); } /*==========================================================================*/ /*==========================================================================*/ /* * Open the device special file. Check that a disk is in. Read TOC. */ int sbpcd_open(struct inode *ip, struct file *fp) { int i; if (ndrives==0) return (-ENXIO); /* no hardware */ i = MINOR(ip->i_rdev); if ( (i<0) || (i>=NR_SBPCD) ) { DPRINTF((DBG_INF,"SBPCD: open: bad device: %d\n", i)); return (-ENODEV); /* no such drive */ } switch_drive(i); if (!st_spinning) xx_SpinUp(); flags_cmd_out |= f_respo2; xx_ReadStatus(); /* command: give 1-byte status */ i=ResponseStatus(); if (i<0) { DPRINTF((DBG_INF,"SBPCD: sbpcd_open: xx_ReadStatus timed out\n")); return (-EIO); /* drive doesn't respond */ } DPRINTF((DBG_STA,"SBPCD: sbpcd_open: status %02X\n", DS[d].status_byte)); if (!st_door_closed||!st_caddy_in) { DPRINTF((DBG_INF,"SBPCD: sbpcd_open: no disk in drive\n")); return (-EIO); } /* * we could try to keep an "open" counter here and lock the door if 0->1. * not done yet. */ if (!st_spinning) xx_SpinUp(); i=DiskInfo(); if ((DS[d].ored_ctl_adr&0x40)==0) DPRINTF((DBG_INF,"SBPCD: CD contains no data tracks.\n")); return (0); } /*==========================================================================*/ /* * On close, we flush all sbp blocks from the buffer cache. */ static void sbpcd_release(struct inode * ip, struct file * file) { int i; /* * we could try to count down an "open" counter here * and unlock the door if zero. * not done yet. */ i = MINOR(ip->i_rdev); if ( (i<0) || (i>=NR_SBPCD) ) { DPRINTF((DBG_INF,"SBPCD: release: bad device: %d\n", i)); return; } switch_drive(i); DS[d].sbp_first_frame=DS[d].sbp_last_frame=-1; sync_dev(ip->i_rdev); /* nonsense if read only device? */ invalidate_buffers(ip->i_rdev); DS[d].diskstate_flags &= ~cd_size_bit; } /*==========================================================================*/ /* * */ static struct file_operations sbpcd_fops = { NULL, /* lseek - default */ block_read, /* read - general block-dev read */ block_write, /* write - general block-dev write */ NULL, /* readdir - bad */ NULL, /* select */ sbpcd_ioctl, /* ioctl */ NULL, /* mmap */ sbpcd_open, /* open */ sbpcd_release /* release */ }; /*==========================================================================*/ /* * SBP interrupt descriptor */ #if SBPCD_USE_IRQ static struct sigaction sbpcd_sigaction = { sbpcd_interrupt, 0, SA_INTERRUPT, NULL }; #endif SBPCD_USE_IRQ /*==========================================================================*/ /* * accept "kernel command line" parameters * (suggested by Peter MacDonald with SLS 1.03) * * use: tell LILO: * sbpcd=0x230,SoundBlaster * or * sbpcd=0x300,LaserMate * * (upper/lower case sensitive here!!!). * * the address value has to be the TRUE CDROM PORT ADDRESS - * not the soundcard base address. * */ void sbpcd_setup(char *s, int *p) { DPRINTF((DBG_INI,"SBPCD: sbpcd_setup called with %04X,%s\n",p[1], s)); if (!strcmp(s,str_sb)) sbpro_type=1; else sbpro_type=0; if (p[0]>0) sbpcd_ioaddr=p[1]; CDo_command=sbpcd_ioaddr; CDi_info=sbpcd_ioaddr; CDi_status=sbpcd_ioaddr+1; CDo_reset=sbpcd_ioaddr+2; CDo_enable=sbpcd_ioaddr+3; if (sbpro_type==1) { MIXER_addr=sbpcd_ioaddr-0x10+0x04; MIXER_data=sbpcd_ioaddr-0x10+0x05; CDo_sel_d_i=sbpcd_ioaddr+1; CDi_data=sbpcd_ioaddr; } else CDi_data=sbpcd_ioaddr+2; } /*==========================================================================*/ /* * Test for presence of drive and initialize it. Called at boot time. */ u_long sbpcd_init(u_long mem_start, u_long mem_end) { int i=0, j=0; int addr[2]={1, CDROM_PORT}; int port_index; DPRINTF((DBG_INF,"SBPCD version %s\n", VERSION)); DPRINTF((DBG_INF,"SBPCD: Looking for a SoundBlaster/Matsushita CD-ROM drive\n")); DPRINTF((DBG_WRN,"SBPCD: \n")); DPRINTF((DBG_WRN,"SBPCD: = = = = = = = = = = W A R N I N G = = = = = = = = = =\n")); DPRINTF((DBG_WRN,"SBPCD: Auto-Probing can cause a hang (f.e. touching an ethernet card).\n")); DPRINTF((DBG_WRN,"SBPCD: If that happens, you have to reboot and use the\n")); DPRINTF((DBG_WRN,"SBPCD: LILO (kernel) command line feature like:\n")); DPRINTF((DBG_WRN,"SBPCD: \n")); DPRINTF((DBG_WRN,"SBPCD: LILO boot: linux sbpcd=0x230,SoundBlaster\n")); DPRINTF((DBG_WRN,"SBPCD: or like:\n")); DPRINTF((DBG_WRN,"SBPCD: LILO boot: linux sbpcd=0x300,LaserMate\n")); DPRINTF((DBG_WRN,"SBPCD: \n")); DPRINTF((DBG_WRN,"SBPCD: with your REAL address.\n")); DPRINTF((DBG_WRN,"SBPCD: = = = = = = = = = = END of WARNING = = = = = = = = = =\n")); DPRINTF((DBG_WRN,"SBPCD: \n")); sti(); /* to avoid possible "printk" bug */ autoprobe[0]=sbpcd_ioaddr; /* possibly changed by kernel command line */ autoprobe[1]=sbpro_type; /* possibly changed by kernel command line */ for (port_index=0;port_index=0) break; /* drive found */ printk ("\n"); sti(); /* to avoid possible "printk" bug */ } /* end of cycling through the set of possible I/O port addresses */ if (ndrives==0) { DPRINTF((DBG_INF,"SBPCD: No drive found.\n")); sti(); return (mem_start); } DPRINTF((DBG_INF,"SBPCD: %d %s CD-ROM drive(s) at 0x%04X.\n", ndrives, type, CDo_command)); sti(); /* to avoid possible "printk" bug */ check_datarate(); DPRINTF((DBG_INI,"SBPCD: check_datarate done.\n")); sti(); /* to avoid possible "printk" bug */ for (j=0;j=0) DS[d].CD_changed=1; } if (sbpro_type) { OUT(MIXER_addr,MIXER_CD_Volume); OUT(MIXER_data,0xCC); /* one nibble per channel */ } if (register_blkdev(MATSUSHITA_CDROM_MAJOR, "sbpcd", &sbpcd_fops) != 0) { DPRINTF((DBG_INF,"SBPCD: Can't get MAJOR %d for Matsushita CDROM\n", MATSUSHITA_CDROM_MAJOR)); sti(); /* to avoid possible "printk" bug */ return (mem_start); } blk_dev[MATSUSHITA_CDROM_MAJOR].request_fn = DEVICE_REQUEST; read_ahead[MATSUSHITA_CDROM_MAJOR] = 4; /* just one frame */ snarf_region(CDo_command,4); #if SBPCD_USE_IRQ if (irqaction(SBPCD_INTR_NR, &sbpcd_sigaction)) { DPRINTF((DBG_INF,"SBPCD: Can't get IRQ%d for sbpcd driver\n", SBPCD_INTR_NR)); sti(); /* to avoid possible "printk" bug */ } #endif SBPCD_USE_IRQ /* * allocate memory for the frame buffers */ for (j=0;j