/* * Copyright (c) 2014-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include "ufs-qcom-ice.h" #include "ufs-qcom-debugfs.h" #include "ufshcd.h" #define UFS_QCOM_CRYPTO_LABEL "ufs-qcom-crypto" /* Timeout waiting for ICE initialization, that requires TZ access */ #define UFS_QCOM_ICE_COMPLETION_TIMEOUT_MS 500 #define UFS_QCOM_ICE_DEFAULT_DBG_PRINT_EN 0 static void ufs_qcom_ice_dump_regs(struct ufs_qcom_host *qcom_host, int offset, int len, char *prefix) { print_hex_dump(KERN_ERR, prefix, len > 4 ? DUMP_PREFIX_OFFSET : DUMP_PREFIX_NONE, 16, 4, qcom_host->hba->mmio_base + offset, len * 4, false); } void ufs_qcom_ice_print_regs(struct ufs_qcom_host *qcom_host) { int i; if (!(qcom_host->dbg_print_en & UFS_QCOM_DBG_PRINT_ICE_REGS_EN)) return; ufs_qcom_ice_dump_regs(qcom_host, REG_UFS_QCOM_ICE_CFG, 1, "REG_UFS_QCOM_ICE_CFG "); for (i = 0; i < NUM_QCOM_ICE_CTRL_INFO_n_REGS; i++) { pr_err("REG_UFS_QCOM_ICE_CTRL_INFO_1_%d = 0x%08X\n", i, ufshcd_readl(qcom_host->hba, (REG_UFS_QCOM_ICE_CTRL_INFO_1_n + 8 * i))); pr_err("REG_UFS_QCOM_ICE_CTRL_INFO_2_%d = 0x%08X\n", i, ufshcd_readl(qcom_host->hba, (REG_UFS_QCOM_ICE_CTRL_INFO_2_n + 8 * i))); } if (qcom_host->ice.pdev && qcom_host->ice.vops && qcom_host->ice.vops->debug) qcom_host->ice.vops->debug(qcom_host->ice.pdev); } static void ufs_qcom_ice_error_cb(void *host_ctrl, u32 error) { struct ufs_qcom_host *qcom_host = (struct ufs_qcom_host *)host_ctrl; dev_err(qcom_host->hba->dev, "%s: Error in ice operation 0x%x", __func__, error); if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_ACTIVE) qcom_host->ice.state = UFS_QCOM_ICE_STATE_DISABLED; } static struct platform_device *ufs_qcom_ice_get_pdevice(struct device *ufs_dev) { struct device_node *node; struct platform_device *ice_pdev = NULL; node = of_parse_phandle(ufs_dev->of_node, UFS_QCOM_CRYPTO_LABEL, 0); if (!node) { dev_err(ufs_dev, "%s: ufs-qcom-crypto property not specified\n", __func__); goto out; } ice_pdev = qcom_ice_get_pdevice(node); out: return ice_pdev; } static struct qcom_ice_variant_ops *ufs_qcom_ice_get_vops(struct device *ufs_dev) { struct qcom_ice_variant_ops *ice_vops = NULL; struct device_node *node; node = of_parse_phandle(ufs_dev->of_node, UFS_QCOM_CRYPTO_LABEL, 0); if (!node) { dev_err(ufs_dev, "%s: ufs-qcom-crypto property not specified\n", __func__); goto out; } ice_vops = qcom_ice_get_variant_ops(node); if (!ice_vops) dev_err(ufs_dev, "%s: invalid ice_vops\n", __func__); of_node_put(node); out: return ice_vops; } /** * ufs_qcom_ice_get_dev() - sets pointers to ICE data structs in UFS QCom host * @qcom_host: Pointer to a UFS QCom internal host structure. * * Sets ICE platform device pointer and ICE vops structure * corresponding to the current UFS device. * * Return: -EINVAL in-case of invalid input parameters: * qcom_host, qcom_host->hba or qcom_host->hba->dev * -ENODEV in-case ICE device is not required * -EPROBE_DEFER in-case ICE is required and hasn't been probed yet * 0 otherwise */ int ufs_qcom_ice_get_dev(struct ufs_qcom_host *qcom_host) { struct device *ufs_dev; int err = 0; if (!qcom_host || !qcom_host->hba || !qcom_host->hba->dev) { pr_err("%s: invalid qcom_host %p or qcom_host->hba or qcom_host->hba->dev\n", __func__, qcom_host); err = -EINVAL; goto out; } ufs_dev = qcom_host->hba->dev; qcom_host->ice.vops = ufs_qcom_ice_get_vops(ufs_dev); qcom_host->ice.pdev = ufs_qcom_ice_get_pdevice(ufs_dev); if (qcom_host->ice.pdev == ERR_PTR(-EPROBE_DEFER)) { dev_err(ufs_dev, "%s: ICE device not probed yet\n", __func__); qcom_host->ice.pdev = NULL; qcom_host->ice.vops = NULL; err = -EPROBE_DEFER; goto out; } if (!qcom_host->ice.pdev || !qcom_host->ice.vops) { dev_err(ufs_dev, "%s: invalid platform device %p or vops %p\n", __func__, qcom_host->ice.pdev, qcom_host->ice.vops); qcom_host->ice.pdev = NULL; qcom_host->ice.vops = NULL; err = -ENODEV; goto out; } qcom_host->ice.state = UFS_QCOM_ICE_STATE_DISABLED; out: return err; } static void ufs_qcom_ice_cfg_work(struct work_struct *work) { unsigned long flags; struct ice_data_setting ice_set; struct ufs_qcom_host *qcom_host = container_of(work, struct ufs_qcom_host, ice_cfg_work); struct request *req_pending = NULL; if (!qcom_host->ice.vops->config_start) return; spin_lock_irqsave(&qcom_host->ice_work_lock, flags); req_pending = qcom_host->req_pending; if (!req_pending) { spin_unlock_irqrestore(&qcom_host->ice_work_lock, flags); return; } spin_unlock_irqrestore(&qcom_host->ice_work_lock, flags); /* * config_start is called again as previous attempt returned -EAGAIN, * this call shall now take care of the necessary key setup. * 'ice_set' will not actually be used, instead the next call to * config_start() for this request, in the normal call flow, will * succeed as the key has now been setup. */ qcom_host->ice.vops->config_start(qcom_host->ice.pdev, qcom_host->req_pending, &ice_set, false); spin_lock_irqsave(&qcom_host->ice_work_lock, flags); qcom_host->req_pending = NULL; spin_unlock_irqrestore(&qcom_host->ice_work_lock, flags); /* * Resume with requests processing. We assume config_start has been * successful, but even if it wasn't we still must resume in order to * allow for the request to be retried. */ ufshcd_scsi_unblock_requests(qcom_host->hba); } /** * ufs_qcom_ice_init() - initializes the ICE-UFS interface and ICE device * @qcom_host: Pointer to a UFS QCom internal host structure. * qcom_host, qcom_host->hba and qcom_host->hba->dev should all * be valid pointers. * * Return: -EINVAL in-case of an error * 0 otherwise */ int ufs_qcom_ice_init(struct ufs_qcom_host *qcom_host) { struct device *ufs_dev = qcom_host->hba->dev; int err; err = qcom_host->ice.vops->init(qcom_host->ice.pdev, qcom_host, ufs_qcom_ice_error_cb); if (err) { dev_err(ufs_dev, "%s: ice init failed. err = %d\n", __func__, err); goto out; } else { qcom_host->ice.state = UFS_QCOM_ICE_STATE_ACTIVE; } qcom_host->dbg_print_en |= UFS_QCOM_ICE_DEFAULT_DBG_PRINT_EN; INIT_WORK(&qcom_host->ice_cfg_work, ufs_qcom_ice_cfg_work); out: return err; } static inline bool ufs_qcom_is_data_cmd(char cmd_op, bool is_write) { if (is_write) { if (cmd_op == WRITE_6 || cmd_op == WRITE_10 || cmd_op == WRITE_16) return true; } else { if (cmd_op == READ_6 || cmd_op == READ_10 || cmd_op == READ_16) return true; } return false; } int ufs_qcom_ice_req_setup(struct ufs_qcom_host *qcom_host, struct scsi_cmnd *cmd, u8 *cc_index, bool *enable) { struct ice_data_setting ice_set; char cmd_op = cmd->cmnd[0]; int err; unsigned long flags; if (!qcom_host->ice.pdev || !qcom_host->ice.vops) { dev_dbg(qcom_host->hba->dev, "%s: ice device is not enabled\n", __func__); return 0; } if (qcom_host->ice.vops->config_start) { memset(&ice_set, 0, sizeof(ice_set)); spin_lock_irqsave( &qcom_host->ice_work_lock, flags); err = qcom_host->ice.vops->config_start(qcom_host->ice.pdev, cmd->request, &ice_set, true); if (err) { /* * config_start() returns -EAGAIN when a key slot is * available but still not configured. As configuration * requires a non-atomic context, this means we should * call the function again from the worker thread to do * the configuration. For this request the error will * propagate so it will be re-queued and until the * configuration is is completed we block further * request processing. */ if (err == -EAGAIN) { dev_dbg(qcom_host->hba->dev, "%s: scheduling task for ice setup\n", __func__); if (!qcom_host->req_pending) { ufshcd_scsi_block_requests( qcom_host->hba); qcom_host->req_pending = cmd->request; if (!schedule_work( &qcom_host->ice_cfg_work)) { qcom_host->req_pending = NULL; spin_unlock_irqrestore( &qcom_host->ice_work_lock, flags); ufshcd_scsi_unblock_requests( qcom_host->hba); return err; } } } else { if (err != -EBUSY) dev_err(qcom_host->hba->dev, "%s: error in ice_vops->config %d\n", __func__, err); } spin_unlock_irqrestore(&qcom_host->ice_work_lock, flags); return err; } spin_unlock_irqrestore(&qcom_host->ice_work_lock, flags); if (ufs_qcom_is_data_cmd(cmd_op, true)) *enable = !ice_set.encr_bypass; else if (ufs_qcom_is_data_cmd(cmd_op, false)) *enable = !ice_set.decr_bypass; if (ice_set.crypto_data.key_index >= 0) *cc_index = (u8)ice_set.crypto_data.key_index; } return 0; } /** * ufs_qcom_ice_cfg_start() - starts configuring UFS's ICE registers * for an ICE transaction * @qcom_host: Pointer to a UFS QCom internal host structure. * qcom_host, qcom_host->hba and qcom_host->hba->dev should all * be valid pointers. * @cmd: Pointer to a valid scsi command. cmd->request should also be * a valid pointer. * * Return: -EINVAL in-case of an error * 0 otherwise */ int ufs_qcom_ice_cfg_start(struct ufs_qcom_host *qcom_host, struct scsi_cmnd *cmd) { struct device *dev = qcom_host->hba->dev; int err = 0; struct ice_data_setting ice_set; unsigned int slot = 0; sector_t lba = 0; unsigned int ctrl_info_val = 0; unsigned int bypass = 0; struct request *req; char cmd_op; unsigned long flags; if (!qcom_host->ice.pdev || !qcom_host->ice.vops) { dev_dbg(dev, "%s: ice device is not enabled\n", __func__); goto out; } if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) { dev_err(dev, "%s: ice state (%d) is not active\n", __func__, qcom_host->ice.state); return -EINVAL; } if (qcom_host->hw_ver.major == 0x3) { /* nothing to do here for version 0x3, exit silently */ return 0; } req = cmd->request; if (req->bio) lba = (req->bio->bi_iter.bi_sector) >> UFS_QCOM_ICE_TR_DATA_UNIT_4_KB; slot = req->tag; if (slot < 0 || slot > qcom_host->hba->nutrs) { dev_err(dev, "%s: slot (%d) is out of boundaries (0...%d)\n", __func__, slot, qcom_host->hba->nutrs); return -EINVAL; } if (qcom_host->ice.vops->config_start) { memset(&ice_set, 0, sizeof(ice_set)); spin_lock_irqsave( &qcom_host->ice_work_lock, flags); err = qcom_host->ice.vops->config_start(qcom_host->ice.pdev, req, &ice_set, true); if (err) { /* * config_start() returns -EAGAIN when a key slot is * available but still not configured. As configuration * requires a non-atomic context, this means we should * call the function again from the worker thread to do * the configuration. For this request the error will * propagate so it will be re-queued and until the * configuration is is completed we block further * request processing. */ if (err == -EAGAIN) { dev_dbg(qcom_host->hba->dev, "%s: scheduling task for ice setup\n", __func__); if (!qcom_host->req_pending) { ufshcd_scsi_block_requests( qcom_host->hba); qcom_host->req_pending = cmd->request; if (!schedule_work( &qcom_host->ice_cfg_work)) { qcom_host->req_pending = NULL; spin_unlock_irqrestore( &qcom_host->ice_work_lock, flags); ufshcd_scsi_unblock_requests( qcom_host->hba); return err; } } } else { if (err != -EBUSY) dev_err(qcom_host->hba->dev, "%s: error in ice_vops->config %d\n", __func__, err); } spin_unlock_irqrestore( &qcom_host->ice_work_lock, flags); return err; } spin_unlock_irqrestore( &qcom_host->ice_work_lock, flags); } cmd_op = cmd->cmnd[0]; #define UFS_QCOM_DIR_WRITE true #define UFS_QCOM_DIR_READ false /* if non data command, bypass shall be enabled */ if (!ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_WRITE) && !ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_READ)) bypass = UFS_QCOM_ICE_ENABLE_BYPASS; /* if writing data command */ else if (ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_WRITE)) bypass = ice_set.encr_bypass ? UFS_QCOM_ICE_ENABLE_BYPASS : UFS_QCOM_ICE_DISABLE_BYPASS; /* if reading data command */ else if (ufs_qcom_is_data_cmd(cmd_op, UFS_QCOM_DIR_READ)) bypass = ice_set.decr_bypass ? UFS_QCOM_ICE_ENABLE_BYPASS : UFS_QCOM_ICE_DISABLE_BYPASS; /* Configure ICE index */ ctrl_info_val = (ice_set.crypto_data.key_index & MASK_UFS_QCOM_ICE_CTRL_INFO_KEY_INDEX) << OFFSET_UFS_QCOM_ICE_CTRL_INFO_KEY_INDEX; /* Configure data unit size of transfer request */ ctrl_info_val |= UFS_QCOM_ICE_TR_DATA_UNIT_4_KB << OFFSET_UFS_QCOM_ICE_CTRL_INFO_CDU; /* Configure ICE bypass mode */ ctrl_info_val |= (bypass & MASK_UFS_QCOM_ICE_CTRL_INFO_BYPASS) << OFFSET_UFS_QCOM_ICE_CTRL_INFO_BYPASS; if (qcom_host->hw_ver.major == 0x1) { ufshcd_writel(qcom_host->hba, lba, (REG_UFS_QCOM_ICE_CTRL_INFO_1_n + 8 * slot)); ufshcd_writel(qcom_host->hba, ctrl_info_val, (REG_UFS_QCOM_ICE_CTRL_INFO_2_n + 8 * slot)); } if (qcom_host->hw_ver.major == 0x2) { ufshcd_writel(qcom_host->hba, (lba & 0xFFFFFFFF), (REG_UFS_QCOM_ICE_CTRL_INFO_1_n + 16 * slot)); ufshcd_writel(qcom_host->hba, ((lba >> 32) & 0xFFFFFFFF), (REG_UFS_QCOM_ICE_CTRL_INFO_2_n + 16 * slot)); ufshcd_writel(qcom_host->hba, ctrl_info_val, (REG_UFS_QCOM_ICE_CTRL_INFO_3_n + 16 * slot)); } /* * Ensure UFS-ICE registers are being configured * before next operation, otherwise UFS Host Controller might * set get errors */ mb(); out: return err; } /** * ufs_qcom_ice_cfg_end() - finishes configuring UFS's ICE registers * for an ICE transaction * @qcom_host: Pointer to a UFS QCom internal host structure. * qcom_host, qcom_host->hba and * qcom_host->hba->dev should all * be valid pointers. * @cmd: Pointer to a valid scsi command. cmd->request should also be * a valid pointer. * * Return: -EINVAL in-case of an error * 0 otherwise */ int ufs_qcom_ice_cfg_end(struct ufs_qcom_host *qcom_host, struct request *req) { int err = 0; struct device *dev = qcom_host->hba->dev; if (qcom_host->ice.vops->config_end) { err = qcom_host->ice.vops->config_end(req); if (err) { dev_err(dev, "%s: error in ice_vops->config_end %d\n", __func__, err); return err; } } return 0; } /** * ufs_qcom_ice_reset() - resets UFS-ICE interface and ICE device * @qcom_host: Pointer to a UFS QCom internal host structure. * qcom_host, qcom_host->hba and qcom_host->hba->dev should all * be valid pointers. * * Return: -EINVAL in-case of an error * 0 otherwise */ int ufs_qcom_ice_reset(struct ufs_qcom_host *qcom_host) { struct device *dev = qcom_host->hba->dev; int err = 0; if (!qcom_host->ice.pdev) { dev_dbg(dev, "%s: ice device is not enabled\n", __func__); goto out; } if (!qcom_host->ice.vops) { dev_err(dev, "%s: invalid ice_vops\n", __func__); return -EINVAL; } if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) goto out; if (qcom_host->ice.vops->reset) { err = qcom_host->ice.vops->reset(qcom_host->ice.pdev); if (err) { dev_err(dev, "%s: ice_vops->reset failed. err %d\n", __func__, err); goto out; } } if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) { dev_err(qcom_host->hba->dev, "%s: error. ice.state (%d) is not in active state\n", __func__, qcom_host->ice.state); err = -EINVAL; } out: return err; } /** * ufs_qcom_ice_resume() - resumes UFS-ICE interface and ICE device from power * collapse * @qcom_host: Pointer to a UFS QCom internal host structure. * qcom_host, qcom_host->hba and qcom_host->hba->dev should all * be valid pointers. * * Return: -EINVAL in-case of an error * 0 otherwise */ int ufs_qcom_ice_resume(struct ufs_qcom_host *qcom_host) { struct device *dev = qcom_host->hba->dev; int err = 0; if (!qcom_host->ice.pdev) { dev_dbg(dev, "%s: ice device is not enabled\n", __func__); goto out; } if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_SUSPENDED) { goto out; } if (!qcom_host->ice.vops) { dev_err(dev, "%s: invalid ice_vops\n", __func__); return -EINVAL; } if (qcom_host->ice.vops->resume) { err = qcom_host->ice.vops->resume(qcom_host->ice.pdev); if (err) { dev_err(dev, "%s: ice_vops->resume failed. err %d\n", __func__, err); return err; } } qcom_host->ice.state = UFS_QCOM_ICE_STATE_ACTIVE; out: return err; } /** * ufs_qcom_ice_suspend() - suspends UFS-ICE interface and ICE device * @qcom_host: Pointer to a UFS QCom internal host structure. * qcom_host, qcom_host->hba and qcom_host->hba->dev should all * be valid pointers. * * Return: -EINVAL in-case of an error * 0 otherwise */ int ufs_qcom_ice_suspend(struct ufs_qcom_host *qcom_host) { struct device *dev = qcom_host->hba->dev; int err = 0; if (!qcom_host->ice.pdev) { dev_dbg(dev, "%s: ice device is not enabled\n", __func__); goto out; } if (qcom_host->ice.vops->suspend) { err = qcom_host->ice.vops->suspend(qcom_host->ice.pdev); if (err) { dev_err(qcom_host->hba->dev, "%s: ice_vops->suspend failed. err %d\n", __func__, err); return -EINVAL; } } if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_ACTIVE) { qcom_host->ice.state = UFS_QCOM_ICE_STATE_SUSPENDED; } else if (qcom_host->ice.state == UFS_QCOM_ICE_STATE_DISABLED) { dev_err(qcom_host->hba->dev, "%s: ice state is invalid: disabled\n", __func__); err = -EINVAL; } out: return err; } /** * ufs_qcom_ice_get_status() - returns the status of an ICE transaction * @qcom_host: Pointer to a UFS QCom internal host structure. * qcom_host, qcom_host->hba and qcom_host->hba->dev should all * be valid pointers. * @ice_status: Pointer to a valid output parameter. * < 0 in case of ICE transaction failure. * 0 otherwise. * * Return: -EINVAL in-case of an error * 0 otherwise */ int ufs_qcom_ice_get_status(struct ufs_qcom_host *qcom_host, int *ice_status) { struct device *dev = NULL; int err = 0; int stat = -EINVAL; *ice_status = 0; dev = qcom_host->hba->dev; if (!dev) { err = -EINVAL; goto out; } if (!qcom_host->ice.pdev) { dev_dbg(dev, "%s: ice device is not enabled\n", __func__); goto out; } if (qcom_host->ice.state != UFS_QCOM_ICE_STATE_ACTIVE) { err = -EINVAL; goto out; } if (!qcom_host->ice.vops) { dev_err(dev, "%s: invalid ice_vops\n", __func__); return -EINVAL; } if (qcom_host->ice.vops->status) { stat = qcom_host->ice.vops->status(qcom_host->ice.pdev); if (stat < 0) { dev_err(dev, "%s: ice_vops->status failed. stat %d\n", __func__, stat); err = -EINVAL; goto out; } *ice_status = stat; } out: return err; }