scsi: ufs: add ioctl in UFS driver
authorhgchu <hg.chu@samsung.com>
Fri, 12 Jan 2018 05:55:23 +0000 (14:55 +0900)
committerJaeHun Jung <jh0801.jung@samsung.com>
Tue, 8 May 2018 08:20:33 +0000 (17:20 +0900)
Change-Id: I7668678f46a49045f76631ac25febf9b8a944f5a
Signed-off-by: hgchu <hg.chu@samsung.com>
drivers/scsi/ufs/ufs.h
drivers/scsi/ufs/ufshcd.c
include/uapi/scsi/ufs/Kbuild [new file with mode: 0644]
include/uapi/scsi/ufs/ioctl.h [new file with mode: 0644]

index 1ec5fe80f0b250f6085a02ce9190db74655bd09a..6ae13f0b7b32b06b37c3253e9986de1a62159a84 100644 (file)
@@ -73,6 +73,16 @@ enum {
        UFS_UPIU_RPMB_WLUN              = 0xC4,
 };
 
+/**
+ * ufs_is_valid_unit_desc_lun - checks if the given LUN has a unit descriptor
+ * @lun: LU number to check
+ * @return: true if the lun has a matching unit descriptor, false otherwise
+ */
+static inline bool ufs_is_valid_unit_desc_lun(u8 lun)
+{
+       return (lun == UFS_UPIU_RPMB_WLUN || (lun < UFS_UPIU_MAX_GENERAL_LUN));
+}
+
 /*
  * UFS Protocol Information Unit related definitions
  */
