diff options
Diffstat (limited to 'drivers/scsi/sd.c')
-rw-r--r-- | drivers/scsi/sd.c | 178 |
1 files changed, 109 insertions, 69 deletions
diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index 956496182c8..e56730214c0 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -583,7 +583,7 @@ static int sd_prep_fn(struct request_queue *q, struct request *rq) * quietly refuse to do anything to a changed disc until * the changed bit has been reset */ - /* printk("SCSI disk has been changed. Prohibiting further I/O.\n"); */ + /* printk("SCSI disk has been changed or is not present. Prohibiting further I/O.\n"); */ goto out; } @@ -990,30 +990,51 @@ out: static void set_media_not_present(struct scsi_disk *sdkp) { - sdkp->media_present = 0; - sdkp->capacity = 0; - sdkp->device->changed = 1; + if (sdkp->media_present) + sdkp->device->changed = 1; + + if (sdkp->device->removable) { + sdkp->media_present = 0; + sdkp->capacity = 0; + } +} + +static int media_not_present(struct scsi_disk *sdkp, + struct scsi_sense_hdr *sshdr) +{ + if (!scsi_sense_valid(sshdr)) + return 0; + + /* not invoked for commands that could return deferred errors */ + switch (sshdr->sense_key) { + case UNIT_ATTENTION: + case NOT_READY: + /* medium not present */ + if (sshdr->asc == 0x3A) { + set_media_not_present(sdkp); + return 1; + } + } + return 0; } /** - * sd_media_changed - check if our medium changed - * @disk: kernel device descriptor + * sd_check_events - check media events + * @disk: kernel device descriptor + * @clearing: disk events currently being cleared * - * Returns 0 if not applicable or no change; 1 if change + * Returns mask of DISK_EVENT_*. * * Note: this function is invoked from the block subsystem. **/ -static int sd_media_changed(struct gendisk *disk) +static unsigned int sd_check_events(struct gendisk *disk, unsigned int clearing) { struct scsi_disk *sdkp = scsi_disk(disk); struct scsi_device *sdp = sdkp->device; struct scsi_sense_hdr *sshdr = NULL; int retval; - SCSI_LOG_HLQUEUE(3, sd_printk(KERN_INFO, sdkp, "sd_media_changed\n")); - - if (!sdp->removable) - return 0; + SCSI_LOG_HLQUEUE(3, sd_printk(KERN_INFO, sdkp, "sd_check_events\n")); /* * If the device is offline, don't send any commands - just pretend as @@ -1023,7 +1044,6 @@ static int sd_media_changed(struct gendisk *disk) */ if (!scsi_device_online(sdp)) { set_media_not_present(sdkp); - retval = 1; goto out; } @@ -1044,34 +1064,32 @@ 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)) { + /* failed to execute TUR, assume media not present */ + if (host_byte(retval)) { set_media_not_present(sdkp); - retval = 1; goto out; } + if (media_not_present(sdkp, sshdr)) + goto out; + /* * For removable scsi disk we have to recognise the presence - * of a disk in the drive. This is kept in the struct scsi_disk - * struct and tested at open ! Daniel Roche (dan@lectra.fr) + * of a disk in the drive. */ + if (!sdkp->media_present) + sdp->changed = 1; sdkp->media_present = 1; - - retval = sdp->changed; - sdp->changed = 0; out: - if (retval != sdkp->previous_state) - sdev_evt_send_simple(sdp, SDEV_EVT_MEDIA_CHANGE, GFP_KERNEL); - sdkp->previous_state = retval; + /* + * sdp->changed is set under the following conditions: + * + * Medium present state has changed in either direction. + * Device has indicated UNIT_ATTENTION. + */ kfree(sshdr); + retval = sdp->changed ? DISK_EVENT_MEDIA_CHANGE : 0; + sdp->changed = 0; return retval; } @@ -1164,7 +1182,7 @@ static const struct block_device_operations sd_fops = { #ifdef CONFIG_COMPAT .compat_ioctl = sd_compat_ioctl, #endif - .media_changed = sd_media_changed, + .check_events = sd_check_events, .revalidate_disk = sd_revalidate_disk, .unlock_native_capacity = sd_unlock_native_capacity, }; @@ -1175,6 +1193,12 @@ static unsigned int sd_completed_bytes(struct scsi_cmnd *scmd) u64 end_lba = blk_rq_pos(scmd->request) + (scsi_bufflen(scmd) / 512); u64 bad_lba; int info_valid; + /* + * resid is optional but mostly filled in. When it's unused, + * its value is zero, so we assume the whole buffer transferred + */ + unsigned int transferred = scsi_bufflen(scmd) - scsi_get_resid(scmd); + unsigned int good_bytes; if (scmd->request->cmd_type != REQ_TYPE_FS) return 0; @@ -1208,7 +1232,8 @@ static unsigned int sd_completed_bytes(struct scsi_cmnd *scmd) /* This computation should always be done in terms of * the resolution of the device's medium. */ - return (bad_lba - start_lba) * scmd->device->sector_size; + good_bytes = (bad_lba - start_lba) * scmd->device->sector_size; + return min(good_bytes, transferred); } /** @@ -1300,23 +1325,6 @@ static int sd_done(struct scsi_cmnd *SCpnt) return good_bytes; } -static int media_not_present(struct scsi_disk *sdkp, - struct scsi_sense_hdr *sshdr) -{ - - if (!scsi_sense_valid(sshdr)) - return 0; - /* not invoked for commands that could return deferred errors */ - if (sshdr->sense_key != NOT_READY && - sshdr->sense_key != UNIT_ATTENTION) - return 0; - if (sshdr->asc != 0x3A) /* medium not present */ - return 0; - - set_media_not_present(sdkp); - return 1; -} - /* * spinup disk - called only in sd_revalidate_disk() */ @@ -1491,7 +1499,7 @@ static void read_capacity_error(struct scsi_disk *sdkp, struct scsi_device *sdp, */ if (sdp->removable && sense_valid && sshdr->sense_key == NOT_READY) - sdp->changed = 1; + set_media_not_present(sdkp); /* * We used to set media_present to 0 here to indicate no media @@ -1902,10 +1910,14 @@ sd_read_cache_type(struct scsi_disk *sdkp, unsigned char *buffer) int old_rcd = sdkp->RCD; int old_dpofua = sdkp->DPOFUA; - if (sdp->skip_ms_page_8) - goto defaults; - - if (sdp->type == TYPE_RBC) { + if (sdp->skip_ms_page_8) { + if (sdp->type == TYPE_RBC) + goto defaults; + else { + modepage = 0x3F; + dbd = 0; + } + } else if (sdp->type == TYPE_RBC) { modepage = 6; dbd = 8; } else { @@ -1933,13 +1945,11 @@ sd_read_cache_type(struct scsi_disk *sdkp, unsigned char *buffer) */ if (len < 3) goto bad_sense; - if (len > 20) - len = 20; - - /* Take headers and block descriptors into account */ - len += data.header_length + data.block_descriptor_length; - if (len > SD_BUF_SIZE) - goto bad_sense; + else if (len > SD_BUF_SIZE) { + sd_printk(KERN_NOTICE, sdkp, "Truncating mode parameter " + "data from %d to %d bytes\n", len, SD_BUF_SIZE); + len = SD_BUF_SIZE; + } /* Get the data */ res = sd_do_mode_sense(sdp, dbd, modepage, buffer, len, &data, &sshdr); @@ -1947,16 +1957,45 @@ sd_read_cache_type(struct scsi_disk *sdkp, unsigned char *buffer) if (scsi_status_is_good(res)) { int offset = data.header_length + data.block_descriptor_length; - if (offset >= SD_BUF_SIZE - 2) { - sd_printk(KERN_ERR, sdkp, "Malformed MODE SENSE response\n"); - goto defaults; + while (offset < len) { + u8 page_code = buffer[offset] & 0x3F; + u8 spf = buffer[offset] & 0x40; + + if (page_code == 8 || page_code == 6) { + /* We're interested only in the first 3 bytes. + */ + if (len - offset <= 2) { + sd_printk(KERN_ERR, sdkp, "Incomplete " + "mode parameter data\n"); + goto defaults; + } else { + modepage = page_code; + goto Page_found; + } + } else { + /* Go to the next page */ + if (spf && len - offset > 3) + offset += 4 + (buffer[offset+2] << 8) + + buffer[offset+3]; + else if (!spf && len - offset > 1) + offset += 2 + buffer[offset+1]; + else { + sd_printk(KERN_ERR, sdkp, "Incomplete " + "mode parameter data\n"); + goto defaults; + } + } } - if ((buffer[offset] & 0x3f) != modepage) { + if (modepage == 0x3F) { + sd_printk(KERN_ERR, sdkp, "No Caching mode page " + "present\n"); + goto defaults; + } else if ((buffer[offset] & 0x3f) != modepage) { sd_printk(KERN_ERR, sdkp, "Got wrong page\n"); goto defaults; } - + Page_found: if (modepage == 8) { sdkp->WCE = ((buffer[offset + 2] & 0x04) != 0); sdkp->RCD = ((buffer[offset + 2] & 0x01) != 0); @@ -2346,8 +2385,10 @@ static void sd_probe_async(void *data, async_cookie_t cookie) gd->driverfs_dev = &sdp->sdev_gendev; gd->flags = GENHD_FL_EXT_DEVT; - if (sdp->removable) + if (sdp->removable) { gd->flags |= GENHD_FL_REMOVABLE; + gd->events |= DISK_EVENT_MEDIA_CHANGE; + } add_disk(gd); sd_dif_config_host(sdkp); @@ -2429,7 +2470,6 @@ static int sd_probe(struct device *dev) sdkp->disk = gd; sdkp->index = index; atomic_set(&sdkp->openers, 0); - sdkp->previous_state = 1; if (!sdp->request_queue->rq_timeout) { if (sdp->type != TYPE_MOD) |