| // 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); |
| } |
| |
| 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(); |
| } |