| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* Copyright(c) 2023 Advanced Micro Devices, Inc */ |
| 3 | |
| 4 | #include <linux/errno.h> |
| 5 | #include <linux/pci.h> |
| 6 | #include <linux/utsname.h> |
| 7 | |
| 8 | #include "core.h" |
| 9 | |
| 10 | int pdsc_err_to_errno(enum pds_core_status_code code) |
| 11 | { |
| 12 | switch (code) { |
| 13 | case PDS_RC_SUCCESS: |
| 14 | return 0; |
| 15 | case PDS_RC_EVERSION: |
| 16 | case PDS_RC_EQTYPE: |
| 17 | case PDS_RC_EQID: |
| 18 | case PDS_RC_EINVAL: |
| 19 | case PDS_RC_ENOSUPP: |
| 20 | return -EINVAL; |
| 21 | case PDS_RC_EPERM: |
| 22 | return -EPERM; |
| 23 | case PDS_RC_ENOENT: |
| 24 | return -ENOENT; |
| 25 | case PDS_RC_EAGAIN: |
| 26 | return -EAGAIN; |
| 27 | case PDS_RC_ENOMEM: |
| 28 | return -ENOMEM; |
| 29 | case PDS_RC_EFAULT: |
| 30 | return -EFAULT; |
| 31 | case PDS_RC_EBUSY: |
| 32 | return -EBUSY; |
| 33 | case PDS_RC_EEXIST: |
| 34 | return -EEXIST; |
| 35 | case PDS_RC_EVFID: |
| 36 | return -ENODEV; |
| 37 | case PDS_RC_ECLIENT: |
| 38 | return -ECHILD; |
| 39 | case PDS_RC_ENOSPC: |
| 40 | return -ENOSPC; |
| 41 | case PDS_RC_ERANGE: |
| 42 | return -ERANGE; |
| 43 | case PDS_RC_BAD_ADDR: |
| 44 | return -EFAULT; |
| 45 | case PDS_RC_BAD_PCI: |
| 46 | return -ENXIO; |
| 47 | case PDS_RC_EOPCODE: |
| 48 | case PDS_RC_EINTR: |
| 49 | case PDS_RC_DEV_CMD: |
| 50 | case PDS_RC_ERROR: |
| 51 | case PDS_RC_ERDMA: |
| 52 | case PDS_RC_EIO: |
| 53 | default: |
| 54 | return -EIO; |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | bool pdsc_is_fw_running(struct pdsc *pdsc) |
| 59 | { |
| 60 | if (!pdsc->info_regs) |
| 61 | return false; |
| 62 | |
| 63 | pdsc->fw_status = ioread8(&pdsc->info_regs->fw_status); |
| 64 | pdsc->last_fw_time = jiffies; |
| 65 | pdsc->last_hb = ioread32(&pdsc->info_regs->fw_heartbeat); |
| 66 | |
| 67 | /* Firmware is useful only if the running bit is set and |
| 68 | * fw_status != 0xff (bad PCI read) |
| 69 | */ |
| 70 | return (pdsc->fw_status != PDS_RC_BAD_PCI) && |
| 71 | (pdsc->fw_status & PDS_CORE_FW_STS_F_RUNNING); |
| 72 | } |
| 73 | |
| 74 | bool pdsc_is_fw_good(struct pdsc *pdsc) |
| 75 | { |
| 76 | bool fw_running = pdsc_is_fw_running(pdsc); |
| 77 | u8 gen; |
| 78 | |
| 79 | /* Make sure to update the cached fw_status by calling |
| 80 | * pdsc_is_fw_running() before getting the generation |
| 81 | */ |
| 82 | gen = pdsc->fw_status & PDS_CORE_FW_STS_F_GENERATION; |
| 83 | |
| 84 | return fw_running && gen == pdsc->fw_generation; |
| 85 | } |
| 86 | |
| 87 | static u8 pdsc_devcmd_status(struct pdsc *pdsc) |
| 88 | { |
| 89 | return ioread8(&pdsc->cmd_regs->comp.status); |
| 90 | } |
| 91 | |
| 92 | static bool pdsc_devcmd_done(struct pdsc *pdsc) |
| 93 | { |
| 94 | return ioread32(&pdsc->cmd_regs->done) & PDS_CORE_DEV_CMD_DONE; |
| 95 | } |
| 96 | |
| 97 | static void pdsc_devcmd_dbell(struct pdsc *pdsc) |
| 98 | { |
| 99 | iowrite32(0, &pdsc->cmd_regs->done); |
| 100 | iowrite32(1, &pdsc->cmd_regs->doorbell); |
| 101 | } |
| 102 | |
| 103 | static void pdsc_devcmd_clean(struct pdsc *pdsc) |
| 104 | { |
| 105 | iowrite32(0, &pdsc->cmd_regs->doorbell); |
| 106 | memset_io(&pdsc->cmd_regs->cmd, 0, sizeof(pdsc->cmd_regs->cmd)); |
| 107 | } |
| 108 | |
| 109 | static const char *pdsc_devcmd_str(int opcode) |
| 110 | { |
| 111 | switch (opcode) { |
| 112 | case PDS_CORE_CMD_NOP: |
| 113 | return "PDS_CORE_CMD_NOP" ; |
| 114 | case PDS_CORE_CMD_IDENTIFY: |
| 115 | return "PDS_CORE_CMD_IDENTIFY" ; |
| 116 | case PDS_CORE_CMD_RESET: |
| 117 | return "PDS_CORE_CMD_RESET" ; |
| 118 | case PDS_CORE_CMD_INIT: |
| 119 | return "PDS_CORE_CMD_INIT" ; |
| 120 | case PDS_CORE_CMD_FW_DOWNLOAD: |
| 121 | return "PDS_CORE_CMD_FW_DOWNLOAD" ; |
| 122 | case PDS_CORE_CMD_FW_CONTROL: |
| 123 | return "PDS_CORE_CMD_FW_CONTROL" ; |
| 124 | default: |
| 125 | return "PDS_CORE_CMD_UNKNOWN" ; |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | static int pdsc_devcmd_wait(struct pdsc *pdsc, u8 opcode, int max_seconds) |
| 130 | { |
| 131 | struct device *dev = pdsc->dev; |
| 132 | unsigned long start_time; |
| 133 | unsigned long max_wait; |
| 134 | unsigned long duration; |
| 135 | int timeout = 0; |
| 136 | bool running; |
| 137 | int done = 0; |
| 138 | int err = 0; |
| 139 | int status; |
| 140 | |
| 141 | start_time = jiffies; |
| 142 | max_wait = start_time + (max_seconds * HZ); |
| 143 | |
| 144 | while (!done && !timeout) { |
| 145 | running = pdsc_is_fw_running(pdsc); |
| 146 | if (!running) |
| 147 | break; |
| 148 | |
| 149 | done = pdsc_devcmd_done(pdsc); |
| 150 | if (done) |
| 151 | break; |
| 152 | |
| 153 | timeout = time_after(jiffies, max_wait); |
| 154 | if (timeout) |
| 155 | break; |
| 156 | |
| 157 | usleep_range(min: 100, max: 200); |
| 158 | } |
| 159 | duration = jiffies - start_time; |
| 160 | |
| 161 | if (done && duration > HZ) |
| 162 | dev_dbg(dev, "DEVCMD %d %s after %ld secs\n" , |
| 163 | opcode, pdsc_devcmd_str(opcode), duration / HZ); |
| 164 | |
| 165 | if ((!done || timeout) && running) { |
| 166 | dev_err(dev, "DEVCMD %d %s timeout, done %d timeout %d max_seconds=%d\n" , |
| 167 | opcode, pdsc_devcmd_str(opcode), done, timeout, |
| 168 | max_seconds); |
| 169 | err = -ETIMEDOUT; |
| 170 | pdsc_devcmd_clean(pdsc); |
| 171 | } |
| 172 | |
| 173 | status = pdsc_devcmd_status(pdsc); |
| 174 | err = pdsc_err_to_errno(code: status); |
| 175 | if (err && err != -EAGAIN) |
| 176 | dev_err(dev, "DEVCMD %d %s failed, status=%d err %d %pe\n" , |
| 177 | opcode, pdsc_devcmd_str(opcode), status, err, |
| 178 | ERR_PTR(err)); |
| 179 | |
| 180 | return err; |
| 181 | } |
| 182 | |
| 183 | int pdsc_devcmd_locked(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, |
| 184 | union pds_core_dev_comp *comp, int max_seconds) |
| 185 | { |
| 186 | int err; |
| 187 | |
| 188 | if (!pdsc->cmd_regs) |
| 189 | return -ENXIO; |
| 190 | |
| 191 | memcpy_toio(&pdsc->cmd_regs->cmd, cmd, sizeof(*cmd)); |
| 192 | pdsc_devcmd_dbell(pdsc); |
| 193 | err = pdsc_devcmd_wait(pdsc, opcode: cmd->opcode, max_seconds); |
| 194 | |
| 195 | if ((err == -ENXIO || err == -ETIMEDOUT) && pdsc->wq) |
| 196 | queue_work(wq: pdsc->wq, work: &pdsc->health_work); |
| 197 | else |
| 198 | memcpy_fromio(comp, &pdsc->cmd_regs->comp, sizeof(*comp)); |
| 199 | |
| 200 | return err; |
| 201 | } |
| 202 | |
| 203 | int pdsc_devcmd(struct pdsc *pdsc, union pds_core_dev_cmd *cmd, |
| 204 | union pds_core_dev_comp *comp, int max_seconds) |
| 205 | { |
| 206 | int err; |
| 207 | |
| 208 | mutex_lock(&pdsc->devcmd_lock); |
| 209 | err = pdsc_devcmd_locked(pdsc, cmd, comp, max_seconds); |
| 210 | mutex_unlock(lock: &pdsc->devcmd_lock); |
| 211 | |
| 212 | return err; |
| 213 | } |
| 214 | |
| 215 | int pdsc_devcmd_init(struct pdsc *pdsc) |
| 216 | { |
| 217 | union pds_core_dev_comp comp = {}; |
| 218 | union pds_core_dev_cmd cmd = { |
| 219 | .opcode = PDS_CORE_CMD_INIT, |
| 220 | }; |
| 221 | |
| 222 | return pdsc_devcmd(pdsc, cmd: &cmd, comp: &comp, max_seconds: pdsc->devcmd_timeout); |
| 223 | } |
| 224 | |
| 225 | int pdsc_devcmd_reset(struct pdsc *pdsc) |
| 226 | { |
| 227 | union pds_core_dev_comp comp = {}; |
| 228 | union pds_core_dev_cmd cmd = { |
| 229 | .reset.opcode = PDS_CORE_CMD_RESET, |
| 230 | }; |
| 231 | |
| 232 | if (!pdsc_is_fw_running(pdsc)) |
| 233 | return 0; |
| 234 | |
| 235 | return pdsc_devcmd(pdsc, cmd: &cmd, comp: &comp, max_seconds: pdsc->devcmd_timeout); |
| 236 | } |
| 237 | |
| 238 | static int pdsc_devcmd_identify_locked(struct pdsc *pdsc) |
| 239 | { |
| 240 | union pds_core_dev_comp comp = {}; |
| 241 | union pds_core_dev_cmd cmd = { |
| 242 | .identify.opcode = PDS_CORE_CMD_IDENTIFY, |
| 243 | .identify.ver = PDS_CORE_IDENTITY_VERSION_1, |
| 244 | }; |
| 245 | |
| 246 | return pdsc_devcmd_locked(pdsc, cmd: &cmd, comp: &comp, max_seconds: pdsc->devcmd_timeout); |
| 247 | } |
| 248 | |
| 249 | static void pdsc_init_devinfo(struct pdsc *pdsc) |
| 250 | { |
| 251 | pdsc->dev_info.asic_type = ioread8(&pdsc->info_regs->asic_type); |
| 252 | pdsc->dev_info.asic_rev = ioread8(&pdsc->info_regs->asic_rev); |
| 253 | pdsc->fw_generation = PDS_CORE_FW_STS_F_GENERATION & |
| 254 | ioread8(&pdsc->info_regs->fw_status); |
| 255 | |
| 256 | memcpy_fromio(pdsc->dev_info.fw_version, |
| 257 | pdsc->info_regs->fw_version, |
| 258 | PDS_CORE_DEVINFO_FWVERS_BUFLEN); |
| 259 | pdsc->dev_info.fw_version[PDS_CORE_DEVINFO_FWVERS_BUFLEN] = 0; |
| 260 | |
| 261 | memcpy_fromio(pdsc->dev_info.serial_num, |
| 262 | pdsc->info_regs->serial_num, |
| 263 | PDS_CORE_DEVINFO_SERIAL_BUFLEN); |
| 264 | pdsc->dev_info.serial_num[PDS_CORE_DEVINFO_SERIAL_BUFLEN] = 0; |
| 265 | |
| 266 | dev_dbg(pdsc->dev, "fw_version %s\n" , pdsc->dev_info.fw_version); |
| 267 | } |
| 268 | |
| 269 | static int pdsc_identify(struct pdsc *pdsc) |
| 270 | { |
| 271 | struct pds_core_drv_identity drv = {}; |
| 272 | size_t sz; |
| 273 | int err; |
| 274 | int n; |
| 275 | |
| 276 | drv.drv_type = cpu_to_le32(PDS_DRIVER_LINUX); |
| 277 | /* Catching the return quiets a Wformat-truncation complaint */ |
| 278 | n = snprintf(buf: drv.driver_ver_str, size: sizeof(drv.driver_ver_str), |
| 279 | fmt: "%s %s" , PDS_CORE_DRV_NAME, utsname()->release); |
| 280 | if (n > sizeof(drv.driver_ver_str)) |
| 281 | dev_dbg(pdsc->dev, "release name truncated, don't care\n" ); |
| 282 | |
| 283 | /* Next let's get some info about the device |
| 284 | * We use the devcmd_lock at this level in order to |
| 285 | * get safe access to the cmd_regs->data before anyone |
| 286 | * else can mess it up |
| 287 | */ |
| 288 | mutex_lock(&pdsc->devcmd_lock); |
| 289 | |
| 290 | sz = min_t(size_t, sizeof(drv), sizeof(pdsc->cmd_regs->data)); |
| 291 | memcpy_toio(&pdsc->cmd_regs->data, &drv, sz); |
| 292 | |
| 293 | err = pdsc_devcmd_identify_locked(pdsc); |
| 294 | if (!err) { |
| 295 | sz = min_t(size_t, sizeof(pdsc->dev_ident), |
| 296 | sizeof(pdsc->cmd_regs->data)); |
| 297 | memcpy_fromio(&pdsc->dev_ident, &pdsc->cmd_regs->data, sz); |
| 298 | } |
| 299 | mutex_unlock(lock: &pdsc->devcmd_lock); |
| 300 | |
| 301 | if (err) { |
| 302 | dev_err(pdsc->dev, "Cannot identify device: %pe\n" , |
| 303 | ERR_PTR(err)); |
| 304 | return err; |
| 305 | } |
| 306 | |
| 307 | if (isprint(pdsc->dev_info.fw_version[0]) && |
| 308 | isascii(pdsc->dev_info.fw_version[0])) |
| 309 | dev_info(pdsc->dev, "FW: %.*s\n" , |
| 310 | (int)(sizeof(pdsc->dev_info.fw_version) - 1), |
| 311 | pdsc->dev_info.fw_version); |
| 312 | else |
| 313 | dev_info(pdsc->dev, "FW: (invalid string) 0x%02x 0x%02x 0x%02x 0x%02x ...\n" , |
| 314 | (u8)pdsc->dev_info.fw_version[0], |
| 315 | (u8)pdsc->dev_info.fw_version[1], |
| 316 | (u8)pdsc->dev_info.fw_version[2], |
| 317 | (u8)pdsc->dev_info.fw_version[3]); |
| 318 | |
| 319 | return 0; |
| 320 | } |
| 321 | |
| 322 | void pdsc_dev_uninit(struct pdsc *pdsc) |
| 323 | { |
| 324 | if (pdsc->intr_info) { |
| 325 | int i; |
| 326 | |
| 327 | for (i = 0; i < pdsc->nintrs; i++) |
| 328 | pdsc_intr_free(pdsc, index: i); |
| 329 | |
| 330 | kfree(objp: pdsc->intr_info); |
| 331 | pdsc->intr_info = NULL; |
| 332 | pdsc->nintrs = 0; |
| 333 | } |
| 334 | |
| 335 | pci_free_irq_vectors(dev: pdsc->pdev); |
| 336 | } |
| 337 | |
| 338 | int pdsc_dev_init(struct pdsc *pdsc) |
| 339 | { |
| 340 | unsigned int nintrs; |
| 341 | int err; |
| 342 | |
| 343 | /* Initial init and reset of device */ |
| 344 | pdsc_init_devinfo(pdsc); |
| 345 | pdsc->devcmd_timeout = PDS_CORE_DEVCMD_TIMEOUT; |
| 346 | |
| 347 | err = pdsc_devcmd_reset(pdsc); |
| 348 | if (err) |
| 349 | return err; |
| 350 | |
| 351 | err = pdsc_identify(pdsc); |
| 352 | if (err) |
| 353 | return err; |
| 354 | |
| 355 | pdsc_debugfs_add_ident(pdsc); |
| 356 | |
| 357 | /* Now we can reserve interrupts */ |
| 358 | nintrs = le32_to_cpu(pdsc->dev_ident.nintrs); |
| 359 | nintrs = min_t(unsigned int, num_online_cpus(), nintrs); |
| 360 | |
| 361 | /* Get intr_info struct array for tracking */ |
| 362 | pdsc->intr_info = kcalloc(nintrs, sizeof(*pdsc->intr_info), GFP_KERNEL); |
| 363 | if (!pdsc->intr_info) |
| 364 | return -ENOMEM; |
| 365 | |
| 366 | err = pci_alloc_irq_vectors(dev: pdsc->pdev, min_vecs: nintrs, max_vecs: nintrs, PCI_IRQ_MSIX); |
| 367 | if (err != nintrs) { |
| 368 | dev_err(pdsc->dev, "Can't get %d intrs from OS: %pe\n" , |
| 369 | nintrs, ERR_PTR(err)); |
| 370 | err = -ENOSPC; |
| 371 | goto err_out; |
| 372 | } |
| 373 | pdsc->nintrs = nintrs; |
| 374 | |
| 375 | return 0; |
| 376 | |
| 377 | err_out: |
| 378 | kfree(objp: pdsc->intr_info); |
| 379 | pdsc->intr_info = NULL; |
| 380 | |
| 381 | return err; |
| 382 | } |
| 383 | |