mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 17:25:35 +03:00
Add the updated Tegra SE driver with the below improvements. - Remove dependency with nvhost apis and use upstream Host1x APIs. - Implement software fallbacks for keys/plaintexts unsupported by hardware. - Support only T234 and later. Prior chips will use tegra-se-nvhost driver. Bug 4221414 Bug 3579794 Signed-off-by: Akhil R <akhilrajeev@nvidia.com> Change-Id: I398a5b7cc3f752b44d01d6d1c81f813f862e4cd9 Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2977810 Tested-by: mobile promotions <svcmobile_promotions@nvidia.com> Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
490 lines
11 KiB
C
490 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
// SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
/*
|
|
* Crypto driver for NVIDIA Security Engine in Tegra Chips
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/host1x-next.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/mod_devicetable.h>
|
|
|
|
#include <crypto/engine.h>
|
|
|
|
#include "tegra-se.h"
|
|
|
|
static struct host1x_bo *tegra_se_cmdbuf_get(struct host1x_bo *host_bo)
|
|
{
|
|
struct tegra_se_cmdbuf *cmdbuf = container_of(host_bo, struct tegra_se_cmdbuf, bo);
|
|
|
|
kref_get(&cmdbuf->ref);
|
|
|
|
return host_bo;
|
|
}
|
|
|
|
static void tegra_se_cmdbuf_release(struct kref *ref)
|
|
{
|
|
struct tegra_se_cmdbuf *cmdbuf = container_of(ref, struct tegra_se_cmdbuf, ref);
|
|
|
|
dma_free_attrs(cmdbuf->dev, cmdbuf->size, cmdbuf->addr,
|
|
cmdbuf->iova, 0);
|
|
|
|
kfree(cmdbuf);
|
|
}
|
|
|
|
static void tegra_se_cmdbuf_put(struct host1x_bo *host_bo)
|
|
{
|
|
struct tegra_se_cmdbuf *cmdbuf = container_of(host_bo, struct tegra_se_cmdbuf, bo);
|
|
|
|
kref_put(&cmdbuf->ref, tegra_se_cmdbuf_release);
|
|
}
|
|
|
|
static struct host1x_bo_mapping *
|
|
tegra_se_cmdbuf_pin(struct device *dev, struct host1x_bo *bo, enum dma_data_direction direction)
|
|
{
|
|
struct tegra_se_cmdbuf *cmdbuf = container_of(bo, struct tegra_se_cmdbuf, bo);
|
|
struct host1x_bo_mapping *map;
|
|
int err;
|
|
|
|
map = kzalloc(sizeof(*map), GFP_KERNEL);
|
|
if (!map)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
kref_init(&map->ref);
|
|
map->bo = host1x_bo_get(bo);
|
|
map->direction = direction;
|
|
map->dev = dev;
|
|
|
|
map->sgt = kzalloc(sizeof(*map->sgt), GFP_KERNEL);
|
|
if (!map->sgt) {
|
|
err = -ENOMEM;
|
|
goto free;
|
|
}
|
|
|
|
err = dma_get_sgtable(dev, map->sgt, cmdbuf->addr,
|
|
cmdbuf->iova, cmdbuf->words * 4);
|
|
if (err)
|
|
goto free_sgt;
|
|
|
|
err = dma_map_sgtable(dev, map->sgt, direction, 0);
|
|
if (err)
|
|
goto free_sgt;
|
|
|
|
map->phys = sg_dma_address(map->sgt->sgl);
|
|
map->size = cmdbuf->words * 4;
|
|
map->chunks = err;
|
|
|
|
return map;
|
|
|
|
free_sgt:
|
|
sg_free_table(map->sgt);
|
|
kfree(map->sgt);
|
|
free:
|
|
kfree(map);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
static void tegra_se_cmdbuf_unpin(struct host1x_bo_mapping *map)
|
|
{
|
|
if (!map)
|
|
return;
|
|
|
|
dma_unmap_sgtable(map->dev, map->sgt, map->direction, 0);
|
|
sg_free_table(map->sgt);
|
|
kfree(map->sgt);
|
|
host1x_bo_put(map->bo);
|
|
|
|
kfree(map);
|
|
}
|
|
|
|
static void *tegra_se_cmdbuf_mmap(struct host1x_bo *host_bo)
|
|
{
|
|
struct tegra_se_cmdbuf *cmdbuf = container_of(host_bo, struct tegra_se_cmdbuf, bo);
|
|
|
|
return cmdbuf->addr;
|
|
}
|
|
|
|
static void tegra_se_cmdbuf_munmap(struct host1x_bo *host_bo, void *addr)
|
|
{
|
|
}
|
|
|
|
static const struct host1x_bo_ops tegra_se_cmdbuf_ops = {
|
|
.get = tegra_se_cmdbuf_get,
|
|
.put = tegra_se_cmdbuf_put,
|
|
.pin = tegra_se_cmdbuf_pin,
|
|
.unpin = tegra_se_cmdbuf_unpin,
|
|
.mmap = tegra_se_cmdbuf_mmap,
|
|
.munmap = tegra_se_cmdbuf_munmap,
|
|
};
|
|
|
|
static struct tegra_se_cmdbuf *tegra_se_host1x_bo_alloc(struct tegra_se *se, ssize_t size)
|
|
{
|
|
struct tegra_se_cmdbuf *cmdbuf;
|
|
struct device *dev = se->dev->parent;
|
|
|
|
cmdbuf = kzalloc(sizeof(struct tegra_se_cmdbuf), GFP_KERNEL);
|
|
if (!cmdbuf)
|
|
return NULL;
|
|
|
|
cmdbuf->addr = dma_alloc_attrs(dev, size, &cmdbuf->iova,
|
|
GFP_KERNEL, 0);
|
|
if (!cmdbuf->addr)
|
|
return NULL;
|
|
|
|
cmdbuf->size = size;
|
|
cmdbuf->dev = dev;
|
|
|
|
host1x_bo_init(&cmdbuf->bo, &tegra_se_cmdbuf_ops);
|
|
kref_init(&cmdbuf->ref);
|
|
|
|
return cmdbuf;
|
|
}
|
|
|
|
int tegra_se_host1x_submit(struct tegra_se *se, u32 size)
|
|
{
|
|
struct host1x_job *job;
|
|
int ret;
|
|
|
|
job = host1x_job_alloc(se->channel, 1, 0, true);
|
|
if (!job) {
|
|
dev_err(se->dev, "failed to allocate host1x job\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
job->syncpt = host1x_syncpt_get(se->syncpt);
|
|
job->syncpt_incrs = 1;
|
|
job->client = &se->client;
|
|
job->class = se->client.class;
|
|
job->serialize = true;
|
|
job->engine_fallback_streamid = se->stream_id;
|
|
job->engine_streamid_offset = SE_STREAM_ID;
|
|
|
|
se->cmdbuf->words = size;
|
|
|
|
host1x_job_add_gather(job, &se->cmdbuf->bo, size, 0);
|
|
|
|
ret = host1x_job_pin(job, se->dev);
|
|
if (ret) {
|
|
dev_err(se->dev, "failed to pin host1x job\n");
|
|
goto err_job_pin;
|
|
}
|
|
|
|
ret = host1x_job_submit(job);
|
|
if (ret) {
|
|
dev_err(se->dev, "failed to submit host1x job\n");
|
|
goto err_job_submit;
|
|
}
|
|
|
|
ret = host1x_syncpt_wait(job->syncpt, job->syncpt_end,
|
|
MAX_SCHEDULE_TIMEOUT, NULL);
|
|
if (ret) {
|
|
dev_err(se->dev, "host1x job timed out\n");
|
|
return ret;
|
|
}
|
|
|
|
host1x_job_put(job);
|
|
return 0;
|
|
|
|
err_job_submit:
|
|
host1x_job_unpin(job);
|
|
err_job_pin:
|
|
host1x_job_put(job);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_se_client_init(struct host1x_client *client)
|
|
{
|
|
struct tegra_se *se = container_of(client, struct tegra_se, client);
|
|
int ret;
|
|
|
|
se->channel = host1x_channel_request(&se->client);
|
|
if (!se->channel) {
|
|
dev_err(se->dev, "host1x channel map failed\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
se->syncpt = host1x_syncpt_request(&se->client, 0);
|
|
if (!se->syncpt) {
|
|
dev_err(se->dev, "host1x syncpt allocation failed\n");
|
|
ret = -EINVAL;
|
|
goto err_syncpt;
|
|
}
|
|
|
|
se->syncpt_id = host1x_syncpt_id(se->syncpt);
|
|
|
|
se->cmdbuf = tegra_se_host1x_bo_alloc(se, SZ_4K);
|
|
if (!se->cmdbuf) {
|
|
ret = -ENOMEM;
|
|
goto err_bo;
|
|
}
|
|
|
|
ret = se->hw->init_alg(se);
|
|
if (ret) {
|
|
dev_err(se->dev, "failed to register algorithms\n");
|
|
goto err_alg_reg;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_alg_reg:
|
|
tegra_se_cmdbuf_put(&se->cmdbuf->bo);
|
|
err_bo:
|
|
host1x_syncpt_put(se->syncpt);
|
|
err_syncpt:
|
|
host1x_channel_put(se->channel);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_se_client_deinit(struct host1x_client *client)
|
|
{
|
|
struct tegra_se *se = container_of(client, struct tegra_se, client);
|
|
|
|
se->hw->deinit_alg();
|
|
tegra_se_cmdbuf_put(&se->cmdbuf->bo);
|
|
host1x_syncpt_put(se->syncpt);
|
|
host1x_channel_put(se->channel);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct host1x_client_ops tegra_se_client_ops = {
|
|
.init = tegra_se_client_init,
|
|
.exit = tegra_se_client_deinit,
|
|
};
|
|
|
|
int tegra_se_host1x_register(struct tegra_se *se)
|
|
{
|
|
INIT_LIST_HEAD(&se->client.list);
|
|
se->client.dev = se->dev;
|
|
se->client.ops = &tegra_se_client_ops;
|
|
se->client.class = se->hw->host1x_class;
|
|
se->client.num_syncpts = 1;
|
|
|
|
host1x_client_register(&se->client);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra_se_clk_init(struct tegra_se *se)
|
|
{
|
|
int i, ret;
|
|
|
|
se->num_clks = devm_clk_bulk_get_all(se->dev, &se->clks);
|
|
if (se->num_clks < 0) {
|
|
dev_err(se->dev, "failed to get clocks\n");
|
|
return se->num_clks;
|
|
}
|
|
|
|
for (i = 0; i < se->num_clks; i++) {
|
|
ret = clk_set_rate(se->clks[i].clk, ULONG_MAX);
|
|
if (ret) {
|
|
dev_err(se->dev, "failed to set %d clock rate", i);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
ret = clk_bulk_prepare_enable(se->num_clks, se->clks);
|
|
if (ret) {
|
|
dev_err(se->dev, "failed to enable clocks\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tegra_se_clk_deinit(struct tegra_se *se)
|
|
{
|
|
clk_bulk_disable_unprepare(se->num_clks, se->clks);
|
|
}
|
|
|
|
static int tegra_se_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct tegra_se *se;
|
|
int ret;
|
|
|
|
se = devm_kzalloc(dev, sizeof(*se), GFP_KERNEL);
|
|
if (!se)
|
|
return -ENOMEM;
|
|
|
|
se->dev = dev;
|
|
se->hw = device_get_match_data(&pdev->dev);
|
|
|
|
se->base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(se->base))
|
|
return PTR_ERR(se->base);
|
|
|
|
se->owner = TEGRA_GPSE_ID;
|
|
|
|
dma_set_mask_and_coherent(dev, DMA_BIT_MASK(39));
|
|
platform_set_drvdata(pdev, se);
|
|
|
|
ret = tegra_se_clk_init(se);
|
|
if (ret) {
|
|
dev_err(dev, "failed to init clocks\n");
|
|
return ret;
|
|
}
|
|
|
|
if (!tegra_dev_iommu_get_stream_id(dev, &se->stream_id)) {
|
|
dev_err(dev, "failed to get IOMMU stream ID\n");
|
|
goto err_iommu_spec;
|
|
}
|
|
|
|
se_writel(se, se->stream_id, SE_STREAM_ID);
|
|
|
|
se->engine = crypto_engine_alloc_init(dev, 0);
|
|
if (!se->engine) {
|
|
dev_err(dev, "failed to init crypto engine\n");
|
|
ret = -ENOMEM;
|
|
goto err_engine_alloc;
|
|
}
|
|
|
|
ret = crypto_engine_start(se->engine);
|
|
if (ret) {
|
|
dev_err(dev, "failed to start crypto engine\n");
|
|
goto err_engine_start;
|
|
}
|
|
|
|
ret = tegra_se_host1x_register(se);
|
|
if (ret) {
|
|
dev_err(dev, "failed to init host1x params\n");
|
|
goto err_host1x_init;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_host1x_init:
|
|
crypto_engine_stop(se->engine);
|
|
err_engine_start:
|
|
crypto_engine_exit(se->engine);
|
|
err_engine_alloc:
|
|
iommu_fwspec_free(se->dev);
|
|
err_iommu_spec:
|
|
tegra_se_clk_deinit(se);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_se_remove(struct platform_device *pdev)
|
|
{
|
|
struct tegra_se *se = platform_get_drvdata(pdev);
|
|
|
|
crypto_engine_stop(se->engine);
|
|
crypto_engine_exit(se->engine);
|
|
iommu_fwspec_free(se->dev);
|
|
host1x_client_unregister(&se->client);
|
|
tegra_se_clk_deinit(se);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct tegra_se_regs tegra234_aes1_regs = {
|
|
.config = SE_AES1_CFG,
|
|
.op = SE_AES1_OPERATION,
|
|
.last_blk = SE_AES1_LAST_BLOCK,
|
|
.linear_ctr = SE_AES1_LINEAR_CTR,
|
|
.aad_len = SE_AES1_AAD_LEN,
|
|
.cryp_msg_len = SE_AES1_CRYPTO_MSG_LEN,
|
|
.manifest = SE_AES1_KEYMANIFEST,
|
|
.key_addr = SE_AES1_KEY_ADDR,
|
|
.key_data = SE_AES1_KEY_DATA,
|
|
.key_dst = SE_AES1_KEY_DST,
|
|
.result = SE_AES1_CMAC_RESULT,
|
|
};
|
|
|
|
static const struct tegra_se_regs tegra234_hash_regs = {
|
|
.config = SE_SHA_CFG,
|
|
.op = SE_SHA_OPERATION,
|
|
.manifest = SE_SHA_KEYMANIFEST,
|
|
.key_addr = SE_SHA_KEY_ADDR,
|
|
.key_data = SE_SHA_KEY_DATA,
|
|
.key_dst = SE_SHA_KEY_DST,
|
|
.result = SE_SHA_HASH_RESULT,
|
|
};
|
|
|
|
static const struct tegra_se_hw tegra234_aes_hw = {
|
|
.regs = &tegra234_aes1_regs,
|
|
.kac_ver = 1,
|
|
.host1x_class = 0x3b,
|
|
.init_alg = tegra_init_aes,
|
|
.deinit_alg = tegra_deinit_aes,
|
|
};
|
|
|
|
static const struct tegra_se_hw tegra234_hash_hw = {
|
|
.regs = &tegra234_hash_regs,
|
|
.kac_ver = 1,
|
|
.host1x_class = 0x3d,
|
|
.init_alg = tegra_init_hash,
|
|
.deinit_alg = tegra_deinit_hash,
|
|
};
|
|
|
|
static const struct of_device_id tegra_se_of_match[] = {
|
|
{
|
|
.compatible = "nvidia,tegra234-se2-aes",
|
|
.data = &tegra234_aes_hw
|
|
}, {
|
|
.compatible = "nvidia,tegra234-se4-hash",
|
|
.data = &tegra234_hash_hw,
|
|
},
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, tegra_se_of_match);
|
|
|
|
static struct platform_driver tegra_se_driver = {
|
|
.driver = {
|
|
.name = "tegra-se",
|
|
.of_match_table = tegra_se_of_match,
|
|
},
|
|
.probe = tegra_se_probe,
|
|
.remove = tegra_se_remove,
|
|
};
|
|
|
|
static int tegra_se_host1x_probe(struct host1x_device *dev)
|
|
{
|
|
return host1x_device_init(dev);
|
|
}
|
|
|
|
static int tegra_se_host1x_remove(struct host1x_device *dev)
|
|
{
|
|
host1x_device_exit(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct host1x_driver tegra_se_host1x_driver = {
|
|
.driver = {
|
|
.name = "tegra-se-host1x",
|
|
},
|
|
.probe = tegra_se_host1x_probe,
|
|
.remove = tegra_se_host1x_remove,
|
|
.subdevs = tegra_se_of_match,
|
|
};
|
|
|
|
static int __init tegra_se_module_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = host1x_driver_register(&tegra_se_host1x_driver);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return platform_driver_register(&tegra_se_driver);
|
|
}
|
|
|
|
static void __exit tegra_se_module_exit(void)
|
|
{
|
|
host1x_driver_unregister(&tegra_se_host1x_driver);
|
|
platform_driver_unregister(&tegra_se_driver);
|
|
}
|
|
|
|
module_init(tegra_se_module_init);
|
|
module_exit(tegra_se_module_exit);
|
|
|
|
MODULE_DESCRIPTION("NVIDIA Tegra Security Engine Driver");
|
|
MODULE_AUTHOR("Akhil R <akhilrajeev@nvidia.com>");
|
|
MODULE_LICENSE("GPL v2");
|