/*- * Copyright (c) 2018, Mellanox Technologies, Ltd. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS `AS IS' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include extern const struct mlx5_crspace_regmap mlx5_crspace_regmap_mt4117[]; extern const struct mlx5_crspace_regmap mlx5_crspace_regmap_mt4115[]; extern const struct mlx5_crspace_regmap mlx5_crspace_regmap_connectx5[]; struct mlx5_dump_data { const struct mlx5_crspace_regmap *rege; uint32_t *dump; unsigned dump_size; int dump_valid; struct mtx dump_lock; }; static MALLOC_DEFINE(M_MLX5_DUMP, "MLX5DUMP", "MLX5 Firmware dump"); static unsigned mlx5_fwdump_getsize(const struct mlx5_crspace_regmap *rege) { const struct mlx5_crspace_regmap *r; unsigned sz; for (sz = 0, r = rege; r->cnt != 0; r++) sz += r->cnt; return (sz); } static void mlx5_fwdump_destroy_dd(struct mlx5_dump_data *dd) { mtx_destroy(&dd->dump_lock); free(dd->dump, M_MLX5_DUMP); free(dd, M_MLX5_DUMP); } void mlx5_fwdump_prep(struct mlx5_core_dev *mdev) { struct mlx5_dump_data *dd; int error; error = mlx5_vsc_find_cap(mdev); if (error != 0) { /* Inability to create a firmware dump is not fatal. */ device_printf((&mdev->pdev->dev)->bsddev, "WARN: " "mlx5_fwdump_prep failed %d\n", error); return; } dd = malloc(sizeof(struct mlx5_dump_data), M_MLX5_DUMP, M_WAITOK); switch (pci_get_device(mdev->pdev->dev.bsddev)) { case 0x1013: dd->rege = mlx5_crspace_regmap_mt4115; break; case 0x1015: dd->rege = mlx5_crspace_regmap_mt4117; break; case 0x1017: case 0x1019: dd->rege = mlx5_crspace_regmap_connectx5; break; default: free(dd, M_MLX5_DUMP); return; /* silently fail, do not prevent driver attach */ } dd->dump_size = mlx5_fwdump_getsize(dd->rege); dd->dump = malloc(dd->dump_size * sizeof(uint32_t), M_MLX5_DUMP, M_WAITOK | M_ZERO); dd->dump_valid = 0; mtx_init(&dd->dump_lock, "mlx5dmp", NULL, MTX_DEF | MTX_NEW); if (atomic_cmpset_rel_ptr((uintptr_t *)&mdev->dump_data, 0, (uintptr_t)dd) == 0) mlx5_fwdump_destroy_dd(dd); } void mlx5_fwdump(struct mlx5_core_dev *mdev) { struct mlx5_dump_data *dd; const struct mlx5_crspace_regmap *r; uint32_t i, ri; int error; dev_info(&mdev->pdev->dev, "Issuing FW dump\n"); dd = (struct mlx5_dump_data *)atomic_load_acq_ptr((uintptr_t *) &mdev->dump_data); if (dd == NULL) return; mtx_lock(&dd->dump_lock); if (dd->dump_valid) { /* only one dump */ dev_warn(&mdev->pdev->dev, "Only one FW dump can be captured aborting FW dump\n"); goto failed; } /* mlx5_vsc already warns, be silent. */ error = mlx5_vsc_lock(mdev); if (error != 0) goto failed; error = mlx5_vsc_set_space(mdev, MLX5_VSC_DOMAIN_PROTECTED_CRSPACE); if (error != 0) goto unlock_vsc; for (i = 0, r = dd->rege; r->cnt != 0; r++) { for (ri = 0; ri < r->cnt; ri++) { error = mlx5_vsc_read(mdev, r->addr + ri * 4, &dd->dump[i]); if (error != 0) goto unlock_vsc; i++; } } atomic_store_rel_int(&dd->dump_valid, 1); unlock_vsc: mlx5_vsc_unlock(mdev); failed: mtx_unlock(&dd->dump_lock); } void mlx5_fwdump_clean(struct mlx5_core_dev *mdev) { struct mlx5_dump_data *dd; for (;;) { dd = mdev->dump_data; if (dd == NULL) return; if (atomic_cmpset_ptr((uintptr_t *)&mdev->dump_data, (uintptr_t)dd, 0) == 1) { mlx5_fwdump_destroy_dd(dd); return; } } } static int mlx5_dbsf_to_core(const struct mlx5_tool_addr *devaddr, struct mlx5_core_dev **mdev) { device_t dev; struct pci_dev *pdev; dev = pci_find_dbsf(devaddr->domain, devaddr->bus, devaddr->slot, devaddr->func); if (dev == NULL) return (ENOENT); if (device_get_devclass(dev) != mlx5_core_driver.bsdclass) return (EINVAL); pdev = device_get_softc(dev); *mdev = pci_get_drvdata(pdev); if (*mdev == NULL) return (ENOENT); return (0); } static int mlx5_fwdump_copyout(struct mlx5_dump_data *dd, struct mlx5_fwdump_get *fwg) { const struct mlx5_crspace_regmap *r; struct mlx5_fwdump_reg rv, *urv; uint32_t i, ri; int error; if (dd == NULL) return (ENOENT); if (fwg->buf == NULL) { fwg->reg_filled = dd->dump_size; return (0); } if (atomic_load_acq_int(&dd->dump_valid) == 0) return (ENOENT); urv = fwg->buf; for (i = 0, r = dd->rege; r->cnt != 0; r++) { for (ri = 0; ri < r->cnt; ri++) { if (i >= fwg->reg_cnt) goto out; rv.addr = r->addr + ri * 4; rv.val = dd->dump[i]; error = copyout(&rv, urv, sizeof(rv)); if (error != 0) return (error); urv++; i++; } } out: fwg->reg_filled = i; return (0); } static int mlx5_fwdump_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, struct thread *td) { struct mlx5_core_dev *mdev; struct mlx5_fwdump_get *fwg; struct mlx5_tool_addr *devaddr; struct mlx5_dump_data *dd; int error; error = 0; switch (cmd) { case MLX5_FWDUMP_GET: if ((fflag & FREAD) == 0) { error = EBADF; break; } fwg = (struct mlx5_fwdump_get *)data; devaddr = &fwg->devaddr; error = mlx5_dbsf_to_core(devaddr, &mdev); if (error != 0) break; error = mlx5_fwdump_copyout(mdev->dump_data, fwg); break; case MLX5_FWDUMP_RESET: if ((fflag & FWRITE) == 0) { error = EBADF; break; } devaddr = (struct mlx5_tool_addr *)data; error = mlx5_dbsf_to_core(devaddr, &mdev); if (error != 0) break; dd = mdev->dump_data; if (dd != NULL) atomic_store_rel_int(&dd->dump_valid, 0); else error = ENOENT; break; case MLX5_FWDUMP_FORCE: if ((fflag & FWRITE) == 0) { error = EBADF; break; } devaddr = (struct mlx5_tool_addr *)data; error = mlx5_dbsf_to_core(devaddr, &mdev); if (error != 0) break; mlx5_fwdump(mdev); break; default: error = ENOTTY; break; } return (error); } static struct cdevsw mlx5_fwdump_devsw = { .d_version = D_VERSION, .d_ioctl = mlx5_fwdump_ioctl, }; static struct cdev *mlx5_fwdump_dev; int mlx5_fwdump_init(void) { struct make_dev_args mda; int error; make_dev_args_init(&mda); mda.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME; mda.mda_devsw = &mlx5_fwdump_devsw; mda.mda_uid = UID_ROOT; mda.mda_gid = GID_OPERATOR; mda.mda_mode = 0640; error = make_dev_s(&mda, &mlx5_fwdump_dev, "mlx5ctl"); return (-error); } void mlx5_fwdump_fini(void) { if (mlx5_fwdump_dev != NULL) destroy_dev(mlx5_fwdump_dev); }