diff options
author | Kay Sievers <kay.sievers@vrfy.org> | 2011-06-30 15:03:48 +0200 |
---|---|---|
committer | James Bottomley <JBottomley@Parallels.com> | 2011-07-21 14:15:58 -0700 |
commit | 79b9677d885d1a792bc103f2febb06f91f92de43 (patch) | |
tree | 5971cd5ec435ca750b8ba77929df53e1fd78dbad /drivers/scsi/sr.c | |
parent | 57a6fa9acd6b4a479a6ede4d6d2258f04afd3a6f (diff) | |
download | linux-79b9677d885d1a792bc103f2febb06f91f92de43.tar.bz2 |
[SCSI] sr: check_events() ignore GET_EVENT when TUR says otherwise
Some broken devices indicates that media has changed on every
GET_EVENT_STATUS_NOTIFICATION. This translates into MEDIA_CHANGE
uevent on every open() which lets udev run into a loop.
Verify GET_EVENT result against TUR and if it generates spurious
events for several times in a row, ignore the GET_EVENT events, and
trust only the TUR status.
This is the log of a USB stick with a (broken) fake CDROM drive:
scsi 5:0:0:0: Direct-Access SanDisk U3 Cruzer Micro 8.02 PQ: 0 ANSI: 0 CCS
sd 5:0:0:0: Attached scsi generic sg3 type 0
scsi 5:0:0:1: CD-ROM SanDisk U3 Cruzer Micro 8.02 PQ: 0 ANSI: 0
sd 5:0:0:0: [sdb] Attached SCSI removable disk
sr2: scsi3-mmc drive: 48x/48x tray
sr 5:0:0:1: Attached scsi CD-ROM sr2
sr 5:0:0:1: Attached scsi generic sg4 type 5
sr2: GET_EVENT and TUR disagree continuously, suppress GET_EVENT events
sd 5:0:0:0: [sdb] 31777279 512-byte logical blocks: (16.2 GB/15.1 GiB)
sd 5:0:0:0: [sdb] No Caching mode page present
sd 5:0:0:0: [sdb] Assuming drive cache: write through
sd 5:0:0:0: [sdb] No Caching mode page present
sd 5:0:0:0: [sdb] Assuming drive cache: write through
sdb: sdb1
-tj: Updated to consider only spurious GET_EVENT events among
different types of disagreement and allow using TUR for kernel
event polling after GET_EVENT is ignored.
Reported-By: Markus Rathgeb maggu2810@googlemail.com
Signed-off-by: Kay Sievers <kay.sievers@vrfy.org>
Signed-off-by: Tejun Heo <tj@kernel.org>
Cc: stable@kernel.org # >= v2.6.38, fixes udev busy looping w/ certain devices
Signed-off-by: James Bottomley <JBottomley@Parallels.com>
Diffstat (limited to 'drivers/scsi/sr.c')
-rw-r--r-- | drivers/scsi/sr.c | 46 |
1 files changed, 42 insertions, 4 deletions
diff --git a/drivers/scsi/sr.c b/drivers/scsi/sr.c index 4778e2707168..5fc97d2ba2fd 100644 --- a/drivers/scsi/sr.c +++ b/drivers/scsi/sr.c @@ -221,14 +221,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 +261,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; } |