554 lines
14 KiB
C
554 lines
14 KiB
C
/*
|
|
* Copyright (c) 2014-2015, 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 <linux/io.h>
|
|
#include <linux/of.h>
|
|
#include <linux/blkdev.h>
|
|
#include <crypto/ice.h>
|
|
|
|
#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)));
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
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;
|
|
|
|
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) {
|
|
memset(&ice_set, 0, sizeof(ice_set));
|
|
err = qcom_host->ice.vops->config(qcom_host->ice.pdev,
|
|
cmd->request, &ice_set);
|
|
if (err) {
|
|
dev_err(qcom_host->hba->dev,
|
|
"%s: error in ice_vops->config %d\n",
|
|
__func__, err);
|
|
return err;
|
|
}
|
|
|
|
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() - configures 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(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;
|
|
|
|
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;
|
|
}
|
|
|
|
req = cmd->request;
|
|
if (req->bio)
|
|
lba = req->bio->bi_iter.bi_sector;
|
|
|
|
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;
|
|
}
|
|
|
|
memset(&ice_set, 0, sizeof(ice_set));
|
|
if (qcom_host->ice.vops->config) {
|
|
err = qcom_host->ice.vops->config(qcom_host->ice.pdev,
|
|
req, &ice_set);
|
|
|
|
if (err) {
|
|
dev_err(dev, "%s: error in ice_vops->config %d\n",
|
|
__func__, err);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
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 &
|
|
MASK_UFS_QCOM_ICE_CTRL_INFO_CDU)
|
|
<< 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 < 0x2) {
|
|
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));
|
|
} else {
|
|
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_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;
|
|
}
|