URL
https://opencores.org/ocsvn/or1k/or1k/trunk
Subversion Repositories or1k
[/] [or1k/] [trunk/] [rc203soc/] [sw/] [uClinux/] [drivers/] [cdrom/] [cdrom.c] - Rev 1765
Compare with Previous | Blame | View Log
/* 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 1.1 2005-12-20 10:16:38 jcastillo Exp $" /* 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(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_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. */ #ifndef ENOMEDIUM #define ENOMEDIUM EAGAIN /* no medium in removable device */ #endif /* 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. */ int open_for_data(struct cdrom_device_ops *, kdev_t); int cdrom_open(struct inode *ip, struct file *fp) { kdev_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, kdev_t 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) { kdev_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(kdev_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(kdev_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 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; \ 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 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. */ int cdrom_ioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg) { kdev_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 <= cdo->speed && 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_msf, 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_volctrl, volume); return cdo->audio_ioctl(dev, cmd, &volume); } case CDROMVOLREAD: { struct cdrom_volctrl volume; if (!cdo->audio_ioctl(dev, cmd, &volume)) { PUTARG(struct cdrom_volctrl, 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: */