Commit 79b9677d authored by Kay Sievers's avatar Kay Sievers Committed by James Bottomley

[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: default avatarKay Sievers <kay.sievers@vrfy.org>
Signed-off-by: default avatarTejun Heo <tj@kernel.org>
Cc: stable@kernel.org	# >= v2.6.38, fixes udev busy looping w/ certain devices
Signed-off-by: default avatarJames Bottomley <JBottomley@Parallels.com>
parent 57a6fa9a
...@@ -221,14 +221,33 @@ static unsigned int sr_check_events(struct cdrom_device_info *cdi, ...@@ -221,14 +221,33 @@ static unsigned int sr_check_events(struct cdrom_device_info *cdi,
return 0; return 0;
events = sr_get_events(cd->device); 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 * GET_EVENT_STATUS_NOTIFICATION is enough unless MEDIA_CHANGE
* is being cleared. Note that there are devices which hang * is being cleared. Note that there are devices which hang
* if asked to execute TUR repeatedly. * if asked to execute TUR repeatedly.
*/ */
if (!(clearing & DISK_EVENT_MEDIA_CHANGE)) if (cd->device->changed) {
goto skip_tur; 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 */ /* let's see whether the media is there with TUR */
last_present = cd->media_present; last_present = cd->media_present;
ret = scsi_test_unit_ready(cd->device, SR_TIMEOUT, MAX_RETRIES, &sshdr); 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, ...@@ -242,12 +261,31 @@ static unsigned int sr_check_events(struct cdrom_device_info *cdi,
(scsi_sense_valid(&sshdr) && sshdr.asc != 0x3a); (scsi_sense_valid(&sshdr) && sshdr.asc != 0x3a);
if (last_present != cd->media_present) if (last_present != cd->media_present)
events |= DISK_EVENT_MEDIA_CHANGE; cd->device->changed = 1;
skip_tur:
if (cd->device->changed) { if (cd->device->changed) {
events |= DISK_EVENT_MEDIA_CHANGE; events |= DISK_EVENT_MEDIA_CHANGE;
cd->device->changed = 0; 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; return events;
} }
......
...@@ -41,6 +41,13 @@ typedef struct scsi_cd { ...@@ -41,6 +41,13 @@ typedef struct scsi_cd {
unsigned readcd_known:1; /* drive supports READ_CD (0xbe) */ unsigned readcd_known:1; /* drive supports READ_CD (0xbe) */
unsigned readcd_cdda:1; /* reading audio data using READ_CD */ unsigned readcd_cdda:1; /* reading audio data using READ_CD */
unsigned media_present:1; /* media is present */ unsigned media_present:1; /* media is present */
/* GET_EVENT spurious event handling, blk layer guarantees exclusion */
int tur_mismatch; /* nr of get_event TUR mismatches */
bool tur_changed:1; /* changed according to TUR */
bool get_event_changed:1; /* changed according to GET_EVENT */
bool ignore_get_event:1; /* GET_EVENT is unreliable, use TUR */
struct cdrom_device_info cdi; struct cdrom_device_info cdi;
/* We hold gendisk and scsi_device references on probe and use /* We hold gendisk and scsi_device references on probe and use
* the refs on this kref to decide when to release them */ * the refs on this kref to decide when to release them */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment