summaryrefslogtreecommitdiffstats
path: root/drivers/cdrom/cdrom.c
diff options
context:
space:
mode:
authorRalf Baechle <ralf@linux-mips.org>1997-01-07 02:33:00 +0000
committer <ralf@linux-mips.org>1997-01-07 02:33:00 +0000
commitbeb116954b9b7f3bb56412b2494b562f02b864b1 (patch)
tree120e997879884e1b9d93b265221b939d2ef1ade1 /drivers/cdrom/cdrom.c
parent908d4681a1dc3792ecafbe64265783a86c4cccb6 (diff)
Import of Linux/MIPS 2.1.14
Diffstat (limited to 'drivers/cdrom/cdrom.c')
-rw-r--r--drivers/cdrom/cdrom.c568
1 files changed, 568 insertions, 0 deletions
diff --git a/drivers/cdrom/cdrom.c b/drivers/cdrom/cdrom.c
new file mode 100644
index 000000000..9d5149655
--- /dev/null
+++ b/drivers/cdrom/cdrom.c
@@ -0,0 +1,568 @@
+/* cdrom.c. Common ioctl and open routines for various Linux cdrom drivers. -*- linux-c -*-
+ Copyright (c) 1996 David van Leeuwen.
+
+ The routines in the file should provide an interface between
+ software accessing cdroms and the various drivers that implement
+ specific hardware devices.
+
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/major.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <asm/fcntl.h>
+#include <asm/segment.h>
+#include <asm/uaccess.h>
+
+#include <linux/cdrom.h>
+#include <linux/ucdrom.h>
+
+#define FM_WRITE 0x2 /* file mode write bit */
+
+#define VERSION "Generic CD-ROM driver, v 1.21 1996/11/08 03:24:49"
+
+/* Not-exported routines. */
+static int cdrom_open(struct inode *ip, struct file *fp);
+static void cdrom_release(struct inode *ip, struct file *fp);
+static int cdrom_ioctl(struct inode *ip, struct file *fp,
+ unsigned int cmd, unsigned long arg);
+static int cdrom_media_changed(kdev_t dev);
+
+struct file_operations cdrom_fops =
+{
+ NULL, /* lseek */
+ block_read, /* read - general block-dev read */
+ block_write, /* write - general block-dev write */
+ NULL, /* readdir */
+ NULL, /* select */
+ cdrom_ioctl, /* ioctl */
+ NULL, /* mmap */
+ cdrom_open, /* open */
+ cdrom_release, /* release */
+ NULL, /* fsync */
+ NULL, /* fasync */
+ cdrom_media_changed, /* media_change */
+ NULL /* revalidate */
+};
+
+static struct cdrom_device_info *cdromdevs[MAX_BLKDEV] = {
+ NULL,
+};
+
+/* This macro makes sure we don't have to check on cdrom_device_ops
+ * existence in the run-time routines below. Change_capability is a
+ * hack to have the capability flags defined const, while we can still
+ * change it here without gcc complaining at every line.
+ */
+
+#define ENSURE(call, bits) if (cdo->call == NULL) *change_capability &= ~(bits)
+
+/* We don't use $name$ yet, but it could be used for the /proc
+ * filesystem in the future, or for other purposes.
+ */
+int register_cdrom(struct cdrom_device_info *cdi, char *name)
+{
+ int major = MAJOR (cdi->dev);
+ struct cdrom_device_ops *cdo = cdi->ops;
+ int *change_capability = (int *)&cdo->capability; /* hack */
+
+ if (major < 0 || major >= MAX_BLKDEV)
+ return -1;
+ if (cdo->open == NULL || cdo->release == NULL)
+ return -2;
+ ENSURE(tray_move, CDC_CLOSE_TRAY | CDC_OPEN_TRAY);
+ ENSURE(lock_door, CDC_LOCK);
+ ENSURE(select_speed, CDC_SELECT_SPEED);
+ ENSURE(select_disc, CDC_SELECT_DISC);
+ ENSURE(get_last_session, CDC_MULTI_SESSION);
+ ENSURE(audio_ioctl, CDC_PLAY_AUDIO);
+ ENSURE(media_changed, CDC_MEDIA_CHANGED);
+ if (cdromdevs[major]==NULL) cdo->n_minors = 0;
+ else cdo->n_minors++;
+ cdi->next = cdromdevs[major];
+ cdromdevs[major] = cdi;
+ cdi->options = CDO_AUTO_CLOSE | CDO_USE_FFLAGS | CDO_LOCK;
+ cdi->mc_flags = 0;
+ return 0;
+}
+#undef ENSURE
+
+int unregister_cdrom(struct cdrom_device_info *unreg)
+{
+ struct cdrom_device_info *cdi, *prev;
+ int major = MAJOR (unreg->dev);
+
+ if (major < 0 || major >= MAX_BLKDEV)
+ return -1;
+
+ prev = NULL;
+ cdi = cdromdevs[major];
+ while (cdi != NULL && cdi->dev != unreg->dev) {
+ prev = cdi;
+ cdi = cdi->next;
+ }
+
+ if (cdi == NULL)
+ return -2;
+ if (prev)
+ prev->next = cdi->next;
+ else
+ cdromdevs[major] = cdi->next;
+ cdi->ops->n_minors--;
+ return 0;
+}
+
+static
+struct cdrom_device_info *cdrom_find_device (kdev_t dev)
+{
+ struct cdrom_device_info *cdi = cdromdevs[MAJOR (dev)];
+
+ while (cdi != NULL && cdi->dev != dev)
+ cdi = cdi->next;
+ return cdi;
+}
+
+/* We use the open-option O_NONBLOCK to indicate that the
+ * purpose of opening is only for subsequent ioctl() calls; no device
+ * integrity checks are performed.
+ *
+ * We hope that all cd-player programs will adopt this convention. It
+ * is in their own interest: device control becomes a lot easier
+ * this way.
+ */
+static
+int open_for_data(struct cdrom_device_info * cdi);
+
+static
+int cdrom_open(struct inode *ip, struct file *fp)
+{
+ kdev_t dev = ip->i_rdev;
+ struct cdrom_device_info *cdi = cdrom_find_device(dev);
+ int purpose = !!(fp->f_flags & O_NONBLOCK);
+ int ret=0;
+
+ if (cdi == NULL)
+ return -ENODEV;
+ if (fp->f_mode & FM_WRITE)
+ return -EROFS;
+ purpose = purpose || !(cdi->options & CDO_USE_FFLAGS);
+ if (cdi->use_count || purpose)
+ ret = cdi->ops->open(cdi, purpose);
+ else
+ ret = open_for_data(cdi);
+ if (!ret) cdi->use_count++;
+ return ret;
+}
+
+static
+int open_for_data(struct cdrom_device_info * cdi)
+{
+ int ret;
+ struct cdrom_device_ops *cdo = cdi->ops;
+ if (cdo->drive_status != NULL) {
+ int ds = cdo->drive_status(cdi, CDSL_CURRENT);
+ if (ds == CDS_TRAY_OPEN) {
+ /* can/may i close it? */
+ if (cdo->capability & ~cdi->mask & CDC_CLOSE_TRAY &&
+ cdi->options & CDO_AUTO_CLOSE) {
+ if (cdo->tray_move(cdi,0))
+ return -EIO;
+ } else
+ return -ENXIO; /* can't close: too bad */
+ ds = cdo->drive_status(cdi, CDSL_CURRENT);
+ if (ds == CDS_NO_DISC)
+ return -ENXIO;
+ }
+ }
+ if (cdo->disc_status != NULL) {
+ int ds = cdo->disc_status(cdi);
+ if (ds == CDS_NO_DISC)
+ return -ENXIO;
+ if (cdi->options & CDO_CHECK_TYPE &&
+ ds != CDS_DATA_1)
+ return -ENODATA;
+ }
+ /* all is well, we can open the device */
+ ret = cdo->open(cdi, 0); /* open for data */
+ if (cdo->capability & ~cdi->mask & CDC_LOCK &&
+ cdi->options & CDO_LOCK)
+ cdo->lock_door(cdi, 1);
+ return ret;
+}
+
+/* Admittedly, the logic below could be performed in a nicer way. */
+static
+void cdrom_release(struct inode *ip, struct file *fp)
+{
+ kdev_t dev = ip->i_rdev;
+ struct cdrom_device_info *cdi = cdrom_find_device (dev);
+ struct cdrom_device_ops *cdo;
+
+ if (cdi == NULL)
+ return;
+ cdo = cdi->ops;
+ if (cdi->use_count == 1 && /* last process that closes dev*/
+ cdi->options & CDO_LOCK &&
+ cdo->capability & ~cdi->mask & CDC_LOCK)
+ cdo->lock_door(cdi, 0);
+ cdo->release(cdi);
+ if (cdi->use_count > 0) cdi->use_count--;
+ if (cdi->use_count == 0) { /* last process that closes dev*/
+ sync_dev(dev);
+ invalidate_buffers(dev);
+ if (cdi->options & CDO_AUTO_EJECT &&
+ cdo->capability & ~cdi->mask & CDC_OPEN_TRAY)
+ cdo->tray_move(cdi, 1);
+ }
+}
+
+/* We want to make media_changed accessible to the user through an
+ * ioctl. The main problem now is that we must double-buffer the
+ * low-level implementation, to assure that the VFS and the user both
+ * see a medium change once.
+ */
+
+static
+int media_changed(struct cdrom_device_info *cdi, int queue)
+{
+ unsigned int mask = (1 << (queue & 1));
+ int ret = !!(cdi->mc_flags & mask);
+
+ /* changed since last call? */
+ if (cdi->ops->media_changed(cdi, CDSL_CURRENT)) {
+ cdi->mc_flags = 0x3; /* set bit on both queues */
+ ret |= 1;
+ }
+ cdi->mc_flags &= ~mask; /* clear bit */
+ return ret;
+}
+
+static
+int cdrom_media_changed(kdev_t dev)
+{
+ struct cdrom_device_info *cdi = cdrom_find_device (dev);
+ if (cdi == NULL)
+ return -ENODEV;
+ if (cdi->ops->media_changed == NULL)
+ return -EINVAL;
+ return media_changed(cdi, 0);
+}
+
+/* Requests to the low-level drivers will /always/ be done in the
+ following format convention:
+
+ CDROM_LBA: all data-related requests.
+ CDROM_MSF: all audio-related requests.
+
+ However, a low-level implementation is allowed to refuse this
+ request, and return information in its own favorite format.
+
+ It doesn't make sense /at all/ to ask for a play_audio in LBA
+ format, or ask for multi-session info in MSF format. However, for
+ backward compatibility these format requests will be satisfied, but
+ the requests to the low-level drivers will be sanitized in the more
+ meaningful format indicated above.
+ */
+
+static
+void sanitize_format(union cdrom_addr *addr,
+ u_char * curr, u_char requested)
+{
+ if (*curr == requested)
+ return; /* nothing to be done! */
+ if (requested == CDROM_LBA) {
+ addr->lba = (int) addr->msf.frame +
+ 75 * (addr->msf.second - 2 + 60 * addr->msf.minute);
+ } else { /* CDROM_MSF */
+ int lba = addr->lba;
+ addr->msf.frame = lba % 75;
+ lba /= 75;
+ lba += 2;
+ addr->msf.second = lba % 60;
+ addr->msf.minute = lba / 60;
+ }
+ *curr = requested;
+}
+
+/* All checking and format change makes this code really hard to read!
+ * So let's make some check and memory move macros. These macros are
+ * a little inefficient when both used in the same piece of code, as
+ * verify_area is used twice, but who cares, as ioctl() calls
+ * shouldn't be in inner loops.
+ */
+#define GETARG(type, x) { \
+ int ret=verify_area(VERIFY_READ, (void *) arg, sizeof x); \
+ if (ret) return ret; \
+ copy_from_user(&x, (type *) arg, sizeof x); }
+#define PUTARG(type, x) { \
+ int ret=verify_area(VERIFY_WRITE, (void *) arg, sizeof x); \
+ if (ret) return ret; \
+ copy_to_user((type *) arg, &x, sizeof x); }
+
+/* Some of the cdrom ioctls are not implemented here, because these
+ * appear to be either too device-specific, or it is not clear to me
+ * what use they are. These are (number of drivers that support them
+ * in parenthesis): CDROMREADMODE1 (2+ide), CDROMREADMODE2 (2+ide),
+ * CDROMREADAUDIO (2+ide), CDROMREADRAW (2), CDROMREADCOOKED (2),
+ * CDROMSEEK (2), CDROMPLAYBLK (scsi), CDROMREADALL (1). Read-audio,
+ * OK (although i guess the record companies aren't too happy with
+ * this, most drives therefore refuse to transport audio data). But
+ * why are there 5 different READs defined? For now, these functions
+ * are left over to the device-specific ioctl routine,
+ * cdo->dev_ioctl. Note that as a result of this, no
+ * memory-verification is performed for these ioctls.
+ */
+static
+int cdrom_ioctl(struct inode *ip, struct file *fp,
+ unsigned int cmd, unsigned long arg)
+{
+ kdev_t dev = ip->i_rdev;
+ struct cdrom_device_info *cdi = cdrom_find_device (dev);
+ struct cdrom_device_ops *cdo;
+
+ if (cdi == NULL)
+ return -ENODEV;
+ cdo = cdi->ops;
+ /* the first few commands do not deal with audio capabilities, but
+ only with routines in cdrom device operations. */
+ switch (cmd) {
+ /* maybe we should order cases after statistics of use? */
+
+ case CDROMMULTISESSION:
+ {
+ struct cdrom_multisession ms_info;
+ u_char requested_format;
+ if (!(cdo->capability & CDC_MULTI_SESSION))
+ return -EINVAL;
+ GETARG(struct cdrom_multisession, ms_info);
+ requested_format = ms_info.addr_format;
+ ms_info.addr_format = CDROM_LBA;
+ cdo->get_last_session(cdi, &ms_info);
+ sanitize_format(&ms_info.addr, &ms_info.addr_format,
+ requested_format);
+ PUTARG(struct cdrom_multisession, ms_info);
+ return 0;
+ }
+
+ case CDROMEJECT:
+ if (cdo->capability & ~cdi->mask & CDC_OPEN_TRAY) {
+ if (cdi->use_count == 1) {
+ if (cdo->capability & ~cdi->mask & CDC_LOCK)
+ cdo->lock_door(cdi, 0);
+ return cdo->tray_move(cdi, 1);
+ } else
+ return -EBUSY;
+ } else
+ return -EINVAL;
+
+ case CDROMCLOSETRAY:
+ if (cdo->capability & ~cdi->mask & CDC_CLOSE_TRAY)
+ return cdo->tray_move(cdi, 0);
+ else
+ return -EINVAL;
+
+ case CDROMEJECT_SW:
+ cdi->options &= ~(CDO_AUTO_CLOSE | CDO_AUTO_EJECT);
+ if (arg)
+ cdi->options |= CDO_AUTO_CLOSE | CDO_AUTO_EJECT;
+ return 0;
+
+ case CDROM_MEDIA_CHANGED:
+ if (cdo->capability & ~cdi->mask & CDC_MEDIA_CHANGED) {
+ if (arg == CDSL_CURRENT)
+ return media_changed(cdi, 1);
+ else if ((int)arg < cdi->capacity &&
+ cdo->capability & ~cdi->mask
+ &CDC_SELECT_DISC)
+ return cdo->media_changed (cdi, arg);
+ else
+ return -EINVAL;
+ }
+ else
+ return -EINVAL;
+
+ case CDROM_SET_OPTIONS:
+ cdi->options |= (int) arg;
+ return cdi->options;
+
+ case CDROM_CLEAR_OPTIONS:
+ cdi->options &= ~(int) arg;
+ return cdi->options;
+
+ case CDROM_SELECT_SPEED:
+ if ((int)arg <= cdi->speed &&
+ cdo->capability & ~cdi->mask & CDC_SELECT_SPEED)
+ return cdo->select_speed(cdi, arg);
+ else
+ return -EINVAL;
+
+ case CDROM_SELECT_DISC:
+ if ((arg == CDSL_CURRENT) || (arg == CDSL_NONE))
+ return cdo->select_disc(cdi, arg);
+ if ((int)arg < cdi->capacity &&
+ cdo->capability & ~cdi->mask & CDC_SELECT_DISC)
+ return cdo->select_disc(cdi, arg);
+ else
+ return -EINVAL;
+
+/* The following function is implemented, although very few audio
+ * discs give Universal Product Code information, which should just be
+ * the Medium Catalog Number on the box. Note, that the way the code
+ * is written on the CD is /not/ uniform across all discs!
+ */
+ case CDROM_GET_MCN: {
+ struct cdrom_mcn mcn;
+ if (!(cdo->capability & CDC_MCN))
+ return -EINVAL;
+ if (!cdo->get_mcn(cdi, &mcn)) {
+ PUTARG(struct cdrom_mcn, mcn);
+ return 0;
+ }
+ return -EINVAL;
+ }
+
+ case CDROM_DRIVE_STATUS:
+ if ((arg == CDSL_CURRENT) || (arg == CDSL_NONE))
+ return cdo->drive_status(cdi, arg);
+ if (cdo->drive_status == NULL ||
+ ! (cdo->capability & ~cdi->mask & CDC_SELECT_DISC
+ && (int)arg < cdi->capacity))
+ return -EINVAL;
+ else
+ return cdo->drive_status(cdi, arg);
+
+ case CDROM_DISC_STATUS:
+ if (cdo->disc_status == NULL)
+ return -EINVAL;
+ else
+ return cdo->disc_status(cdi);
+
+ case CDROM_CHANGER_NSLOTS:
+ return cdi->capacity;
+
+/* The following is not implemented, because there are too many
+ * different data type. We could support /1/ raw mode, that is large
+ * enough to hold everything.
+ */
+
+#if 0
+ case CDROMREADMODE1: {
+ struct cdrom_msf msf;
+ char buf[CD_FRAMESIZE];
+ GETARG(struct cdrom_msf, msf);
+ if (!cdo->read_audio(dev, cmd, &msf, &buf, cdi)) {
+ PUTARG(char *, buf);
+ return 0;
+ }
+ return -EINVAL;
+ }
+#endif
+ } /* switch */
+
+/* Now all the audio-ioctls follow, they are all routed through the
+ same call audio_ioctl(). */
+
+ if (cdo->capability & CDC_PLAY_AUDIO)
+ switch (cmd) {
+ case CDROMSUBCHNL:
+ {
+ struct cdrom_subchnl q;
+ u_char requested, back;
+ GETARG(struct cdrom_subchnl, q);
+ requested = q.cdsc_format;
+ q.cdsc_format = CDROM_MSF;
+ if (!cdo->audio_ioctl(cdi, cmd, &q)) {
+ back = q.cdsc_format; /* local copy */
+ sanitize_format(&q.cdsc_absaddr, &back,
+ requested);
+ sanitize_format(&q.cdsc_reladdr,
+ &q.cdsc_format, requested);
+ PUTARG(struct cdrom_subchnl, q);
+ return 0;
+ } else
+ return -EINVAL;
+ }
+ case CDROMREADTOCHDR: {
+ struct cdrom_tochdr header;
+ GETARG(struct cdrom_tochdr, header);
+ if (!cdo->audio_ioctl(cdi, cmd, &header)) {
+ PUTARG(struct cdrom_tochdr, header);
+ return 0;
+ } else
+ return -EINVAL;
+ }
+ case CDROMREADTOCENTRY: {
+ struct cdrom_tocentry entry;
+ u_char requested_format;
+ GETARG(struct cdrom_tocentry, entry);
+ requested_format = entry.cdte_format;
+ /* make interface to low-level uniform */
+ entry.cdte_format = CDROM_MSF;
+ if (!(cdo->audio_ioctl(cdi, cmd, &entry))) {
+ sanitize_format(&entry.cdte_addr,
+ &entry.cdte_format, requested_format);
+ PUTARG(struct cdrom_tocentry, entry);
+ return 0;
+ } else
+ return -EINVAL;
+ }
+ case CDROMPLAYMSF: {
+ struct cdrom_msf msf;
+ GETARG(struct cdrom_msf, msf);
+ return cdo->audio_ioctl(cdi, cmd, &msf);
+ }
+ case CDROMPLAYTRKIND: {
+ struct cdrom_ti track_index;
+ GETARG(struct cdrom_ti, track_index);
+ return cdo->audio_ioctl(cdi, cmd, &track_index);
+ }
+ case CDROMVOLCTRL: {
+ struct cdrom_volctrl volume;
+ GETARG(struct cdrom_volctrl, volume);
+ return cdo->audio_ioctl(cdi, cmd, &volume);
+ }
+ case CDROMVOLREAD: {
+ struct cdrom_volctrl volume;
+ if (!cdo->audio_ioctl(cdi, cmd, &volume)) {
+ PUTARG(struct cdrom_volctrl, volume);
+ return 0;
+ }
+ return -EINVAL;
+ }
+ case CDROMSTART:
+ case CDROMSTOP:
+ case CDROMPAUSE:
+ case CDROMRESUME:
+ return cdo->audio_ioctl(cdi, cmd, NULL);
+ } /* switch */
+
+ if (cdo->dev_ioctl != NULL) /* device specific ioctls? */
+ return cdo->dev_ioctl(cdi, cmd, arg);
+ return -EINVAL;
+}
+
+#ifdef MODULE
+int init_module(void)
+{
+ printk(KERN_INFO "Module inserted: " VERSION "\n");
+ return 0;
+}
+
+void cleanup_module(void)
+{
+ /*
+ printk(KERN_INFO "Module cdrom removed\n");
+ */
+}
+
+#endif
+/*
+ * Local variables:
+ * comment-column: 40
+ * compile-command: "gcc -DMODULE -D__KERNEL__ -I. -I/usr/src/linux-obj/include -Wall -Wstrict-prototypes -O2 -m486 -c cdrom.c -o cdrom.o"
+ * End:
+ */