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 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 | // SPDX-License-Identifier: GPL-2.0 #include <linux/i2c.h> #include <linux/pci.h> #include <linux/psp-platform-access.h> #include <linux/psp.h> #include <linux/workqueue.h> #include "i2c-designware-core.h" #define PSP_I2C_RESERVATION_TIME_MS 100 #define PSP_I2C_REQ_RETRY_CNT 400 #define PSP_I2C_REQ_RETRY_DELAY_US (25 * USEC_PER_MSEC) #define PSP_I2C_REQ_STS_OK 0x0 #define PSP_I2C_REQ_STS_BUS_BUSY 0x1 #define PSP_I2C_REQ_STS_INV_PARAM 0x3 enum psp_i2c_req_type { PSP_I2C_REQ_ACQUIRE, PSP_I2C_REQ_RELEASE, PSP_I2C_REQ_MAX }; struct psp_i2c_req { struct psp_req_buffer_hdr hdr; enum psp_i2c_req_type type; }; static DEFINE_MUTEX(psp_i2c_access_mutex); static unsigned long psp_i2c_sem_acquired; static u32 psp_i2c_access_count; static bool psp_i2c_mbox_fail; static struct device *psp_i2c_dev; static int (*_psp_send_i2c_req)(struct psp_i2c_req *req); /* Helper to verify status returned by PSP */ static int check_i2c_req_sts(struct psp_i2c_req *req) { u32 status; /* Status field in command-response buffer is updated by PSP */ status = READ_ONCE(req->hdr.status); switch (status) { case PSP_I2C_REQ_STS_OK: return 0; case PSP_I2C_REQ_STS_BUS_BUSY: return -EBUSY; case PSP_I2C_REQ_STS_INV_PARAM: default: return -EIO; } } /* * Errors in x86-PSP i2c-arbitration protocol may occur at two levels: * 1. mailbox communication - PSP is not operational or some IO errors with * basic communication had happened. * 2. i2c-requests - PSP refuses to grant i2c arbitration to x86 for too long. * * In order to distinguish between these in error handling code all mailbox * communication errors on the first level (from CCP symbols) will be passed * up and if -EIO is returned the second level will be checked. */ static int psp_send_i2c_req_cezanne(struct psp_i2c_req *req) { int ret; ret = psp_send_platform_access_msg(PSP_I2C_REQ_BUS_CMD, (struct psp_request *)req); if (ret == -EIO) return check_i2c_req_sts(req); return ret; } static int psp_send_i2c_req_doorbell(struct psp_i2c_req *req) { int ret; ret = psp_ring_platform_doorbell(req->type, &req->hdr.status); if (ret == -EIO) return check_i2c_req_sts(req); return ret; } static int psp_send_i2c_req(enum psp_i2c_req_type i2c_req_type) { struct psp_i2c_req *req; unsigned long start; int status, ret; /* Allocate command-response buffer */ req = kzalloc(sizeof(*req), GFP_KERNEL); if (!req) return -ENOMEM; req->hdr.payload_size = sizeof(*req); req->type = i2c_req_type; start = jiffies; ret = read_poll_timeout(_psp_send_i2c_req, status, (status != -EBUSY), PSP_I2C_REQ_RETRY_DELAY_US, PSP_I2C_REQ_RETRY_CNT * PSP_I2C_REQ_RETRY_DELAY_US, 0, req); if (ret) { dev_err(psp_i2c_dev, "Timed out waiting for PSP to %s I2C bus\n", (i2c_req_type == PSP_I2C_REQ_ACQUIRE) ? "release" : "acquire"); goto cleanup; } ret = status; if (ret) { dev_err(psp_i2c_dev, "PSP communication error\n"); goto cleanup; } dev_dbg(psp_i2c_dev, "Request accepted by PSP after %ums\n", jiffies_to_msecs(jiffies - start)); cleanup: if (ret) { dev_err(psp_i2c_dev, "Assume i2c bus is for exclusive host usage\n"); psp_i2c_mbox_fail = true; } kfree(req); return ret; } static void release_bus(void) { int status; if (!psp_i2c_sem_acquired) return; status = psp_send_i2c_req(PSP_I2C_REQ_RELEASE); if (status) return; dev_dbg(psp_i2c_dev, "PSP semaphore held for %ums\n", jiffies_to_msecs(jiffies - psp_i2c_sem_acquired)); psp_i2c_sem_acquired = 0; } static void psp_release_i2c_bus_deferred(struct work_struct *work) { mutex_lock(&psp_i2c_access_mutex); /* * If there is any pending transaction, cannot release the bus here. * psp_release_i2c_bus will take care of this later. */ if (psp_i2c_access_count) goto cleanup; release_bus(); cleanup: mutex_unlock(&psp_i2c_access_mutex); } static DECLARE_DELAYED_WORK(release_queue, psp_release_i2c_bus_deferred); static int psp_acquire_i2c_bus(void) { int status; mutex_lock(&psp_i2c_access_mutex); /* Return early if mailbox malfunctioned */ if (psp_i2c_mbox_fail) goto cleanup; psp_i2c_access_count++; /* * No need to request bus arbitration once we are inside semaphore * reservation period. */ if (psp_i2c_sem_acquired) goto cleanup; status = psp_send_i2c_req(PSP_I2C_REQ_ACQUIRE); if (status) goto cleanup; psp_i2c_sem_acquired = jiffies; schedule_delayed_work(&release_queue, msecs_to_jiffies(PSP_I2C_RESERVATION_TIME_MS)); /* * In case of errors with PSP arbitrator psp_i2c_mbox_fail variable is * set above. As a consequence consecutive calls to acquire will bypass * communication with PSP. At any case i2c bus is granted to the caller, * thus always return success. */ cleanup: mutex_unlock(&psp_i2c_access_mutex); return 0; } static void psp_release_i2c_bus(void) { mutex_lock(&psp_i2c_access_mutex); /* Return early if mailbox was malfunctional */ if (psp_i2c_mbox_fail) goto cleanup; /* * If we are last owner of PSP semaphore, need to release aribtration * via mailbox. */ psp_i2c_access_count--; if (psp_i2c_access_count) goto cleanup; /* * Send a release command to PSP if the semaphore reservation timeout * elapsed but x86 still owns the controller. */ if (!delayed_work_pending(&release_queue)) release_bus(); cleanup: mutex_unlock(&psp_i2c_access_mutex); } /* * Locking methods are based on the default implementation from * drivers/i2c/i2c-core-base.c, but with psp acquire and release operations * added. With this in place we can ensure that i2c clients on the bus shared * with psp are able to lock HW access to the bus for arbitrary number of * operations - that is e.g. write-wait-read. */ static void i2c_adapter_dw_psp_lock_bus(struct i2c_adapter *adapter, unsigned int flags) { psp_acquire_i2c_bus(); rt_mutex_lock_nested(&adapter->bus_lock, i2c_adapter_depth(adapter)); } static int i2c_adapter_dw_psp_trylock_bus(struct i2c_adapter *adapter, unsigned int flags) { int ret; ret = rt_mutex_trylock(&adapter->bus_lock); if (ret) return ret; psp_acquire_i2c_bus(); return ret; } static void i2c_adapter_dw_psp_unlock_bus(struct i2c_adapter *adapter, unsigned int flags) { psp_release_i2c_bus(); rt_mutex_unlock(&adapter->bus_lock); } static const struct i2c_lock_operations i2c_dw_psp_lock_ops = { .lock_bus = i2c_adapter_dw_psp_lock_bus, .trylock_bus = i2c_adapter_dw_psp_trylock_bus, .unlock_bus = i2c_adapter_dw_psp_unlock_bus, }; int i2c_dw_amdpsp_probe_lock_support(struct dw_i2c_dev *dev) { struct pci_dev *rdev; if (!IS_REACHABLE(CONFIG_CRYPTO_DEV_CCP_DD)) return -ENODEV; if (!dev) return -ENODEV; if (!(dev->flags & ARBITRATION_SEMAPHORE)) return -ENODEV; /* Allow to bind only one instance of a driver */ if (psp_i2c_dev) return -EEXIST; /* Cezanne uses platform mailbox, Mendocino and later use doorbell */ rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0)); if (rdev->device == 0x1630) _psp_send_i2c_req = psp_send_i2c_req_cezanne; else _psp_send_i2c_req = psp_send_i2c_req_doorbell; pci_dev_put(rdev); if (psp_check_platform_access_status()) return -EPROBE_DEFER; psp_i2c_dev = dev->dev; dev_info(psp_i2c_dev, "I2C bus managed by AMD PSP\n"); /* * Install global locking callbacks for adapter as well as internal i2c * controller locks. */ dev->adapter.lock_ops = &i2c_dw_psp_lock_ops; dev->acquire_lock = psp_acquire_i2c_bus; dev->release_lock = psp_release_i2c_bus; return 0; } |