Skip to content

Commit

Permalink
Merge branch 'for-2.6.38/event-handling' into for-2.6.38/core
Browse files Browse the repository at this point in the history
  • Loading branch information
Jens Axboe committed Jan 13, 2011
2 parents 797a455 + fcc5704 commit 81c5e2a
Show file tree
Hide file tree
Showing 14 changed files with 734 additions and 230 deletions.
544 changes: 516 additions & 28 deletions block/genhd.c

Large diffs are not rendered by default.

56 changes: 53 additions & 3 deletions drivers/cdrom/cdrom.c
Original file line number Diff line number Diff line change
Expand Up @@ -1348,7 +1348,10 @@ static int cdrom_select_disc(struct cdrom_device_info *cdi, int slot)
if (!CDROM_CAN(CDC_SELECT_DISC))
return -EDRIVE_CANT_DO_THIS;

(void) cdi->ops->media_changed(cdi, slot);
if (cdi->ops->check_events)
cdi->ops->check_events(cdi, 0, slot);
else
cdi->ops->media_changed(cdi, slot);

if (slot == CDSL_NONE) {
/* set media changed bits, on both queues */
Expand Down Expand Up @@ -1392,6 +1395,42 @@ static int cdrom_select_disc(struct cdrom_device_info *cdi, int slot)
return slot;
}

/*
* As cdrom implements an extra ioctl consumer for media changed
* event, it needs to buffer ->check_events() output, such that event
* is not lost for both the usual VFS and ioctl paths.
* cdi->{vfs|ioctl}_events are used to buffer pending events for each
* path.
*
* XXX: Locking is non-existent. cdi->ops->check_events() can be
* called in parallel and buffering fields are accessed without any
* exclusion. The original media_changed code had the same problem.
* It might be better to simply deprecate CDROM_MEDIA_CHANGED ioctl
* and remove this cruft altogether. It doesn't have much usefulness
* at this point.
*/
static void cdrom_update_events(struct cdrom_device_info *cdi,
unsigned int clearing)
{
unsigned int events;

events = cdi->ops->check_events(cdi, clearing, CDSL_CURRENT);
cdi->vfs_events |= events;
cdi->ioctl_events |= events;
}

unsigned int cdrom_check_events(struct cdrom_device_info *cdi,
unsigned int clearing)
{
unsigned int events;

cdrom_update_events(cdi, clearing);
events = cdi->vfs_events;
cdi->vfs_events = 0;
return events;
}
EXPORT_SYMBOL(cdrom_check_events);

/* We want to make media_changed accessible to the user through an
* ioctl. The main problem now is that we must double-buffer the
* low-level implementation, to assure that the VFS and the user both
Expand All @@ -1403,15 +1442,26 @@ int media_changed(struct cdrom_device_info *cdi, int queue)
{
unsigned int mask = (1 << (queue & 1));
int ret = !!(cdi->mc_flags & mask);
bool changed;

if (!CDROM_CAN(CDC_MEDIA_CHANGED))
return ret;
return ret;

/* changed since last call? */
if (cdi->ops->media_changed(cdi, CDSL_CURRENT)) {
if (cdi->ops->check_events) {
BUG_ON(!queue); /* shouldn't be called from VFS path */
cdrom_update_events(cdi, DISK_EVENT_MEDIA_CHANGE);
changed = cdi->ioctl_events & DISK_EVENT_MEDIA_CHANGE;
cdi->ioctl_events = 0;
} else
changed = cdi->ops->media_changed(cdi, CDSL_CURRENT);

if (changed) {
cdi->mc_flags = 0x3; /* set bit on both queues */
ret |= 1;
cdi->media_written = 0;
}

cdi->mc_flags &= ~mask; /* clear bit */
return ret;
}
Expand Down
13 changes: 1 addition & 12 deletions drivers/scsi/scsi_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -1984,8 +1984,7 @@ EXPORT_SYMBOL(scsi_mode_sense);
* in.
*
* Returns zero if unsuccessful or an error if TUR failed. For
* removable media, a return of NOT_READY or UNIT_ATTENTION is
* translated to success, with the ->changed flag updated.
* removable media, UNIT_ATTENTION sets ->changed flag.
**/
int
scsi_test_unit_ready(struct scsi_device *sdev, int timeout, int retries,
Expand All @@ -2012,16 +2011,6 @@ scsi_test_unit_ready(struct scsi_device *sdev, int timeout, int retries,
} while (scsi_sense_valid(sshdr) &&
sshdr->sense_key == UNIT_ATTENTION && --retries);

if (!sshdr)
/* could not allocate sense buffer, so can't process it */
return result;

if (sdev->removable && scsi_sense_valid(sshdr) &&
(sshdr->sense_key == UNIT_ATTENTION ||
sshdr->sense_key == NOT_READY)) {
sdev->changed = 1;
result = 0;
}
if (!sshdr_external)
kfree(sshdr);
return result;
Expand Down
10 changes: 1 addition & 9 deletions drivers/scsi/sd.c
Original file line number Diff line number Diff line change
Expand Up @@ -1045,15 +1045,7 @@ static int sd_media_changed(struct gendisk *disk)
sshdr);
}

