Skip to content

Commit c96499f

Browse files
ebiggersmartinkpetersen
authored andcommitted
scsi: ufs: exynos: Add support for Flash Memory Protector (FMP)
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. Signed-off-by: Eric Biggers <ebiggers@google.com> Link: https://lore.kernel.org/r/20240708235330.103590-7-ebiggers@kernel.org Reviewed-by: Peter Griffin <peter.griffin@linaro.org> Tested-by: Peter Griffin <peter.griffin@linaro.org> Reviewed-by: Alim Akhtar <alim.akhtar@samsung.com> Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
1 parent 4c45dba commit c96499f

File tree

1 file changed

+234
-6
lines changed

1 file changed

+234
-6
lines changed

drivers/ufs/host/ufs-exynos.c

Lines changed: 234 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
*
99
*/
1010

11+
#include <asm/unaligned.h>
12+
#include <crypto/aes.h>
13+
#include <linux/arm-smccc.h>
1114
#include <linux/clk.h>
1215
#include <linux/delay.h>
1316
#include <linux/module.h>
@@ -25,12 +28,13 @@
2528

2629
#include "ufs-exynos.h"
2730

31+
#define DATA_UNIT_SIZE 4096
32+
2833
/*
2934
* Exynos's Vendor specific registers for UFSHCI
3035
*/
3136
#define HCI_TXPRDT_ENTRY_SIZE 0x00
3237
#define PRDT_PREFECT_EN BIT(31)
33-
#define PRDT_SET_SIZE(x) ((x) & 0x1F)
3438
#define HCI_RXPRDT_ENTRY_SIZE 0x04
3539
#define HCI_1US_TO_CNT_VAL 0x0C
3640
#define CNT_VAL_1US_MASK 0x3FF
@@ -1043,8 +1047,8 @@ static int exynos_ufs_post_link(struct ufs_hba *hba)
10431047
exynos_ufs_fit_aggr_timeout(ufs);
10441048

10451049
hci_writel(ufs, 0xa, HCI_DATA_REORDER);
1046-
hci_writel(ufs, PRDT_SET_SIZE(12), HCI_TXPRDT_ENTRY_SIZE);
1047-
hci_writel(ufs, PRDT_SET_SIZE(12), HCI_RXPRDT_ENTRY_SIZE);
1050+
hci_writel(ufs, ilog2(DATA_UNIT_SIZE), HCI_TXPRDT_ENTRY_SIZE);
1051+
hci_writel(ufs, ilog2(DATA_UNIT_SIZE), HCI_RXPRDT_ENTRY_SIZE);
10481052
hci_writel(ufs, (1 << hba->nutrs) - 1, HCI_UTRL_NEXUS_TYPE);
10491053
hci_writel(ufs, (1 << hba->nutmrs) - 1, HCI_UTMRL_NEXUS_TYPE);
10501054
hci_writel(ufs, 0xf, HCI_AXIDMA_RWDATA_BURST_LEN);
@@ -1151,6 +1155,227 @@ static inline void exynos_ufs_priv_init(struct ufs_hba *hba,
11511155
hba->quirks = ufs->drv_data->quirks;
11521156
}
11531157

