summaryrefslogtreecommitdiffstats
path: root/drivers/ufs
diff options
context:
space:
mode:
authorMartin K. Petersen <martin.petersen@oracle.com>2024-07-11 04:33:34 +0200
committerMartin K. Petersen <martin.petersen@oracle.com>2024-07-11 04:33:34 +0200
commitaf8e69efd78561496cdd736bb1b4f7edd89b2f6b (patch)
treea949e4c837e357c73a5ba939a2f866b0eaa231bc /drivers/ufs
parentMerge patch series "UFS patches for kernel 6.11" (diff)
parentscsi: ufs: exynos: Add support for Flash Memory Protector (FMP) (diff)
downloadlinux-af8e69efd78561496cdd736bb1b4f7edd89b2f6b.tar.xz
linux-af8e69efd78561496cdd736bb1b4f7edd89b2f6b.zip
Merge patch series "Basic inline encryption support for ufs-exynos"
Eric Biggers <ebiggers@kernel.org> says: Add support for Flash Memory Protector (FMP), which is the inline encryption hardware on Exynos and Exynos-based SoCs. Specifically, add support for the "traditional FMP mode" that works on many Exynos-based SoCs including gs101. This is the mode that uses "software keys" and is compatible with the upstream kernel's existing inline encryption framework in the block and filesystem layers. I plan to add support for the wrapped key support on gs101 at a later time. Tested on gs101 (specifically Pixel 6) by running the 'encrypt' group of xfstests on a filesystem mounted with the 'inlinecrypt' mount option. This patchset applies to v6.10-rc6, and it has no prerequisites that aren't already upstream. Link: https://lore.kernel.org/r/20240708235330.103590-1-ebiggers@kernel.org Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
Diffstat (limited to 'drivers/ufs')
-rw-r--r--drivers/ufs/core/ufshcd-crypto.c34
-rw-r--r--drivers/ufs/core/ufshcd-crypto.h36
-rw-r--r--drivers/ufs/core/ufshcd.c3
-rw-r--r--drivers/ufs/host/ufs-exynos.c240
4 files changed, 292 insertions, 21 deletions
diff --git a/drivers/ufs/core/ufshcd-crypto.c b/drivers/ufs/core/ufshcd-crypto.c
index f2c4422cab86..a714dad82cd1 100644
--- a/drivers/ufs/core/ufshcd-crypto.c
+++ b/drivers/ufs/core/ufshcd-crypto.c
@@ -95,8 +95,12 @@ static int ufshcd_crypto_keyslot_program(struct blk_crypto_profile *profile,
return err;
}
-static int ufshcd_clear_keyslot(struct ufs_hba *hba, int slot)
+static int ufshcd_crypto_keyslot_evict(struct blk_crypto_profile *profile,
+ const struct blk_crypto_key *key,
+ unsigned int slot)
{
+ struct ufs_hba *hba =
+ container_of(profile, struct ufs_hba, crypto_profile);
/*
* Clear the crypto cfg on the device. Clearing CFGE
* might not be sufficient, so just clear the entire cfg.
@@ -106,16 +110,10 @@ static int ufshcd_clear_keyslot(struct ufs_hba *hba, int slot)
return ufshcd_program_key(hba, &cfg, slot);
}
-static int ufshcd_crypto_keyslot_evict(struct blk_crypto_profile *profile,
- const struct blk_crypto_key *key,
- unsigned int slot)
-{
- struct ufs_hba *hba =
- container_of(profile, struct ufs_hba, crypto_profile);
-
- return ufshcd_clear_keyslot(hba, slot);
-}
-
+/*
+ * Reprogram the keyslots if needed, and return true if CRYPTO_GENERAL_ENABLE
+ * should be used in the host controller initialization sequence.
+ */
bool ufshcd_crypto_enable(struct ufs_hba *hba)
{
if (!(hba->caps & UFSHCD_CAP_CRYPTO))
@@ -123,6 +121,10 @@ bool ufshcd_crypto_enable(struct ufs_hba *hba)
/* Reset might clear all keys, so reprogram all the keys. */
blk_crypto_reprogram_all_keys(&hba->crypto_profile);
+
+ if (hba->quirks & UFSHCD_QUIRK_BROKEN_CRYPTO_ENABLE)
+ return false;
+
return true;
}
@@ -159,6 +161,9 @@ int ufshcd_hba_init_crypto_capabilities(struct ufs_hba *hba)
int err = 0;
enum blk_crypto_mode_num blk_mode_num;
+ if (hba->quirks & UFSHCD_QUIRK_CUSTOM_CRYPTO_PROFILE)
+ return 0;
+
/*
* Don't use crypto if either the hardware doesn't advertise the
* standard crypto capability bit *or* if the vendor specific driver
@@ -228,9 +233,10 @@ void ufshcd_init_crypto(struct ufs_hba *hba)
if (!(hba->caps & UFSHCD_CAP_CRYPTO))
return;
- /* Clear all keyslots - the number of keyslots is (CFGC + 1) */
- for (slot = 0; slot < hba->crypto_capabilities.config_count + 1; slot++)
- ufshcd_clear_keyslot(hba, slot);
+ /* Clear all keyslots. */
+ for (slot = 0; slot < hba->crypto_profile.num_slots; slot++)
+ hba->crypto_profile.ll_ops.keyslot_evict(&hba->crypto_profile,
+ NULL, slot);
}
void ufshcd_crypto_register(struct ufs_hba *hba, struct request_queue *q)
diff --git a/drivers/ufs/core/ufshcd-crypto.h b/drivers/ufs/core/ufshcd-crypto.h
index be8596f20ba2..89bb97c14c15 100644
--- a/drivers/ufs/core/ufshcd-crypto.h
+++ b/drivers/ufs/core/ufshcd-crypto.h
@@ -37,6 +37,33 @@ ufshcd_prepare_req_desc_hdr_crypto(struct ufshcd_lrb *lrbp,
h->dunu = cpu_to_le32(upper_32_bits(lrbp->data_unit_num));
}
+static inline int ufshcd_crypto_fill_prdt(struct ufs_hba *hba,
+ struct ufshcd_lrb *lrbp)
+{
+ struct scsi_cmnd *cmd = lrbp->cmd;
+ const struct bio_crypt_ctx *crypt_ctx = scsi_cmd_to_rq(cmd)->crypt_ctx;
+
+ if (crypt_ctx && hba->vops && hba->vops->fill_crypto_prdt)
+ return hba->vops->fill_crypto_prdt(hba, crypt_ctx,
+ lrbp->ucd_prdt_ptr,
+ scsi_sg_count(cmd));
+ return 0;
+}
+
+static inline void ufshcd_crypto_clear_prdt(struct ufs_hba *hba,
+ struct ufshcd_lrb *lrbp)
+{
+ if (!(hba->quirks & UFSHCD_QUIRK_KEYS_IN_PRDT))
+ return;
+
+ if (!(scsi_cmd_to_rq(lrbp->cmd)->crypt_ctx))
+ return;
+
+ /* Zeroize the PRDT because it can contain cryptographic keys. */
+ memzero_explicit(lrbp->ucd_prdt_ptr,
+ ufshcd_sg_entry_size(hba) * scsi_sg_count(lrbp->cmd));
+}
+
bool ufshcd_crypto_enable(struct ufs_hba *hba);
int ufshcd_hba_init_crypto_capabilities(struct ufs_hba *hba);
@@ -54,6 +81,15 @@ static inline void
ufshcd_prepare_req_desc_hdr_crypto(struct ufshcd_lrb *lrbp,
struct request_desc_header *h) { }
+static inline int ufshcd_crypto_fill_prdt(struct ufs_hba *hba,
+ struct ufshcd_lrb *lrbp)
+{
+ return 0;
+}
+
+static inline void ufshcd_crypto_clear_prdt(struct ufs_hba *hba,
+ struct ufshcd_lrb *lrbp) { }
+
static inline bool ufshcd_crypto_enable(struct ufs_hba *hba)
{
return false;
diff --git a/drivers/ufs/core/ufshcd.c b/drivers/ufs/core/ufshcd.c
index 0be3e9a089e8..bea00e069e9a 100644
--- a/drivers/ufs/core/ufshcd.c
+++ b/drivers/ufs/core/ufshcd.c
@@ -2640,7 +2640,7 @@ static int ufshcd_map_sg(struct ufs_hba *hba, struct ufshcd_lrb *lrbp)
ufshcd_sgl_to_prdt(hba, lrbp, sg_segments, scsi_sglist(cmd));
- return 0;
+ return ufshcd_crypto_fill_prdt(hba, lrbp);
}
/**
@@ -5479,6 +5479,7 @@ void ufshcd_release_scsi_cmd(struct ufs_hba *hba,
struct scsi_cmnd *cmd = lrbp->cmd;
scsi_dma_unmap(cmd);
+ ufshcd_crypto_clear_prdt(hba, lrbp);
ufshcd_release(hba);
ufshcd_clk_scaling_update_busy(hba);
}
diff --git a/drivers/ufs/host/ufs-exynos.c b/drivers/ufs/host/ufs-exynos.c
index 88d125d1ee3c..16ad3528d80b 100644
--- a/drivers/ufs/host/ufs-exynos.c
+++ b/drivers/ufs/host/ufs-exynos.c
@@ -8,6 +8,9 @@
*
*/
+#include <asm/unaligned.h>
+#include <crypto/aes.h>
+#include <linux/arm-smccc.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/module.h>
@@ -25,12 +28,13 @@
#include "ufs-exynos.h"
+#define DATA_UNIT_SIZE 4096
+
/*
* Exynos's Vendor specific registers for UFSHCI
*/
#define HCI_TXPRDT_ENTRY_SIZE 0x00
#define PRDT_PREFECT_EN BIT(31)
-#define PRDT_SET_SIZE(x) ((x) & 0x1F)
#define HCI_RXPRDT_ENTRY_SIZE 0x04
#define HCI_1US_TO_CNT_VAL 0x0C
#define CNT_VAL_1US_MASK 0x3FF
@@ -1043,8 +1047,8 @@ static int exynos_ufs_post_link(struct ufs_hba *hba)
exynos_ufs_fit_aggr_timeout(ufs);
hci_writel(ufs, 0xa, HCI_DATA_REORDER);
- hci_writel(ufs, PRDT_SET_SIZE(12), HCI_TXPRDT_ENTRY_SIZE);
- hci_writel(ufs, PRDT_SET_SIZE(12), HCI_RXPRDT_ENTRY_SIZE);
+ hci_writel(ufs, ilog2(DATA_UNIT_SIZE), HCI_TXPRDT_ENTRY_SIZE);
+ hci_writel(ufs, ilog2(DATA_UNIT_SIZE), HCI_RXPRDT_ENTRY_SIZE);
hci_writel(ufs, (1 << hba->nutrs) - 1, HCI_UTRL_NEXUS_TYPE);
hci_writel(ufs, (1 << hba->nutmrs) - 1, HCI_UTMRL_NEXUS_TYPE);
hci_writel(ufs, 0xf, HCI_AXIDMA_RWDATA_BURST_LEN);
@@ -1151,6 +1155,227 @@ static inline void exynos_ufs_priv_init(struct ufs_hba *hba,
hba->quirks = ufs->drv_data->quirks;
}
+#ifdef CONFIG_SCSI_UFS_CRYPTO
+
+/*
+ * Support for Flash Memory Protector (FMP), which is the inline encryption
+ * hardware on Exynos and Exynos-based SoCs. The interface to this hardware is
+ * not compatible with the standard UFS crypto. It requires that encryption be
+ * configured in the PRDT using a nonstandard extension.
+ */
+
+enum fmp_crypto_algo_mode {
+ FMP_BYPASS_MODE = 0,
+ FMP_ALGO_MODE_AES_CBC = 1,
+ FMP_ALGO_MODE_AES_XTS = 2,
+};
+enum fmp_crypto_key_length {
+ FMP_KEYLEN_256BIT = 1,
+};
+
+/**
+ * struct fmp_sg_entry - nonstandard format of PRDT entries when FMP is enabled
+ *
+ * @base: The standard PRDT entry, but with nonstandard bitfields in the high
+ * bits of the 'size' field, i.e. the last 32-bit word. When these
+ * nonstandard bitfields are zero, the data segment won't be encrypted or
+ * decrypted. Otherwise they specify the algorithm and key length with
+ * which the data segment will be encrypted or decrypted.
+ * @file_iv: The initialization vector (IV) with all bytes reversed
+ * @file_enckey: The first half of the AES-XTS key with all bytes reserved
+ * @file_twkey: The second half of the AES-XTS key with all bytes reserved
+ * @disk_iv: Unused
+ * @reserved: Unused
+ */
+struct fmp_sg_entry {
+ struct ufshcd_sg_entry base;
+ __be64 file_iv[2];
+ __be64 file_enckey[4];
+ __be64 file_twkey[4];
+ __be64 disk_iv[2];
+ __be64 reserved[2];
+};
+
+#define SMC_CMD_FMP_SECURITY \
+ ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
+ ARM_SMCCC_OWNER_SIP, 0x1810)
+#define SMC_CMD_SMU \
+ ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
+ ARM_SMCCC_OWNER_SIP, 0x1850)
+#define SMC_CMD_FMP_SMU_RESUME \
+ ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
+ ARM_SMCCC_OWNER_SIP, 0x1860)
+#define SMU_EMBEDDED 0
+#define SMU_INIT 0
+#define CFG_DESCTYPE_3 3
+
+static void exynos_ufs_fmp_init(struct ufs_hba *hba, struct exynos_ufs *ufs)
+{
+ struct blk_crypto_profile *profile = &hba->crypto_profile;
+ struct arm_smccc_res res;
+ int err;
+
+ /*
+ * Check for the standard crypto support bit, since it's available even
+ * though the rest of the interface to FMP is nonstandard.
+ *
+ * This check should have the effect of preventing the driver from
+ * trying to use FMP on old Exynos SoCs that don't have FMP.
+ */
+ if (!(ufshcd_readl(hba, REG_CONTROLLER_CAPABILITIES) &
+ MASK_CRYPTO_SUPPORT))
+ return;
+
+ /*
+ * The below sequence of SMC calls to enable FMP can be found in the
+ * downstream driver source for gs101 and other Exynos-based SoCs. It
+ * is the only way to enable FMP that works on SoCs such as gs101 that
+ * don't make the FMP registers accessible to Linux. It probably works
+ * on other Exynos-based SoCs too, and might even still be the only way
+ * that works. But this hasn't been properly tested, and this code is
+ * mutually exclusive with exynos_ufs_config_smu(). So for now only
+ * enable FMP support on SoCs with EXYNOS_UFS_OPT_UFSPR_SECURE.
+ */
+ if (!(ufs->opts & EXYNOS_UFS_OPT_UFSPR_SECURE))
+ return;
+
+ /*
+ * This call (which sets DESCTYPE to 0x3 in the FMPSECURITY0 register)
+ * is needed to make the hardware use the larger PRDT entry size.
+ */
+ BUILD_BUG_ON(sizeof(struct fmp_sg_entry) != 128);
+ arm_smccc_smc(SMC_CMD_FMP_SECURITY, 0, SMU_EMBEDDED, CFG_DESCTYPE_3,
+ 0, 0, 0, 0, &res);
+ if (res.a0) {
+ dev_warn(hba->dev,
+ "SMC_CMD_FMP_SECURITY failed on init: %ld. Disabling FMP support.\n",
+ res.a0);
+ return;
+ }
+ ufshcd_set_sg_entry_size(hba, sizeof(struct fmp_sg_entry));
+
+ /*
+ * This is needed to initialize FMP. Without it, errors occur when
+ * inline encryption is used.
+ */
+ arm_smccc_smc(SMC_CMD_SMU, SMU_INIT, SMU_EMBEDDED, 0, 0, 0, 0, 0, &res);
+ if (res.a0) {
+ dev_err(hba->dev,
+ "SMC_CMD_SMU(SMU_INIT) failed: %ld. Disabling FMP support.\n",
+ res.a0);
+ return;
+ }
+
+ /* Advertise crypto capabilities to the block layer. */
+ err = devm_blk_crypto_profile_init(hba->dev, profile, 0);
+ if (err) {
+ /* Only ENOMEM should be possible here. */
+ dev_err(hba->dev, "Failed to initialize crypto profile: %d\n",
+ err);
+ return;
+ }
+ profile->max_dun_bytes_supported = AES_BLOCK_SIZE;
+ profile->dev = hba->dev;
+ profile->modes_supported[BLK_ENCRYPTION_MODE_AES_256_XTS] =
+ DATA_UNIT_SIZE;
+
+ /* Advertise crypto support to ufshcd-core. */
+ hba->caps |= UFSHCD_CAP_CRYPTO;
+
+ /* Advertise crypto quirks to ufshcd-core. */
+ hba->quirks |= UFSHCD_QUIRK_CUSTOM_CRYPTO_PROFILE |
+ UFSHCD_QUIRK_BROKEN_CRYPTO_ENABLE |
+ UFSHCD_QUIRK_KEYS_IN_PRDT;
+
+}
+
+static void exynos_ufs_fmp_resume(struct ufs_hba *hba)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_smc(SMC_CMD_FMP_SECURITY, 0, SMU_EMBEDDED, CFG_DESCTYPE_3,
+ 0, 0, 0, 0, &res);
+ if (res.a0)
+ dev_err(hba->dev,
+ "SMC_CMD_FMP_SECURITY failed on resume: %ld\n", res.a0);
+
+ arm_smccc_smc(SMC_CMD_FMP_SMU_RESUME, 0, SMU_EMBEDDED, 0, 0, 0, 0, 0,
+ &res);
+ if (res.a0)
+ dev_err(hba->dev,
+ "SMC_CMD_FMP_SMU_RESUME failed: %ld\n", res.a0);
+}
+
+static inline __be64 fmp_key_word(const u8 *key, int j)
+{
+ return cpu_to_be64(get_unaligned_le64(
+ key + AES_KEYSIZE_256 - (j + 1) * sizeof(u64)));
+}
+
+/* Fill the PRDT for a request according to the given encryption context. */
+static int exynos_ufs_fmp_fill_prdt(struct ufs_hba *hba,
+ const struct bio_crypt_ctx *crypt_ctx,
+ void *prdt, unsigned int num_segments)
+{
+ struct fmp_sg_entry *fmp_prdt = prdt;
+ const u8 *enckey = crypt_ctx->bc_key->raw;
+ const u8 *twkey = enckey + AES_KEYSIZE_256;
+ u64 dun_lo = crypt_ctx->bc_dun[0];
+ u64 dun_hi = crypt_ctx->bc_dun[1];
+ unsigned int i;
+
+ /* If FMP wasn't enabled, we shouldn't get any encrypted requests. */
+ if (WARN_ON_ONCE(!(hba->caps & UFSHCD_CAP_CRYPTO)))
+ return -EIO;
+
+ /* Configure FMP on each segment of the request. */
+ for (i = 0; i < num_segments; i++) {
+ struct fmp_sg_entry *prd = &fmp_prdt[i];
+ int j;
+
+ /* Each segment must be exactly one data unit. */
+ if (prd->base.size != cpu_to_le32(DATA_UNIT_SIZE - 1)) {
+ dev_err(hba->dev,
+ "data segment is misaligned for FMP\n");
+ return -EIO;
+ }
+
+ /* Set the algorithm and key length. */
+ prd->base.size |= cpu_to_le32((FMP_ALGO_MODE_AES_XTS << 28) |
+ (FMP_KEYLEN_256BIT << 26));
+
+ /* Set the IV. */
+ prd->file_iv[0] = cpu_to_be64(dun_hi);
+ prd->file_iv[1] = cpu_to_be64(dun_lo);
+
+ /* Set the key. */
+ for (j = 0; j < AES_KEYSIZE_256 / sizeof(u64); j++) {
+ prd->file_enckey[j] = fmp_key_word(enckey, j);
+ prd->file_twkey[j] = fmp_key_word(twkey, j);
+ }
+
+ /* Increment the data unit number. */
+ dun_lo++;
+ if (dun_lo == 0)
+ dun_hi++;
+ }
+ return 0;
+}
+
+#else /* CONFIG_SCSI_UFS_CRYPTO */
+
+static void exynos_ufs_fmp_init(struct ufs_hba *hba, struct exynos_ufs *ufs)
+{
+}
+
+static void exynos_ufs_fmp_resume(struct ufs_hba *hba)
+{
+}
+
+#define exynos_ufs_fmp_fill_prdt NULL
+
+#endif /* !CONFIG_SCSI_UFS_CRYPTO */
+
static int exynos_ufs_init(struct ufs_hba *hba)
{
struct device *dev = hba->dev;
@@ -1198,6 +1423,8 @@ static int exynos_ufs_init(struct ufs_hba *hba)
exynos_ufs_priv_init(hba, ufs);
+ exynos_ufs_fmp_init(hba, ufs);
+
if (ufs->drv_data->drv_init) {
ret = ufs->drv_data->drv_init(dev, ufs);
if (ret) {
@@ -1213,7 +1440,7 @@ static int exynos_ufs_init(struct ufs_hba *hba)
if (!(ufs->opts & EXYNOS_UFS_OPT_UFSPR_SECURE))
exynos_ufs_config_smu(ufs);
- hba->host->dma_alignment = SZ_4K - 1;
+ hba->host->dma_alignment = DATA_UNIT_SIZE - 1;
return 0;
out:
@@ -1332,7 +1559,7 @@ static int exynos_ufs_hce_enable_notify(struct ufs_hba *hba,
* (ufshcd_async_scan()). Note: this callback may also be called
* from other functions than ufshcd_init().
*/
- hba->host->max_segment_size = SZ_4K;
+ hba->host->max_segment_size = DATA_UNIT_SIZE;
if (ufs->drv_data->pre_hce_enable) {
ret = ufs->drv_data->pre_hce_enable(ufs);
@@ -1432,7 +1659,7 @@ static int exynos_ufs_resume(struct ufs_hba *hba, enum ufs_pm_op pm_op)
phy_power_on(ufs->phy);
exynos_ufs_config_smu(ufs);
-
+ exynos_ufs_fmp_resume(hba);
return 0;
}
@@ -1698,6 +1925,7 @@ static const struct ufs_hba_variant_ops ufs_hba_exynos_ops = {
.hibern8_notify = exynos_ufs_hibern8_notify,
.suspend = exynos_ufs_suspend,
.resume = exynos_ufs_resume,
+ .fill_crypto_prdt = exynos_ufs_fmp_fill_prdt,
};
static struct ufs_hba_variant_ops ufs_hba_exynosauto_vh_ops = {