/*
* sr.c Copyright (C) 1992 David Giller
* Copyright (C) 1993, 1994, 1995, 1999 Eric Youngdale
*
* adapted from:
* sd.c Copyright (C) 1992 Drew Eckhardt
* Linux scsi disk driver by
* Drew Eckhardt <drew@colorado.edu>
*
* Modified by Eric Youngdale ericy@andante.org to
* add scatter-gather, multiple outstanding request, and other
* enhancements.
*
* Modified by Eric Youngdale eric@andante.org to support loadable
* low-level scsi drivers.
*
* Modified by Thomas Quinot thomas@melchior.cuivre.fdn.fr to
* provide auto-eject.
*
* Modified by Gerd Knorr <kraxel@cs.tu-berlin.de> to support the
* generic cdrom interface
*
* Modified by Jens Axboe <axboe@suse.de> - Uniform sr_packet()
* interface, capabilities probe additions, ioctl cleanups, etc.
*
* Modified by Richard Gooch <rgooch@atnf.csiro.au> to support devfs
*
* Modified by Jens Axboe <axboe@suse.de> - support DVD-RAM
* transparently and lose the GHOST hack
*
* Modified by Arnaldo Carvalho de Melo <acme@conectiva.com.br>
* check resource allocation in sr_init and some cleanups
*/
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/bio.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/cdrom.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/pm_runtime.h>
#include <linux/uaccess.h>
#include <scsi/scsi.h>
#include <scsi/scsi_dbg.h>
#include <scsi/scsi_device.h>
#include <scsi/scsi_driver.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_eh.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_ioctl.h> /* For the door lock/unlock commands */
#include "scsi_logging.h"
#include "sr.h"
MODULE_DESCRIPTION("SCSI cdrom (sr) driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS_BLOCKDEV_MAJOR(SCSI_CDROM_MAJOR);
MODULE_ALIAS_SCSI_DEVICE(TYPE_ROM);
MODULE_ALIAS_SCSI_DEVICE(TYPE_WORM);
#define SR_DISKS 256
#define SR_CAPABILITIES \
(CDC_CLOSE_TRAY|CDC_OPEN_TRAY|CDC_LOCK|CDC_SELECT_SPEED| \
CDC_SELECT_DISC|CDC_MULTI_SESSION|CDC_MCN|CDC_MEDIA_CHANGED| \
CDC_PLAY_AUDIO|CDC_RESET|CDC_DRIVE_STATUS| \
CDC_CD_R|CDC_CD_RW|CDC_DVD|CDC_DVD_R|CDC_DVD_RAM|CDC_GENERIC_PACKET| \
CDC_MRW|CDC_MRW_W|CDC_RAM)
static DEFINE_MUTEX(sr_mutex);
static int sr_probe(struct device *);
static int sr_remove(struct device *);
static int sr_init_command(struct scsi_cmnd *SCpnt);
static int sr_done(struct scsi_cmnd *);
static int sr_runtime_suspend(struct device *dev);
static const struct dev_pm_ops sr_pm_ops = {
.runtime_suspend = sr_runtime_suspend,
};
static struct scsi_driver sr_template = {
.gendrv = {
.name = "sr",
.owner = THIS_MODULE,
.probe = sr_probe,
.remove = sr_remove,
.pm = &sr_pm_ops,
},
.init_command = sr_init_command,
.done = sr_done,
};
static unsigned long sr_index_bits[SR_DISKS / BITS_PER_LONG];
static DEFINE_SPINLOCK(sr_index_lock);
/* This semaphore is used to mediate the 0->1 reference get in the
* face of object destruction (i.e. we can't allow a get on an
* object after last put) */
static DEFINE_MUTEX(sr_ref_mutex);
static int sr_open(struct cdrom_device_info *, int);
static void sr_release(struct cdrom_device_info *);
static void get_sectorsize(struct scsi_cd *);
static void get_capabilities(struct scsi_cd *);
static unsigned int sr_check_events(struct cdrom_device_info *cdi,
unsigned int clearing, int slot);
static int sr_packet(struct cdrom_device_info *, struct packet_command *);
static const struct cdrom_device_ops sr_dops = {
.open = sr_open,
.release = sr_release,
.drive_status = sr_drive_status,
.check_events = sr_check_events,
.tray_move = sr_tray_move,
.lock_door = sr_lock_door,
.select_speed = sr_select_speed,
.get_last_session = sr_get_last_session,
.get_mcn = sr_get_mcn,
.reset = sr_reset,
.audio_ioctl = sr_audio_ioctl,
.capability = SR_CAPABILITIES,
.generic_packet = sr_packet,
};
static void sr_kref_release(struct kref *kref);
static inline struct scsi_cd *scsi_cd(struct gendisk *disk)
{
return container_of(disk->private_data, struct scsi_cd, driver);
}
static int sr_runtime_suspend(struct device *dev)
{
struct scsi_cd *cd = dev_get_drvdata(dev);
if (!cd) /* E.g.: runtime suspend following sr_remove() */
return 0;
if (cd->media_present)
return -EBUSY;
else
return 0;
}
/*
* The get and put routines for the struct scsi_cd. Note this entity
* has a scsi_device pointer and owns a reference to this.
*/
static inline struct scsi_cd *scsi_cd_get(struct gendisk *disk)
{
struct scsi_cd *cd = NULL;
mutex_lock