summaryrefslogtreecommitdiffstats
path: root/drivers/block/ide-cd.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/block/ide-cd.c')
-rw-r--r--drivers/block/ide-cd.c2126
1 files changed, 2126 insertions, 0 deletions
diff --git a/drivers/block/ide-cd.c b/drivers/block/ide-cd.c
new file mode 100644
index 000000000..1aef40dcf
--- /dev/null
+++ b/drivers/block/ide-cd.c
@@ -0,0 +1,2126 @@
+/*
+ * linux/drivers/block/ide-cd.c
+ *
+ * 1.00 Oct 31, 1994 -- Initial version.
+ * 1.01 Nov 2, 1994 -- Fixed problem with starting request in
+ * cdrom_check_status.
+ * 1.03 Nov 25, 1994 -- leaving unmask_intr[] as a user-setting (as for disks)
+ * (from mlord) -- minor changes to cdrom_setup()
+ * -- renamed ide_dev_s to ide_dev_t, enable irq on command
+ * 2.00 Nov 27, 1994 -- Generalize packet command interface;
+ * add audio ioctls.
+ * 2.01 Dec 3, 1994 -- Rework packet command interface to handle devices
+ * which send an interrupt when ready for a command.
+ * 2.02 Dec 11, 1994 -- Cache the TOC in the driver.
+ * Don't use SCMD_PLAYAUDIO_TI; it's not included
+ * in the current version of ATAPI.
+ * Try to use LBA instead of track or MSF addressing
+ * when possible.
+ * Don't wait for READY_STAT.
+ * 2.03 Jan 10, 1995 -- Rewrite block read routines to handle block sizes
+ * other than 2k and to move multiple sectors in a
+ * single transaction.
+ * 2.04 Apr 21, 1995 -- Add work-around for Creative Labs CD220E drives.
+ * Thanks to Nick Saw <cwsaw@pts7.pts.mot.com> for
+ * help in figuring this out. Ditto for Acer and
+ * Aztech drives, which seem to have the same problem.
+ * 2.04b May 30, 1995 -- Fix to match changes in ide.c version 3.16 -ml
+ * 2.05 Jun 8, 1995 -- Don't attempt to retry after an illegal request
+ * or data protect error.
+ * Use HWIF and DEV_HWIF macros as in ide.c.
+ * Always try to do a request_sense after
+ * a failed command.
+ * Include an option to give textual descriptions
+ * of ATAPI errors.
+ * Fix a bug in handling the sector cache which
+ * showed up if the drive returned data in 512 byte
+ * blocks (like Pioneer drives). Thanks to
+ * Richard Hirst <srh@gpt.co.uk> for diagnosing this.
+ * Properly supply the page number field in the
+ * MODE_SELECT command.
+ * PLAYAUDIO12 is broken on the Aztech; work around it.
+ *
+ *
+ * ATAPI cd-rom driver. To be used with ide.c.
+ *
+ * Copyright (C) 1994, 1995 scott snyder <snyder@fnald0.fnal.gov>
+ * May be copied or modified under the terms of the GNU General Public License
+ * (../../COPYING).
+ */
+
+
+/* Turn this on to have the driver print out the meanings of the
+ ATAPI error codes. This will use up additional kernel-space
+ memory, though. */
+
+#ifndef VERBOSE_IDE_CD_ERRORS
+#define VERBOSE_IDE_CD_ERRORS 0
+#endif
+
+/***************************************************************************/
+
+#include <linux/cdrom.h>
+
+#define SECTOR_SIZE 512
+#define SECTOR_BITS 9
+#define SECTORS_PER_FRAME (CD_FRAMESIZE / SECTOR_SIZE)
+
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+#if 1 /* "old" method */
+#define OUT_WORDS(b,n) outsw (IDE_PORT (HD_DATA, DEV_HWIF), (b), (n))
+#define IN_WORDS(b,n) insw (IDE_PORT (HD_DATA, DEV_HWIF), (b), (n))
+#else /* "new" method -- should really fix each instance instead of this */
+#define OUT_WORDS(b,n) output_ide_data(dev,b,(n)/2)
+#define IN_WORDS(b,n) input_ide_data(dev,b,(n)/2)
+#endif
+
+/* special command codes for strategy routine. */
+#define PACKET_COMMAND 4315
+#define REQUEST_SENSE_COMMAND 4316
+
+#define WIN_PACKETCMD 0xa0 /* Send a packet command. */
+
+/* Some ATAPI command opcodes (just like SCSI).
+ (Some other cdrom-specific codes are in cdrom.h.) */
+#define TEST_UNIT_READY 0x00
+#define REQUEST_SENSE 0x03
+#define START_STOP 0x1b
+#define ALLOW_MEDIUM_REMOVAL 0x1e
+#define READ_10 0x28
+#define MODE_SENSE_10 0x5a
+#define MODE_SELECT_10 0x55
+
+
+/* ATAPI sense keys (mostly copied from scsi.h). */
+
+#define NO_SENSE 0x00
+#define RECOVERED_ERROR 0x01
+#define NOT_READY 0x02
+#define MEDIUM_ERROR 0x03
+#define HARDWARE_ERROR 0x04
+#define ILLEGAL_REQUEST 0x05
+#define UNIT_ATTENTION 0x06
+#define DATA_PROTECT 0x07
+#define ABORTED_COMMAND 0x0b
+#define MISCOMPARE 0x0e
+
+
+struct packet_command {
+ char *buffer;
+ int buflen;
+ int stat;
+ unsigned char c[12];
+};
+
+
+struct atapi_request_sense {
+ unsigned char error_code : 7;
+ unsigned char valid : 1;
+ byte reserved1;
+ unsigned char sense_key : 4;
+ unsigned char reserved2 : 1;
+ unsigned char ili : 1;
+ unsigned char reserved3 : 2;
+ byte info[4];
+ byte sense_len;
+ byte command_info[4];
+ byte asc;
+ byte ascq;
+ byte fru;
+ byte sense_key_specific[3];
+};
+
+/* We want some additional flags for cd-rom drives.
+ To save space in the ide_dev_t struct, use one of the fields which
+ doesn't make sense for cd-roms -- `bios_sect'. */
+
+struct ide_cd_flags {
+ unsigned drq_interrupt : 1; /* Device sends an interrupt when ready
+ for a packet command. */
+ unsigned no_playaudio12: 1; /* The PLAYAUDIO12 command is not supported. */
+
+ unsigned media_changed : 1; /* Driver has noticed a media change. */
+ unsigned toc_valid : 1; /* Saved TOC information is current. */
+ unsigned no_lba_toc : 1; /* Drive cannot return TOC info in LBA format. */
+ unsigned msf_as_bcd : 1; /* Drive uses BCD in PLAYAUDIO_MSF. */
+ unsigned reserved : 2;
+};
+
+#define CDROM_FLAGS(dev) ((struct ide_cd_flags *)&((dev)->bios_sect))
+
+
+/* Space to hold the disk TOC. */
+
+#define MAX_TRACKS 99
+struct atapi_toc_header {
+ unsigned short toc_length;
+ byte first_track;
+ byte last_track;
+};
+
+struct atapi_toc_entry {
+ byte reserved1;
+ unsigned control : 4;
+ unsigned adr : 4;
+ byte track;
+ byte reserved2;
+ unsigned lba;
+};
+
+struct atapi_toc {
+ struct atapi_toc_header hdr;
+ struct atapi_toc_entry ent[MAX_TRACKS+1]; /* One extra for the leadout. */
+};
+
+
+#define SECTOR_BUFFER_SIZE CD_FRAMESIZE
+
+/* Extra per-device info for cdrom drives. */
+struct cdrom_info {
+
+ /* Buffer for table of contents. NULL if we haven't allocated
+ a TOC buffer for this device yet. */
+
+ struct atapi_toc *toc;
+
+ /* Sector buffer. If a read request wants only the first part of a cdrom
+ block, we cache the rest of the block here, in the expectation that that
+ data is going to be wanted soon. SECTOR_BUFFERED is the number of the
+ first buffered sector, and NSECTORS_BUFFERED is the number of sectors
+ in the buffer. Before the buffer is allocated, we should have
+ SECTOR_BUFFER == NULL and NSECTORS_BUFFERED == 0. */
+
+ unsigned long sector_buffered;
+ unsigned long nsectors_buffered;
+ char *sector_buffer;
+
+ /* The result of the last successful request sense command
+ on this device. */
+ struct atapi_request_sense sense_data;
+};
+
+
+static struct cdrom_info cdrom_info[2][MAX_DRIVES];
+
+/* Statically allocate one request packet and one packet command struct
+ for each interface for retrieving sense data during error recovery. */
+
+static struct request request_sense_request[2];
+static struct packet_command request_sense_pc[2];
+
+
+
+/****************************************************************************
+ * Descriptions of ATAPI error codes.
+ */
+
+#define ARY_LEN(a) ((sizeof(a) / sizeof(a[0])))
+
+#if VERBOSE_IDE_CD_ERRORS
+
+/* From Table 124 of the ATAPI 1.2 spec. */
+
+char *sense_key_texts[16] = {
+ "No sense data",
+ "Recovered error",
+ "Not ready",
+ "Medium error",
+ "Hardware error",
+ "Illegal request",
+ "Unit attention",
+ "Data protect",
+ "(reserved)",
+ "(reserved)",
+ "(reserved)",
+ "Aborted command",
+ "(reserved)",
+ "(reserved)",
+ "Miscompare",
+ "(reserved)",
+};
+
+
+/* From Table 125 of the ATAPI 1.2 spec. */
+
+struct {
+ short asc_ascq;
+ char *text;
+} sense_data_texts[] = {
+ { 0x0000, "No additional sense information" },
+ { 0x0011, "Audio play operation in progress" },
+ { 0x0012, "Audio play operation paused" },
+ { 0x0013, "Audio play operation successfully completed" },
+ { 0x0014, "Audio play operation stopped due to error" },
+ { 0x0015, "No current audio status to return" },
+
+ { 0x0200, "No seek complete" },
+
+ { 0x0400, "Logical unit not ready - cause not reportable" },
+ { 0x0401, "Logical unit not ready - in progress (sic) of becoming ready" },
+ { 0x0402, "Logical unit not ready - initializing command required" },
+ { 0x0403, "Logical unit not ready - manual intervention required" },
+
+ { 0x0600, "No reference position found" },
+
+ { 0x0900, "Track following error" },
+ { 0x0901, "Tracking servo failure" },
+ { 0x0902, "Focus servo failure" },
+ { 0x0903, "Spindle servo failure" },
+
+ { 0x1100, "Unrecovered read error" },
+ { 0x1106, "CIRC unrecovered error" },
+
+ { 0x1500, "Random positioning error" },
+ { 0x1501, "Mechanical positioning error" },
+ { 0x1502, "Positioning error detected by read of medium" },
+
+ { 0x1700, "Recovered data with no error correction applied" },
+ { 0x1701, "Recovered data with retries" },
+ { 0x1702, "Recovered data with positive head offset" },
+ { 0x1703, "Recovered data with negative head offset" },
+ { 0x1704, "Recovered data with retries and/or CIRC applied" },
+ { 0x1705, "Recovered data using previous sector ID" },
+
+ { 0x1800, "Recovered data with error correction applied" },
+ { 0x1801, "Recovered data with error correction and retries applied" },
+ { 0x1802, "Recovered data - the data was auto-reallocated" },
+ { 0x1803, "Recovered data with CIRC" },
+ { 0x1804, "Recovered data with L-EC" },
+ { 0x1805, "Recovered data - recommend reassignment" },
+ { 0x1806, "Recovered data - recommend rewrite" },
+
+ { 0x1a00, "Parameter list length error" },
+
+ { 0x2000, "Invalid command operation code" },
+
+ { 0x2100, "Logical block address out of range" },
+
+ { 0x2400, "Invalid field in command packet" },
+
+ { 0x2600, "Invalid field in parameter list" },
+ { 0x2601, "Parameter not supported" },
+ { 0x2602, "Parameter value invalid" },
+ { 0x2603, "Threshold parameters not supported" },
+
+ { 0x2800, "Not ready to ready transition, medium may have changed" },
+
+ { 0x2900, "Power on, reset or bus device reset occurred" },
+
+ { 0x2a00, "Parameters changed" },
+ { 0x2a01, "Mode parameters changed" },
+
+ { 0x3000, "Incompatible medium installed" },
+ { 0x3001, "Cannot read medium - unknown format" },
+ { 0x3002, "Cannot read medium - incompatible format" },
+
+ { 0x3700, "Rounded parameter" },
+
+ { 0x3900, "Saving parameters not supported" },
+
+ { 0x3a00, "Medium not present" },
+
+ { 0x3f00, "ATAPI CD-ROM drive operating conditions have changed" },
+ { 0x3f01, "Microcode has been changed" },
+ { 0x3f02, "Changed operating definition" },
+ { 0x3f03, "Inquiry data has changed" },
+
+ { 0x4000, "Diagnostic failure on component (ASCQ)" },
+
+ { 0x4400, "Internal ATAPI CD-ROM drive failure" },
+
+ { 0x4e00, "Overlapped commands attempted" },
+
+ { 0x5300, "Media load or eject failed" },
+ { 0x5302, "Medium removal prevented" },
+
+ { 0x5700, "Unable to recover table of contents" },
+
+ { 0x5a00, "Operator request or state change input (unspecified)" },
+ { 0x5a01, "Operator medium removal request" },
+
+ { 0x5b00, "Threshold condition met" },
+
+ { 0x5c00, "Status change" },
+
+ { 0x6300, "End of user area encountered on this track" },
+
+ { 0x6400, "Illegal mode for this track" },
+
+ { 0xbf00, "Loss of streaming" },
+};
+#endif
+
+
+
+/****************************************************************************
+ * Generic packet command support routines.
+ */
+
+
+static
+void cdrom_analyze_sense_data (ide_dev_t *dev,
+ struct atapi_request_sense *reqbuf,
+ struct packet_command *failed_command)
+{
+ /* Don't print not ready or unit attention errors for READ_SUBCHANNEL.
+ Workman (and probably other programs) uses this command to poll
+ the drive, and we don't want to fill the syslog with useless errors. */
+ if (failed_command &&
+ failed_command->c[0] == SCMD_READ_SUBCHANNEL &&
+ (reqbuf->sense_key == 2 || reqbuf->sense_key == 6))
+ return;
+
+#if VERBOSE_IDE_CD_ERRORS
+ {
+ int i;
+ char *s;
+ char buf[80];
+
+ printk ("ATAPI device %s:\n", dev->name);
+
+ printk (" Error code: %x\n", reqbuf->error_code);
+
+ if (reqbuf->sense_key >= 0 &&
+ reqbuf->sense_key < ARY_LEN (sense_key_texts))
+ s = sense_key_texts[reqbuf->sense_key];
+ else
+ s = "(bad sense key)";
+
+ printk (" Sense key: %x - %s\n", reqbuf->sense_key, s);
+
+ if (reqbuf->asc == 0x40) {
+ sprintf (buf, "Diagnostic failure on component %x", reqbuf->ascq);
+ s = buf;
+ }
+
+ else {
+ int lo, hi;
+ int key = (reqbuf->asc << 8);
+ if ( ! (reqbuf->ascq >= 0x80 && reqbuf->ascq <= 0xdd) )
+ key |= reqbuf->ascq;
+
+ lo = 0;
+ hi = ARY_LEN (sense_data_texts);
+ s = NULL;
+
+ while (hi > lo) {
+ int mid = (lo + hi) / 2;
+ if (sense_data_texts[mid].asc_ascq == key) {
+ s = sense_data_texts[mid].text;
+ break;
+ }
+ else if (sense_data_texts[mid].asc_ascq > key)
+ hi = mid;
+ else
+ lo = mid+1;
+ }
+ }
+
+ if (s == NULL) {
+ if (reqbuf->asc > 0x80)
+ s = "(vendor-specific error)";
+ else
+ s = "(reserved error code)";
+ }
+
+ printk (" Additional sense data: %x, %x - %s\n",
+ reqbuf->asc, reqbuf->ascq, s);
+
+ if (failed_command != NULL) {
+ printk (" Failed packet command: ");
+ for (i=0; i<sizeof (failed_command->c); i++)
+ printk ("%02x ", failed_command->c[i]);
+ printk ("\n");
+ }
+ }
+
+#else
+ printk ("%s: code: %x key: %x asc: %x ascq: %x\n",
+ dev->name,
+ reqbuf->error_code, reqbuf->sense_key, reqbuf->asc, reqbuf->ascq);
+#endif
+}
+
+
+/* Fix up a possibly partially-processed request so that we can
+ start it over entirely, or even put it back on the request queue. */
+static void restore_request (struct request *rq)
+{
+ if (rq->buffer != rq->bh->b_data)
+ {
+ int n = (rq->buffer - rq->bh->b_data) / SECTOR_SIZE;
+ rq->buffer = rq->bh->b_data;
+ rq->nr_sectors += n;
+ rq->sector -= n;
+ }
+ rq->current_nr_sectors = rq->bh->b_size >> SECTOR_BITS;
+}
+
+
+static void cdrom_queue_request_sense (ide_dev_t *dev)
+{
+ struct request *rq;
+ struct packet_command *pc;
+ struct atapi_request_sense *reqbuf;
+ unsigned long flags;
+
+ int major = ide_major[DEV_HWIF];
+
+ save_flags (flags);
+ cli (); /* safety */
+
+ rq = ide_cur_rq[DEV_HWIF];
+
+ /* If we're processing a request, put it back on the request queue. */
+ if (rq != NULL)
+ {
+ restore_request (rq);
+ rq->next = blk_dev[major].current_request;
+ blk_dev[major].current_request = rq;
+ ide_cur_rq[DEV_HWIF] = NULL;
+ }
+
+ restore_flags (flags);
+
+ /* Make up a new request to retrieve sense information. */
+ reqbuf = &cdrom_info[DEV_HWIF][dev->select.b.drive].sense_data;
+
+ pc = &request_sense_pc[DEV_HWIF];
+ memset (pc, 0, sizeof (*pc));
+
+ pc->c[0] = REQUEST_SENSE;
+ pc->c[4] = sizeof (*reqbuf);
+ pc->buffer = (char *)reqbuf;
+ pc->buflen = sizeof (*reqbuf);
+
+ rq = &request_sense_request[DEV_HWIF];
+ rq->dev = MKDEV (major, (dev->select.b.drive) << PARTN_BITS);
+ rq->cmd = REQUEST_SENSE_COMMAND;
+ rq->errors = 0;
+ rq->sector = 0;
+ rq->nr_sectors = 0;
+ rq->current_nr_sectors = 0;
+ rq->buffer = (char *)pc;
+ rq->sem = NULL;
+ rq->bh = NULL;
+ rq->bhtail = NULL;
+ rq->next = NULL;
+
+ save_flags (flags);
+ cli (); /* safety */
+
+ /* Stick it onto the front of the queue. */
+ rq->next = blk_dev[major].current_request;
+ blk_dev[major].current_request = rq;
+
+ restore_flags (flags);
+}
+
+
+static void cdrom_end_request (int uptodate, ide_dev_t *dev)
+{
+ struct request *rq = ide_cur_rq[DEV_HWIF];
+
+ /* The code in blk.h can screw us up on error recovery if the block
+ size is larger than 1k. Fix that up here. */
+ if (!uptodate && rq->bh != 0)
+ {
+ int adj = rq->current_nr_sectors - 1;
+ rq->current_nr_sectors -= adj;
+ rq->sector += adj;
+ }
+
+ if (rq->cmd == REQUEST_SENSE_COMMAND && uptodate)
+ {
+ struct atapi_request_sense *reqbuf;
+ reqbuf = &cdrom_info[DEV_HWIF][dev->select.b.drive].sense_data;
+ cdrom_analyze_sense_data (dev, reqbuf, NULL);
+ }
+
+ end_request (uptodate, DEV_HWIF);
+}
+
+
+/* Mark that we've seen a media change, and invalidate our internal
+ buffers. */
+static void cdrom_saw_media_change (ide_dev_t *dev)
+{
+ CDROM_FLAGS (dev)->media_changed = 1;
+ CDROM_FLAGS (dev)->toc_valid = 0;
+ cdrom_info[DEV_HWIF][dev->select.b.drive].nsectors_buffered = 0;
+}
+
+
+/* Returns 0 if the request should be continued.
+ Returns 1 if the request was ended. */
+static int cdrom_decode_status (ide_dev_t *dev, int good_stat, int *stat_ret)
+{
+ struct request *rq = ide_cur_rq[DEV_HWIF];
+ int stat, err, sense_key, cmd;
+
+ /* Check for errors. */
+ stat = GET_STAT (DEV_HWIF);
+ *stat_ret = stat;
+
+ if (OK_STAT (stat, good_stat, BAD_R_STAT))
+ return 0;
+
+ /* Got an error. */
+ err = IN_BYTE (HD_ERROR, DEV_HWIF);
+ sense_key = err >> 4;
+
+ if (rq == NULL)
+ printk ("%s : missing request in cdrom_decode_status\n", dev->name);
+ else
+ {
+ cmd = rq->cmd;
+
+ /* Check for tray open */
+ if (sense_key == NOT_READY)
+ {
+ struct packet_command *pc;
+ cdrom_saw_media_change (dev);
+
+ /* Fail the request if this is a read command. */
+ if (cmd == READ)
+ {
+ printk ("%s : tray open\n", dev->name);
+ cdrom_end_request (0, dev);
+ }
+
+ else
+ {
+ /* Otherwise, it's some other packet command.
+ Print an error message to the syslog.
+ Exception: don't print anything if this is a read subchannel
+ command. This is because workman constantly polls the drive
+ with this command, and we don't want to uselessly fill up
+ the syslog. */
+ pc = (struct packet_command *)rq->buffer;
+ if (pc->c[0] != SCMD_READ_SUBCHANNEL)
+ printk ("%s : tray open\n", dev->name);
+
+ /* Set the error flag and complete the request. */
+ pc->stat = 1;
+ cdrom_end_request (1, dev);
+ }
+ }
+
+ /* Check for media change. */
+ else if (sense_key == UNIT_ATTENTION)
+ {
+ cdrom_saw_media_change (dev);
+ printk ("%s: media changed\n", dev->name);
+
+ /* Return failure for a packet command, so that
+ cdrom_queue_packet_command can do a request sense before
+ the command gets retried. */
+
+ if (cmd == PACKET_COMMAND)
+ {
+ struct packet_command *pc = (struct packet_command *)rq->buffer;
+ pc->stat = 1;
+ cdrom_end_request (1, dev);
+ }
+
+ /* Otherwise, it's a block read. Arrange to retry it.
+ But be sure to give up if we've retried too many times. */
+ else if ((++rq->errors > ERROR_MAX))
+ {
+ cdrom_end_request (0, dev);
+ }
+ }
+
+ /* Don't attempt to retry if this was a packet command. */
+ else if (cmd == PACKET_COMMAND)
+ {
+ struct packet_command *pc = (struct packet_command *)rq->buffer;
+ dump_status (DEV_HWIF, "packet command error", stat);
+ pc->stat = 1; /* signal error */
+ cdrom_end_request (1, dev);
+ }
+
+ /* No point in retrying after an illegal request or data protect error.*/
+ else if (sense_key == ILLEGAL_REQUEST || sense_key == DATA_PROTECT)
+ {
+ dump_status (DEV_HWIF, "command error", stat);
+ cdrom_end_request (0, dev);
+ }
+
+ /* If there were other errors, go to the default handler. */
+ else if ((err & ~ABRT_ERR) != 0)
+ {
+ ide_error (dev, "cdrom_decode_status", stat);
+ }
+
+ /* Else, abort if we've racked up too many retries. */
+ else if ((++rq->errors > ERROR_MAX))
+ {
+ cdrom_end_request (0, dev);
+ }
+
+ /* If we got a CHECK_STATUS condition, and this was a READ request,
+ queue a request sense command to try to find out more about
+ what went wrong (and clear a unit attention)? For packet commands,
+ this is done separately in cdrom_queue_packet_command. */
+ if ((stat & ERR_STAT) != 0 && cmd == READ)
+ cdrom_queue_request_sense (dev);
+ }
+
+ /* Retry, or handle the next request. */
+ DO_REQUEST;
+ return 1;
+}
+
+
+/* Set up the device registers for transferring a packet command on DEV,
+ expecting to later transfer XFERLEN bytes. This should be followed
+ by a call to cdrom_transfer_packet_command; however, if this is a
+ drq_interrupt device, one must wait for an interrupt first. */
+static int cdrom_start_packet_command (ide_dev_t *dev, int xferlen)
+{
+ /* Wait for the controller to be idle. */
+ if (wait_stat (dev, 0, BUSY_STAT, WAIT_READY)) return 1;
+
+ /* Set up the controller registers. */
+ OUT_BYTE (0, HD_FEATURE);
+ OUT_BYTE (0, HD_NSECTOR);
+ OUT_BYTE (0, HD_SECTOR);
+
+ OUT_BYTE (xferlen & 0xff, HD_LCYL);
+ OUT_BYTE (xferlen >> 8 , HD_HCYL);
+ OUT_BYTE (dev->ctl, HD_CMD);
+ OUT_BYTE (WIN_PACKETCMD, HD_COMMAND); /* packet command */
+
+ return 0;
+}
+
+
+/* Send a packet command to DEV described by CMD_BUF and CMD_LEN.
+ The device registers must have already been prepared
+ by cdrom_start_packet_command. */
+static int cdrom_transfer_packet_command (ide_dev_t *dev,
+ char *cmd_buf, int cmd_len)
+{
+ if (CDROM_FLAGS (dev)->drq_interrupt)
+ {
+ /* Here we should have been called after receiving an interrupt
+ from the device. DRQ should how be set. */
+ int stat_dum;
+
+ /* Check for errors. */
+ if (cdrom_decode_status (dev, DRQ_STAT, &stat_dum)) return 1;
+ }
+ else
+ {
+ /* Otherwise, we must wait for DRQ to get set. */
+ if (wait_stat (dev, DRQ_STAT, BUSY_STAT, WAIT_READY)) return 1;
+ }
+
+ /* Send the command to the device. */
+ OUT_WORDS (cmd_buf, cmd_len/2);
+
+ return 0;
+}
+
+
+
+/****************************************************************************
+ * Block read functions.
+ */
+
+/*
+ * Buffer up to SECTORS_TO_TRANSFER sectors from the drive in our sector
+ * buffer. Once the first sector is added, any subsequent sectors are
+ * assumed to be continuous (until the buffer is cleared). For the first
+ * sector added, SECTOR is its sector number. (SECTOR is then ignored until
+ * the buffer is cleared.)
+ */
+static void cdrom_buffer_sectors (ide_dev_t *dev, unsigned long sector,
+ int sectors_to_transfer)
+{
+ struct cdrom_info *info = &cdrom_info[DEV_HWIF][dev->select.b.drive];
+
+ /* Number of sectors to read into the buffer. */
+ int sectors_to_buffer = MIN (sectors_to_transfer,
+ (SECTOR_BUFFER_SIZE >> SECTOR_BITS) -
+ info->nsectors_buffered);
+
+ char *dest;
+
+ /* If we don't yet have a sector buffer, try to allocate one.
+ If we can't get one atomically, it's not fatal -- we'll just throw
+ the data away rather than caching it. */
+ if (info->sector_buffer == NULL)
+ {
+ info->sector_buffer = (char *) kmalloc (SECTOR_BUFFER_SIZE, GFP_ATOMIC);
+
+ /* If we couldn't get a buffer, don't try to buffer anything... */
+ if (info->sector_buffer == NULL)
+ sectors_to_buffer = 0;
+ }
+
+ /* If this is the first sector in the buffer, remember its number. */
+ if (info->nsectors_buffered == 0)
+ info->sector_buffered = sector;
+
+ /* Read the data into the buffer. */
+ dest = info->sector_buffer + info->nsectors_buffered * SECTOR_SIZE;
+ while (sectors_to_buffer > 0)
+ {
+ IN_WORDS (dest, SECTOR_SIZE / 2);
+ --sectors_to_buffer;
+ --sectors_to_transfer;
+ ++info->nsectors_buffered;
+ dest += SECTOR_SIZE;
+ }
+
+ /* Throw away any remaining data. */
+ while (sectors_to_transfer > 0)
+ {
+ char dum[SECTOR_SIZE];
+ IN_WORDS (dum, sizeof (dum) / 2);
+ --sectors_to_transfer;
+ }
+}
+
+
+/*
+ * Check the contents of the interrupt reason register from the cdrom
+ * and attempt to recover if there are problems. Returns 0 if everything's
+ * ok; nonzero if the request has been terminated.
+ */
+static inline
+int cdrom_read_check_ireason (ide_dev_t *dev, int len, int ireason)
+{
+ ireason &= 3;
+ if (ireason == 2) return 0;
+
+ if (ireason == 0)
+ {
+ /* Whoops... The drive is expecting to receive data from us! */
+ printk ("%s: cdrom_read_intr: "
+ "Drive wants to transfer data the wrong way!\n",
+ dev->name);
+
+ /* Throw some data at the drive so it doesn't hang
+ and quit this request. */
+ while (len > 0)
+ {
+ short dum = 0;
+ OUT_WORDS (&dum, 1);
+ len -= 2;
+ }
+ }
+
+ else
+ {
+ /* Drive wants a command packet, or invalid ireason... */
+ printk ("%s: cdrom_read_intr: bad interrupt reason %d\n",
+ dev->name, ireason);
+ }
+
+ cdrom_end_request (0, dev);
+ DO_REQUEST;
+ return -1;
+}
+
+
+/*
+ * Interrupt routine. Called when a read request has completed.
+ */
+static void cdrom_read_intr (ide_dev_t *dev)
+{
+ int stat;
+ int ireason, len, sectors_to_transfer, nskip;
+
+ struct request *rq = ide_cur_rq[DEV_HWIF];
+
+ /* Check for errors. */
+ if (cdrom_decode_status (dev, 0, &stat)) return;
+
+ /* Read the interrupt reason and the transfer length. */
+ ireason = IN_BYTE (HD_NSECTOR, DEV_HWIF);
+ len = IN_BYTE (HD_LCYL, DEV_HWIF) + 256 * IN_BYTE (HD_HCYL, DEV_HWIF);
+
+ /* If DRQ is clear, the command has completed. */
+ if ((stat & DRQ_STAT) == 0)
+ {
+ /* If we're not done filling the current buffer, complain.
+ Otherwise, complete the command normally. */
+ if (rq->current_nr_sectors > 0)
+ {
+ printk ("%s: cdrom_read_intr: data underrun (%ld blocks)\n",
+ dev->name, rq->current_nr_sectors);
+ cdrom_end_request (0, dev);
+ }
+ else
+ cdrom_end_request (1, dev);
+
+ DO_REQUEST;
+ return;
+ }
+
+ /* Check that the drive is expecting to do the same thing that we are. */
+ if (cdrom_read_check_ireason (dev, len, ireason)) return;
+
+ /* Assume that the drive will always provide data in multiples of at least
+ SECTOR_SIZE, as it gets hairy to keep track of the transfers otherwise. */
+ if ((len % SECTOR_SIZE) != 0)
+ {
+ printk ("%s: cdrom_read_intr: Bad transfer size %d\n",
+ dev->name, len);
+ printk (" This drive is not supported by this version of the driver\n");
+ cdrom_end_request (0, dev);
+ DO_REQUEST;
+ return;
+ }
+
+ /* The number of sectors we need to read from the drive. */
+ sectors_to_transfer = len / SECTOR_SIZE;
+
+ /* First, figure out if we need to bit-bucket any of the leading sectors. */
+ nskip = MIN ((int)(rq->current_nr_sectors - (rq->bh->b_size >> SECTOR_BITS)),
+ sectors_to_transfer);
+
+ while (nskip > 0)
+ {
+ /* We need to throw away a sector. */
+ char dum[SECTOR_SIZE];
+ IN_WORDS (dum, sizeof (dum) / 2);
+
+ --rq->current_nr_sectors;
+ --nskip;
+ --sectors_to_transfer;
+ }
+
+ /* Now loop while we still have data to read from the drive. */
+ while (sectors_to_transfer > 0)
+ {
+ int this_transfer;
+
+ /* If we've filled the present buffer but there's another chained
+ buffer after it, move on. */
+ if (rq->current_nr_sectors == 0 &&
+ rq->nr_sectors > 0)
+ cdrom_end_request (1, dev);
+
+ /* If the buffers are full, cache the rest of the data in our
+ internal buffer. */
+ if (rq->current_nr_sectors == 0)
+ {
+ cdrom_buffer_sectors (dev, rq->sector, sectors_to_transfer);
+ sectors_to_transfer = 0;
+ }
+ else
+ {
+ /* Transfer data to the buffers.
+ Figure out how many sectors we can transfer
+ to the current buffer. */
+ this_transfer = MIN (sectors_to_transfer,
+ rq->current_nr_sectors);
+
+ /* Read this_transfer sectors into the current buffer. */
+ while (this_transfer > 0)
+ {
+ IN_WORDS (rq->buffer, SECTOR_SIZE / 2);
+ rq->buffer += SECTOR_SIZE;
+ --rq->nr_sectors;
+ --rq->current_nr_sectors;
+ ++rq->sector;
+ --this_transfer;
+ --sectors_to_transfer;
+ }
+ }
+ }
+
+ /* Done moving data!
+ Wait for another interrupt. */
+ ide_handler[DEV_HWIF] = cdrom_read_intr;
+}
+
+
+/*
+ * Try to satisfy some of the current read request from our cached data.
+ * Returns nonzero if the request has been completed, zero otherwise.
+ */
+static int cdrom_read_from_buffer (ide_dev_t *dev)
+{
+ struct cdrom_info *info = &cdrom_info[DEV_HWIF][dev->select.b.drive];
+ struct request *rq = ide_cur_rq[DEV_HWIF];
+
+ /* Can't do anything if there's no buffer. */
+ if (info->sector_buffer == NULL) return 0;
+
+ /* Loop while this request needs data and the next block is present
+ in our cache. */
+ while (rq->nr_sectors > 0 &&
+ rq->sector >= info->sector_buffered &&
+ rq->sector < info->sector_buffered + info->nsectors_buffered)
+ {
+ if (rq->current_nr_sectors == 0)
+ cdrom_end_request (1, dev);
+
+ memcpy (rq->buffer,
+ info->sector_buffer +
+ (rq->sector - info->sector_buffered) * SECTOR_SIZE,
+ SECTOR_SIZE);
+ rq->buffer += SECTOR_SIZE;
+ --rq->current_nr_sectors;
+ --rq->nr_sectors;
+ ++rq->sector;
+ }
+
+ /* If we've satisfied the current request, terminate it successfully. */
+ if (rq->nr_sectors == 0)
+ {
+ cdrom_end_request (1, dev);
+ return -1;
+ }
+
+ /* Move on to the next buffer if needed. */
+ if (rq->current_nr_sectors == 0)
+ cdrom_end_request (1, dev);
+
+ /* If this condition does not hold, then the kluge i use to
+ represent the number of sectors to skip at the start of a transfer
+ will fail. I think that this will never happen, but let's be
+ paranoid and check. */
+ if (rq->current_nr_sectors < (rq->bh->b_size >> SECTOR_BITS) &&
+ (rq->sector % SECTORS_PER_FRAME) != 0)
+ {
+ printk ("%s: cdrom_read_from_buffer: buffer botch (%ld)\n",
+ dev->name, rq->sector);
+ cdrom_end_request (0, dev);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+
+/*
+ * Routine to send a read packet command to the drive.
+ * This is usually called directly from cdrom_start_read.
+ * However, for drq_interrupt devices, it is called from an interrupt
+ * when the drive is ready to accept the command.
+ */
+static int cdrom_start_read_continuation (ide_dev_t *dev)
+{
+ struct packet_command pc;
+ struct request *rq = ide_cur_rq[DEV_HWIF];
+
+ int nsect, sector, nframes, frame, nskip;
+
+ /* Number of sectors to transfer. */
+ nsect = rq->nr_sectors;
+
+ /* Starting sector. */
+ sector = rq->sector;
+
+ /* If the requested sector doesn't start on a cdrom block boundary,
+ we must adjust the start of the transfer so that it does,
+ and remember to skip the first few sectors. If the CURRENT_NR_SECTORS
+ field is larger than the size of the buffer, it will mean that
+ we're to skip a number of sectors equal to the amount by which
+ CURRENT_NR_SECTORS is larger than the buffer size. */
+ nskip = (sector % SECTORS_PER_FRAME);
+ if (nskip > 0)
+ {
+ /* Sanity check... */
+ if (rq->current_nr_sectors != (rq->bh->b_size >> SECTOR_BITS))
+ {
+ printk ("%s: cdrom_start_read_continuation: buffer botch (%ld)\n",
+ dev->name, rq->current_nr_sectors);
+ cdrom_end_request (0, dev);
+ DO_REQUEST;
+ return 1;
+ }
+
+ sector -= nskip;
+ nsect += nskip;
+ rq->current_nr_sectors += nskip;
+ }
+
+ /* Convert from sectors to cdrom blocks, rounding up the transfer
+ length if needed. */
+ nframes = (nsect + SECTORS_PER_FRAME-1) / SECTORS_PER_FRAME;
+ frame = sector / SECTORS_PER_FRAME;
+
+ /* Largest number of frames was can transfer at once is 64k-1. */
+ nframes = MIN (nframes, 65535);
+
+ /* Set up the command */
+ memset (&pc.c, 0, sizeof (pc.c));
+ pc.c[0] = READ_10;
+ pc.c[7] = (nframes >> 8);
+ pc.c[8] = (nframes & 0xff);
+
+ /* Write the sector address into the command image. */
+ {
+ union {
+ struct {unsigned char b0, b1, b2, b3;} b;
+ struct {unsigned long l0;} l;
+ } conv;
+ conv.l.l0 = frame;
+ pc.c[2] = conv.b.b3;
+ pc.c[3] = conv.b.b2;
+ pc.c[4] = conv.b.b1;
+ pc.c[5] = conv.b.b0;
+ }
+
+ if (cdrom_transfer_packet_command (dev, pc.c, sizeof (pc.c)))
+ return 1;
+
+ /* Set up our interrupt handler and return. */
+ ide_handler[DEV_HWIF] = cdrom_read_intr;
+
+ return 0;
+}
+
+
+/*
+ * Start a read request from the CD-ROM.
+ * Returns 0 if the request was started successfully,
+ * 1 if there was an error and we should either retry or move on to the
+ * next request.
+ */
+static int cdrom_start_read (ide_dev_t *dev, unsigned int block)
+{
+ struct request *rq = ide_cur_rq[DEV_HWIF];
+
+ /* We may be retrying this request after an error.
+ Fix up any weirdness which might be present in the request packet. */
+ restore_request (rq);
+
+ /* Satisfy whatever we can of this request from our cached sector. */
+ if (cdrom_read_from_buffer (dev))
+ return 1;
+
+ /* Clear the local sector buffer. */
+ cdrom_info[DEV_HWIF][dev->select.b.drive].nsectors_buffered = 0;
+
+ if (cdrom_start_packet_command (dev, 32768))
+ return 1;
+
+ if (CDROM_FLAGS (dev)->drq_interrupt)
+ ide_handler[DEV_HWIF] = (void (*)(ide_dev_t *))cdrom_start_read_continuation;
+ else
+ {
+ if (cdrom_start_read_continuation (dev))
+ return 1;
+ }
+
+ return 0;
+}
+
+
+
+
+/****************************************************************************
+ * Execute all other packet commands.
+ */
+
+/* Forward declaration */
+static int
+cdrom_request_sense (ide_dev_t *dev, struct atapi_request_sense *reqbuf);
+
+
+/* Interrupt routine for packet command completion. */
+static void cdrom_pc_intr (ide_dev_t *dev)
+{
+ int ireason, len, stat, thislen;
+ struct request *rq = ide_cur_rq[DEV_HWIF];
+ struct packet_command *pc = (struct packet_command *)rq->buffer;
+
+ /* Check for errors. */
+ if (cdrom_decode_status (dev, 0, &stat)) return;
+
+ /* Read the interrupt reason and the transfer length. */
+ ireason = IN_BYTE (HD_NSECTOR, DEV_HWIF);
+ len = IN_BYTE (HD_LCYL, DEV_HWIF) + 256 * IN_BYTE (HD_HCYL, DEV_HWIF);
+
+ /* If DRQ is clear, the command has completed.
+ Complain if we still have data left to transfer. */
+ if ((stat & DRQ_STAT) == 0)
+ {
+ /* Some of the trailing request sense fields are optional, and
+ some drives don't send them. Sigh. */
+ if (pc->c[0] == REQUEST_SENSE && pc->buflen > 0 && pc->buflen <= 5) {
+ while (pc->buflen > 0) {
+ *pc->buffer++ = 0;
+ --pc->buflen;
+ }
+ }
+
+ if (pc->buflen == 0)
+ cdrom_end_request (1, dev);
+ else
+ {
+ printk ("%s: cdrom_pc_intr: data underrun %d\n",
+ dev->name, pc->buflen);
+ pc->stat = 1;
+ cdrom_end_request (1, dev);
+ }
+ DO_REQUEST;
+ return;
+ }
+
+ /* Figure out how much data to transfer. */
+ thislen = pc->buflen;
+ if (thislen < 0) thislen = -thislen;
+ if (thislen > len) thislen = len;
+
+ /* The drive wants to be written to. */
+ if ((ireason & 3) == 0)
+ {
+ /* Check that we want to write. */
+ if (pc->buflen > 0)
+ {
+ printk ("%s: cdrom_pc_intr: Drive wants to transfer data the wrong way!\n",
+ dev->name);
+ pc->stat = 1;
+ thislen = 0;
+ }
+
+ /* Transfer the data. */
+ OUT_WORDS (pc->buffer, thislen / 2);
+
+ /* If we haven't moved enough data to satisfy the drive,
+ add some padding. */
+ while (len > thislen)
+ {
+ short dum = 0;
+ OUT_WORDS (&dum, 1);
+ len -= 2;
+ }
+
+ /* Keep count of how much data we've moved. */
+ pc->buffer += thislen;
+ pc->buflen += thislen;
+ }
+
+ /* Same drill for reading. */
+ else if ((ireason & 3) == 2)
+ {
+ /* Check that we want to read. */
+ if (pc->buflen < 0)
+ {
+ printk ("%s: cdrom_pc_intr: Drive wants to transfer data the wrong way!\n",
+ dev->name);
+ pc->stat = 1;
+ thislen = 0;
+ }
+
+ /* Transfer the data. */
+ IN_WORDS (pc->buffer, thislen / 2);
+
+ /* If we haven't moved enough data to satisfy the drive,
+ add some padding. */
+ while (len > thislen)
+ {
+ short dum = 0;
+ IN_WORDS (&dum, 1);
+ len -= 2;
+ }
+
+ /* Keep count of how much data we've moved. */
+ pc->buffer += thislen;
+ pc->buflen -= thislen;
+ }
+
+ else
+ {
+ printk ("%s: cdrom_pc_intr: The drive appears confused (ireason = 0x%2x)\n",
+ dev->name, ireason);
+ pc->stat = 1;
+ }
+
+ /* Now we wait for another interrupt. */
+ ide_handler[DEV_HWIF] = cdrom_pc_intr;
+}
+
+
+static int cdrom_do_pc_continuation (ide_dev_t *dev)
+{
+ struct request *rq = ide_cur_rq[DEV_HWIF];
+ struct packet_command *pc = (struct packet_command *)rq->buffer;
+
+ if (cdrom_transfer_packet_command (dev, pc->c, sizeof (pc->c)))
+ return 1;
+
+ /* Set up our interrupt handler and return. */
+ ide_handler[DEV_HWIF] = cdrom_pc_intr;
+
+ return 0;
+}
+
+
+static int cdrom_do_packet_command (ide_dev_t *dev)
+{
+ int len;
+ struct request *rq = ide_cur_rq[DEV_HWIF];
+ struct packet_command *pc = (struct packet_command *)rq->buffer;
+
+ len = pc->buflen;
+ if (len < 0) len = -len;
+
+ pc->stat = 0;
+
+ if (cdrom_start_packet_command (dev, len))
+ return 1;
+
+ if (CDROM_FLAGS (dev)->drq_interrupt)
+ ide_handler[DEV_HWIF] = (void (*)(ide_dev_t *))cdrom_do_pc_continuation;
+ else
+ {
+ if (cdrom_do_pc_continuation (dev))
+ return 1;
+ }
+
+ return 0;
+}
+
+
+static
+int cdrom_queue_packet_command (ide_dev_t *dev, struct packet_command *pc)
+{
+ int retries = 3;
+ unsigned long flags;
+ struct request req, **p, **pfirst;
+ struct semaphore sem = MUTEX_LOCKED;
+ int major = ide_major[DEV_HWIF];
+
+ retry:
+ req.dev = MKDEV (major, (dev->select.b.drive) << PARTN_BITS);
+ req.cmd = PACKET_COMMAND;
+ req.errors = 0;
+ req.sector = 0;
+ req.nr_sectors = 0;
+ req.current_nr_sectors = 0;
+ req.buffer = (char *)pc;
+ req.sem = &sem;
+ req.bh = NULL;
+ req.bhtail = NULL;
+ req.next = NULL;
+
+ save_flags (flags);
+ cli ();
+
+ p = &blk_dev[major].current_request;
+ pfirst = p;
+ while ((*p) != NULL)
+ {
+ p = &((*p)->next);
+ }
+ *p = &req;
+ if (p == pfirst)
+ blk_dev[major].request_fn ();
+
+ restore_flags (flags);
+
+ down (&sem);
+
+ if (pc->stat != 0)
+ {
+ /* The request failed. Try to do a request sense to get more information
+ about the error; store the result in the cdrom_info struct
+ for this drive. Check to be sure that it wasn't a request sense
+ request that failed, though, to prevent infinite loops. */
+
+ struct atapi_request_sense *reqbuf =
+ &cdrom_info[DEV_HWIF][dev->select.b.drive].sense_data;
+
+ if (pc->c[0] == REQUEST_SENSE || cdrom_request_sense (dev, reqbuf))
+ {
+ memset (reqbuf, 0, sizeof (*reqbuf));
+ reqbuf->asc = 0xff;
+ }
+ cdrom_analyze_sense_data (dev, reqbuf, pc);
+
+ /* If the error was a unit attention (usually means media was changed),
+ retry the command. */
+ if (reqbuf->sense_key == UNIT_ATTENTION && retries > 0)
+ {
+ --retries;
+ goto retry;
+ }
+
+ return -EIO;
+ }
+ else
+ return 0;
+}
+
+
+
+/****************************************************************************
+ * cdrom driver request routine.
+ */
+
+static int do_rw_cdrom (ide_dev_t *dev, unsigned long block)
+{
+ struct request *rq = ide_cur_rq[DEV_HWIF];
+
+ if (rq -> cmd == PACKET_COMMAND || rq -> cmd == REQUEST_SENSE_COMMAND)
+ return cdrom_do_packet_command (dev);
+
+ if (rq -> cmd != READ)
+ {
+ printk ("ide-cd: bad cmd %d\n", rq -> cmd);
+ cdrom_end_request (0, dev);
+ return 1;
+ }
+
+ return cdrom_start_read (dev, block);
+}
+
+
+
+/****************************************************************************
+ * ioctl handling.
+ */
+
+static inline
+void byte_swap_word (unsigned short *x)
+{
+ char *c = (char *)x;
+ char d = c[0];
+ c[0] = c[1];
+ c[1] = d;
+}
+
+
+static inline
+void byte_swap_long (unsigned *x)
+{
+ char *c = (char *)x;
+ char d = c[0];
+ c[0] = c[3];
+ c[3] = d;
+ d = c[1];
+ c[1] = c[2];
+ c[2] = d;
+}
+
+
+static
+int bin2bcd (int x)
+{
+ return (x%10) | ((x/10) << 4);
+}
+
+
+static inline
+void lba_to_msf (int lba, byte *m, byte *s, byte *f)
+{
+ lba += CD_BLOCK_OFFSET;
+ lba &= 0xffffff; /* negative lbas use only 24 bits */
+ *m = lba / (CD_SECS * CD_FRAMES);
+ lba %= (CD_SECS * CD_FRAMES);
+ *s = lba / CD_FRAMES;
+ *f = lba % CD_FRAMES;
+}
+
+
+static inline
+int msf_to_lba (byte m, byte s, byte f)
+{
+ return (((m * CD_SECS) + s) * CD_FRAMES + f) - CD_BLOCK_OFFSET;
+}
+
+
+static void
+cdrom_check_status (ide_dev_t *dev)
+{
+ struct packet_command pc;
+
+ memset (&pc, 0, sizeof (pc));
+
+ pc.c[0] = TEST_UNIT_READY;
+
+ (void) cdrom_queue_packet_command (dev, &pc);
+}
+
+
+static int
+cdrom_request_sense (ide_dev_t *dev, struct atapi_request_sense *reqbuf)
+{
+ struct packet_command pc;
+
+ memset (&pc, 0, sizeof (pc));
+
+ pc.c[0] = REQUEST_SENSE;
+ pc.c[4] = sizeof (*reqbuf);
+ pc.buffer = (char *)reqbuf;
+ pc.buflen = sizeof (*reqbuf);
+
+ return cdrom_queue_packet_command (dev, &pc);
+}
+
+
+#if 0
+/* Lock the door if LOCKFLAG is nonzero; unlock it otherwise. */
+static int
+cdrom_lockdoor (ide_dev_t *dev, int lockflag)
+{
+ struct packet_command pc;
+
+ memset (&pc, 0, sizeof (pc));
+
+ pc.c[0] = ALLOW_MEDIUM_REMOVAL;
+ pc.c[4] = (lockflag != 0);
+ return cdrom_queue_packet_command (dev, &pc);
+}
+#endif
+
+
+/* Eject the disk if EJECTFLAG is 0.
+ If EJECTFLAG is 1, try to reload the disk. */
+static int
+cdrom_eject (ide_dev_t *dev, int ejectflag)
+{
+ struct packet_command pc;
+
+ memset (&pc, 0, sizeof (pc));
+
+ pc.c[0] = START_STOP;
+ pc.c[4] = 2 + (ejectflag != 0);
+ return cdrom_queue_packet_command (dev, &pc);
+}
+
+
+static int
+cdrom_pause (ide_dev_t *dev, int pauseflag)
+{
+ struct packet_command pc;
+
+ memset (&pc, 0, sizeof (pc));
+
+ pc.c[0] = SCMD_PAUSE_RESUME;
+ pc.c[8] = !pauseflag;
+ return cdrom_queue_packet_command (dev, &pc);
+}
+
+
+static int
+cdrom_startstop (ide_dev_t *dev, int startflag)
+{
+ struct packet_command pc;
+
+ memset (&pc, 0, sizeof (pc));
+
+ pc.c[0] = START_STOP;
+ pc.c[1] = 1;
+ pc.c[4] = startflag;
+ return cdrom_queue_packet_command (dev, &pc);
+}
+
+
+static int
+cdrom_read_tocentry (ide_dev_t *dev, int trackno, int msf_flag,
+ char *buf, int buflen)
+{
+ struct packet_command pc;
+
+ memset (&pc, 0, sizeof (pc));
+
+ pc.buffer = buf;
+ pc.buflen = buflen;
+ pc.c[0] = SCMD_READ_TOC;
+ pc.c[6] = trackno;
+ pc.c[7] = (buflen >> 8);
+ pc.c[8] = (buflen & 0xff);
+ if (msf_flag) pc.c[1] = 2;
+ return cdrom_queue_packet_command (dev, &pc);
+}
+
+
+/* Try to read the entire TOC for the disk into our internal buffer. */
+static int
+cdrom_read_toc (ide_dev_t *dev)
+{
+ int msf_flag;
+ int stat, ntracks, i;
+ struct atapi_toc *toc = cdrom_info[DEV_HWIF][dev->select.b.drive].toc;
+
+ if (toc == NULL)
+ {
+ /* Try to allocate space. */
+ toc = (struct atapi_toc *) kmalloc (sizeof (struct atapi_toc),
+ GFP_KERNEL);
+ cdrom_info[DEV_HWIF][dev->select.b.drive].toc = toc;
+ }
+
+ if (toc == NULL)
+ {
+ printk ("%s: No cdrom TOC buffer!\n", dev->name);
+ return -EIO;
+ }
+
+ /* Check to see if the existing data is still valid.
+ If it is, just return. */
+ if (CDROM_FLAGS (dev)->toc_valid)
+ cdrom_check_status (dev);
+
+ if (CDROM_FLAGS (dev)->toc_valid) return 0;
+
+ /* Some drives can't return TOC data in LBA format. */
+ msf_flag = (CDROM_FLAGS (dev)->no_lba_toc);
+
+ /* First read just the header, so we know how long the TOC is. */
+ stat = cdrom_read_tocentry (dev, 0, msf_flag, (char *)toc,
+ sizeof (struct atapi_toc_header) +
+ sizeof (struct atapi_toc_entry));
+ if (stat) return stat;
+
+ ntracks = toc->hdr.last_track - toc->hdr.first_track + 1;
+ if (ntracks <= 0) return -EIO;
+ if (ntracks > MAX_TRACKS) ntracks = MAX_TRACKS;
+
+ /* Now read the whole schmeer. */
+ stat = cdrom_read_tocentry (dev, 0, msf_flag, (char *)toc,
+ sizeof (struct atapi_toc_header) +
+ (ntracks+1) * sizeof (struct atapi_toc_entry));
+ if (stat) return stat;
+ byte_swap_word (&toc->hdr.toc_length);
+ for (i=0; i<=ntracks; i++)
+ {
+ if (msf_flag)
+ {
+ byte *adr = (byte *)&(toc->ent[i].lba);
+ toc->ent[i].lba = msf_to_lba (adr[1], adr[2], adr[3]);
+ }
+ else
+ byte_swap_long (&toc->ent[i].lba);
+ }
+
+ /* Remember that we've read this stuff. */
+ CDROM_FLAGS (dev)->toc_valid = 1;
+
+ return 0;
+}
+
+
+static int
+cdrom_read_subchannel (ide_dev_t *dev,
+ char *buf, int buflen)
+{
+ struct packet_command pc;
+
+ memset (&pc, 0, sizeof (pc));
+
+ pc.buffer = buf;
+ pc.buflen = buflen;
+ pc.c[0] = SCMD_READ_SUBCHANNEL;
+ pc.c[2] = 0x40; /* request subQ data */
+ pc.c[3] = 0x01; /* Format 1: current position */
+ pc.c[7] = (buflen >> 8);
+ pc.c[8] = (buflen & 0xff);
+ return cdrom_queue_packet_command (dev, &pc);
+}
+
+
+/* modeflag: 0 = current, 1 = changeable mask, 2 = default, 3 = saved */
+static int
+cdrom_mode_sense (ide_dev_t *dev, int pageno, int modeflag,
+ char *buf, int buflen)
+{
+ struct packet_command pc;
+
+ memset (&pc, 0, sizeof (pc));
+
+ pc.buffer = buf;
+ pc.buflen = buflen;
+ pc.c[0] = MODE_SENSE_10;
+ pc.c[2] = pageno | (modeflag << 6);
+ pc.c[7] = (buflen >> 8);
+ pc.c[8] = (buflen & 0xff);
+ return cdrom_queue_packet_command (dev, &pc);
+}
+
+
+static int
+cdrom_mode_select (ide_dev_t *dev, int pageno, char *buf, int buflen)
+{
+ struct packet_command pc;
+
+ memset (&pc, 0, sizeof (pc));
+
+ pc.buffer = buf;
+ pc.buflen = - buflen;
+ pc.c[0] = MODE_SELECT_10;
+ pc.c[1] = 0x10;
+ pc.c[2] = pageno;
+ pc.c[7] = (buflen >> 8);
+ pc.c[8] = (buflen & 0xff);
+ return cdrom_queue_packet_command (dev, &pc);
+}
+
+
+static int
+cdrom_play_lba_range_play12 (ide_dev_t *dev, int lba_start, int lba_end)
+{
+ struct packet_command pc;
+
+ memset (&pc, 0, sizeof (pc));
+
+ pc.c[0] = SCMD_PLAYAUDIO12;
+ *(int *)(&pc.c[2]) = lba_start;
+ *(int *)(&pc.c[6]) = lba_end - lba_start;
+ byte_swap_long ((int *)(&pc.c[2]));
+ byte_swap_long ((int *)(&pc.c[6]));
+
+ return cdrom_queue_packet_command (dev, &pc);
+}
+
+
+static int
+cdrom_play_lba_range_msf (ide_dev_t *dev, int lba_start, int lba_end)
+{
+ struct packet_command pc;
+
+ memset (&pc, 0, sizeof (pc));
+
+ pc.c[0] = SCMD_PLAYAUDIO_MSF;
+ lba_to_msf (lba_start, &pc.c[3], &pc.c[4], &pc.c[5]);
+ lba_to_msf (lba_end-1, &pc.c[6], &pc.c[7], &pc.c[8]);
+
+ if (CDROM_FLAGS (dev)->msf_as_bcd)
+ {
+ pc.c[3] = bin2bcd (pc.c[3]);
+ pc.c[4] = bin2bcd (pc.c[4]);
+ pc.c[5] = bin2bcd (pc.c[5]);
+ pc.c[6] = bin2bcd (pc.c[6]);
+ pc.c[7] = bin2bcd (pc.c[7]);
+ pc.c[8] = bin2bcd (pc.c[8]);
+ }
+
+ return cdrom_queue_packet_command (dev, &pc);
+}
+
+
+/* Play audio starting at LBA LBA_START and finishing with the
+ LBA before LBA_END. */
+static int
+cdrom_play_lba_range (ide_dev_t *dev, int lba_start, int lba_end)
+{
+ /* This is rather annoying.
+ My NEC-260 won't recognize group 5 commands such as PLAYAUDIO12;
+ the only way to get it to play more than 64k of blocks at once
+ seems to be the PLAYAUDIO_MSF command. However, the parameters
+ the NEC 260 wants for the PLAYMSF command are incompatible with
+ the new version of the spec.
+
+ So what i'll try is this. First try for PLAYAUDIO12. If it works,
+ great. Otherwise, if the drive reports an illegal command code,
+ try PLAYAUDIO_MSF using the NEC 260-style bcd parameters. */
+
+ if (CDROM_FLAGS (dev)->no_playaudio12)
+ return cdrom_play_lba_range_msf (dev, lba_start, lba_end);
+ else
+ {
+ int stat;
+ struct atapi_request_sense *reqbuf;
+
+ stat = cdrom_play_lba_range_play12 (dev, lba_start, lba_end);
+ if (stat == 0) return 0;
+
+ /* It failed. Try to find out why. */
+ reqbuf = &cdrom_info[DEV_HWIF][dev->select.b.drive].sense_data;
+ if (reqbuf->sense_key == 0x05 && reqbuf->asc == 0x20)
+ {
+ /* The drive didn't recognize the command.
+ Retry with the MSF variant. */
+ printk ("%s: Drive does not support PLAYAUDIO12; "
+ "trying PLAYAUDIO_MSF\n", dev->name);
+ CDROM_FLAGS (dev)->no_playaudio12 = 1;
+ CDROM_FLAGS (dev)->msf_as_bcd = 1;
+ return cdrom_play_lba_range_msf (dev, lba_start, lba_end);
+ }
+
+ /* Failed for some other reason. Give up. */
+ return stat;
+ }
+}
+
+
+static
+int cdrom_get_toc_entry (ide_dev_t *dev, int track,
+ struct atapi_toc_entry **ent)
+{
+ int stat, ntracks;
+ struct atapi_toc *toc;
+
+ /* Make sure our saved TOC is valid. */
+ stat = cdrom_read_toc (dev);
+ if (stat) return stat;
+
+ toc = cdrom_info[DEV_HWIF][dev->select.b.drive].toc;
+
+ /* Check validity of requested track number. */
+ ntracks = toc->hdr.last_track - toc->hdr.first_track + 1;
+ if (track == CDROM_LEADOUT)
+ *ent = &toc->ent[ntracks];
+ else if (track < toc->hdr.first_track ||
+ track > toc->hdr.last_track)
+ return -EINVAL;
+ else
+ *ent = &toc->ent[track - toc->hdr.first_track];
+
+ return 0;
+}
+
+
+static int ide_cdrom_ioctl (ide_dev_t *dev, struct inode *inode,
+ struct file *file, unsigned int cmd, unsigned long arg)
+{
+ switch (cmd)
+ {
+ case CDROMEJECT:
+ return cdrom_eject (dev, 0);
+
+ case CDROMPAUSE:
+ return cdrom_pause (dev, 1);
+
+ case CDROMRESUME:
+ return cdrom_pause (dev, 0);
+
+ case CDROMSTART:
+ return cdrom_startstop (dev, 1);
+
+ case CDROMSTOP:
+ return cdrom_startstop (dev, 0);
+
+ case CDROMPLAYMSF:
+ {
+ struct cdrom_msf msf;
+ int stat, lba_start, lba_end;
+
+ stat = verify_area (VERIFY_READ, (void *)arg, sizeof (msf));
+ if (stat) return stat;
+
+ memcpy_fromfs (&msf, (void *) arg, sizeof(msf));
+
+ lba_start = msf_to_lba (msf.cdmsf_min0, msf.cdmsf_sec0,
+ msf.cdmsf_frame0);
+ lba_end = msf_to_lba (msf.cdmsf_min1, msf.cdmsf_sec1,
+ msf.cdmsf_frame1) + 1;
+
+ if (lba_end <= lba_start) return -EINVAL;
+
+ return cdrom_play_lba_range (dev, lba_start, lba_end);
+ }
+
+ /* Like just about every other Linux cdrom driver, we ignore the
+ index part of the request here. */
+ case CDROMPLAYTRKIND:
+ {
+ int stat, lba_start, lba_end;
+ struct cdrom_ti ti;
+ struct atapi_toc_entry *first_toc, *last_toc;
+
+ stat = verify_area (VERIFY_READ, (void *)arg, sizeof (ti));
+ if (stat) return stat;
+
+ memcpy_fromfs (&ti, (void *) arg, sizeof(ti));
+
+ stat = cdrom_get_toc_entry (dev, ti.cdti_trk0, &first_toc);
+ if (stat) return stat;
+ stat = cdrom_get_toc_entry (dev, ti.cdti_trk1, &last_toc);
+ if (stat) return stat;
+
+ if (ti.cdti_trk1 != CDROM_LEADOUT) ++last_toc;
+ lba_start = first_toc->lba;
+ lba_end = last_toc->lba;
+
+ if (lba_end <= lba_start) return -EINVAL;
+
+ return cdrom_play_lba_range (dev, lba_start, lba_end);
+ }
+
+ case CDROMREADTOCHDR:
+ {
+ int stat;
+ struct cdrom_tochdr tochdr;
+ struct atapi_toc *toc;
+
+ stat = verify_area (VERIFY_WRITE, (void *) arg, sizeof (tochdr));
+ if (stat) return stat;
+
+ /* Make sure our saved TOC is valid. */
+ stat = cdrom_read_toc (dev);
+ if (stat) return stat;
+
+ toc = cdrom_info[DEV_HWIF][dev->select.b.drive].toc;
+ tochdr.cdth_trk0 = toc->hdr.first_track;
+ tochdr.cdth_trk1 = toc->hdr.last_track;
+
+ memcpy_tofs ((void *) arg, &tochdr, sizeof (tochdr));
+
+ return stat;
+ }
+
+ case CDROMREADTOCENTRY:
+ {
+ int stat;
+ struct cdrom_tocentry tocentry;
+ struct atapi_toc_entry *toce;
+
+ stat = verify_area (VERIFY_READ, (void *) arg, sizeof (tocentry));
+ if (stat) return stat;
+ stat = verify_area (VERIFY_WRITE, (void *) arg, sizeof (tocentry));
+ if (stat) return stat;
+
+ memcpy_fromfs (&tocentry, (void *) arg, sizeof (tocentry));
+
+ stat = cdrom_get_toc_entry (dev, tocentry.cdte_track, &toce);
+ if (stat) return stat;
+
+ tocentry.cdte_ctrl = toce->control;
+ tocentry.cdte_adr = toce->adr;
+
+ if (tocentry.cdte_format == CDROM_MSF)
+ {
+ /* convert to MSF */
+ lba_to_msf (toce->lba,
+ &tocentry.cdte_addr.msf.minute,
+ &tocentry.cdte_addr.msf.second,
+ &tocentry.cdte_addr.msf.frame);
+ }
+ else
+ tocentry.cdte_addr.lba = toce->lba;
+
+ memcpy_tofs ((void *) arg, &tocentry, sizeof (tocentry));
+
+ return stat;
+ }
+
+ case CDROMSUBCHNL:
+ {
+ char buffer[16];
+ int stat, abs_lba, rel_lba;
+ struct cdrom_subchnl subchnl;
+
+ stat = verify_area (VERIFY_WRITE, (void *) arg, sizeof (subchnl));
+ if (stat) return stat;
+ stat = verify_area (VERIFY_READ, (void *) arg, sizeof (subchnl));
+ if (stat) return stat;
+
+ memcpy_fromfs (&subchnl, (void *) arg, sizeof (subchnl));
+
+ stat = cdrom_read_subchannel (dev, buffer, sizeof (buffer));
+ if (stat) return stat;
+
+ abs_lba = *(int *)&buffer[8];
+ rel_lba = *(int *)&buffer[12];
+ byte_swap_long (&abs_lba);
+ byte_swap_long (&rel_lba);
+
+ if (subchnl.cdsc_format == CDROM_MSF)
+ {
+ lba_to_msf (abs_lba,
+ &subchnl.cdsc_absaddr.msf.minute,
+ &subchnl.cdsc_absaddr.msf.second,
+ &subchnl.cdsc_absaddr.msf.frame);
+ lba_to_msf (rel_lba,
+ &subchnl.cdsc_reladdr.msf.minute,
+ &subchnl.cdsc_reladdr.msf.second,
+ &subchnl.cdsc_reladdr.msf.frame);
+ }
+ else
+ {
+ subchnl.cdsc_absaddr.lba = abs_lba;
+ subchnl.cdsc_reladdr.lba = rel_lba;
+ }
+
+ subchnl.cdsc_audiostatus = buffer[1];
+ subchnl.cdsc_ctrl = buffer[5] & 0xf;
+ subchnl.cdsc_trk = buffer[6];
+ subchnl.cdsc_ind = buffer[7];
+
+ memcpy_tofs ((void *) arg, &subchnl, sizeof (subchnl));
+
+ return stat;
+ }
+
+ case CDROMVOLCTRL:
+ {
+ struct cdrom_volctrl volctrl;
+ char buffer[24], mask[24];
+ int stat;
+
+ stat = verify_area (VERIFY_READ, (void *) arg, sizeof (volctrl));
+ if (stat) return stat;
+ memcpy_fromfs (&volctrl, (void *) arg, sizeof (volctrl));
+
+ stat = cdrom_mode_sense (dev, 0x0e, 0, buffer, sizeof (buffer));
+ if (stat) return stat;
+ stat = cdrom_mode_sense (dev, 0x0e, 1, mask , sizeof (buffer));
+ if (stat) return stat;
+
+ buffer[1] = buffer[2] = 0;
+
+ buffer[17] = volctrl.channel0 & mask[17];
+ buffer[19] = volctrl.channel1 & mask[19];
+ buffer[21] = volctrl.channel2 & mask[21];
+ buffer[23] = volctrl.channel3 & mask[23];
+
+ return cdrom_mode_select (dev, 0x0e, buffer, sizeof (buffer));
+ }
+
+#ifdef TEST
+ case 0x1234:
+ {
+ int stat;
+ struct packet_command pc;
+
+ memset (&pc, 0, sizeof (pc));
+
+ stat = verify_area (VERIFY_READ, (void *) arg, sizeof (pc.c));
+ if (stat) return stat;
+ memcpy_fromfs (&pc.c, (void *) arg, sizeof (pc.c));
+
+ return cdrom_queue_packet_command (dev, &pc);
+ }
+
+ case 0x1235:
+ {
+ int stat;
+ struct atapi_request_sense reqbuf;
+
+ stat = verify_area (VERIFY_WRITE, (void *) arg, sizeof (reqbuf));
+ if (stat) return stat;
+
+ stat = cdrom_request_sense (dev, &reqbuf);
+
+ memcpy_tofs ((void *) arg, &reqbuf, sizeof (reqbuf));
+
+ return stat;
+ }
+#endif
+
+ default:
+ return -EPERM;
+ }
+
+}
+
+
+
+/****************************************************************************
+ * Other driver requests (open, close, check media change).
+ */
+
+static int cdrom_check_media_change (ide_dev_t *dev)
+{
+ int retval;
+
+ cdrom_check_status (dev);
+
+ retval = CDROM_FLAGS (dev)->media_changed;
+ CDROM_FLAGS (dev)->media_changed = 0;
+
+ return retval;
+}
+
+
+static int
+cdrom_open (struct inode *ip, struct file *fp, ide_dev_t *dev)
+{
+ /* no write access */
+ if (fp->f_mode & 2) return -EROFS;
+
+#if 0 /* With this, one cannot eject a disk with workman */
+ /* If this is the first open, lock the door. */
+ if (dev->usage == 1)
+ (void) cdrom_lockdoor (dev, 1);
+#endif
+
+ /* Should check that there's a disk in the drive? */
+ return 0;
+}
+
+
+/*
+ * Close down the device. Invalidate all cached blocks.
+ */
+
+static void
+cdrom_release (struct inode *inode, struct file *file, ide_dev_t *dev)
+{
+ if (dev->usage == 0)
+ {
+ invalidate_buffers (inode->i_rdev);
+
+#if 0
+ /* Unlock the door. */
+ (void) cdrom_lockdoor (dev, 0);
+#endif
+ }
+}
+
+
+
+/****************************************************************************
+ * Device initialization.
+ */
+
+static void cdrom_setup (ide_dev_t *dev)
+{
+ /* Just guess at capacity for now. */
+ ide_capacity[DEV_HWIF][dev->select.b.drive] = 0x1fffff;
+
+ ide_blksizes[DEV_HWIF][dev->select.b.drive << PARTN_BITS] = CD_FRAMESIZE;
+
+ dev->special.all = 0;
+
+ CDROM_FLAGS (dev)->media_changed = 0;
+ CDROM_FLAGS (dev)->toc_valid = 0;
+
+ CDROM_FLAGS (dev)->no_playaudio12 = 0;
+ CDROM_FLAGS (dev)->no_lba_toc = 0;
+ CDROM_FLAGS (dev)->msf_as_bcd = 0;
+ CDROM_FLAGS (dev)->drq_interrupt = ((dev->id->config & 0x0060) == 0x20);
+
+ /* Accommodate some broken drives... */
+ if (strcmp (dev->id->model, "CD220E") == 0) /* Creative Labs */
+ CDROM_FLAGS (dev)->no_lba_toc = 1;
+
+ else if (strcmp (dev->id->model, "TO-ICSLYAL") == 0 || /* Acer CD525E */
+ strcmp (dev->id->model, "OTI-SCYLLA") == 0)
+ CDROM_FLAGS (dev)->no_lba_toc = 1;
+
+ else if (strcmp (dev->id->model, "CDA26803I SE") == 0) /* Aztech */
+ {
+ CDROM_FLAGS (dev)->no_lba_toc = 1;
+
+ /* This drive _also_ does not implement PLAYAUDIO12 correctly. */
+ CDROM_FLAGS (dev)->no_playaudio12 = 1;
+ }
+
+ cdrom_info[DEV_HWIF][dev->select.b.drive].toc = NULL;
+ cdrom_info[DEV_HWIF][dev->select.b.drive].sector_buffer = NULL;
+ cdrom_info[DEV_HWIF][dev->select.b.drive].sector_buffered = 0;
+ cdrom_info[DEV_HWIF][dev->select.b.drive].nsectors_buffered = 0;
+}
+
+
+#undef MIN
+#undef SECTOR_SIZE
+#undef SECTOR_BITS
+
+
+/*
+ * TODO:
+ * Read actual disk capacity.
+ * Multisession support.
+ * Direct reading of audio data.
+ * Eject-on-dismount.
+ * Lock door while there's a mounted volume.
+ * Establish interfaces for an IDE port driver, and break out the cdrom
+ * code into a loadable module.
+ */
+