/*
* Unable to test, unit probably not ready. This usually
* means there is no disc in the drive. Mark as changed,
* and we will figure it out later once the drive is
* available again.
*/
if (retval || (scsi_sense_valid(sshdr) &&
/* 0x3a is medium not present */
sshdr->asc == 0x3a)) {
if (retval) {
set_media_not_present(sdkp);
retval = 1;
goto out;
Expand Down
174 changes: 99 additions & 75 deletions drivers/scsi/sr.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,15 @@ static void sr_release(struct cdrom_device_info *);
static void get_sectorsize(struct scsi_cd *);
static void get_capabilities(struct scsi_cd *);

static int sr_media_change(struct cdrom_device_info *, int);
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 struct cdrom_device_ops sr_dops = {
.open = sr_open,
.release = sr_release,
.drive_status = sr_drive_status,
.media_changed = sr_media_change,
.check_events = sr_check_events,
.tray_move = sr_tray_move,
.lock_door = sr_lock_door,
.select_speed = sr_select_speed,
Expand Down Expand Up @@ -165,90 +166,96 @@ static void scsi_cd_put(struct scsi_cd *cd)
mutex_unlock(&sr_ref_mutex);
}

/* identical to scsi_test_unit_ready except that it doesn't
* eat the NOT_READY returns for removable media */
int sr_test_unit_ready(struct scsi_device *sdev, struct scsi_sense_hdr *sshdr)
static unsigned int sr_get_events(struct scsi_device *sdev)
{
int retries = MAX_RETRIES;
int the_result;
u8 cmd[] = {TEST_UNIT_READY, 0, 0, 0, 0, 0 };
u8 buf[8];
u8 cmd[] = { GET_EVENT_STATUS_NOTIFICATION,
1, /* polled */
0, 0, /* reserved */
1 << 4, /* notification class: media */
0, 0, /* reserved */
0, sizeof(buf), /* allocation length */
0, /* control */
};
struct event_header *eh = (void *)buf;
struct media_event_desc *med = (void *)(buf + 4);
struct scsi_sense_hdr sshdr;
int result;

/* issue TEST_UNIT_READY until the initial startup UNIT_ATTENTION
* conditions are gone, or a timeout happens
*/
do {
the_result = scsi_execute_req(sdev, cmd, DMA_NONE, NULL,
0, sshdr, SR_TIMEOUT,
retries--, NULL);
if (scsi_sense_valid(sshdr) &&
sshdr->sense_key == UNIT_ATTENTION)
sdev->changed = 1;

} while (retries > 0 &&
(!scsi_status_is_good(the_result) ||
(scsi_sense_valid(sshdr) &&
sshdr->sense_key == UNIT_ATTENTION)));
return the_result;
result = scsi_execute_req(sdev, cmd, DMA_FROM_DEVICE, buf, sizeof(buf),
&sshdr, SR_TIMEOUT, MAX_RETRIES, NULL);
if (scsi_sense_valid(&sshdr) && sshdr.sense_key == UNIT_ATTENTION)
return DISK_EVENT_MEDIA_CHANGE;

if (result || be16_to_cpu(eh->data_len) < sizeof(*med))
return 0;

if (eh->nea || eh->notification_class != 0x4)
return 0;

if (med->media_event_code == 1)
return DISK_EVENT_EJECT_REQUEST;
else if (med->media_event_code == 2)
return DISK_EVENT_MEDIA_CHANGE;
return 0;
}

/*
* This function checks to see if the media has been changed in the
* CDROM drive. It is possible that we have already sensed a change,
* or the drive may have sensed one and not yet reported it. We must
* be ready for either case. This function always reports the current
* value of the changed bit. If flag is 0, then the changed bit is reset.
* This function could be done as an ioctl, but we would need to have
* an inode for that to work, and we do not always have one.
* This function checks to see if the media has been changed or eject
* button has been pressed. It is possible that we have already
* sensed a change, or the drive may have sensed one and not yet
* reported it. The past events are accumulated in sdev->changed and
* returned together with the current state.
*/

static int sr_media_change(struct cdrom_device_info *cdi, int slot)
static unsigned int sr_check_events(struct cdrom_device_info *cdi,
unsigned int clearing, int slot)
{
struct scsi_cd *cd = cdi->handle;
int retval;
struct scsi_sense_hdr *sshdr;
bool last_present;
struct scsi_sense_hdr sshdr;
unsigned int events;
int ret;

if (CDSL_CURRENT != slot) {
/* no changer support */
return -EINVAL;
}
/* no changer support */
if (CDSL_CURRENT != slot)
return 0;

sshdr = kzalloc(sizeof(*sshdr), GFP_KERNEL);
retval = sr_test_unit_ready(cd->device, sshdr);
if (retval || (scsi_sense_valid(sshdr) &&
/* 0x3a is medium not present */
sshdr->asc == 0x3a)) {
/* Media not present or unable to test, unit probably not
* ready. This usually means there is no disc in the drive.
* Mark as changed, and we will figure it out later once
* the drive is available again.
*/
cd->device->changed = 1;
/* This will force a flush, if called from check_disk_change */
retval = 1;
goto out;
};
events = sr_get_events(cd->device);
/*
* 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;

/* 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);

/*
* Media is considered to be present if TUR succeeds or fails with
* sense data indicating something other than media-not-present
* (ASC 0x3a).
*/
cd->media_present = scsi_status_is_good(ret) ||
(scsi_sense_valid(&sshdr) && sshdr.asc != 0x3a);

retval = cd->device->changed;
cd->device->changed = 0;
/* If the disk changed, the capacity will now be different,
* so we force a re-read of this information */
if (retval) {
/* check multisession offset etc */
sr_cd_check(cdi);
get_sectorsize(cd);
if (last_present != cd->media_present)
events |= DISK_EVENT_MEDIA_CHANGE;
skip_tur:
if (cd->device->changed) {
events |= DISK_EVENT_MEDIA_CHANGE;
cd->device->changed = 0;
}

out:
/* Notify userspace, that media has changed. */
if (retval != cd->previous_state)
/* for backward compatibility */
if (events & DISK_EVENT_MEDIA_CHANGE)
sdev_evt_send_simple(cd->device, SDEV_EVT_MEDIA_CHANGE,
GFP_KERNEL);
cd->previous_state = retval;
kfree(sshdr);

return retval;
return events;
}

/*
* sr_done is the interrupt routine for the device driver.
*
Expand Down Expand Up @@ -533,10 +540,25 @@ static int sr_block_ioctl(struct block_device *bdev, fmode_t mode, unsigned cmd,
return ret;
}

static int sr_block_media_changed(struct gendisk *disk)
static unsigned int sr_block_check_events(struct gendisk *disk,
unsigned int clearing)
{
struct scsi_cd *cd = scsi_cd(disk);
return cdrom_media_changed(&cd->cdi);
return cdrom_check_events(&cd->cdi, clearing);
}

static int sr_block_revalidate_disk(struct gendisk *disk)
{
struct scsi_cd *cd = scsi_cd(disk);
struct scsi_sense_hdr sshdr;

/* if the unit is not ready, nothing more to do */
if (scsi_test_unit_ready(cd->device, SR_TIMEOUT, MAX_RETRIES, &sshdr))
return 0;

sr_cd_check(&cd->cdi);
get_sectorsize(cd);
return 0;
}

static const struct block_device_operations sr_bdops =
Expand All @@ -545,7 +567,8 @@ static const struct block_device_operations sr_bdops =
.open = sr_block_open,
.release = sr_block_release,
.ioctl = sr_block_ioctl,
.media_changed = sr_block_media_changed,
.check_events = sr_block_check_events,
.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.
Expand Down Expand Up @@ -618,6 +641,7 @@ static int sr_probe(struct device *dev)
sprintf(disk->disk_name, "sr%d", minor);
disk->fops = &sr_bdops;
disk->flags = GENHD_FL_CD;
disk->events = DISK_EVENT_MEDIA_CHANGE | DISK_EVENT_EJECT_REQUEST;

blk_queue_rq_timeout(sdev->request_queue, SR_TIMEOUT);

Expand All @@ -627,7 +651,7 @@ static int sr_probe(struct device *dev)
cd->disk = disk;
cd->capacity = 0x1fffff;
cd->device->changed = 1; /* force recheck CD type */
cd->previous_state = 1;
cd->media_present = 1;
cd->use = 1;
cd->readcd_known = 0;
cd->readcd_cdda = 0;
Expand Down Expand Up @@ -780,7 +804,7 @@ static void get_capabilities(struct scsi_cd *cd)
}

/* eat unit attentions */
sr_test_unit_ready(cd->device, &sshdr);
scsi_test_unit_ready(cd->device, SR_TIMEOUT, MAX_RETRIES, &sshdr);

/* ask for mode page 0x2a */
rc = scsi_mode_sense(cd->device, 0, 0x2a, buffer, 128,
Expand Down
3 changes: 1 addition & 2 deletions drivers/scsi/sr.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ typedef struct scsi_cd {
unsigned xa_flag:1; /* CD has XA sectors ? */
unsigned readcd_known:1; /* drive supports READ_CD (0xbe) */
unsigned readcd_cdda:1; /* reading audio data using READ_CD */
unsigned previous_state:1; /* media has changed */
unsigned media_present:1; /* media is present */
struct cdrom_device_info cdi;
/* We hold gendisk and scsi_device references on probe and use
* the refs on this kref to decide when to release them */
Expand All @@ -61,7 +61,6 @@ int sr_select_speed(struct cdrom_device_info *cdi, int speed);
int sr_audio_ioctl(struct cdrom_device_info *, unsigned int, void *);

int sr_is_xa(Scsi_CD *);
int sr_test_unit_ready(struct scsi_device *sdev, struct scsi_sense_hdr *sshdr);

/* sr_vendor.c */
void sr_vendor_init(Scsi_CD *);
Expand Down
Loading

0 comments on commit 81c5e2a

Please sign in to comment.