Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | // SPDX-License-Identifier: GPL-2.0 /* * AMD Platform Security Processor (PSP) Platform Access interface * * Copyright (C) 2023 Advanced Micro Devices, Inc. * * Author: Mario Limonciello <mario.limonciello@amd.com> * * Some of this code is adapted from drivers/i2c/busses/i2c-designware-amdpsp.c * developed by Jan Dabros <jsd@semihalf.com> and Copyright (C) 2022 Google Inc. * */ #include <linux/bitfield.h> #include <linux/errno.h> #include <linux/iopoll.h> #include <linux/mutex.h> #include "platform-access.h" #define PSP_CMD_TIMEOUT_US (500 * USEC_PER_MSEC) #define DOORBELL_CMDRESP_STS GENMASK(7, 0) /* Recovery field should be equal 0 to start sending commands */ static int check_recovery(u32 __iomem *cmd) { return FIELD_GET(PSP_CMDRESP_RECOVERY, ioread32(cmd)); } static int wait_cmd(u32 __iomem *cmd) { u32 tmp, expected; /* Expect mbox_cmd to be cleared and ready bit to be set by PSP */ expected = FIELD_PREP(PSP_CMDRESP_RESP, 1); /* * Check for readiness of PSP mailbox in a tight loop in order to * process further as soon as command was consumed. */ return readl_poll_timeout(cmd, tmp, (tmp & expected), 0, PSP_CMD_TIMEOUT_US); } int psp_check_platform_access_status(void) { struct psp_device *psp = psp_get_master_device(); if (!psp || !psp->platform_access_data) return -ENODEV; return 0; } EXPORT_SYMBOL(psp_check_platform_access_status); int psp_send_platform_access_msg(enum psp_platform_access_msg msg, struct psp_request *req) { struct psp_device *psp = psp_get_master_device(); u32 __iomem *cmd, *lo, *hi; struct psp_platform_access_device *pa_dev; phys_addr_t req_addr; u32 cmd_reg; int ret; if (!psp || !psp->platform_access_data) return -ENODEV; pa_dev = psp->platform_access_data; if (!pa_dev->vdata->cmdresp_reg || !pa_dev->vdata->cmdbuff_addr_lo_reg || !pa_dev->vdata->cmdbuff_addr_hi_reg) return -ENODEV; cmd = psp->io_regs + pa_dev->vdata->cmdresp_reg; lo = psp->io_regs + pa_dev->vdata->cmdbuff_addr_lo_reg; hi = psp->io_regs + pa_dev->vdata->cmdbuff_addr_hi_reg; mutex_lock(&pa_dev->mailbox_mutex); if (check_recovery(cmd)) { dev_dbg(psp->dev, "platform mailbox is in recovery\n"); ret = -EBUSY; goto unlock; } if (wait_cmd(cmd)) { dev_dbg(psp->dev, "platform mailbox is not done processing command\n"); ret = -EBUSY; goto unlock; } /* * Fill mailbox with address of command-response buffer, which will be * used for sending i2c requests as well as reading status returned by * PSP. Use physical address of buffer, since PSP will map this region. */ req_addr = __psp_pa(req); iowrite32(lower_32_bits(req_addr), lo); iowrite32(upper_32_bits(req_addr), hi); print_hex_dump_debug("->psp ", DUMP_PREFIX_OFFSET, 16, 2, req, req->header.payload_size, false); /* Write command register to trigger processing */ cmd_reg = FIELD_PREP(PSP_CMDRESP_CMD, msg); iowrite32(cmd_reg, cmd); if (wait_cmd(cmd)) { ret = -ETIMEDOUT; goto unlock; } /* Ensure it was triggered by this driver */ if (ioread32(lo) != lower_32_bits(req_addr) || ioread32(hi) != upper_32_bits(req_addr)) { ret = -EBUSY; goto unlock; } /* * Read status from PSP. If status is non-zero, it indicates an error * occurred during "processing" of the command. * If status is zero, it indicates the command was "processed" * successfully, but the result of the command is in the payload. * Return both cases to the caller as -EIO to investigate. */ cmd_reg = ioread32(cmd); if (FIELD_GET(PSP_CMDRESP_STS, cmd_reg)) req->header.status = FIELD_GET(PSP_CMDRESP_STS, cmd_reg); if (req->header.status) { ret = -EIO; goto unlock; } print_hex_dump_debug("<-psp ", DUMP_PREFIX_OFFSET, 16, 2, req, req->header.payload_size, false); ret = 0; unlock: mutex_unlock(&pa_dev->mailbox_mutex); return ret; } EXPORT_SYMBOL_GPL(psp_send_platform_access_msg); int psp_ring_platform_doorbell(int msg, u32 *result) { struct psp_device *psp = psp_get_master_device(); struct psp_platform_access_device *pa_dev; u32 __iomem *button, *cmd; int ret, val; if (!psp || !psp->platform_access_data) return -ENODEV; pa_dev = psp->platform_access_data; button = psp->io_regs + pa_dev->vdata->doorbell_button_reg; cmd = psp->io_regs + pa_dev->vdata->doorbell_cmd_reg; mutex_lock(&pa_dev->doorbell_mutex); if (wait_cmd(cmd)) { dev_err(psp->dev, "doorbell command not done processing\n"); ret = -EBUSY; goto unlock; } iowrite32(FIELD_PREP(DOORBELL_CMDRESP_STS, msg), cmd); iowrite32(PSP_DRBL_RING, button); if (wait_cmd(cmd)) { ret = -ETIMEDOUT; goto unlock; } val = FIELD_GET(DOORBELL_CMDRESP_STS, ioread32(cmd)); if (val) { if (result) *result = val; ret = -EIO; goto unlock; } ret = 0; unlock: mutex_unlock(&pa_dev->doorbell_mutex); return ret; } EXPORT_SYMBOL_GPL(psp_ring_platform_doorbell); void platform_access_dev_destroy(struct psp_device *psp) { struct psp_platform_access_device *pa_dev = psp->platform_access_data; if (!pa_dev) return; mutex_destroy(&pa_dev->mailbox_mutex); mutex_destroy(&pa_dev->doorbell_mutex); psp->platform_access_data = NULL; } int platform_access_dev_init(struct psp_device *psp) { struct device *dev = psp->dev; struct psp_platform_access_device *pa_dev; pa_dev = devm_kzalloc(dev, sizeof(*pa_dev), GFP_KERNEL); if (!pa_dev) return -ENOMEM; psp->platform_access_data = pa_dev; pa_dev->psp = psp; pa_dev->dev = dev; pa_dev->vdata = (struct platform_access_vdata *)psp->vdata->platform_access; mutex_init(&pa_dev->mailbox_mutex); mutex_init(&pa_dev->doorbell_mutex); dev_dbg(dev, "platform access enabled\n"); return 0; } |