forked from Minki/linux
15f7a4c652
Introduce clear_psp_master_device() to ensure that sp_dev_master gets
properly cleared on the release of a psp device.
Fixes: 2a6170dfe7
("crypto: ccp: Add Platform Security Processor (PSP) device support")
Signed-off-by: John Allen <john.allen@amd.com>
Acked-by: Tom Lendacky <thomas.lendacky@amd.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
271 lines
5.3 KiB
C
271 lines
5.3 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* AMD Platform Security Processor (PSP) interface
|
|
*
|
|
* Copyright (C) 2016,2019 Advanced Micro Devices, Inc.
|
|
*
|
|
* Author: Brijesh Singh <brijesh.singh@amd.com>
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/irqreturn.h>
|
|
|
|
#include "sp-dev.h"
|
|
#include "psp-dev.h"
|
|
#include "sev-dev.h"
|
|
#include "tee-dev.h"
|
|
|
|
struct psp_device *psp_master;
|
|
|
|
static struct psp_device *psp_alloc_struct(struct sp_device *sp)
|
|
{
|
|
struct device *dev = sp->dev;
|
|
struct psp_device *psp;
|
|
|
|
psp = devm_kzalloc(dev, sizeof(*psp), GFP_KERNEL);
|
|
if (!psp)
|
|
return NULL;
|
|
|
|
psp->dev = dev;
|
|
psp->sp = sp;
|
|
|
|
snprintf(psp->name, sizeof(psp->name), "psp-%u", sp->ord);
|
|
|
|
return psp;
|
|
}
|
|
|
|
static irqreturn_t psp_irq_handler(int irq, void *data)
|
|
{
|
|
struct psp_device *psp = data;
|
|
unsigned int status;
|
|
|
|
/* Read the interrupt status: */
|
|
status = ioread32(psp->io_regs + psp->vdata->intsts_reg);
|
|
|
|
/* invoke subdevice interrupt handlers */
|
|
if (status) {
|
|
if (psp->sev_irq_handler)
|
|
psp->sev_irq_handler(irq, psp->sev_irq_data, status);
|
|
|
|
if (psp->tee_irq_handler)
|
|
psp->tee_irq_handler(irq, psp->tee_irq_data, status);
|
|
}
|
|
|
|
/* Clear the interrupt status by writing the same value we read. */
|
|
iowrite32(status, psp->io_regs + psp->vdata->intsts_reg);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static unsigned int psp_get_capability(struct psp_device *psp)
|
|
{
|
|
unsigned int val = ioread32(psp->io_regs + psp->vdata->feature_reg);
|
|
|
|
/*
|
|
* Check for a access to the registers. If this read returns
|
|
* 0xffffffff, it's likely that the system is running a broken
|
|
* BIOS which disallows access to the device. Stop here and
|
|
* fail the PSP initialization (but not the load, as the CCP
|
|
* could get properly initialized).
|
|
*/
|
|
if (val == 0xffffffff) {
|
|
dev_notice(psp->dev, "psp: unable to access the device: you might be running a broken BIOS.\n");
|
|
return 0;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static int psp_check_sev_support(struct psp_device *psp,
|
|
unsigned int capability)
|
|
{
|
|
/* Check if device supports SEV feature */
|
|
if (!(capability & 1)) {
|
|
dev_dbg(psp->dev, "psp does not support SEV\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int psp_check_tee_support(struct psp_device *psp,
|
|
unsigned int capability)
|
|
{
|
|
/* Check if device supports TEE feature */
|
|
if (!(capability & 2)) {
|
|
dev_dbg(psp->dev, "psp does not support TEE\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int psp_check_support(struct psp_device *psp,
|
|
unsigned int capability)
|
|
{
|
|
int sev_support = psp_check_sev_support(psp, capability);
|
|
int tee_support = psp_check_tee_support(psp, capability);
|
|
|
|
/* Return error if device neither supports SEV nor TEE */
|
|
if (sev_support && tee_support)
|
|
return -ENODEV;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int psp_init(struct psp_device *psp, unsigned int capability)
|
|
{
|
|
int ret;
|
|
|
|
if (!psp_check_sev_support(psp, capability)) {
|
|
ret = sev_dev_init(psp);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
if (!psp_check_tee_support(psp, capability)) {
|
|
ret = tee_dev_init(psp);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int psp_dev_init(struct sp_device *sp)
|
|
{
|
|
struct device *dev = sp->dev;
|
|
struct psp_device *psp;
|
|
unsigned int capability;
|
|
int ret;
|
|
|
|
ret = -ENOMEM;
|
|
psp = psp_alloc_struct(sp);
|
|
if (!psp)
|
|
goto e_err;
|
|
|
|
sp->psp_data = psp;
|
|
|
|
psp->vdata = (struct psp_vdata *)sp->dev_vdata->psp_vdata;
|
|
if (!psp->vdata) {
|
|
ret = -ENODEV;
|
|
dev_err(dev, "missing driver data\n");
|
|
goto e_err;
|
|
}
|
|
|
|
psp->io_regs = sp->io_map;
|
|
|
|
capability = psp_get_capability(psp);
|
|
if (!capability)
|
|
goto e_disable;
|
|
|
|
ret = psp_check_support(psp, capability);
|
|
if (ret)
|
|
goto e_disable;
|
|
|
|
/* Disable and clear interrupts until ready */
|
|
iowrite32(0, psp->io_regs + psp->vdata->inten_reg);
|
|
iowrite32(-1, psp->io_regs + psp->vdata->intsts_reg);
|
|
|
|
/* Request an irq */
|
|
ret = sp_request_psp_irq(psp->sp, psp_irq_handler, psp->name, psp);
|
|
if (ret) {
|
|
dev_err(dev, "psp: unable to allocate an IRQ\n");
|
|
goto e_err;
|
|
}
|
|
|
|
ret = psp_init(psp, capability);
|
|
if (ret)
|
|
goto e_irq;
|
|
|
|
if (sp->set_psp_master_device)
|
|
sp->set_psp_master_device(sp);
|
|
|
|
/* Enable interrupt */
|
|
iowrite32(-1, psp->io_regs + psp->vdata->inten_reg);
|
|
|
|
dev_notice(dev, "psp enabled\n");
|
|
|
|
return 0;
|
|
|
|
e_irq:
|
|
sp_free_psp_irq(psp->sp, psp);
|
|
e_err:
|
|
sp->psp_data = NULL;
|
|
|
|
dev_notice(dev, "psp initialization failed\n");
|
|
|
|
return ret;
|
|
|
|
e_disable:
|
|
sp->psp_data = NULL;
|
|
|
|
return ret;
|
|
}
|
|
|
|
void psp_dev_destroy(struct sp_device *sp)
|
|
{
|
|
struct psp_device *psp = sp->psp_data;
|
|
|
|
if (!psp)
|
|
return;
|
|
|
|
sev_dev_destroy(psp);
|
|
|
|
tee_dev_destroy(psp);
|
|
|
|
sp_free_psp_irq(sp, psp);
|
|
|
|
if (sp->clear_psp_master_device)
|
|
sp->clear_psp_master_device(sp);
|
|
}
|
|
|
|
void psp_set_sev_irq_handler(struct psp_device *psp, psp_irq_handler_t handler,
|
|
void *data)
|
|
{
|
|
psp->sev_irq_data = data;
|
|
psp->sev_irq_handler = handler;
|
|
}
|
|
|
|
void psp_clear_sev_irq_handler(struct psp_device *psp)
|
|
{
|
|
psp_set_sev_irq_handler(psp, NULL, NULL);
|
|
}
|
|
|
|
void psp_set_tee_irq_handler(struct psp_device *psp, psp_irq_handler_t handler,
|
|
void *data)
|
|
{
|
|
psp->tee_irq_data = data;
|
|
psp->tee_irq_handler = handler;
|
|
}
|
|
|
|
void psp_clear_tee_irq_handler(struct psp_device *psp)
|
|
{
|
|
psp_set_tee_irq_handler(psp, NULL, NULL);
|
|
}
|
|
|
|
struct psp_device *psp_get_master_device(void)
|
|
{
|
|
struct sp_device *sp = sp_get_psp_master_device();
|
|
|
|
return sp ? sp->psp_data : NULL;
|
|
}
|
|
|
|
void psp_pci_init(void)
|
|
{
|
|
psp_master = psp_get_master_device();
|
|
|
|
if (!psp_master)
|
|
return;
|
|
|
|
sev_pci_init();
|
|
}
|
|
|
|
void psp_pci_exit(void)
|
|
{
|
|
if (!psp_master)
|
|
return;
|
|
|
|
sev_pci_exit();
|
|
}
|