| // 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 "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_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->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; |
| |
| sev_dev_destroy(psp); |
| |
| tee_dev_destroy(psp); |
| |
| dbc_dev_destroy(psp); |
| |
| platform_access_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; |
| } |
| |
| 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(); |
| } |