diff options
Diffstat (limited to 'drivers/scsi/sr.c')
| -rw-r--r-- | drivers/scsi/sr.c | 117 |
1 files changed, 88 insertions, 29 deletions
diff --git a/drivers/scsi/sr.c b/drivers/scsi/sr.c index aefadc6a160..93cbd36c990 100644 --- a/drivers/scsi/sr.c +++ b/drivers/scsi/sr.c @@ -45,6 +45,7 @@ #include <linux/blkdev.h> #include <linux/mutex.h> #include <linux/slab.h> +#include <linux/pm_runtime.h> #include <asm/uaccess.h> #include <scsi/scsi.h> @@ -78,7 +79,13 @@ MODULE_ALIAS_SCSI_DEVICE(TYPE_WORM); 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 struct dev_pm_ops sr_pm_ops = { + .runtime_suspend = sr_runtime_suspend, +}; static struct scsi_driver sr_template = { .owner = THIS_MODULE, @@ -86,7 +93,9 @@ static struct scsi_driver sr_template = { .name = "sr", .probe = sr_probe, .remove = sr_remove, + .pm = &sr_pm_ops, }, + .init_command = sr_init_command, .done = sr_done, }; @@ -131,6 +140,16 @@ 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->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. @@ -144,13 +163,10 @@ static inline struct scsi_cd *scsi_cd_get(struct gendisk *disk) goto out; cd = scsi_cd(disk); kref_get(&cd->kref); - if (scsi_device_get(cd->device)) - goto out_put; - goto out; - - out_put: - kref_put(&cd->kref, sr_kref_release); - cd = NULL; + if (scsi_device_get(cd->device)) { + kref_put(&cd->kref, sr_kref_release); + cd = NULL; + } out: mutex_unlock(&sr_ref_mutex); return cd; @@ -221,14 +237,33 @@ static unsigned int sr_check_events(struct cdrom_device_info *cdi, return 0; events = sr_get_events(cd->device); + cd->get_event_changed |= events & DISK_EVENT_MEDIA_CHANGE; + + /* + * If earlier GET_EVENT_STATUS_NOTIFICATION and TUR did not agree + * for several times in a row. We rely on TUR only for this likely + * broken device, to prevent generating incorrect media changed + * events for every open(). + */ + if (cd->ignore_get_event) { + events &= ~DISK_EVENT_MEDIA_CHANGE; + goto do_tur; + } + /* * GET_EVENT_STATUS_NOTIFICATION is enough unless MEDIA_CHANGE * is being cleared. Note that there are devices which hang * if asked to execute TUR repeatedly. */ - if (!(clearing & DISK_EVENT_MEDIA_CHANGE)) - goto skip_tur; + if (cd->device->changed) { + events |= DISK_EVENT_MEDIA_CHANGE; + cd->device->changed = 0; + cd->tur_changed = true; + } + if (!(clearing & DISK_EVENT_MEDIA_CHANGE)) + return events; +do_tur: /* let's see whether the media is there with TUR */ last_present = cd->media_present; ret = scsi_test_unit_ready(cd->device, SR_TIMEOUT, MAX_RETRIES, &sshdr); @@ -242,12 +277,31 @@ static unsigned int sr_check_events(struct cdrom_device_info *cdi, (scsi_sense_valid(&sshdr) && sshdr.asc != 0x3a); if (last_present != cd->media_present) - events |= DISK_EVENT_MEDIA_CHANGE; -skip_tur: + cd->device->changed = 1; + if (cd->device->changed) { events |= DISK_EVENT_MEDIA_CHANGE; cd->device->changed = 0; + cd->tur_changed = true; + } + + if (cd->ignore_get_event) + return events; + + /* check whether GET_EVENT is reporting spurious MEDIA_CHANGE */ + if (!cd->tur_changed) { + if (cd->get_event_changed) { + if (cd->tur_mismatch++ > 8) { + sdev_printk(KERN_WARNING, cd->device, + "GET_EVENT and TUR disagree continuously, suppress GET_EVENT events\n"); + cd->ignore_get_event = true; + } + } else { + cd->tur_mismatch = 0; + } } + cd->tur_changed = false; + cd->get_event_changed = false; return events; } @@ -326,21 +380,14 @@ static int sr_done(struct scsi_cmnd *SCpnt) return good_bytes; } -static int sr_prep_fn(struct request_queue *q, struct request *rq) +static int sr_init_command(struct scsi_cmnd *SCpnt) { int block = 0, this_count, s_size; struct scsi_cd *cd; - struct scsi_cmnd *SCpnt; - struct scsi_device *sdp = q->queuedata; + struct request *rq = SCpnt->request; + struct scsi_device *sdp = SCpnt->device; int ret; - if (rq->cmd_type == REQ_TYPE_BLOCK_PC) { - ret = scsi_setup_blk_pc_cmnd(sdp, rq); - goto out; - } else if (rq->cmd_type != REQ_TYPE_FS) { - ret = BLKPREP_KILL; - goto out; - } ret = scsi_setup_fs_cmnd(sdp, rq); if (ret != BLKPREP_OK) goto out; @@ -465,7 +512,7 @@ static int sr_prep_fn(struct request_queue *q, struct request *rq) */ ret = BLKPREP_OK; out: - return scsi_prep_return(q, rq, ret); + return ret; } static int sr_block_open(struct block_device *bdev, fmode_t mode) @@ -484,14 +531,13 @@ static int sr_block_open(struct block_device *bdev, fmode_t mode) return ret; } -static int sr_block_release(struct gendisk *disk, fmode_t mode) +static void sr_block_release(struct gendisk *disk, fmode_t mode) { struct scsi_cd *cd = scsi_cd(disk); mutex_lock(&sr_mutex); cdrom_release(&cd->cdi, mode); scsi_cd_put(cd); mutex_unlock(&sr_mutex); - return 0; } static int sr_block_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd, @@ -540,6 +586,10 @@ static unsigned int sr_block_check_events(struct gendisk *disk, unsigned int clearing) { struct scsi_cd *cd = scsi_cd(disk); + + if (atomic_read(&cd->device->disk_events_disable_depth)) + return 0; + return cdrom_check_events(&cd->cdi, clearing); } @@ -550,10 +600,11 @@ static int sr_block_revalidate_disk(struct gendisk *disk) /* if the unit is not ready, nothing more to do */ if (scsi_test_unit_ready(cd->device, SR_TIMEOUT, MAX_RETRIES, &sshdr)) - return 0; + goto out; sr_cd_check(&cd->cdi); get_sectorsize(cd); +out: return 0; } @@ -567,7 +618,7 @@ static const struct block_device_operations sr_bdops = .revalidate_disk = sr_block_revalidate_disk, /* * No compat_ioctl for now because sr_block_ioctl never - * seems to pass arbitary ioctls down to host drivers. + * seems to pass arbitrary ioctls down to host drivers. */ }; @@ -636,7 +687,7 @@ static int sr_probe(struct device *dev) disk->first_minor = minor; sprintf(disk->disk_name, "sr%d", minor); disk->fops = &sr_bdops; - disk->flags = GENHD_FL_CD; + disk->flags = GENHD_FL_CD | GENHD_FL_BLOCK_EVENTS_ON_EXCL_WRITE; disk->events = DISK_EVENT_MEDIA_CHANGE | DISK_EVENT_EJECT_REQUEST; blk_queue_rq_timeout(sdev->request_queue, SR_TIMEOUT); @@ -662,7 +713,6 @@ static int sr_probe(struct device *dev) /* FIXME: need to handle a get_capabilities failure properly ?? */ get_capabilities(cd); - blk_queue_prep_rq(sdev->request_queue, sr_prep_fn); sr_vendor_init(cd); disk->driverfs_dev = &sdev->sdev_gendev; @@ -674,12 +724,20 @@ static int sr_probe(struct device *dev) if (register_cdrom(&cd->cdi)) goto fail_put; + /* + * Initialize block layer runtime PM stuffs before the + * periodic event checking request gets started in add_disk. + */ + blk_pm_runtime_init(sdev->request_queue, dev); + dev_set_drvdata(dev, cd); disk->flags |= GENHD_FL_REMOVABLE; add_disk(disk); sdev_printk(KERN_DEBUG, sdev, "Attached scsi CD-ROM %s\n", cd->cdi.name); + scsi_autopm_put_device(cd->device); + return 0; fail_put: @@ -927,7 +985,8 @@ static int sr_remove(struct device *dev) { struct scsi_cd *cd = dev_get_drvdata(dev); - blk_queue_prep_rq(cd->device->request_queue, scsi_prep_fn); + scsi_autopm_get_device(cd->device); + del_gendisk(cd->disk); mutex_lock(&sr_ref_mutex); |
