forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
270 lines
5.3 KiB
270 lines
5.3 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* AMD Platform Security Processor (PSP) interface |
|
* |
|
* Copyright (C) 2016,2019 Advanced Micro Devices, Inc. |
|
* |
|
* Author: Brijesh Singh <[email protected]> |
|
*/ |
|
|
|
#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(); |
|
}
|
|
|