| // SPDX-License-Identifier: GPL-2.0-only | |
| // SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| /* | |
| * Crypto driver file to manage keys of NVIDIA Security Engine. | |
| */ | |
| #include <linux/bitops.h> | |
| #include <linux/module.h> | |
| #include <crypto/aes.h> | |
| #include "tegra-se.h" | |
| #define SE_KEY_FULL_MASK GENMASK(SE_MAX_KEYSLOT, 0) | |
| /* Reserve keyslot 0, 14, 15 */ | |
| #define SE_KEY_RSVD_MASK (BIT(0) | BIT(14) | BIT(15)) | |
| #define SE_KEY_VALID_MASK (SE_KEY_FULL_MASK & ~SE_KEY_RSVD_MASK) | |
| /* Mutex lock to guard keyslots */ | |
| static DEFINE_MUTEX(kslt_lock); | |
| /* Keyslot bitmask (0 = available, 1 = in use/not available) */ | |
| static u16 tegra_se_keyslots = SE_KEY_RSVD_MASK; | |
| static u16 tegra_keyslot_alloc(void) | |
| { | |
| u16 keyid; | |
| mutex_lock(&kslt_lock); | |
| /* Check if all key slots are full */ | |
| if (tegra_se_keyslots == GENMASK(SE_MAX_KEYSLOT, 0)) { | |
| mutex_unlock(&kslt_lock); | |
| return 0; | |
| } | |
| keyid = ffz(tegra_se_keyslots); | |
| tegra_se_keyslots |= BIT(keyid); | |
| mutex_unlock(&kslt_lock); | |
| return keyid; | |
| } | |
| static void tegra_keyslot_free(u16 slot) | |
| { | |
| mutex_lock(&kslt_lock); | |
| tegra_se_keyslots &= ~(BIT(slot)); | |
| mutex_unlock(&kslt_lock); | |
| } | |
| static unsigned int tegra_key_prep_ins_cmd(struct tegra_se *se, u32 *cpuvaddr, | |
| const u32 *key, u32 keylen, u16 slot, u32 alg) | |
| { | |
| int i = 0, j; | |
| cpuvaddr[i++] = host1x_opcode_setpayload(1); | |
| cpuvaddr[i++] = se_host1x_opcode_incr_w(se->hw->regs->op); | |
| cpuvaddr[i++] = SE_AES_OP_WRSTALL | SE_AES_OP_DUMMY; | |
| cpuvaddr[i++] = host1x_opcode_setpayload(1); | |
| cpuvaddr[i++] = se_host1x_opcode_incr_w(se->hw->regs->manifest); | |
| cpuvaddr[i++] = se->manifest(se->owner, alg, keylen); | |
| cpuvaddr[i++] = host1x_opcode_setpayload(1); | |
| cpuvaddr[i++] = se_host1x_opcode_incr_w(se->hw->regs->key_dst); | |
| cpuvaddr[i++] = SE_AES_KEY_DST_INDEX(slot); | |
| for (j = 0; j < keylen / 4; j++) { | |
| /* Set key address */ | |
| cpuvaddr[i++] = host1x_opcode_setpayload(1); | |
| cpuvaddr[i++] = se_host1x_opcode_incr_w(se->hw->regs->key_addr); | |
| cpuvaddr[i++] = j; | |
| /* Set key data */ | |
| cpuvaddr[i++] = host1x_opcode_setpayload(1); | |
| cpuvaddr[i++] = se_host1x_opcode_incr_w(se->hw->regs->key_data); | |
| cpuvaddr[i++] = key[j]; | |
| } | |
| cpuvaddr[i++] = host1x_opcode_setpayload(1); | |
| cpuvaddr[i++] = se_host1x_opcode_incr_w(se->hw->regs->config); | |
| cpuvaddr[i++] = SE_CFG_INS; | |
| cpuvaddr[i++] = host1x_opcode_setpayload(1); | |
| cpuvaddr[i++] = se_host1x_opcode_incr_w(se->hw->regs->op); | |
| cpuvaddr[i++] = SE_AES_OP_WRSTALL | SE_AES_OP_START | | |
| SE_AES_OP_LASTBUF; | |
| cpuvaddr[i++] = se_host1x_opcode_nonincr(host1x_uclass_incr_syncpt_r(), 1); | |
| cpuvaddr[i++] = host1x_uclass_incr_syncpt_cond_f(1) | | |
| host1x_uclass_incr_syncpt_indx_f(se->syncpt_id); | |
| dev_dbg(se->dev, "key-slot %u key-manifest %#x\n", | |
| slot, se->manifest(se->owner, alg, keylen)); | |
| return i; | |
| } | |
| static bool tegra_key_in_kslt(u32 keyid) | |
| { | |
| bool ret; | |
| if (keyid > SE_MAX_KEYSLOT) | |
| return false; | |
| mutex_lock(&kslt_lock); | |
| ret = ((BIT(keyid) & SE_KEY_VALID_MASK) && | |
| (BIT(keyid) & tegra_se_keyslots)); | |
| mutex_unlock(&kslt_lock); | |
| return ret; | |
| } | |
| static int tegra_key_insert(struct tegra_se *se, const u8 *key, | |
| u32 keylen, u16 slot, u32 alg) | |
| { | |
| const u32 *keyval = (u32 *)key; | |
| u32 *addr = se->keybuf->addr, size; | |
| int ret; | |
| mutex_lock(&kslt_lock); | |
| size = tegra_key_prep_ins_cmd(se, addr, keyval, keylen, slot, alg); | |
| ret = tegra_se_host1x_submit(se, se->keybuf, size); | |
| mutex_unlock(&kslt_lock); | |
| return ret; | |
| } | |
| void tegra_key_invalidate(struct tegra_se *se, u32 keyid, u32 alg) | |
| { | |
| u8 zkey[AES_MAX_KEY_SIZE] = {0}; | |
| if (!keyid) | |
| return; | |
| /* Overwrite the key with 0s */ | |
| tegra_key_insert(se, zkey, AES_MAX_KEY_SIZE, keyid, alg); | |
| tegra_keyslot_free(keyid); | |
| } | |
| void tegra_key_invalidate_reserved(struct tegra_se *se, u32 keyid, u32 alg) | |
| { | |
| u8 zkey[AES_MAX_KEY_SIZE] = {0}; | |
| if (!keyid) | |
| return; | |
| /* Overwrite the key with 0s */ | |
| tegra_key_insert(se, zkey, AES_MAX_KEY_SIZE, keyid, alg); | |
| } | |
| inline int tegra_key_submit_reserved(struct tegra_se *se, const u8 *key, | |
| u32 keylen, u32 alg, u32 *keyid) | |
| { | |
| return tegra_key_insert(se, key, keylen, *keyid, alg); | |
| } | |
| int tegra_key_submit(struct tegra_se *se, const u8 *key, u32 keylen, u32 alg, u32 *keyid) | |
| { | |
| int ret; | |
| /* Use the existing slot if it is already allocated */ | |
| if (!tegra_key_in_kslt(*keyid)) { | |
| *keyid = tegra_keyslot_alloc(); | |
| if (!(*keyid)) { | |
| dev_dbg(se->dev, "failed to allocate key slot\n"); | |
| return -ENOMEM; | |
| } | |
| } | |
| ret = tegra_key_insert(se, key, keylen, *keyid, alg); | |
| if (ret) | |
| return ret; | |
| return 0; | |
| } |