These are patches for a prototype driver for the NEC CDR-260 cd-rom drive. They are to be applied in linux/drivers/block. The patches were made against v1.1 of the kernel, but they should work with earlier kernels as well, back at least to 0.99pl15. This code assumes that the cdrom is the slave device on the first IDE bus. If it isn't register addresses and the unit selection may need to be changed. The major,minor numbers i use are 22,0. Create an appropriate device (i called it /dev/idecd). E.g., `mknod /dev/idecd b 22 0'. You should then be able to cat from the device, or mount an iso9660 filesystem and access it normally. No audio functions are supported at present. One warning if you have a hard drive on the same controller: since this driver has to get at the cdrom through the same controller to which the hard drive it attached, it has every opportunity to screw up the hard drive. I have not seen any such problems, but i'm not making any promises. Backups are recommended... I have had a report that this code is not compatible with some of the other cd-rom drivers. So if you have trouble getting it to work and you have other cd-rom drivers configured into the kernel, try removing them and see if that helps. Things which would be nice to do in the near future are integrating this better into the linux configuration process, and maybe turning it into a loadable module. Adding audio functions would also be nice, but more difficult since i've been working with absolutely no technical documentation on this device. Hope this helps someone, sss snyder@fnald0.fnal.gov *** Makefile.orig Sun Mar 6 08:08:21 1994 --- Makefile Sun May 22 02:09:06 1994 *************** *** 49,54 **** --- 49,57 ---- SRCS := $(SRCS) xd.c endif + OBJS := $(OBJS) idecd.o + SRCS := $(SRCS) idecd.c + all: block.a block.a: $(OBJS) *** blk.h.orig Wed Apr 6 02:40:57 1994 --- blk.h Sun May 22 02:09:24 1994 *************** *** 1,6 **** --- 1,8 ---- #ifndef _BLK_H #define _BLK_H + #define IDE_CDROM_MAJOR 22 + #include #include #include *************** *** 196,201 **** --- 198,212 ---- #define DEVICE_NR(device) (MINOR(device)) #define DEVICE_ON(device) #define DEVICE_OFF(device) + + #elif (MAJOR_NR == IDE_CDROM_MAJOR) + + #define DEVICE_NAME "NEC IDE CD-ROM" + #define DEVICE_REQUEST do_idecd_request + #define DEVICE_NR(device) (MINOR(device)) + #define DEVICE_ON(device) + #define DEVICE_OFF(device) + #define DEVICE_INTR do_idecd #else *** hd.c.orig Fri Jan 21 06:50:11 1994 --- hd.c Tue May 31 01:35:03 1994 *************** *** 1,3 **** --- 1,4 ---- + #define IDECD /* * linux/kernel/hd.c * *************** *** 422,427 **** --- 423,431 ---- if (!CURRENT) return; printk(KERN_DEBUG "HD timeout\n"); + #ifdef IDECD + if (CURRENT->dev < 0) return; + #endif cli(); if (++CURRENT->errors >= MAX_ERRORS) { #ifdef DEBUG *************** *** 452,457 **** --- 456,468 ---- return; repeat: timer_active &= ~(1<dev < 0) return; + #endif sti(); INIT_REQUEST; dev = MINOR(CURRENT->dev); *** idecd.c.orig Sun May 22 02:10:09 1994 --- idecd.c Tue May 31 19:12:18 1994 *************** *** 0 **** --- 1,455 ---- + /* + * idecd.c - NEC CDR-260 driver + * some pieces derived from mcd.c. + * + * scott snyder + * + * 0.1 May 31, 1994 Initial alpha release. + * + * Copyright (c) 1994 scott snyder + * + * 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. + */ + + + #define MAJOR_NR 22 + #include "blk.h" + + #define REALLY_SLOW_IO + #include + + #include + #include + #include + #include + #include + + #define CD_BLOCK_SIZE 2048 + + static void idecd_timeout (unsigned long x); + static void do_read_callback (void); + static void do_read_callback_2 (void); + + /* Values to write to the controller registers to start a transaction. */ + + static char regimage[7] = { + 0, /* HD_PRECOMP */ + 0, /* HD_NSECTOR */ + 0, /* HD_SECTOR */ + (CD_BLOCK_SIZE & 0xff), /* HD_LCYL # of bytes to read (lo) */ + (CD_BLOCK_SIZE >> 8), /* HD_HCYL # of bytes to read (hi) */ + 0xf0, /* HD_CURRENT (device select) */ + 0xa0 /* HD_COMMAND */ + }; + + + /* Image of command to be sent to the device for a read. + Portions of this will be overwritten to set the sector number. + If i actually had documentation for this device, i could probably + give these values some meaningful names... */ + + static char cmdimage[12] = {0x28, 0, 0, 0, 0x0f, 0x54, 0, 0, 1, 0, 0, 0}; + + + static int idecd_request_in_progress = 0; + static int idecd_waiting_for_int = 0; + + extern void (*do_hd)(void); + + static struct timer_list idecd_timer = {NULL, NULL, 0, 0, idecd_timeout}; + + /* The idecd buffer. */ + static char idecd_buf[CD_BLOCK_SIZE]; + static int idecd_bn = -1; + + /* The dummy request which we use to plug up the HD queue. */ + static struct request plug; + + /* + * Wait until the controller is idle. + * Returns the status field; if it has the bit BUSY_STAT set, + * we timed out. + */ + static int controller_busy (void) + { + int retries = 100000; + unsigned char status; + + do + { + status = inb_p(HD_STATUS); + } while ((status & BUSY_STAT) && --retries); + return status; + } + + + /* + * Wait for the controller to be ready to receive a command. + * Returns 0 if successful, -1 if we timed out. + */ + static inline int wait_DRQ (void) + { + int retries = 100000; + + while (--retries > 0) + if (inb_p(HD_STATUS) & DRQ_STAT) + return 0; + return -1; + } + + + /* + * Stick a dummy request at the head of the HD request queue + * to prevent any HD activity while we're using the controller. + * The HD queue must be empty. + */ + static void plug_hd (void) + { + cli (); /* safety */ + if (blk_dev[HD_MAJOR].current_request != NULL) { + printk ("idecd (plug_hd): hd already active!\n"); + return; + } + blk_dev[HD_MAJOR].current_request = &plug; + plug.dev = -1; + plug.next = NULL; + + /* exits with ints clear */ + } + + + /* + * Remove the dummy request from the start of the HD queue. + */ + static void unplug_hd (void) + { + cli (); /* safety */ + if (blk_dev[HD_MAJOR].current_request != &plug) { + printk ("idecd (unplug_hd): hd not plugged!\n"); + return; + } + blk_dev[HD_MAJOR].current_request = plug.next; + (blk_dev[HD_MAJOR].request_fn) (); + } + + + /* + * Unplug the HD queue and end a idecd request. + */ + static void end_read_request (int flag) + { + unplug_hd (); + end_request (flag); + idecd_request_in_progress = 0; + } + + + /* + * Transfer as much data as we can from IDECD_BUF to the output buffer. + */ + static void + idecd_transfer(void) + { + long offs; + + while (CURRENT -> nr_sectors > 0 && idecd_bn == CURRENT -> sector / 4) + { + offs = (CURRENT -> sector & 3) * 512; + memcpy (CURRENT -> buffer, idecd_buf + offs, 512); + CURRENT -> nr_sectors--; + CURRENT -> sector++; + CURRENT -> buffer += 512; + } + } + + + /* + * Complete a read request with status STAT, and call the request routine + * to start off the next one. + */ + static void complete_read_request (int stat) + { + del_timer (&idecd_timer); /* Cancel the timeout */ + end_read_request (stat); + cli (); + do_idecd_request (); + } + + + /* + * Called when our timer goes off. + */ + static void idecd_timeout (unsigned long x) + { + /* Ignore if we're not waiting for an interrupt. */ + if (! idecd_waiting_for_int) return; + + /* Complete the request with error status. */ + idecd_waiting_for_int = 0; + printk ("idecd: request timed out\n"); + if (do_hd == do_read_callback || do_hd == do_read_callback_2) + do_hd = NULL; + else + printk ("idecd: funny value for interrupt handler\n"); + complete_read_request (0); + } + + /* + * Interrupt routine to swallow the extra interrupt from the device. + */ + static void do_read_callback_2 (void) + { + int stat; + + if (! idecd_waiting_for_int) { + printk ("idecd (do_read_callback_2): spurious call?\n"); + return; + } + + idecd_waiting_for_int = 0; + + /* Check error flag and complete the I/O. */ + stat = inb (HD_ERROR); + stat = ((stat & ERR_STAT) == 0); + complete_read_request (stat); + } + + + /* + * Interrupt routine. Called when a read request has completed. + */ + static void do_read_callback (void) + { + int stat, len, thislen; + + if (! idecd_waiting_for_int) { + printk ("idecd (do_read_callback): spurious call?\n"); + return; + } + + idecd_waiting_for_int = 0; + + /* Check for errors. */ + stat = inb (HD_ERROR); + if (stat & ERR_STAT) + stat = 0; + else + { + /* No error. + Read the device registers to see how much data is waiting. */ + len = inb_p (HD_LCYL) + 256 * inb_p (HD_HCYL); + + /* Read the data into our buffer. */ + thislen = len; + if (thislen > sizeof (idecd_buf)) thislen = sizeof (idecd_buf); + insw (HD_DATA, idecd_buf, thislen/2); + len -= thislen; + + /* Warn if the size of the data from the device is + larger than the buffer. */ + if (len > 0) + { + printk ("discarding %x bytes\n", len); + while (len > 0) + { + (void) inw_p (HD_DATA); + len -= 2; + } + } + + /* Copy as much as we can into the output buffer. */ + idecd_bn = CURRENT->sector / 4; + idecd_transfer (); + + stat = 1; + } + + /* If there was an error, complete the request now with an error. + But if the read is successful, the device is going to be sending + us another interrupt. It likes long goodbyes, i guess. Anyway, + wait until we see this extra interrupt before ending the request. */ + if (stat) + { + do_hd = do_read_callback_2; + idecd_waiting_for_int = 1; + } + else + complete_read_request (stat); + } + + + /* + * Start a read request from the CD-ROM. + * Returns 0 if the request was started successfully, + * -1 if there was an error. + * Note: The NSECT arg is presently ignored; we always read exactly + * one block. + */ + static int do_read (unsigned int dev, unsigned int block, unsigned int nsect) + { + int i; + + /* Wait for the controller to be idle. */ + if (controller_busy () & BUSY_STAT) return -1; + + /* Set up the controller registers. */ + for (i=0; isector / 4; + cmdimage[2] = conv.b.b3; + cmdimage[3] = conv.b.b2; + cmdimage[4] = conv.b.b1; + cmdimage[5] = conv.b.b0; + } + + /* Send the command to the device. */ + outsw (HD_DATA, cmdimage, sizeof (cmdimage)/2); + + /* Set up our interrupt handler and return. */ + do_hd = do_read_callback; + idecd_waiting_for_int = 1; + + /* Set up a timeout. */ + idecd_timer.expires = 500; + add_timer (&idecd_timer); + + return 0; + } + + + /* + * Start a read request. + * If there is an error starting it, terminate the current request + * immediately with an error. + */ + static int test_read (unsigned int dev, unsigned int block, + unsigned int nsect) + { + int stat; + stat = do_read (dev, block, nsect); + + if (stat) + end_read_request (0); + return 1; + } + + + /* + * I/O request routine called from kernel. + */ + static void do_idecd_request (void) + { + unsigned int block,dev; + unsigned int nsect; + + /* Don't do anything if we're waiting for a request to complete. + (Can this actually happen?) */ + if (idecd_request_in_progress) return; + + repeat: + cli (); /* safety */ + idecd_request_in_progress = 0; + + /* Return if our queue is plugged. */ + if (!(CURRENT) || CURRENT->dev < 0) return; + + /* Get the next request on the queue. */ + INIT_REQUEST; + dev = MINOR (CURRENT->dev); + block = CURRENT->sector; + nsect = CURRENT->nr_sectors; + + /* Return if there wasn't one. */ + if (CURRENT == NULL || CURRENT -> sector == -1) + return; + + /* We can only read. */ + if (CURRENT -> cmd != READ) + { + printk ("idecd: bad cmd %d\n", CURRENT -> cmd); + end_request (0); + goto repeat; + } + + /* Try to satisfy the request from the buffer. */ + idecd_transfer (); + + /* If we got the entire request, we're done. */ + if (CURRENT->nr_sectors == 0) + { + end_request (1); + goto repeat; + } + + /* If the HD is currently active, return - we must wait for it to finish. + If it is idle (no requests in the queue), plug up the queue with a dummy + request until we're done using the controller. */ + if (blk_dev[HD_MAJOR].current_request) return; + idecd_request_in_progress = 1; + plug_hd (); + + if (!test_read (dev, block, nsect)) + goto repeat; + + sti (); + return; + } + + + static struct file_operations idecd_fops = { + NULL, /* lseek - default */ + block_read, /* read - general block-dev read */ + block_write, /* write - general block-dev write */ + NULL, /* readdir - bad */ + NULL, /* select */ + NULL, /*mcd_ioctl*/ /* ioctl */ + NULL, /* mmap */ + NULL /*mcd_open*/, /* open */ + NULL /*mcd_release*/ /* release */ + }; + + + /* + * Called at boot time to initialize the driver. + */ + + unsigned long + idecd_init (unsigned long mem_start, unsigned long mem_end) + { + /* Register ourselves with the kernel, installing our file_operations + table. */ + if (register_blkdev (MAJOR_NR, "idecd", &idecd_fops) != 0) + { + printk("idecd: Unable to get major %d for IDE CD-ROM\n", MAJOR_NR); + return mem_start; + } + + /* should do some sort of probing?? */ + + /* Install our device request routine. */ + blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; + read_ahead[MAJOR_NR] = 4; + + printk("idecd: initialized\n"); + + return mem_start; + } *** ll_rw_blk.c.orig Tue Feb 1 01:03:51 1994 --- ll_rw_blk.c Sun May 22 02:09:56 1994 *************** *** 22,28 **** --- 22,30 ---- #ifdef CONFIG_SBPCD extern u_long sbpcd_init(u_long, u_long); #endif CONFIG_SBPCD + extern u_long idecd_init(u_long, u_long); + /* * The request-struct contains all necessary data * to load a nr of sectors into memory *************** *** 497,502 **** --- 499,505 ---- #ifdef CONFIG_SBPCD mem_start = sbpcd_init(mem_start, mem_end); #endif CONFIG_SBPCD + mem_start = idecd_init (mem_start, mem_end); if (ramdisk_size) mem_start += rd_init(mem_start, ramdisk_size*1024); return mem_start;