@@ -130,9 +140,15 @@ enum {
 
 /* Flag idn for Query Requests*/
 enum flag_idn {
-       QUERY_FLAG_IDN_FDEVICEINIT      = 0x01,
-       QUERY_FLAG_IDN_PWR_ON_WPE       = 0x03,
-       QUERY_FLAG_IDN_BKOPS_EN         = 0x04,
+       QUERY_FLAG_IDN_FDEVICEINIT              = 0x01,
+       QUERY_FLAG_IDN_PERMANENT_WPE            = 0x02,
+       QUERY_FLAG_IDN_PWR_ON_WPE                       = 0x03,
+       QUERY_FLAG_IDN_BKOPS_EN                         = 0x04,
+       QUERY_FLAG_IDN_RESERVED1                        = 0x05,
+       QUERY_FLAG_IDN_PURGE_ENABLE             = 0x06,
+       QUERY_FLAG_IDN_RESERVED2                        = 0x07,
+       QUERY_FLAG_IDN_FPHYRESOURCEREMOVAL      = 0x08,
+       QUERY_FLAG_IDN_BUSY_RTC                         = 0x09,
 };
 
 /* Attribute idn for Query requests */
index ec3ac029f7cd20ff5e984961f1b6f73651871c00..6c527b85372bb3768c6af060875d05e720cf3264 100644 (file)
@@ -42,6 +42,7 @@
 #include <linux/devfreq.h>
 #endif
 #include <linux/nls.h>
+#include <scsi/ufs/ioctl.h>
 #include <linux/of.h>
 #include "ufshcd.h"
 #include "ufs_quirks.h"
@@ -97,6 +98,9 @@
 /* UFS link setup retries */
 #define UFS_LINK_SETUP_RETRIES 5
 
+/* IOCTL opcode for command - ufs set device read only */
+#define UFS_IOCTL_BLKROSET      BLKROSET
+
 #define ufshcd_toggle_vreg(_dev, _vreg, _on)                           \
        ({                                                              \
                int _ret;                                               \
@@ -6794,6 +6798,233 @@ static enum blk_eh_timer_return ufshcd_eh_timed_out(struct scsi_cmnd *scmd)
        return found ? BLK_EH_NOT_HANDLED : BLK_EH_RESET_TIMER;
 }
 
+/**
+ * ufshcd_query_ioctl - perform user read queries
+ * @hba: per-adapter instance
+ * @lun: used for lun specific queries
+ * @buffer: user space buffer for reading and submitting query data and params
+ * @return: 0 for success negative error code otherwise
+ *
+ * Expected/Submitted buffer structure is struct ufs_ioctl_query_data.
+ * It will read the opcode, idn and buf_length parameters, and, put the
+ * response in the buffer field while updating the used size in buf_length.
+ */
+static int ufshcd_query_ioctl(struct ufs_hba *hba, u8 lun, void __user *buffer)
+{
+       struct ufs_ioctl_query_data *ioctl_data;
+       int err = 0;
+       int length = 0;
+       void *data_ptr;
+       bool flag;
+       u32 att;
+       u8 index;
+       u8 *desc = NULL;
+
+       ioctl_data = kzalloc(sizeof(struct ufs_ioctl_query_data), GFP_KERNEL);
+       if (!ioctl_data) {
+               dev_err(hba->dev, "%s: Failed allocating %zu bytes\n", __func__,
+                               sizeof(struct ufs_ioctl_query_data));
+               err = -ENOMEM;
+               goto out;
+       }
+
+       /* extract params from user buffer */
+       err = copy_from_user(ioctl_data, buffer,
+                       sizeof(struct ufs_ioctl_query_data));
+       if (err) {
+               dev_err(hba->dev,
+                       "%s: Failed copying buffer from user, err %d\n",
+                       __func__, err);
+               goto out_release_mem;
+       }
+
+       /* verify legal parameters & send query */
+       switch (ioctl_data->opcode) {
+       case UPIU_QUERY_OPCODE_READ_DESC:
+               switch (ioctl_data->idn) {
+               case QUERY_DESC_IDN_DEVICE:
+               case QUERY_DESC_IDN_CONFIGURATION:
+               case QUERY_DESC_IDN_INTERCONNECT:
+               case QUERY_DESC_IDN_GEOMETRY:
+               case QUERY_DESC_IDN_POWER:
+               case QUERY_DESC_IDN_HEALTH:
+                       index = 0;
+                       break;
+               case QUERY_DESC_IDN_UNIT:
+                       if (!ufs_is_valid_unit_desc_lun(lun)) {
+                               dev_err(hba->dev,
+                                       "%s: No unit descriptor for lun 0x%x\n",
+                                       __func__, lun);
+                               err = -EINVAL;
+                               goto out_release_mem;
+                       }
+                       index = lun;
+                       break;
+               default:
+                       goto out_einval;
+               }
+               length = min_t(int, QUERY_DESC_MAX_SIZE,
+                               ioctl_data->buf_size);
+               desc = kzalloc(length, GFP_KERNEL);
+               if (!desc) {
+                       dev_err(hba->dev, "%s: Failed allocating %d bytes\n",
+                                       __func__, length);
+                       err = -ENOMEM;
+                       goto out_release_mem;
+               }
+               err = ufshcd_query_descriptor_retry(hba, ioctl_data->opcode,
+                               ioctl_data->idn, index, 0, desc, &length);
+               break;
+       case UPIU_QUERY_OPCODE_READ_ATTR:
+               switch (ioctl_data->idn) {
+               case QUERY_ATTR_IDN_BOOT_LU_EN:
+               case QUERY_ATTR_IDN_POWER_MODE:
+               case QUERY_ATTR_IDN_ACTIVE_ICC_LVL:
+               case QUERY_ATTR_IDN_OOO_DATA_EN:
+               case QUERY_ATTR_IDN_BKOPS_STATUS:
+               case QUERY_ATTR_IDN_PURGE_STATUS:
+               case QUERY_ATTR_IDN_MAX_DATA_IN:
+               case QUERY_ATTR_IDN_MAX_DATA_OUT:
+               case QUERY_ATTR_IDN_REF_CLK_FREQ:
+               case QUERY_ATTR_IDN_CONF_DESC_LOCK:
+               case QUERY_ATTR_IDN_MAX_NUM_OF_RTT:
+               case QUERY_ATTR_IDN_EE_CONTROL:
+               case QUERY_ATTR_IDN_EE_STATUS:
+               case QUERY_ATTR_IDN_SECONDS_PASSED:
+                       index = 0;
+                       break;
+               case QUERY_ATTR_IDN_DYN_CAP_NEEDED:
+               case QUERY_ATTR_IDN_CORR_PRG_BLK_NUM:
+                       index = lun;
+                       break;
+               default:
+                       goto out_einval;
+               }
+               err = ufshcd_query_attr_retry(hba, ioctl_data->opcode,
+                                       ioctl_data->idn, index, 0, &att);
+               break;
+       case UPIU_QUERY_OPCODE_READ_FLAG:
+               switch (ioctl_data->idn) {
+               case QUERY_FLAG_IDN_FDEVICEINIT:
+               case QUERY_FLAG_IDN_PERMANENT_WPE:
+               case QUERY_FLAG_IDN_PWR_ON_WPE:
+               case QUERY_FLAG_IDN_BKOPS_EN:
+               case QUERY_FLAG_IDN_PURGE_ENABLE:
+               case QUERY_FLAG_IDN_FPHYRESOURCEREMOVAL:
+               case QUERY_FLAG_IDN_BUSY_RTC:
+                       break;
+               default:
+                       goto out_einval;
+               }
+               err = ufshcd_query_flag_retry(hba, ioctl_data->opcode,
+                                       ioctl_data->idn, &flag);
+               break;
+       default:
+               goto out_einval;
+       }
+
+       if (err) {
+               dev_err(hba->dev, "%s: Query for idn %d failed\n", __func__,
+                                       ioctl_data->idn);
+               goto out_release_mem;
+       }
+
+       /*
+        * copy response data
+        * As we might end up reading less data then what is specified in
+        * "ioct_data->buf_size". So we are updating "ioct_data->
+        * buf_size" to what exactly we have read.
+        */
+       switch (ioctl_data->opcode) {
+       case UPIU_QUERY_OPCODE_READ_DESC:
+               ioctl_data->buf_size = min_t(int, ioctl_data->buf_size, length);
+               data_ptr = desc;
+               break;
+       case UPIU_QUERY_OPCODE_READ_ATTR:
+               ioctl_data->buf_size = sizeof(u32);
+               data_ptr = &att;
+               break;
+       case UPIU_QUERY_OPCODE_READ_FLAG:
+               ioctl_data->buf_size = 1;
+               data_ptr = &flag;
+               break;
+       default:
+               BUG_ON(true);
+       }
+
+       /* copy to user */
+       err = copy_to_user(buffer, ioctl_data,
+                       sizeof(struct ufs_ioctl_query_data));
+       if (err)
+               dev_err(hba->dev, "%s: Failed copying back to user.\n",
+                       __func__);
+       err = copy_to_user(buffer + sizeof(struct ufs_ioctl_query_data),
+                       data_ptr, ioctl_data->buf_size);
+       if (err)
+               dev_err(hba->dev, "%s: err %d copying back to user.\n",
+                               __func__, err);
+       goto out_release_mem;
+
+out_einval:
+       dev_err(hba->dev,
+               "%s: illegal ufs query ioctl data, opcode 0x%x, idn 0x%x\n",
+               __func__, ioctl_data->opcode, (unsigned int)ioctl_data->idn);
+       err = -EINVAL;
+out_release_mem:
+       kfree(ioctl_data);
+       kfree(desc);
+out:
+       return err;
+}
+
+/**
+ * ufshcd_ioctl - ufs ioctl callback registered in scsi_host
+ * @dev: scsi device required for per LUN queries
+ * @cmd: command opcode
+ * @buffer: user space buffer for transferring data
+ *
+ * Supported commands:
+ * UFS_IOCTL_QUERY
+ */
+static int ufshcd_ioctl(struct scsi_device *dev, int cmd, void __user *buffer)
+{
+       struct ufs_hba *hba = shost_priv(dev->host);
+       int err = 0;
+
+       BUG_ON(!hba);
+       if (!buffer) {
+               if (cmd != SCSI_UFS_REQUEST_SENSE) {
+                       dev_err(hba->dev, "%s: User buffer is NULL!\n", __func__);
+                       return -EINVAL;
+               }
+       }
+       switch (cmd) {
+       case SCSI_UFS_REQUEST_SENSE:
+               err = ufshcd_send_request_sense(hba, hba->sdev_rpmb);
+               if (err) {
+                       dev_warn(hba->dev, "%s failed to clear uac on rpmb(w-lu) %d\n",
+                                       __func__, err);
+                       }
+               hba->host->wlun_clr_uac = false;
+               break;
+       case UFS_IOCTL_QUERY:
+               //pm_runtime_get_sync(hba->dev);
+               err = ufshcd_query_ioctl(hba, ufshcd_scsi_to_upiu_lun(dev->lun),
+                               buffer);
+               //pm_runtime_put_sync(hba->dev);
+               break;
+       case UFS_IOCTL_BLKROSET:
+               err = -ENOIOCTLCMD;
+               break;
+       default:
+               err = -EINVAL;
+               dev_err(hba->dev, "%s: Illegal ufs-IOCTL cmd %d\n", __func__,
+                               cmd);
+               break;
+       }
+
+       return err;
+}
 static struct scsi_host_template ufshcd_driver_template = {
        .module                 = THIS_MODULE,
        .name                   = UFSHCD,
diff --git a/include/uapi/scsi/ufs/Kbuild b/include/uapi/scsi/ufs/Kbuild
new file mode 100644 (file)
index 0000000..8c1d2cb
--- /dev/null
@@ -0,0 +1,2 @@
+# UAPI Header export list
+header-y += ioctl.h
diff --git a/include/uapi/scsi/ufs/ioctl.h b/include/uapi/scsi/ufs/ioctl.h
new file mode 100644 (file)
index 0000000..f7a6718
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef UAPI_UFS_IOCTL_H_
+#define UAPI_UFS_IOCTL_H_
+
+#include <linux/types.h>
+
+/*
+ *  IOCTL opcode for ufs queries has the following opcode after
+ *  SCSI_IOCTL_GET_PCI
+ */
+#define UFS_IOCTL_QUERY                        0x5388
+
+/**
+ * struct ufs_ioctl_query_data - used to transfer data to and from user via ioctl
+ * @opcode: type of data to query (descriptor/attribute/flag)
+ * @idn: id of the data structure
+ * @buf_size: number of allocated bytes/data size on return
+ * @buffer: data location
+ *
+ * Received: buffer and buf_size (available space for transfered data)
+ * Submitted: opcode, idn, length, buf_size
+ */
+struct ufs_ioctl_query_data {
+       /*
+        * User should select one of the opcode defined in "enum query_opcode".
+        * Please check include/uapi/scsi/ufs/ufs.h for the definition of it.
+        * Note that only UPIU_QUERY_OPCODE_READ_DESC,
+        * UPIU_QUERY_OPCODE_READ_ATTR & UPIU_QUERY_OPCODE_READ_FLAG are
+        * supported as of now. All other query_opcode would be considered
+        * invalid.
+        * As of now only read query operations are supported.
+        */
+       __u32 opcode;
+       /*
+        * User should select one of the idn from "enum flag_idn" or "enum
+        * attr_idn" or "enum desc_idn" based on whether opcode above is
+        * attribute, flag or descriptor.
+        * Please check include/uapi/scsi/ufs/ufs.h for the definition of it.
+        */
+       __u8 idn;
+       /*
+        * User should specify the size of the buffer (buffer[0] below) where
+        * it wants to read the query data (attribute/flag/descriptor).
+        * As we might end up reading less data then what is specified in
+        * buf_size. So we are updating buf_size to what exactly we have read.
+        */
+       __u16 buf_size;
+       /*
+        * placeholder for the start of the data buffer where kernel will copy
+        * the query data (attribute/flag/descriptor) read from the UFS device
+        * Note:
+        * For Read Attribute you will have to allocate 4 bytes
+        * For Read Flag you will have to allocate 1 byte
+        */
+       __u8 buffer[0];
+};
+
+#endif /* UAPI_UFS_IOCTL_H_ */