summaryrefslogtreecommitdiff
path: root/drivers/scsi/scsi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/scsi/scsi.c')
-rw-r--r--drivers/scsi/scsi.c80
1 files changed, 59 insertions, 21 deletions
diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
index 3e0c0381277a..518a252eb6aa 100644
--- a/drivers/scsi/scsi.c
+++ b/drivers/scsi/scsi.c
@@ -55,7 +55,7 @@
#include <linux/notifier.h>
#include <linux/cpu.h>
#include <linux/mutex.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
#include <scsi/scsi.h>
#include <scsi/scsi_cmnd.h>
@@ -350,6 +350,13 @@ static int scsi_get_vpd_size(struct scsi_device *sdev, u8 page)
if (result < SCSI_VPD_HEADER_SIZE)
return 0;
+ if (result > sizeof(vpd)) {
+ dev_warn_once(&sdev->sdev_gendev,
+ "%s: long VPD page 0 length: %d bytes\n",
+ __func__, result);
+ result = sizeof(vpd);
+ }
+
result -= SCSI_VPD_HEADER_SIZE;
if (!memchr(&vpd[SCSI_VPD_HEADER_SIZE], page, result))
return 0;
@@ -503,22 +510,34 @@ void scsi_attach_vpd(struct scsi_device *sdev)
return;
for (i = 4; i < vpd_buf->len; i++) {
- if (vpd_buf->data[i] == 0x0)
+ switch (vpd_buf->data[i]) {
+ case 0x0:
scsi_update_vpd_page(sdev, 0x0, &sdev->vpd_pg0);
- if (vpd_buf->data[i] == 0x80)
+ break;
+ case 0x80:
scsi_update_vpd_page(sdev, 0x80, &sdev->vpd_pg80);
- if (vpd_buf->data[i] == 0x83)
+ break;
+ case 0x83:
scsi_update_vpd_page(sdev, 0x83, &sdev->vpd_pg83);
- if (vpd_buf->data[i] == 0x89)
+ break;
+ case 0x89:
scsi_update_vpd_page(sdev, 0x89, &sdev->vpd_pg89);
- if (vpd_buf->data[i] == 0xb0)
+ break;
+ case 0xb0:
scsi_update_vpd_page(sdev, 0xb0, &sdev->vpd_pgb0);
- if (vpd_buf->data[i] == 0xb1)
+ break;
+ case 0xb1:
scsi_update_vpd_page(sdev, 0xb1, &sdev->vpd_pgb1);
- if (vpd_buf->data[i] == 0xb2)
+ break;
+ case 0xb2:
scsi_update_vpd_page(sdev, 0xb2, &sdev->vpd_pgb2);
- if (vpd_buf->data[i] == 0xb7)
+ break;
+ case 0xb7:
scsi_update_vpd_page(sdev, 0xb7, &sdev->vpd_pgb7);
+ break;
+ default:
+ break;
+ }
}
kfree(vpd_buf);
}
@@ -666,6 +685,13 @@ void scsi_cdl_check(struct scsi_device *sdev)
sdev->use_10_for_rw = 0;
sdev->cdl_supported = 1;
+
+ /*
+ * If the device supports CDL, make sure that the current drive
+ * feature status is consistent with the user controlled
+ * cdl_enable state.
+ */
+ scsi_cdl_enable(sdev, sdev->cdl_enable);
} else {
sdev->cdl_supported = 0;
}
@@ -681,26 +707,23 @@ void scsi_cdl_check(struct scsi_device *sdev)
*/
int scsi_cdl_enable(struct scsi_device *sdev, bool enable)
{
- struct scsi_mode_data data;
- struct scsi_sense_hdr sshdr;
- struct scsi_vpd *vpd;
- bool is_ata = false;
char buf[64];
+ bool is_ata;
int ret;
if (!sdev->cdl_supported)
return -EOPNOTSUPP;
rcu_read_lock();
- vpd = rcu_dereference(sdev->vpd_pg89);
- if (vpd)
- is_ata = true;
+ is_ata = rcu_dereference(sdev->vpd_pg89);
rcu_read_unlock();
/*
* For ATA devices, CDL needs to be enabled with a SET FEATURES command.
*/
if (is_ata) {
+ struct scsi_mode_data data;
+ struct scsi_sense_hdr sshdr;
char *buf_data;
int len;
@@ -709,16 +732,30 @@ int scsi_cdl_enable(struct scsi_device *sdev, bool enable)
if (ret)
return -EINVAL;
- /* Enable CDL using the ATA feature page */
+ /* Enable or disable CDL using the ATA feature page */
len = min_t(size_t, sizeof(buf),
data.length - data.header_length -
data.block_descriptor_length);
buf_data = buf + data.header_length +
data.block_descriptor_length;
- if (enable)
- buf_data[4] = 0x02;
- else
- buf_data[4] = 0;
+
+ /*
+ * If we want to enable CDL and CDL is already enabled on the
+ * device, do nothing. This avoids needlessly resetting the CDL
+ * statistics on the device as that is implied by the CDL enable
+ * action. Similar to this, there is no need to do anything if
+ * we want to disable CDL and CDL is already disabled.
+ */
+ if (enable) {
+ if ((buf_data[4] & 0x03) == 0x02)
+ goto out;
+ buf_data[4] &= ~0x03;
+ buf_data[4] |= 0x02;
+ } else {
+ if ((buf_data[4] & 0x03) == 0x00)
+ goto out;
+ buf_data[4] &= ~0x03;
+ }
ret = scsi_mode_select(sdev, 1, 0, buf_data, len, 5 * HZ, 3,
&data, &sshdr);
@@ -730,6 +767,7 @@ int scsi_cdl_enable(struct scsi_device *sdev, bool enable)
}
}
+out:
sdev->cdl_enable = enable;
return 0;