| // 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 <linux/mutex.h> | |
| #include <linux/bitfield.h> | |
| #include <linux/delay.h> | |
| #include "sp-dev.h" | |
| #include "psp-dev.h" | |
| #include "sev-dev.h" | |
| #include "tee-dev.h" | |
| #include "sfs.h" | |
| #include "platform-access.h" | |
| #include "dbc.h" | |
| #include "hsti.h" | |
| struct psp_device *psp_master; | |
| #define PSP_C2PMSG_17_CMDRESP_CMD GENMASK(19, 16) | |
| static int psp_mailbox_poll(const void __iomem *cmdresp_reg, unsigned int *cmdresp, | |
| unsigned int timeout_msecs) | |
| { | |
| while (true) { | |
| *cmdresp = ioread32(cmdresp_reg); | |
| if (FIELD_GET(PSP_CMDRESP_RESP, *cmdresp)) | |
| return 0; | |
| if (!timeout_msecs--) | |
| break; | |
| usleep_range(1000, 1100); | |
| } | |
| return -ETIMEDOUT; | |
| } | |
| int psp_mailbox_command(struct psp_device *psp, enum psp_cmd cmd, void *cmdbuff, | |
| unsigned int timeout_msecs, unsigned int *cmdresp) | |
| { | |
| void __iomem *cmdresp_reg, *cmdbuff_lo_reg, *cmdbuff_hi_reg; | |
| int ret; | |
| if (!psp || !psp->vdata || !psp->vdata->cmdresp_reg || | |
| !psp->vdata->cmdbuff_addr_lo_reg || !psp->vdata->cmdbuff_addr_hi_reg) | |
| return -ENODEV; | |
| cmdresp_reg = psp->io_regs + psp->vdata->cmdresp_reg; | |
| cmdbuff_lo_reg = psp->io_regs + psp->vdata->cmdbuff_addr_lo_reg; | |
| cmdbuff_hi_reg = psp->io_regs + psp->vdata->cmdbuff_addr_hi_reg; | |
| mutex_lock(&psp->mailbox_mutex); | |
| /* Ensure mailbox is ready for a command */ | |
| ret = -EBUSY; | |
| if (psp_mailbox_poll(cmdresp_reg, cmdresp, 0)) | |
| goto unlock; | |
| if (cmdbuff) { | |
| iowrite32(lower_32_bits(__psp_pa(cmdbuff)), cmdbuff_lo_reg); | |
| iowrite32(upper_32_bits(__psp_pa(cmdbuff)), cmdbuff_hi_reg); | |
| } | |
| *cmdresp = FIELD_PREP(PSP_C2PMSG_17_CMDRESP_CMD, cmd); | |
| iowrite32(*cmdresp, cmdresp_reg); | |
| ret = psp_mailbox_poll(cmdresp_reg, cmdresp, timeout_msecs); | |
| unlock: | |
| mutex_unlock(&psp->mailbox_mutex); | |
| return ret; | |
| } | |
| int psp_extended_mailbox_cmd(struct psp_device *psp, unsigned int timeout_msecs, | |
| struct psp_ext_request *req) | |
| { | |
| unsigned int reg; | |
| int ret; | |
| print_hex_dump_debug("->psp ", DUMP_PREFIX_OFFSET, 16, 2, req, | |
| req->header.payload_size, false); | |
| ret = psp_mailbox_command(psp, PSP_CMD_TEE_EXTENDED_CMD, (void *)req, | |
| timeout_msecs, ®); | |
| if (ret) { | |
| return ret; | |
| } else if (FIELD_GET(PSP_CMDRESP_STS, reg)) { | |
| req->header.status = FIELD_GET(PSP_CMDRESP_STS, reg); | |
| return -EIO; | |
| } | |
| print_hex_dump_debug("<-psp ", DUMP_PREFIX_OFFSET, 16, 2, req, | |
| req->header.payload_size, false); | |
| return 0; | |
| } | |
| 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); | |
| /* Clear the interrupt status by writing the same value we read. */ | |
| iowrite32(status, 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); | |
| } | |
| 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 -ENODEV; | |
| } | |
| psp->capability.raw = val; | |
| return 0; | |
| } | |
| static int psp_check_sev_support(struct psp_device *psp) | |
| { | |
| /* Check if device supports SEV feature */ | |
| if (!psp->capability.sev) { | |
| dev_dbg(psp->dev, "psp does not support SEV\n"); | |
| return -ENODEV; | |
| } | |
| return 0; | |
| } | |
| static int psp_check_tee_support(struct psp_device *psp) | |
| { | |
| /* Check if device supports TEE feature */ | |
| if (!psp->capability.tee) { | |
| dev_dbg(psp->dev, "psp does not support TEE\n"); | |
| return -ENODEV; | |
| } | |
| return 0; | |
| } | |
| static int psp_check_sfs_support(struct psp_device *psp) | |
| { | |
| /* Check if device supports SFS feature */ | |
| if (!psp->capability.sfs) { | |
| dev_dbg(psp->dev, "psp does not support SFS\n"); | |
| return -ENODEV; | |
| } | |
| return 0; | |
| } | |
| static int psp_init(struct psp_device *psp) | |
| { | |
| int ret; | |
| if (!psp_check_sev_support(psp)) { | |
| ret = sev_dev_init(psp); | |
| if (ret) | |
| return ret; | |
| } | |
| if (!psp_check_tee_support(psp)) { | |
| ret = tee_dev_init(psp); | |
| if (ret) | |
| return ret; | |
| } | |
| if (!psp_check_sfs_support(psp)) { | |
| ret = sfs_dev_init(psp); | |
| if (ret) | |
| return ret; | |
| } | |
| if (psp->vdata->platform_access) { | |
| ret = platform_access_dev_init(psp); | |
| if (ret) | |
| return ret; | |
| } | |
| /* dbc must come after platform access as it tests the feature */ | |
| if (PSP_FEATURE(psp, DBC) || | |
| psp->capability.dbc_thru_ext) { | |
| ret = dbc_dev_init(psp); | |
| if (ret) | |
| return ret; | |
| } | |
| /* HSTI uses platform access on some systems. */ | |
| ret = psp_init_hsti(psp); | |
| if (ret) | |
| return ret; | |
| return 0; | |
| } | |
| int psp_dev_init(struct sp_device *sp) | |
| { | |
| struct device *dev = sp->dev; | |
| struct psp_device *psp; | |
| 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; | |
| mutex_init(&psp->mailbox_mutex); | |
| ret = psp_get_capability(psp); | |
| 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; | |
| } | |
| /* master device must be set for platform access */ | |
| if (psp->sp->set_psp_master_device) | |
| psp->sp->set_psp_master_device(psp->sp); | |
| ret = psp_init(psp); | |
| if (ret) | |
| goto e_irq; | |
| /* Enable interrupt */ | |
| iowrite32(-1, psp->io_regs + psp->vdata->inten_reg); | |
| dev_notice(dev, "psp enabled\n"); | |
| return 0; | |
| e_irq: | |
| if (sp->clear_psp_master_device) | |
| sp->clear_psp_master_device(sp); | |
| 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; | |
| dbc_dev_destroy(psp); | |
| platform_access_dev_destroy(psp); | |
| sfs_dev_destroy(psp); | |
| tee_dev_destroy(psp); | |
| sev_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); | |
| } | |
| struct psp_device *psp_get_master_device(void) | |
| { | |
| struct sp_device *sp = sp_get_psp_master_device(); | |
| return sp ? sp->psp_data : NULL; | |
| } | |
| int psp_restore(struct sp_device *sp) | |
| { | |
| struct psp_device *psp = sp->psp_data; | |
| int ret = 0; | |
| if (psp->tee_data) | |
| ret = tee_restore(psp); | |
| return ret; | |
| } | |
| 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(); | |
| } |