Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[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>

authored by

Kay Sievers and committed by
James Bottomley
79b9677d 57a6fa9a

+49 -4
+42 -4
drivers/scsi/sr.c
··· 221 221 return 0; 222 222 223 223 events = sr_get_events(cd->device); 224 + cd->get_event_changed |= events & DISK_EVENT_MEDIA_CHANGE; 225 + 226 + /* 227 + * If earlier GET_EVENT_STATUS_NOTIFICATION and TUR did not agree 228 + * for several times in a row. We rely on TUR only for this likely 229 + * broken device, to prevent generating incorrect media changed 230 + * events for every open(). 231 + */ 232 + if (cd->ignore_get_event) { 233 + events &= ~DISK_EVENT_MEDIA_CHANGE; 234 + goto do_tur; 235 + } 236 + 224 237 /* 225 238 * GET_EVENT_STATUS_NOTIFICATION is enough unless MEDIA_CHANGE 226 239 * is being cleared. Note that there are devices which hang 227 240 * if asked to execute TUR repeatedly. 228 241 */ 229 - if (!(clearing & DISK_EVENT_MEDIA_CHANGE)) 230 - goto skip_tur; 242 + if (cd->device->changed) { 243 + events |= DISK_EVENT_MEDIA_CHANGE; 244 + cd->device->changed = 0; 245 + cd->tur_changed = true; 246 + } 231 247 248 + if (!(clearing & DISK_EVENT_MEDIA_CHANGE)) 249 + return events; 250 + do_tur: 232 251 /* let's see whether the media is there with TUR */ 233 252 last_present = cd->media_present; 234 253 ret = scsi_test_unit_ready(cd->device, SR_TIMEOUT, MAX_RETRIES, &sshdr); ··· 261 242 (scsi_sense_valid(&sshdr) && sshdr.asc != 0x3a); 262 243 263 244 if (last_present != cd->media_present) 264 - events |= DISK_EVENT_MEDIA_CHANGE; 265 - skip_tur: 245 + cd->device->changed = 1; 246 + 266 247 if (cd->device->changed) { 267 248 events |= DISK_EVENT_MEDIA_CHANGE; 268 249 cd->device->changed = 0; 250 + cd->tur_changed = true; 269 251 } 252 + 253 + if (cd->ignore_get_event) 254 + return events; 255 + 256 + /* check whether GET_EVENT is reporting spurious MEDIA_CHANGE */ 257 + if (!cd->tur_changed) { 258 + if (cd->get_event_changed) { 259 + if (cd->tur_mismatch++ > 8) { 260 + sdev_printk(KERN_WARNING, cd->device, 261 + "GET_EVENT and TUR disagree continuously, suppress GET_EVENT events\n"); 262 + cd->ignore_get_event = true; 263 + } 264 + } else { 265 + cd->tur_mismatch = 0; 266 + } 267 + } 268 + cd->tur_changed = false; 269 + cd->get_event_changed = false; 270 270 271 271 return events; 272 272 }
+7
drivers/scsi/sr.h
··· 41 41 unsigned readcd_known:1; /* drive supports READ_CD (0xbe) */ 42 42 unsigned readcd_cdda:1; /* reading audio data using READ_CD */ 43 43 unsigned media_present:1; /* media is present */ 44 + 45 + /* GET_EVENT spurious event handling, blk layer guarantees exclusion */ 46 + int tur_mismatch; /* nr of get_event TUR mismatches */ 47 + bool tur_changed:1; /* changed according to TUR */ 48 + bool get_event_changed:1; /* changed according to GET_EVENT */ 49 + bool ignore_get_event:1; /* GET_EVENT is unreliable, use TUR */ 50 + 44 51 struct cdrom_device_info cdi; 45 52 /* We hold gendisk and scsi_device references on probe and use 46 53 * the refs on this kref to decide when to release them */