1158+
#ifdef CONFIG_SCSI_UFS_CRYPTO
1159+
1160+
/*
1161+
* Support for Flash Memory Protector (FMP), which is the inline encryption
1162+
* hardware on Exynos and Exynos-based SoCs. The interface to this hardware is
1163+
* not compatible with the standard UFS crypto. It requires that encryption be
1164+
* configured in the PRDT using a nonstandard extension.
1165+
*/
1166+
1167+
enum fmp_crypto_algo_mode {
1168+
FMP_BYPASS_MODE = 0,
1169+
FMP_ALGO_MODE_AES_CBC = 1,
1170+
FMP_ALGO_MODE_AES_XTS = 2,
1171+
};
1172+
enum fmp_crypto_key_length {
1173+
FMP_KEYLEN_256BIT = 1,
1174+
};
1175+
1176+
/**
1177+
* struct fmp_sg_entry - nonstandard format of PRDT entries when FMP is enabled
1178+
*
1179+
* @base: The standard PRDT entry, but with nonstandard bitfields in the high
1180+
* bits of the 'size' field, i.e. the last 32-bit word. When these
1181+
* nonstandard bitfields are zero, the data segment won't be encrypted or
1182+
* decrypted. Otherwise they specify the algorithm and key length with
1183+
* which the data segment will be encrypted or decrypted.
1184+
* @file_iv: The initialization vector (IV) with all bytes reversed
1185+
* @file_enckey: The first half of the AES-XTS key with all bytes reserved
1186+
* @file_twkey: The second half of the AES-XTS key with all bytes reserved
1187+
* @disk_iv: Unused
1188+
* @reserved: Unused
1189+
*/
1190+
struct fmp_sg_entry {
1191+
struct ufshcd_sg_entry base;
1192+
__be64 file_iv[2];
1193+
__be64 file_enckey[4];
1194+
__be64 file_twkey[4];
1195+
__be64 disk_iv[2];
1196+
__be64 reserved[2];
1197+
};
1198+
1199+
#define SMC_CMD_FMP_SECURITY \
1200+
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
1201+
ARM_SMCCC_OWNER_SIP, 0x1810)
1202+
#define SMC_CMD_SMU \
1203+
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
1204+
ARM_SMCCC_OWNER_SIP, 0x1850)
1205+
#define SMC_CMD_FMP_SMU_RESUME \
1206+
ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64, \
1207+
ARM_SMCCC_OWNER_SIP, 0x1860)
1208+
#define SMU_EMBEDDED 0
1209+
#define SMU_INIT 0
1210+
#define CFG_DESCTYPE_3 3
1211+
1212+
static void exynos_ufs_fmp_init(struct ufs_hba *hba, struct exynos_ufs *ufs)
1213+
{
1214+
struct blk_crypto_profile *profile = &hba->crypto_profile;
1215+
struct arm_smccc_res res;
1216+
int err;
1217+
1218+
/*
1219+
* Check for the standard crypto support bit, since it's available even
1220+
* though the rest of the interface to FMP is nonstandard.
1221+
*
1222+
* This check should have the effect of preventing the driver from
1223+
* trying to use FMP on old Exynos SoCs that don't have FMP.
1224+
*/
1225+
if (!(ufshcd_readl(hba, REG_CONTROLLER_CAPABILITIES) &
1226+
MASK_CRYPTO_SUPPORT))
1227+
return;
1228+
1229+
/*
1230+
* The below sequence of SMC calls to enable FMP can be found in the
1231+
* downstream driver source for gs101 and other Exynos-based SoCs. It
1232+
* is the only way to enable FMP that works on SoCs such as gs101 that
1233+
* don't make the FMP registers accessible to Linux. It probably works
1234+
* on other Exynos-based SoCs too, and might even still be the only way
1235+
* that works. But this hasn't been properly tested, and this code is
1236+
* mutually exclusive with exynos_ufs_config_smu(). So for now only
1237+
* enable FMP support on SoCs with EXYNOS_UFS_OPT_UFSPR_SECURE.
1238+
*/
1239+
if (!(ufs->opts & EXYNOS_UFS_OPT_UFSPR_SECURE))
1240+
return;
1241+
1242+
/*
1243+
* This call (which sets DESCTYPE to 0x3 in the FMPSECURITY0 register)
1244+
* is needed to make the hardware use the larger PRDT entry size.
1245+
*/
1246+
BUILD_BUG_ON(sizeof(struct fmp_sg_entry) != 128);
1247+
arm_smccc_smc(SMC_CMD_FMP_SECURITY, 0, SMU_EMBEDDED, CFG_DESCTYPE_3,
1248+
0, 0, 0, 0, &res);
1249+
if (res.a0) {
1250+
dev_warn(hba->dev,
1251+
"SMC_CMD_FMP_SECURITY failed on init: %ld. Disabling FMP support.\n",
1252+
res.a0);
1253+
return;
1254+
}
1255+
ufshcd_set_sg_entry_size(hba, sizeof(struct fmp_sg_entry));
1256+
1257+
/*
1258+
* This is needed to initialize FMP. Without it, errors occur when
1259+
* inline encryption is used.
1260+
*/
1261+
arm_smccc_smc(SMC_CMD_SMU, SMU_INIT, SMU_EMBEDDED, 0, 0, 0, 0, 0, &res);
1262+
if (res.a0) {
1263+
dev_err(hba->dev,
1264+
"SMC_CMD_SMU(SMU_INIT) failed: %ld. Disabling FMP support.\n",
1265+
res.a0);
1266+
return;
1267+
}
1268+
1269+
/* Advertise crypto capabilities to the block layer. */
1270+
err = devm_blk_crypto_profile_init(hba->dev, profile, 0);
1271+
if (err) {
1272+
/* Only ENOMEM should be possible here. */
1273+
dev_err(hba->dev, "Failed to initialize crypto profile: %d\n",
1274+
err);
1275+
return;
1276+
}
1277+
profile->max_dun_bytes_supported = AES_BLOCK_SIZE;
1278+
profile->dev = hba->dev;
1279+
profile->modes_supported[BLK_ENCRYPTION_MODE_AES_256_XTS] =
1280+
DATA_UNIT_SIZE;
1281+
1282+
/* Advertise crypto support to ufshcd-core. */
1283+
hba->caps |= UFSHCD_CAP_CRYPTO;
1284+
1285+
/* Advertise crypto quirks to ufshcd-core. */
1286+
hba->quirks |= UFSHCD_QUIRK_CUSTOM_CRYPTO_PROFILE |
1287+
UFSHCD_QUIRK_BROKEN_CRYPTO_ENABLE |
1288+
UFSHCD_QUIRK_KEYS_IN_PRDT;
1289+
1290+
}
1291+
1292+
static void exynos_ufs_fmp_resume(struct ufs_hba *hba)
1293+
{
1294+
struct arm_smccc_res res;
1295+
1296+
arm_smccc_smc(SMC_CMD_FMP_SECURITY, 0, SMU_EMBEDDED, CFG_DESCTYPE_3,
1297+
0, 0, 0, 0, &res);
1298+
if (res.a0)
1299+
dev_err(hba->dev,
1300+
"SMC_CMD_FMP_SECURITY failed on resume: %ld\n", res.a0);
1301+
1302+
arm_smccc_smc(SMC_CMD_FMP_SMU_RESUME, 0, SMU_EMBEDDED, 0, 0, 0, 0, 0,
1303+
&res);
1304+
if (res.a0)
1305+
dev_err(hba->dev,
1306+
"SMC_CMD_FMP_SMU_RESUME failed: %ld\n", res.a0);
1307+
}
1308+
1309+
static inline __be64 fmp_key_word(const u8 *key, int j)
1310+
{
1311+
return cpu_to_be64(get_unaligned_le64(
1312+
key + AES_KEYSIZE_256 - (j + 1) * sizeof(u64)));
1313+
}
1314+
1315+
/* Fill the PRDT for a request according to the given encryption context. */
1316+
static int exynos_ufs_fmp_fill_prdt(struct ufs_hba *hba,
1317+
const struct bio_crypt_ctx *crypt_ctx,
1318+
void *prdt, unsigned int num_segments)
1319+
{
1320+
struct fmp_sg_entry *fmp_prdt = prdt;
1321+
const u8 *enckey = crypt_ctx->bc_key->raw;
1322+
const u8 *twkey = enckey + AES_KEYSIZE_256;
1323+
u64 dun_lo = crypt_ctx->bc_dun[0];
1324+
u64 dun_hi = crypt_ctx->bc_dun[1];
1325+
unsigned int i;
1326+
1327+
/* If FMP wasn't enabled, we shouldn't get any encrypted requests. */
1328+
if (WARN_ON_ONCE(!(hba->caps & UFSHCD_CAP_CRYPTO)))
1329+
return -EIO;
1330+
1331+
/* Configure FMP on each segment of the request. */
1332+
for (i = 0; i < num_segments; i++) {
1333+
struct fmp_sg_entry *prd = &fmp_prdt[i];
1334+
int j;
1335+
1336+
/* Each segment must be exactly one data unit. */
1337+
if (prd->base.size != cpu_to_le32(DATA_UNIT_SIZE - 1)) {
1338+
dev_err(hba->dev,
1339+
"data segment is misaligned for FMP\n");
1340+
return -EIO;
1341+
}
1342+
1343+
/* Set the algorithm and key length. */
1344+
prd->base.size |= cpu_to_le32((FMP_ALGO_MODE_AES_XTS << 28) |
1345+
(FMP_KEYLEN_256BIT << 26));
1346+
1347+
/* Set the IV. */
1348+
prd->file_iv[0] = cpu_to_be64(dun_hi);
1349+
prd->file_iv[1] = cpu_to_be64(dun_lo);
1350+
1351+
/* Set the key. */
1352+
for (j = 0; j < AES_KEYSIZE_256 / sizeof(u64); j++) {
1353+
prd->file_enckey[j] = fmp_key_word(enckey, j);
1354+
prd->file_twkey[j] = fmp_key_word(twkey, j);
1355+
}
1356+
1357+
/* Increment the data unit number. */
1358+
dun_lo++;
1359+
if (dun_lo == 0)
1360+
dun_hi++;
1361+
}
1362+
return 0;
1363+
}
1364+
1365+
#else /* CONFIG_SCSI_UFS_CRYPTO */
1366+
1367+
static void exynos_ufs_fmp_init(struct ufs_hba *hba, struct exynos_ufs *ufs)
1368+
{
1369+
}
1370+
1371+
static void exynos_ufs_fmp_resume(struct ufs_hba *hba)
1372+
{
1373+
}
1374+
1375+
#define exynos_ufs_fmp_fill_prdt NULL
1376+
1377+
#endif /* !CONFIG_SCSI_UFS_CRYPTO */
1378+
11541379
static int exynos_ufs_init(struct ufs_hba *hba)
11551380
{
11561381
struct device *dev = hba->dev;
@@ -1198,6 +1423,8 @@ static int exynos_ufs_init(struct ufs_hba *hba)
11981423

11991424
exynos_ufs_priv_init(hba, ufs);
12001425

1426+
exynos_ufs_fmp_init(hba, ufs);
1427+
12011428
if (ufs->drv_data->drv_init) {
12021429
ret = ufs->drv_data->drv_init(dev, ufs);
12031430
if (ret) {
@@ -1213,7 +1440,7 @@ static int exynos_ufs_init(struct ufs_hba *hba)
12131440
if (!(ufs->opts & EXYNOS_UFS_OPT_UFSPR_SECURE))
12141441
exynos_ufs_config_smu(ufs);
12151442

1216-
hba->host->dma_alignment = SZ_4K - 1;
1443+
hba->host->dma_alignment = DATA_UNIT_SIZE - 1;
12171444
return 0;
12181445

12191446
out:
@@ -1332,7 +1559,7 @@ static int exynos_ufs_hce_enable_notify(struct ufs_hba *hba,
13321559
* (ufshcd_async_scan()). Note: this callback may also be called
13331560
* from other functions than ufshcd_init().
13341561
*/
1335-
hba->host->max_segment_size = SZ_4K;
1562+
hba->host->max_segment_size = DATA_UNIT_SIZE;
13361563

13371564
if (ufs->drv_data->pre_hce_enable) {
13381565
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)
14321659
phy_power_on(ufs->phy);
14331660

14341661
exynos_ufs_config_smu(ufs);
1435-
1662+
exynos_ufs_fmp_resume(hba);
14361663
return 0;
14371664
}
14381665

@@ -1698,6 +1925,7 @@ static const struct ufs_hba_variant_ops ufs_hba_exynos_ops = {
16981925
.hibern8_notify = exynos_ufs_hibern8_notify,
16991926
.suspend = exynos_ufs_suspend,
17001927
.resume = exynos_ufs_resume,
1928+
.fill_crypto_prdt = exynos_ufs_fmp_fill_prdt,
17011929
};
17021930

17031931
static struct ufs_hba_variant_ops ufs_hba_exynosauto_vh_ops = {

0 commit comments

Comments
 (0)