patch-2.1.17 linux/drivers/cdrom/bpcd.c
Next file: linux/drivers/char/Makefile
Previous file: linux/drivers/cdrom/Makefile
Back to the patch index
Back to the overall index
- Lines: 792
- Date:
Wed Dec 18 15:57:28 1996
- Orig file:
v2.1.16/linux/drivers/cdrom/bpcd.c
- Orig date:
Thu Jan 1 02:00:00 1970
diff -u --recursive --new-file v2.1.16/linux/drivers/cdrom/bpcd.c linux/drivers/cdrom/bpcd.c
@@ -0,0 +1,791 @@
+/*
+ bpcd.c (c) 1996 Grant R. Guenther <grant@torque.net>
+ Under the terms of the GNU public license.
+
+ bpcd.c is a driver for the MicroSolutions "backpack" CDrom,
+ an external parallel port device.
+
+ There are apparently two versions of the backpack protocol. This
+ driver knows about the version 2 protocol - as is used in the 4x
+ and 6x products. There is no support for the sound hardware that
+ is included in some models. It should not be difficult to add
+ support for the ATAPI audio play functions and the corresponding
+ ioctls.
+
+ The driver was developed by reverse engineering the protocol
+ and testing it on the backpack model 164550. This model
+ is actually a stock ATAPI drive packaged with a custom
+ ASIC that implements the IDE over parallel protocol.
+ I tested with a backpack that happened to contain a Goldstar
+ drive, but I've seen reports of Sony and Mitsumi drives as well.
+
+ bpcd.c can be compiled for version 1.2.13 of the Linux kernel
+ and for the 2.0 series. (It should also work with most of the
+ later 1.3 kernels.)
+
+ If you have a copy of the driver that has not been integrated into
+ the kernel source tree, you can compile the driver manually, as a
+ module. Ensure that /usr/include/linux and /usr/include/asm are
+ links to the correct include files for the target system. Compile
+ the driver with
+
+ cc -D__KERNEL__ -DMODULE -O2 -c bpcd.c
+
+ You must then load it with insmod. If you are using
+ MODVERSIONS, add the following to the cc command:
+
+ -DMODVERSIONS -I /usr/include/linux/modversions.h
+
+ Before attempting to access the new driver, you will need to
+ create a new device special file. The following commands will
+ do that for you:
+
+ mknod /dev/bpcd b 41 0
+ chown root:disk /dev/bpcd
+ chmod 660 /dev/bpcd
+
+ Afterward, you can mount a disk in the usual way:
+
+ mount -t iso9660 /dev/bpcd /cdrom
+
+ (assuming you have made a directory /cdrom to use as a
+ mount point).
+
+ The driver will attempt to detect which parallel port your
+ backpack is connected to. If this fails for any reason, you
+ can override it by specifying a port on the LILO command line
+ (for built in drivers) or the insmod command (for drivers built
+ as modules). If your drive is on the port at 0x3bc, you would
+ use one of these commands:
+
+ LILO: bpcd=0x3bc
+
+ insmod: insmod bpcd bp_base=0x3bc
+
+ The driver can detect if the parallel port supports 8-bit
+ transfers. If so, it will use them. You can force it to use
+ 4-bit (nybble) mode by setting the variable bp_nybble to 1 on
+ an insmod command, or using the following LILO parameters:
+
+ bpcd=0x3bc,1
+
+ (you must specify the correct port address if you use this method.)
+
+ There is currently no support for EPP or ECP modes. Also,
+ as far as I can tell, the MicroSolutions protocol does not
+ support interrupts in the 4-bit and 8-bit modes.
+
+ MicroSolutions' protocol allows for several drives to be
+ chained together off the same parallel port. Currently, this
+ driver will recognise only one of them. If you do have more
+ than one drive, it will choose the one with the lowest id number,
+ where the id number is the last two digits of the product's
+ serial number.
+
+ It is not currently possible to connect a printer to the chained
+ port on the BackPack and expect Linux to use both devices at once.
+
+ If you need to use this driver together with a printer on the
+ same port, build both the bpcd and lp drivers are modules.
+
+ Keep an eye on http://www.torque.net/bpcd.html for news and
+ other information about the driver. If you have any problems
+ with this driver, please send me, grant@torque.net, some mail
+ directly before posting into the newsgroups or mailing lists.
+
+*/
+
+#define BP_VERSION "0.14"
+
+#define BP_BASE 0 /* set to 0 for autoprobe */
+#define BP_REP 4
+
+#include <linux/version.h>
+#include <linux/module.h>
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/fs.h>
+#include <linux/major.h>
+#include <linux/kernel.h>
+#include <linux/tqueue.h>
+#include <linux/delay.h>
+#include <linux/cdrom.h>
+#include <linux/ioport.h>
+
+#include <asm/system.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#ifndef BPCD_MAJOR
+#define BPCD_MAJOR 41
+#endif
+
+#define MAJOR_NR BPCD_MAJOR
+
+/* set up defines for blk.h, why don't all drivers do it this way ? */
+
+#define DEVICE_NAME "BackPack"
+#define DEVICE_REQUEST do_bp_request
+#define DEVICE_NR(device) (MINOR(device))
+#define DEVICE_ON(device)
+#define DEVICE_OFF(device)
+
+#include <linux/blk.h>
+
+#define BP_TMO 300 /* timeout in jiffies */
+#define BP_DELAY 50 /* spin delay in uS */
+
+#define BP_SPIN (10000/BP_DELAY)*BP_TMO
+
+int bpcd_init(void);
+void bpcd_setup(char * str, int * ints);
+void cleanup_module( void );
+
+static int bp_open(struct inode *inode, struct file *file);
+static void do_bp_request(void);
+static void do_bp_read(void);
+static int bp_ioctl(struct inode *inode,struct file *file,
+ unsigned int cmd, unsigned long arg);
+static void bp_release (struct inode *inode, struct file *file);
+
+static int bp_detect(void);
+static int bp_lock(void);
+static void bp_unlock(void);
+static void bp_eject(void);
+static void bp_interrupt(void *data);
+
+static int bp_base = BP_BASE;
+static int bp_rep = BP_REP;
+static int bp_nybble = 0; /* force 4-bit mode ? */
+
+static int bp_unit_id;
+
+static int bp_access = 0; /* count of active opens ... */
+static int bp_mode = 1; /* 4- or 8-bit mode */
+static int bp_busy = 0; /* request being processed ? */
+static int bp_timeout; /* "interrupt" loop limiter */
+static int bp_sector; /* address of next requested sector */
+static int bp_count; /* number of blocks still to do */
+static char * bp_buf; /* buffer for request in progress */
+static char bp_buffer[2048]; /* raw block buffer */
+static int bp_bufblk = -1; /* block in buffer, in CD units,
+ -1 for nothing there */
+static int nyb_map[256]; /* decodes a nybble */
+static int PortCache = 0; /* cache of the control port */
+
+static struct tq_struct bp_tq = {0,0,bp_interrupt,NULL};
+
+/* kernel glue structures */
+
+static struct file_operations bp_fops = {
+ NULL, /* lseek - default */
+ block_read, /* read - general block-dev read */
+ block_write, /* write - general block-dev write */
+ NULL, /* readdir - bad */
+ NULL, /* select */
+ bp_ioctl, /* ioctl */
+ NULL, /* mmap */
+ bp_open, /* open */
+ bp_release, /* release */
+ block_fsync, /* fsync */
+ NULL, /* fasync */
+ NULL, /* media change ? */
+ NULL /* revalidate new media */
+};
+
+
+/* the MicroSolutions protocol uses bits 3,4,5 & 7 of the status port to
+ deliver a nybble in 4 bit mode. We use a table lookup to extract the
+ nybble value. The following function initialises the table.
+*/
+
+static void init_nyb_map( void )
+
+{ int i, j;
+
+ for(i=0;i<256;i++) {
+ j = (i >> 3) & 0x7;
+ if (i & 0x80) j |= 8;
+ nyb_map[i] = j;
+ }
+}
+
+int bpcd_init (void) /* preliminary initialisation */
+
+{ init_nyb_map();
+
+ if (bp_detect()) return -1;
+
+ if (register_blkdev(MAJOR_NR,"bpcd",&bp_fops)) {
+ printk("bpcd: unable to get major number %d\n",MAJOR_NR);
+ return -1;
+ }
+ blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
+ read_ahead[MAJOR_NR] = 8; /* 8 sector (4kB) read ahead */
+
+ return 0;
+}
+
+static int bp_open (struct inode *inode, struct file *file)
+
+{
+ if (file->f_mode & 2) return -EROFS; /* wants to write ? */
+
+ MOD_INC_USE_COUNT;
+
+ if (!bp_access)
+ if (bp_lock()) {
+ MOD_DEC_USE_COUNT;
+ return -ENXIO;
+ }
+ bp_access++;
+ return 0;
+}
+
+static void do_bp_request (void)
+
+{ if (bp_busy) return;
+ while (1) {
+ if ((!CURRENT) || (CURRENT->rq_status == RQ_INACTIVE)) return;
+ INIT_REQUEST;
+ if (CURRENT->cmd == READ) {
+ bp_sector = CURRENT->sector;
+ bp_count = CURRENT->nr_sectors;
+ bp_buf = CURRENT->buffer;
+ do_bp_read();
+ if (bp_busy) return;
+ }
+ else end_request(0);
+ }
+}
+
+static int bp_ioctl(struct inode *inode,struct file *file,
+ unsigned int cmd, unsigned long arg)
+
+/* we currently support only the EJECT ioctl. */
+
+{ switch (cmd) {
+ case CDROMEJECT: if (bp_access == 1) {
+ bp_eject();
+ return 0;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static void bp_release (struct inode *inode, struct file *file)
+
+{ kdev_t devp;
+
+ bp_access--;
+ if (!bp_access) {
+ devp = inode->i_rdev;
+ fsync_dev(devp);
+ invalidate_inodes(devp);
+ invalidate_buffers(devp);
+ bp_unlock();
+ }
+ MOD_DEC_USE_COUNT;
+}
+
+#ifdef MODULE
+
+/* Glue for modules ... */
+
+int init_module(void)
+
+{ int err;
+ long flags;
+
+ save_flags(flags);
+ cli();
+
+ err = bpcd_init();
+
+ restore_flags(flags);
+ return err;
+}
+
+void cleanup_module(void)
+
+{ long flags;
+
+ save_flags(flags);
+ cli();
+ unregister_blkdev(MAJOR_NR,"bpcd");
+ release_region(bp_base,3);
+ restore_flags(flags);
+}
+
+#else
+
+/* bpcd_setup: process lilo command parameters ...
+
+ syntax: bpcd=base[,nybble[,rep]]
+*/
+
+void bpcd_setup(char *str, int *ints)
+
+{ if (ints[0] > 0) bp_base = ints[1];
+ if (ints[0] > 1) bp_nybble = ints[2];
+ if (ints[0] > 2) bp_rep = ints[3];
+}
+
+#endif
+
+static void out_p( short port, char byte)
+
+{ int i;
+
+ for(i=0;i<bp_rep;i++) outb(byte,bp_base+port);
+}
+
+static int in_p( short port)
+
+{ int i;
+ char c;
+
+ for(i=0;i<bp_rep;i++) c=inb(bp_base+port);
+ c=inb(bp_base+port);
+ return c & 0xff;
+}
+
+/* Unlike other PP devices I've worked on, the backpack protocol seems
+ to be driven by *changes* in the values of certain bits on the control
+ port, rather than their absolute value. Hence the unusual macros ...
+*/
+
+#define w0(byte) out_p(0,byte)
+#define r0() (in_p(0) & 0xff)
+#define w1(byte) out_p(1,byte)
+#define r1() (in_p(1) & 0xff)
+#define w2(byte) out_p(2,byte) ; PortCache = byte
+#define t2(pat) PortCache ^= pat; out_p(2,PortCache)
+#define e2() PortCache &= 0xfe; out_p(2,PortCache)
+#define o2() PortCache |= 1; out_p(2,PortCache)
+
+static int read_byte( void )
+
+{ int l, h;
+
+ t2(4);
+ if (bp_mode == 2) return r0();
+ l = nyb_map[r1()];
+ t2(4);
+ h = nyb_map[r1()];
+ return (h << 4) + l;
+}
+
+static int read_regr( char regr )
+
+{ int r;
+
+ w0(regr & 0xf); w0(regr); t2(2);
+ if (bp_mode == 2) { e2(); t2(0x20); }
+ r = read_byte();
+ if (bp_mode == 2) { t2(1); t2(0x20); }
+ return r;
+}
+
+static void write_regr( char regr, char val )
+
+{ w0(regr);
+ t2(2);
+ w0(val);
+ o2(); t2(4); t2(1);
+}
+
+static void write_cmd( char * cmd, int len )
+
+{ int i, f;
+
+ if (bp_mode == 2) f = 0x10; else f = 0;
+ write_regr(4,0x40|f);
+ w0(0x40); t2(2); t2(1);
+ i = 0;
+ while (i < len) {
+ w0(cmd[i++]);
+ t2(4);
+ }
+ write_regr(4,f);
+}
+
+static void read_data( char * buf, int len )
+
+{ int i, f;
+
+ if (bp_mode == 2) f = 0x50; else f = 0x40;
+ write_regr(4,f);
+ w0(0x40); t2(2);
+ if (bp_mode == 2) t2(0x20);
+ for(i=0;i<len;i++) buf[i] = read_byte();
+ if (bp_mode == 2) { t2(1); t2(0x20); }
+}
+
+static int probe( int id )
+
+{ int l, h, t;
+ int r = -1;
+
+ w2(4); w2(0xe); w2(0xec); w1(0x7f);
+ if ((r1() & 0xf8) != 0x78) return -1;
+ w0(255-id); w2(4); w0(id);
+ t2(8); t2(8); t2(8);
+ t2(2); t = (r1() & 0xf8);
+ if (t != 0x78) {
+ l = nyb_map[t];
+ t2(2); h = nyb_map[r1()];
+ t2(8);
+ r = 0;
+ }
+ w0(0); t2(2); w2(0x4c); w0(0x13);
+ return r;
+}
+
+static void connect ( void )
+
+{ int f;
+
+ w0(0xff-bp_unit_id); w2(4); w0(bp_unit_id);
+ t2(8); t2(8); t2(8);
+ t2(2); t2(2); t2(8);
+ if (bp_mode == 2) f = 0x10; else f = 0;
+ write_regr(4,f);
+ write_regr(5,8);
+ write_regr(0x46,0x10);
+ write_regr(0x4c,0x38);
+ write_regr(0x4d,0x88);
+ write_regr(0x46,0xa0);
+ write_regr(0x41,0);
+ write_regr(0x4e,8);
+}
+
+static void disconnect ( void )
+
+{ w0(0); t2(2); w2(0x4c);
+ if (bp_mode == 2) w0(0xff); else w0(0x13);
+}
+
+static int bp_wait_drq( char * lab, char * fun )
+
+{ int j, r, e;
+
+ j = 0;
+
+ while (1) {
+ r = read_regr(0x47);
+ e = read_regr(0x41);
+ if ((r & 9) || (j++ >= BP_SPIN)) break;
+ udelay(BP_DELAY);
+ }
+
+ if ((j >= BP_SPIN) || (r & 1)) {
+ if (lab && fun)
+ printk("bpcd: %s (%s): %s status: %x error: %x\n",
+ lab,fun,(j >= BP_SPIN)?"timeout, ":"",r,e);
+ return -1;
+ }
+ return 0;
+}
+
+static int bp_wait( char * lab, char * fun )
+
+{ int j, r, e;
+
+ j = 0;
+ while ((!(read_regr(0xb) & 0x80)) && (j++ < BP_SPIN)) udelay(BP_DELAY);
+ r = read_regr(0x47);
+ e = read_regr(0x41);
+ if ((j >= BP_SPIN) || (r & 1)) {
+ if (lab && fun)
+ printk("bpcd: %s (%s): %s status: %x error: %x\n",
+ lab,fun,(j >= BP_SPIN)?"timeout, ":"",r,e);
+ return -1;
+ }
+ return 0;
+}
+
+static int bp_command( char * cmd, int dlen, char * fun )
+
+{ int r;
+
+ connect();
+ write_regr(0x44,dlen % 256);
+ write_regr(0x45,dlen / 256);
+ write_regr(0x46,0xa0); /* drive 0 */
+ write_regr(0x47,0xa0); /* ATAPI packet command */
+ if ((r=bp_wait_drq("bp_command",fun))) {
+ disconnect();
+ return r;
+ }
+ write_cmd(cmd,12);
+ return 0;
+}
+
+static int bp_completion( char * fun )
+
+{ int r, n;
+
+ if (!(r=bp_wait("bp_completion",fun))) {
+ if (read_regr(0x42) == 2) {
+ n = (read_regr(0x44) + 256*read_regr(0x45));
+ read_data(bp_buffer,n);
+ r=bp_wait("transfer done",fun);
+ }
+ }
+ disconnect();
+ return r;
+}
+
+static int bp_atapi( char * cmd, int dlen, char * fun )
+
+{ int r;
+
+ if (!(r=bp_command(cmd,dlen,fun)))
+ r = bp_completion(fun);
+ return r;
+}
+
+static int bp_req_sense( char * msg )
+
+{ char rs_cmd[12] = { 0x03,0,0,0,18,0,0,0,0,0,0,0 };
+ int r;
+
+ r = bp_atapi(rs_cmd,18,"request sense");
+ if (msg) printk("bpcd: %s: sense key: %x, ASC: %x, ASQ: %x\n",msg,
+ bp_buffer[2]&0xf, bp_buffer[12], bp_buffer[13]);
+ return r;
+}
+
+static int bp_lock(void)
+
+{ char lo_cmd[12] = { 0x1e,0,0,0,1,0,0,0,0,0,0,0 };
+ char cl_cmd[12] = { 0x1b,0,0,0,3,0,0,0,0,0,0,0 };
+
+ bp_atapi(cl_cmd,0,"close door");
+ if (bp_req_sense(NULL)) return -1; /* check for disk */
+ bp_atapi(lo_cmd,0,NULL);
+ bp_req_sense(NULL); /* in case there was a media change */
+ bp_atapi(lo_cmd,0,"lock door");
+ return 0;
+}
+
+static void bp_unlock( void)
+
+{ char un_cmd[12] = { 0x1e,0,0,0,0,0,0,0,0,0,0,0 };
+
+ bp_atapi(un_cmd,0,"unlock door");
+}
+
+static void bp_eject( void)
+
+{ char ej_cmd[12] = { 0x1b,0,0,0,2,0,0,0,0,0,0,0 };
+
+ bp_unlock();
+ bp_atapi(ej_cmd,0,"eject");
+}
+
+static int bp_reset( void )
+
+/* the ATAPI standard actually specifies the contents of all 7 registers
+ after a reset, but the specification is ambiguous concerning the last
+ two bytes, and different drives interpret the standard differently.
+*/
+
+{ int i, flg;
+ int expect[5] = {1,1,1,0x14,0xeb};
+ long flags;
+
+ connect();
+ write_regr(0x46,0xa0);
+ write_regr(0x47,8);
+
+ save_flags(flags);
+ sti();
+ udelay(500000); /* delay 0.5 seconds */
+ restore_flags(flags);
+
+ flg = 1;
+ for(i=0;i<5;i++) flg &= (read_regr(i+0x41) == expect[i]);
+
+ disconnect();
+ return flg-1;
+}
+
+static int bp_identify( char * id )
+
+{ int k;
+ char id_cmd[12] = {0x12,0,0,0,36,0,0,0,0,0,0,0};
+
+ bp_bufblk = -1;
+ if (bp_atapi(id_cmd,36,"identify")) return -1;
+ for (k=0;k<16;k++) id[k] = bp_buffer[16+k];
+ id[16] = 0;
+ return 0;
+}
+
+static int bp_port_check( void ) /* check for 8-bit port */
+
+{ int r;
+
+ w2(0);
+ w0(0x55); if (r0() != 0x55) return 0;
+ w0(0xaa); if (r0() != 0xaa) return 0;
+ w2(0x20); w0(0x55); r = r0(); w0(0xaa);
+ if (r0() == r) return 2;
+ if (r0() == 0xaa) return 1;
+ return 0;
+}
+
+static int bp_locate( void )
+
+{ int k;
+
+ for(k=0;k<100;k++)
+ if (!probe(k)) {
+ bp_unit_id = k;
+ return 0;
+ }
+ return -1;
+}
+
+static int bp_do_detect( int autop )
+
+{ char id[18];
+
+ if (autop) bp_base = autop;
+
+ if (check_region(bp_base,3)) {
+ if (!autop)
+ printk("bpcd: Ports at 0x%x are not available\n",bp_base);
+ return -1;
+ }
+
+ bp_mode = bp_port_check();
+
+ if (!bp_mode) {
+ if (!autop)
+ printk("bpcd: No parallel port at 0x%x\n",bp_base);
+ return -1;
+ }
+
+ if (bp_nybble) bp_mode = 1;
+
+ if (bp_locate()) {
+ if (!autop)
+ printk("bpcd: Couldn't find a backpack adapter at 0x%x\n",
+ bp_base);
+ return -1;
+ }
+
+ if (bp_reset()) {
+ if (!autop)
+ printk("bpcd: Failed to reset CD drive\n");
+ return -1;
+ }
+
+ if (bp_identify(id)) {
+ if (!autop)
+ printk("bpcd: ATAPI inquiry failed\n");
+ return -1;
+ }
+
+ request_region(bp_base,3,"bpcd");
+
+ printk("bpcd: Found %s, ID %d, using port 0x%x in %d-bit mode\n",
+ id,bp_unit_id,bp_base,4*bp_mode);
+
+ return 0;
+}
+
+/* If you know about some other weird parallel port base address,
+ add it here ....
+*/
+
+static int bp_detect( void )
+
+{ if (bp_base) return bp_do_detect(0);
+ if (!bp_do_detect(0x378)) return 0;
+ if (!bp_do_detect(0x278)) return 0;
+ if (!bp_do_detect(0x3bc)) return 0;
+ printk("bpcd: Autoprobe failed\n");
+ return -1;
+}
+
+
+static void bp_transfer( void )
+
+{ int k, o;
+
+ while (bp_count && (bp_sector/4 == bp_bufblk)) {
+ o = (bp_sector % 4) * 512;
+ for(k=0;k<512;k++) bp_buf[k] = bp_buffer[o+k];
+ bp_count--;
+ bp_buf += 512;
+ bp_sector++;
+ }
+}
+
+static void do_bp_read( void )
+
+{ int b, i;
+ char rd_cmd[12] = {0xa8,0,0,0,0,0,0,0,0,1,0,0};
+
+ bp_busy = 1;
+ bp_transfer();
+ if (!bp_count) {
+ end_request(1);
+ bp_busy = 0;
+ return;
+ }
+ sti();
+
+ bp_bufblk = bp_sector / 4;
+ b = bp_bufblk;
+ for(i=0;i<4;i++) {
+ rd_cmd[5-i] = b & 0xff;
+ b = b >> 8;
+ }
+
+ if (bp_command(rd_cmd,2048,"read block")) {
+ bp_bufblk = -1;
+ bp_busy = 0;
+ cli();
+ bp_req_sense("send read command");
+ end_request(0);
+ return;
+ }
+ bp_timeout = jiffies + BP_TMO;
+ queue_task(&bp_tq,&tq_scheduler);
+}
+
+static void bp_interrupt( void *data)
+
+{ if (!(read_regr(0xb) & 0x80)) {
+ if (jiffies > bp_timeout) {
+ bp_bufblk = -1;
+ bp_busy = 0;
+ bp_req_sense("interrupt timeout");
+ end_request(0);
+ do_bp_request();
+ }
+ queue_task(&bp_tq,&tq_scheduler);
+ return;
+ }
+ sti();
+ if (bp_completion("read completion")) {
+ cli();
+ bp_busy = 0;
+ bp_bufblk = -1;
+ bp_req_sense("read completion");
+ end_request(0);
+ do_bp_request();
+ }
+ do_bp_read();
+ do_bp_request();
+}
+
+/* end of bpcd.c */
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov