patch-pre2.0.1 linux/drivers/cdrom/cdrom.c
Next file: linux/drivers/cdrom/cm206.c
Previous file: linux/drivers/cdrom/Makefile
Back to the patch index
Back to the overall index
- Lines: 512
- Date:
Sun May 12 08:13:07 1996
- Orig file:
v1.3.100/linux/drivers/cdrom/cdrom.c
- Orig date:
Thu Jan 1 02:00:00 1970
diff -u --recursive --new-file v1.3.100/linux/drivers/cdrom/cdrom.c linux/drivers/cdrom/cdrom.c
@@ -0,0 +1,511 @@
+/* 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 <linux/cdrom.h>
+#include <linux/ucdrom.h>
+
+#define FM_WRITE 0x2 /* file mode write bit */
+
+#define VERSION "$Id: cdrom.c,v 0.4 1996/04/17 20:47:50 david Exp david $"
+
+/* Not-exported routines. */
+int cdrom_open(struct inode *ip, struct file *fp);
+void cdrom_release(struct inode *ip, struct file *fp);
+int cdrom_ioctl(struct inode *ip, struct file *fp,
+ unsigned int cmd, unsigned long arg);
+int cdrom_media_changed(dev_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_ops *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(int major, char *name, struct cdrom_device_ops *cdo)
+{
+ int *change_capability = &cdo->capability; /* hack, gcc complains OK */
+
+ if (major < 0 || major >= MAX_BLKDEV)
+ return -1;
+ if (cdo->open_files == NULL || 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);
+ cdromdevs[major] = cdo;
+ cdo->options = CDO_AUTO_CLOSE | CDO_USE_FFLAGS | CDO_LOCK;
+ cdo->mc_flags = 0;
+ return 0;
+}
+#undef ENSURE
+
+int unregister_cdrom(int major, char *name)
+{
+ if (major < 0 || major >= MAX_BLKDEV)
+ return -1;
+ if (cdromdevs[major] == NULL)
+ return -2;
+ cdromdevs[major] = NULL;
+ return 0;
+}
+
+/* We need our own cdrom error types! This is a temporary solution. */
+
+#define ENOMEDIUM EAGAIN /* no medium in removable device */
+
+/* We use the open-option O_NONBLOCK as in indicator of 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 advantage: device conotrol becomes a lot easier
+ * this way.
+ */
+int open_for_data(struct cdrom_device_ops *, int);
+
+int cdrom_open(struct inode *ip, struct file *fp)
+{
+ dev_t dev = ip->i_rdev;
+ struct cdrom_device_ops *cdo = cdromdevs[MAJOR(dev)];
+ int purpose = !!(fp->f_flags & O_NONBLOCK);
+
+ if (cdo == NULL || MINOR(dev) >= cdo->minors)
+ return -ENODEV;
+ if (fp->f_mode & FM_WRITE)
+ return -EROFS;
+ purpose = purpose || !(cdo->options & CDO_USE_FFLAGS);
+ if (cdo->open_files(dev) || purpose)
+ return cdo->open(dev, purpose);
+ else
+ return open_for_data(cdo, dev);
+}
+
+int open_for_data(struct cdrom_device_ops * cdo, int dev)
+{
+ int ret;
+ if (cdo->drive_status != NULL) {
+ int ds = cdo->drive_status(dev);
+ if (ds == CDS_TRAY_OPEN) {
+ /* can/may i close it? */
+ if (cdo->capability & ~cdo->mask & CDC_CLOSE_TRAY &&
+ cdo->options & CDO_AUTO_CLOSE) {
+ if (cdo->tray_move(dev, 0))
+ return -EIO;
+ } else
+ return -ENOMEDIUM; /* can't close: too bad */
+ ds = cdo->drive_status(dev);
+ if (ds == CDS_NO_DISC)
+ return -ENOMEDIUM;
+ }
+ }
+ if (cdo->disc_status != NULL) {
+ int ds = cdo->disc_status(dev);
+ if (ds == CDS_NO_DISC)
+ return -ENOMEDIUM;
+ if (cdo->options & CDO_CHECK_TYPE &&
+ ds != CDS_DATA_1)
+ return -ENODATA;
+ }
+ /* all is well, we can open the device */
+ ret = cdo->open(dev, 0); /* open for data */
+ if (cdo->capability & ~cdo->mask & CDC_LOCK &&
+ cdo->options & CDO_LOCK)
+ cdo->lock_door(dev, 1);
+ return ret;
+}
+
+/* Admittedly, the logic below could be performed in a nicer way. */
+void cdrom_release(struct inode *ip, struct file *fp)
+{
+ dev_t dev = ip->i_rdev;
+ struct cdrom_device_ops *cdo = cdromdevs[MAJOR(dev)];
+
+ if (cdo == NULL || MINOR(dev) >= cdo->minors)
+ return;
+ if (cdo->open_files(dev) == 1 && /* last process that closes dev */
+ cdo->options & CDO_LOCK &&
+ cdo->capability & ~cdo->mask & CDC_LOCK)
+ cdo->lock_door(dev, 0);
+ cdo->release(dev);
+ if (cdo->open_files(dev) == 0) { /* last process that closes dev */
+ sync_dev(dev);
+ invalidate_buffers(dev);
+ if (cdo->options & CDO_AUTO_EJECT &&
+ cdo->capability & ~cdo->mask & CDC_OPEN_TRAY)
+ cdo->tray_move(dev, 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.
+ *
+ * For now, i've implemented only 16 minor devs (half a long), i have to
+ * think of a better solution... $Queue$ is either 0 or 1. Queue 0 is
+ * in the lower 16 bits, queue 1 in the higher 16 bits.
+ */
+
+int media_changed(dev_t dev, int queue)
+{
+ unsigned int major = MAJOR(dev);
+ unsigned int minor = MINOR(dev);
+ struct cdrom_device_ops *cdo = cdromdevs[major];
+ int ret;
+ unsigned long mask = 1 << (16 * queue + minor);
+
+ queue &= 1;
+ if (cdo == NULL || minor >= 16)
+ return -1;
+ ret = !!(cdo->mc_flags & mask); /* changed since last call? */
+ if (cdo->media_changed(dev)) {
+ cdo->mc_flags |= 0x10001 << minor; /* set bit on both queues */
+ ret |= 1;
+ }
+ cdo->mc_flags &= ~mask; /* clear bit */
+ return ret;
+}
+
+int cdrom_media_changed(dev_t dev)
+{
+ struct cdrom_device_ops *cdo = cdromdevs[MAJOR(dev)];
+ if (cdo == NULL || MINOR(dev) >= cdo->minors)
+ return -ENODEV;
+ if (cdo->media_changed == NULL)
+ return -EINVAL;
+ return media_changed(dev, 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.
+ */
+
+#undef current /* set in sched.h */
+
+void sanitize_format(union cdrom_addr *addr,
+ u_char * current, u_char requested)
+{
+ if (*current == 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;
+ }
+ *current = 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 inenficient when used both 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; \
+ memcpy_fromfs(&x, (type *) arg, sizeof x); }
+#define PUTARG(type, x) { \
+ int ret=verify_area(VERIFY_WRITE, (void *) arg, sizeof x); \
+ if (ret) return ret; \
+ memcpy_tofs((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 hapy with
+ * this, most drives therefor 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.
+ */
+int cdrom_ioctl(struct inode *ip, struct file *fp,
+ unsigned int cmd, unsigned long arg)
+{
+ dev_t dev = ip->i_rdev;
+ struct cdrom_device_ops *cdo = cdromdevs[MAJOR(dev)];
+
+ if (cdo == NULL || MINOR(dev) >= cdo->minors)
+ return -ENODEV;
+ /* 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(dev, &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->open_files(dev) == 1 &&
+ cdo->capability & ~cdo->mask & CDC_OPEN_TRAY)
+ return cdo->tray_move(dev, 1);
+ else
+ return -EINVAL;
+
+ case CDROMCLOSETRAY:
+ if (cdo->open_files(dev) == 1 &&
+ cdo->capability & ~cdo->mask & CDC_CLOSE_TRAY)
+ return cdo->tray_move(dev, 0);
+ else
+ return -EINVAL;
+
+ case CDROMEJECT_SW:
+ cdo->options &= ~(CDO_AUTO_CLOSE | CDO_AUTO_EJECT);
+ if (arg)
+ cdo->options |= CDO_AUTO_CLOSE | CDO_AUTO_EJECT;
+ return 0;
+
+ case CDROM_MEDIA_CHANGED:
+ if (cdo->capability & ~cdo->mask & CDC_MEDIA_CHANGED)
+ return media_changed(dev, 1);
+ else
+ return -EINVAL;
+
+ case CDROM_SET_OPTIONS:
+ cdo->options |= (int) arg;
+ return cdo->options;
+
+ case CDROM_CLEAR_OPTIONS:
+ cdo->options &= ~(int) arg;
+ return cdo->options;
+
+ case CDROM_SELECT_SPEED:
+ if (0 <= arg && arg < (int) (cdo->speed + 0.5) &&
+ cdo->capability & ~cdo->mask & CDC_SELECT_SPEED)
+ return cdo->select_speed(dev, arg);
+ else
+ return -EINVAL;
+
+ case CDROM_SELECT_DISC:
+ if (0 <= arg && arg <= cdo->capacity &&
+ cdo->capability & ~cdo->mask & CDC_SELECT_DISC)
+ return cdo->select_disc(dev, 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(dev, &mcn)) {
+ PUTARG(struct cdrom_mcn, mcn);
+ return 0;
+ }
+ return -EINVAL;
+ }
+
+ case CDROM_DRIVE_STATUS:
+ if (cdo->drive_status == NULL)
+ return -EINVAL;
+ else
+ return cdo->drive_status(dev);
+
+ case CDROM_DISC_STATUS:
+ if (cdo->disc_status == NULL)
+ return -EINVAL;
+ else
+ return cdo->disc_status(dev);
+
+/* 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)) {
+ 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(dev, 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(dev, 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(dev, 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_mdf, msf);
+ return cdo->audio_ioctl(dev, cmd, &msf);
+ }
+ case CDROMPLAYTRKIND: {
+ struct cdrom_ti track_index;
+ GETARG(struct cdrom_ti, track_index);
+ return cdo->audio_ioctl(dev, cmd, &track_index);
+ }
+ case CDROMVOLCTRL: {
+ struct cdrom_volctrl volume;
+ GETARG(struct cdrom_volctl, volume);
+ return cdo->audio_ioctl(dev, cmd, &volume);
+ }
+ case CDROMVOLREAD: {
+ struct cdrom_volctrl volume;
+ if (!cdo->audio_ioctl(dev, cmd, &volume)) {
+ PUTARG(struct cdrom_volctl, volume);
+ return 0;
+ }
+ return -EINVAL;
+ }
+ case CDROMSTART:
+ case CDROMSTOP:
+ case CDROMPAUSE:
+ case CDROMRESUME:
+ return cdo->audio_ioctl(dev, cmd, NULL);
+ } /* switch */
+
+ if (cdo->dev_ioctl != NULL) /* device specific ioctls? */
+ return cdo->dev_ioctl(dev, 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/usr/src/linux-obj/include -Wall -Wstrict-prototypes -O2 -m486 -c cdrom.c -o cdrom.o"
+ * End:
+ */
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov
with Sam's (original) version of this