linux-stable/drivers/crypto/ccp/platform-access.c
Mario Limonciello 0e8fca2f12 crypto: ccp - Avoid discarding errors in psp_send_platform_access_msg()
Errors can potentially occur in the "processing" of PSP commands or
commands can be processed successfully but still return an error code in
the header.

This second case was being discarded because PSP communication worked but
the command returned an error code in the payload header.

Capture both cases and return them to the caller as -EIO for the caller
to investigate. The caller can detect the latter by looking at
`req->header->status`.

Reported-and-tested-by: Tim Van Patten <timvp@google.com>
Fixes: 7ccc4f4e2e ("crypto: ccp - Add support for an interface for platform features")
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
2024-02-24 08:41:20 +08:00

228 lines
5.5 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* AMD Platform Security Processor (PSP) Platform Access interface
*
* Copyright (C) 2023 Advanced Micro Devices, Inc.
*
* Author: Mario Limonciello <mario.limonciello@amd.com>
*
* Some of this code is adapted from drivers/i2c/busses/i2c-designware-amdpsp.c
* developed by Jan Dabros <jsd@semihalf.com> and Copyright (C) 2022 Google Inc.
*
*/
#include <linux/bitfield.h>
#include <linux/errno.h>
#include <linux/iopoll.h>
#include <linux/mutex.h>
#include "platform-access.h"
#define PSP_CMD_TIMEOUT_US (500 * USEC_PER_MSEC)
#define DOORBELL_CMDRESP_STS GENMASK(7, 0)
/* Recovery field should be equal 0 to start sending commands */
static int check_recovery(u32 __iomem *cmd)
{
return FIELD_GET(PSP_CMDRESP_RECOVERY, ioread32(cmd));
}
static int wait_cmd(u32 __iomem *cmd)
{
u32 tmp, expected;
/* Expect mbox_cmd to be cleared and ready bit to be set by PSP */
expected = FIELD_PREP(PSP_CMDRESP_RESP, 1);
/*
* Check for readiness of PSP mailbox in a tight loop in order to
* process further as soon as command was consumed.
*/
return readl_poll_timeout(cmd, tmp, (tmp & expected), 0,
PSP_CMD_TIMEOUT_US);
}
int psp_check_platform_access_status(void)
{
struct psp_device *psp = psp_get_master_device();
if (!psp || !psp->platform_access_data)
return -ENODEV;
return 0;
}
EXPORT_SYMBOL(psp_check_platform_access_status);
int psp_send_platform_access_msg(enum psp_platform_access_msg msg,
struct psp_request *req)
{
struct psp_device *psp = psp_get_master_device();
u32 __iomem *cmd, *lo, *hi;
struct psp_platform_access_device *pa_dev;
phys_addr_t req_addr;
u32 cmd_reg;
int ret;
if (!psp || !psp->platform_access_data)
return -ENODEV;
pa_dev = psp->platform_access_data;
if (!pa_dev->vdata->cmdresp_reg || !pa_dev->vdata->cmdbuff_addr_lo_reg ||
!pa_dev->vdata->cmdbuff_addr_hi_reg)
return -ENODEV;
cmd = psp->io_regs + pa_dev->vdata->cmdresp_reg;
lo = psp->io_regs + pa_dev->vdata->cmdbuff_addr_lo_reg;
hi = psp->io_regs + pa_dev->vdata->cmdbuff_addr_hi_reg;
mutex_lock(&pa_dev->mailbox_mutex);
if (check_recovery(cmd)) {
dev_dbg(psp->dev, "platform mailbox is in recovery\n");
ret = -EBUSY;
goto unlock;
}
if (wait_cmd(cmd)) {
dev_dbg(psp->dev, "platform mailbox is not done processing command\n");
ret = -EBUSY;
goto unlock;
}
/*
* Fill mailbox with address of command-response buffer, which will be
* used for sending i2c requests as well as reading status returned by
* PSP. Use physical address of buffer, since PSP will map this region.
*/
req_addr = __psp_pa(req);
iowrite32(lower_32_bits(req_addr), lo);
iowrite32(upper_32_bits(req_addr), hi);
print_hex_dump_debug("->psp ", DUMP_PREFIX_OFFSET, 16, 2, req,
req->header.payload_size, false);
/* Write command register to trigger processing */
cmd_reg = FIELD_PREP(PSP_CMDRESP_CMD, msg);
iowrite32(cmd_reg, cmd);
if (wait_cmd(cmd)) {
ret = -ETIMEDOUT;
goto unlock;
}
/* Ensure it was triggered by this driver */
if (ioread32(lo) != lower_32_bits(req_addr) ||
ioread32(hi) != upper_32_bits(req_addr)) {
ret = -EBUSY;
goto unlock;
}
/*
* Read status from PSP. If status is non-zero, it indicates an error
* occurred during "processing" of the command.
* If status is zero, it indicates the command was "processed"
* successfully, but the result of the command is in the payload.
* Return both cases to the caller as -EIO to investigate.
*/
cmd_reg = ioread32(cmd);
if (FIELD_GET(PSP_CMDRESP_STS, cmd_reg))
req->header.status = FIELD_GET(PSP_CMDRESP_STS, cmd_reg);
if (req->header.status) {
ret = -EIO;
goto unlock;
}
print_hex_dump_debug("<-psp ", DUMP_PREFIX_OFFSET, 16, 2, req,
req->header.payload_size, false);
ret = 0;
unlock:
mutex_unlock(&pa_dev->mailbox_mutex);
return ret;
}
EXPORT_SYMBOL_GPL(psp_send_platform_access_msg);
int psp_ring_platform_doorbell(int msg, u32 *result)
{
struct psp_device *psp = psp_get_master_device();
struct psp_platform_access_device *pa_dev;
u32 __iomem *button, *cmd;
int ret, val;
if (!psp || !psp->platform_access_data)
return -ENODEV;
pa_dev = psp->platform_access_data;
button = psp->io_regs + pa_dev->vdata->doorbell_button_reg;
cmd = psp->io_regs + pa_dev->vdata->doorbell_cmd_reg;
mutex_lock(&pa_dev->doorbell_mutex);
if (wait_cmd(cmd)) {
dev_err(psp->dev, "doorbell command not done processing\n");
ret = -EBUSY;
goto unlock;
}
iowrite32(FIELD_PREP(DOORBELL_CMDRESP_STS, msg), cmd);
iowrite32(PSP_DRBL_RING, button);
if (wait_cmd(cmd)) {
ret = -ETIMEDOUT;
goto unlock;
}
val = FIELD_GET(DOORBELL_CMDRESP_STS, ioread32(cmd));
if (val) {
if (result)
*result = val;
ret = -EIO;
goto unlock;
}
ret = 0;
unlock:
mutex_unlock(&pa_dev->doorbell_mutex);
return ret;
}
EXPORT_SYMBOL_GPL(psp_ring_platform_doorbell);
void platform_access_dev_destroy(struct psp_device *psp)
{
struct psp_platform_access_device *pa_dev = psp->platform_access_data;
if (!pa_dev)
return;
mutex_destroy(&pa_dev->mailbox_mutex);
mutex_destroy(&pa_dev->doorbell_mutex);
psp->platform_access_data = NULL;
}
int platform_access_dev_init(struct psp_device *psp)
{
struct device *dev = psp->dev;
struct psp_platform_access_device *pa_dev;
pa_dev = devm_kzalloc(dev, sizeof(*pa_dev), GFP_KERNEL);
if (!pa_dev)
return -ENOMEM;
psp->platform_access_data = pa_dev;
pa_dev->psp = psp;
pa_dev->dev = dev;
pa_dev->vdata = (struct platform_access_vdata *)psp->vdata->platform_access;
mutex_init(&pa_dev->mailbox_mutex);
mutex_init(&pa_dev->doorbell_mutex);
dev_dbg(dev, "platform access enabled\n");
return 0;
}