diff --git a/drivers/scsi/ufs/ufs.h b/drivers/scsi/ufs/ufs.h index 42c459a9d3fe..1f023c4634f3 100644 --- a/drivers/scsi/ufs/ufs.h +++ b/drivers/scsi/ufs/ufs.h @@ -38,6 +38,7 @@ #include #include +#include #define MAX_CDB_SIZE 16 #define GENERAL_UPIU_REQUEST_SIZE 32 @@ -71,6 +72,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 */ @@ -126,35 +137,6 @@ enum { UPIU_QUERY_FUNC_STANDARD_WRITE_REQUEST = 0x81, }; -/* 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, -}; - -/* Attribute idn for Query requests */ -enum attr_idn { - QUERY_ATTR_IDN_ACTIVE_ICC_LVL = 0x03, - QUERY_ATTR_IDN_BKOPS_STATUS = 0x05, - QUERY_ATTR_IDN_EE_CONTROL = 0x0D, - QUERY_ATTR_IDN_EE_STATUS = 0x0E, -}; - -/* Descriptor idn for Query requests */ -enum desc_idn { - QUERY_DESC_IDN_DEVICE = 0x0, - QUERY_DESC_IDN_CONFIGURAION = 0x1, - QUERY_DESC_IDN_UNIT = 0x2, - QUERY_DESC_IDN_RFU_0 = 0x3, - QUERY_DESC_IDN_INTERCONNECT = 0x4, - QUERY_DESC_IDN_STRING = 0x5, - QUERY_DESC_IDN_RFU_1 = 0x6, - QUERY_DESC_IDN_GEOMETRY = 0x7, - QUERY_DESC_IDN_POWER = 0x8, - QUERY_DESC_IDN_MAX, -}; - enum desc_header_offset { QUERY_DESC_LENGTH_OFFSET = 0x00, QUERY_DESC_DESC_TYPE_OFFSET = 0x01, @@ -247,19 +229,6 @@ enum bkops_status { BKOPS_STATUS_MAX = BKOPS_STATUS_CRITICAL, }; -/* UTP QUERY Transaction Specific Fields OpCode */ -enum query_opcode { - UPIU_QUERY_OPCODE_NOP = 0x0, - UPIU_QUERY_OPCODE_READ_DESC = 0x1, - UPIU_QUERY_OPCODE_WRITE_DESC = 0x2, - UPIU_QUERY_OPCODE_READ_ATTR = 0x3, - UPIU_QUERY_OPCODE_WRITE_ATTR = 0x4, - UPIU_QUERY_OPCODE_READ_FLAG = 0x5, - UPIU_QUERY_OPCODE_SET_FLAG = 0x6, - UPIU_QUERY_OPCODE_CLEAR_FLAG = 0x7, - UPIU_QUERY_OPCODE_TOGGLE_FLAG = 0x8, -}; - /* Query response result code */ enum { QUERY_RESULT_SUCCESS = 0x00, diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c index b37eca5a7204..a031e78d5532 100644 --- a/drivers/scsi/ufs/ufshcd.c +++ b/drivers/scsi/ufs/ufshcd.c @@ -38,6 +38,7 @@ */ #include +#include #include #include "ufshcd.h" @@ -2010,7 +2011,7 @@ static inline int ufshcd_read_unit_desc_param(struct ufs_hba *hba, * Unit descriptors are only available for general purpose LUs (LUN id * from 0 to 7) and RPMB Well known LU. */ - if (lun != UFS_UPIU_RPMB_WLUN && (lun >= UFS_UPIU_MAX_GENERAL_LUN)) + if (!ufs_is_valid_unit_desc_lun(lun)) return -EOPNOTSUPP; return ufshcd_read_desc_param(hba, QUERY_DESC_IDN_UNIT, lun, @@ -4465,6 +4466,221 @@ static void ufshcd_async_scan(void *data, async_cookie_t cookie) ufshcd_probe_hba(hba); } +/** + * 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 *ioct_data; + int err = 0; + int length = 0; + void *data_ptr; + bool flag; + u32 att; + u8 index; + u8 *desc = NULL; + + ioct_data = kmalloc(sizeof(struct ufs_ioctl_query_data), GFP_KERNEL); + if (!ioct_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(ioct_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 (ioct_data->opcode) { + case UPIU_QUERY_OPCODE_READ_DESC: + switch (ioct_data->idn) { + case QUERY_DESC_IDN_DEVICE: + case QUERY_DESC_IDN_CONFIGURAION: + case QUERY_DESC_IDN_INTERCONNECT: + case QUERY_DESC_IDN_GEOMETRY: + case QUERY_DESC_IDN_POWER: + 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, + ioct_data->buf_size); + desc = kmalloc(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(hba, ioct_data->opcode, + ioct_data->idn, index, 0, desc, &length); + break; + case UPIU_QUERY_OPCODE_READ_ATTR: + switch (ioct_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(hba, ioct_data->opcode, ioct_data->idn, + index, 0, &att); + break; + case UPIU_QUERY_OPCODE_READ_FLAG: + switch (ioct_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(hba, ioct_data->opcode, ioct_data->idn, + &flag); + break; + default: + goto out_einval; + } + + if (err) { + dev_err(hba->dev, "%s: Query for idn %d failed\n", __func__, + ioct_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 (ioct_data->opcode) { + case UPIU_QUERY_OPCODE_READ_DESC: + ioct_data->buf_size = min_t(int, ioct_data->buf_size, length); + data_ptr = desc; + break; + case UPIU_QUERY_OPCODE_READ_ATTR: + ioct_data->buf_size = sizeof(u32); + data_ptr = &att; + break; + case UPIU_QUERY_OPCODE_READ_FLAG: + ioct_data->buf_size = 1; + data_ptr = &flag; + break; + default: + BUG_ON(true); + } + + /* copy to user */ + err = copy_to_user(buffer, ioct_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, ioct_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__, ioct_data->opcode, (unsigned int)ioct_data->idn); + err = -EINVAL; +out_release_mem: + kfree(ioct_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) { + dev_err(hba->dev, "%s: User buffer is NULL!\n", __func__); + return -EINVAL; + } + + switch (cmd) { + 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; + 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, @@ -4477,6 +4693,7 @@ static struct scsi_host_template ufshcd_driver_template = { .eh_abort_handler = ufshcd_abort, .eh_device_reset_handler = ufshcd_eh_device_reset_handler, .eh_host_reset_handler = ufshcd_eh_host_reset_handler, + .ioctl = ufshcd_ioctl, .this_id = -1, .sg_tablesize = SG_ALL, .cmd_per_lun = UFSHCD_CMD_PER_LUN,