Based on prior work by Martin Petersen and James Bottomley, this patch
adds a generic helper for retrieving VPD pages from SCSI devices within
the kernel.

Signed-off-by: Matthew Wilcox <willy@linux.intel.com>

diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c
index 110e776..7e95626 100644
--- a/drivers/scsi/scsi.c
+++ b/drivers/scsi/scsi.c
@@ -972,6 +972,100 @@ int scsi_track_queue_full(struct scsi_device *sdev, int depth)
 EXPORT_SYMBOL(scsi_track_queue_full);
 
 /**
+ * scsi_vpd_inquiry - Request a device provide us with a VPD page
+ * @sdev: The device to ask
+ * @buffer: Where to put the result
+ * @page: Which Vital Product Data to return
+ * @len: The length of the buffer
+ *
+ * This is an internal helper function.  You probably want to use
+ * scsi_get_vpd_page instead.
+ *
+ * Returns 0 on success and -EIO if an error occurs.
+ */
+static int scsi_vpd_inquiry(struct scsi_device *sdev, unsigned char *buffer,
+							u8 page, u16 len)
+{
+	int result;
+	unsigned char cmd[16];
+
+	cmd[0] = INQUIRY;
+	cmd[1] = 1;		/* EVPD */
+	cmd[2] = page;
+	scsi_put_u16(len, &cmd[3]);
+	cmd[5] = 0;		/* Control byte */
+
+	/*
+	 * I'm not convinced we need to try quite this hard to get VPD, but
+	 * all the existing users tried this hard.
+	 */
+	result = scsi_execute_req(sdev, cmd, DMA_FROM_DEVICE, buffer,
+				  len, NULL, 30 * HZ, 3);
+	if (result)
+		return -EIO;
+
+	/* Sanity check that we got the page back that we asked for */
+	if (buffer[1] != page)
+		return -EIO;
+
+	return 0;
+}
+
+/**
+ * scsi_get_vpd_page - Get Vital Product Data from a SCSI device
+ * @sdev: The device to ask
+ * @buffer: Where to put the result
+ * @page: Which Vital Product Data to return
+ * @len: The length of the buffer
+ *
+ * SCSI devices may optionally supply Vital Product Data.  Each 'page'
+ * of VPD is defined in the appropriate SCSI document (eg SPC, SBC).
+ * This routine returns 0 on success and fills in the buffer with up to
+ * 'len' bytes of data returned from the device.  If the device fails to
+ * respond to the request, or returns invalid data, this routine returns
+ * -EIO.  If the device does not provide this kind of VPD, this routine
+ * returns -ENOTTY.
+ */
+int scsi_get_vpd_page(struct scsi_device *sdev, unsigned char *buffer,
+							u8 page, u16 len)
+{
+	int i, result;
+	unsigned char *pages = NULL;
+
+	if (page == 0)
+		goto found;
+
+	if (len > 255) {
+		pages = buffer;
+	} else {
+		pages = kmalloc(256, GFP_KERNEL);
+		if (!pages)
+			return -ENOMEM;
+	}
+
+	/* Ask for all the pages supported by this device */ 
+	result = scsi_vpd_inquiry(sdev, pages, 0, 255);
+	if (result)
+		goto out;
+
+	for (i = 0; i < pages[3]; i++)
+		if (pages[i + 4] == page)
+			goto found;
+
+	result = -ENOTTY;
+	goto out;
+
+ found:
+	result = scsi_vpd_inquiry(sdev, buffer, page, len);
+
+ out:
+	if (pages != buffer)
+		kfree(pages);
+	return result;
+}
+EXPORT_SYMBOL_GPL(scsi_get_vpd_page);
+
+/**
  * scsi_device_get  -  get an additional reference to a scsi_device
  * @sdev:	device to get a reference to
  *
diff --git a/include/scsi/scsi_device.h b/include/scsi/scsi_device.h
index f6a9fe0..06382a1 100644
--- a/include/scsi/scsi_device.h
+++ b/include/scsi/scsi_device.h
@@ -298,6 +298,8 @@ extern int scsi_mode_select(struct scsi_device *sdev, int pf, int sp,
 			    struct scsi_sense_hdr *);
 extern int scsi_test_unit_ready(struct scsi_device *sdev, int timeout,
 				int retries, struct scsi_sense_hdr *sshdr);
+extern int scsi_get_vpd_page(struct scsi_device *, unsigned char *buf,
+				u8 page, u16 len);
 extern int scsi_device_set_state(struct scsi_device *sdev,
 				 enum scsi_device_state state);
 extern struct scsi_event *sdev_evt_alloc(enum scsi_device_event evt_type,
