From c2e6ab68005a18cb30f9374f9f6ae5e65f415b25 Mon Sep 17 00:00:00 2001 From: hgchu Date: Fri, 12 Jan 2018 14:55:23 +0900 Subject: [PATCH] scsi: ufs: add ioctl in UFS driver Change-Id: I7668678f46a49045f76631ac25febf9b8a944f5a Signed-off-by: hgchu --- drivers/scsi/ufs/ufs.h | 22 +++- drivers/scsi/ufs/ufshcd.c | 231 ++++++++++++++++++++++++++++++++++ include/uapi/scsi/ufs/Kbuild | 2 + include/uapi/scsi/ufs/ioctl.h | 57 +++++++++ 4 files changed, 309 insertions(+), 3 deletions(-) create mode 100644 include/uapi/scsi/ufs/Kbuild create mode 100644 include/uapi/scsi/ufs/ioctl.h diff --git a/drivers/scsi/ufs/ufs.h b/drivers/scsi/ufs/ufs.h index 1ec5fe80f0b2..6ae13f0b7b32 100644 --- a/drivers/scsi/ufs/ufs.h +++ b/drivers/scsi/ufs/ufs.h @@ -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 */ diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index ec3ac029f7cd..6c527b85372b 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -42,6 +42,7 @@ #include #endif #include +#include #include #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 index 000000000000..8c1d2cb75e33 --- /dev/null +++ b/include/uapi/scsi/ufs/Kbuild @@ -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 index 000000000000..f7a671873b40 --- /dev/null +++ b/include/uapi/scsi/ufs/ioctl.h @@ -0,0 +1,57 @@ +#ifndef UAPI_UFS_IOCTL_H_ +#define UAPI_UFS_IOCTL_H_ + +#include + +/* + * 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_ */ -- 2.20.1