mlx5_fwdump.c revision 347841
1/*- 2 * Copyright (c) 2018, Mellanox Technologies, Ltd. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS `AS IS' AND 14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE 17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 23 * SUCH DAMAGE. 24 */ 25 26#include <sys/cdefs.h> 27__FBSDID("$FreeBSD: stable/11/sys/dev/mlx5/mlx5_core/mlx5_fwdump.c 347841 2019-05-16 17:50:52Z hselasky $"); 28 29#include <sys/param.h> 30#include <sys/systm.h> 31#include <sys/conf.h> 32#include <sys/fcntl.h> 33#include <dev/mlx5/driver.h> 34#include <dev/mlx5/device.h> 35#include <dev/mlx5/mlx5_core/mlx5_core.h> 36#include <dev/mlx5/mlx5io.h> 37 38extern const struct mlx5_crspace_regmap mlx5_crspace_regmap_mt4117[]; 39extern const struct mlx5_crspace_regmap mlx5_crspace_regmap_mt4115[]; 40extern const struct mlx5_crspace_regmap mlx5_crspace_regmap_connectx5[]; 41 42struct mlx5_dump_data { 43 const struct mlx5_crspace_regmap *rege; 44 uint32_t *dump; 45 unsigned dump_size; 46 int dump_valid; 47 struct mtx dump_lock; 48}; 49 50static MALLOC_DEFINE(M_MLX5_DUMP, "MLX5DUMP", "MLX5 Firmware dump"); 51 52static unsigned 53mlx5_fwdump_getsize(const struct mlx5_crspace_regmap *rege) 54{ 55 const struct mlx5_crspace_regmap *r; 56 unsigned sz; 57 58 for (sz = 0, r = rege; r->cnt != 0; r++) 59 sz += r->cnt; 60 return (sz); 61} 62 63static void 64mlx5_fwdump_destroy_dd(struct mlx5_dump_data *dd) 65{ 66 67 mtx_destroy(&dd->dump_lock); 68 free(dd->dump, M_MLX5_DUMP); 69 free(dd, M_MLX5_DUMP); 70} 71 72void 73mlx5_fwdump_prep(struct mlx5_core_dev *mdev) 74{ 75 struct mlx5_dump_data *dd; 76 int error; 77 78 error = mlx5_vsc_find_cap(mdev); 79 if (error != 0) { 80 /* Inability to create a firmware dump is not fatal. */ 81 device_printf((&mdev->pdev->dev)->bsddev, "WARN: " 82 "mlx5_fwdump_prep failed %d\n", error); 83 return; 84 } 85 dd = malloc(sizeof(struct mlx5_dump_data), M_MLX5_DUMP, M_WAITOK); 86 switch (pci_get_device(mdev->pdev->dev.bsddev)) { 87 case 0x1013: 88 dd->rege = mlx5_crspace_regmap_mt4115; 89 break; 90 case 0x1015: 91 dd->rege = mlx5_crspace_regmap_mt4117; 92 break; 93 case 0x1017: 94 case 0x1019: 95 dd->rege = mlx5_crspace_regmap_connectx5; 96 break; 97 default: 98 free(dd, M_MLX5_DUMP); 99 return; /* silently fail, do not prevent driver attach */ 100 } 101 dd->dump_size = mlx5_fwdump_getsize(dd->rege); 102 dd->dump = malloc(dd->dump_size * sizeof(uint32_t), M_MLX5_DUMP, 103 M_WAITOK | M_ZERO); 104 dd->dump_valid = 0; 105 mtx_init(&dd->dump_lock, "mlx5dmp", NULL, MTX_DEF | MTX_NEW); 106 if (atomic_cmpset_rel_ptr((uintptr_t *)&mdev->dump_data, 0, 107 (uintptr_t)dd) == 0) 108 mlx5_fwdump_destroy_dd(dd); 109} 110 111void 112mlx5_fwdump(struct mlx5_core_dev *mdev) 113{ 114 struct mlx5_dump_data *dd; 115 const struct mlx5_crspace_regmap *r; 116 uint32_t i, ri; 117 int error; 118 119 dev_info(&mdev->pdev->dev, "Issuing FW dump\n"); 120 dd = (struct mlx5_dump_data *)atomic_load_acq_ptr((uintptr_t *) 121 &mdev->dump_data); 122 if (dd == NULL) 123 return; 124 mtx_lock(&dd->dump_lock); 125 if (dd->dump_valid) { 126 /* only one dump */ 127 dev_warn(&mdev->pdev->dev, 128 "Only one FW dump can be captured aborting FW dump\n"); 129 goto failed; 130 } 131 132 /* mlx5_vsc already warns, be silent. */ 133 error = mlx5_vsc_lock(mdev); 134 if (error != 0) 135 goto failed; 136 error = mlx5_vsc_set_space(mdev, MLX5_VSC_DOMAIN_PROTECTED_CRSPACE); 137 if (error != 0) 138 goto unlock_vsc; 139 for (i = 0, r = dd->rege; r->cnt != 0; r++) { 140 for (ri = 0; ri < r->cnt; ri++) { 141 error = mlx5_vsc_read(mdev, r->addr + ri * 4, 142 &dd->dump[i]); 143 if (error != 0) 144 goto unlock_vsc; 145 i++; 146 } 147 } 148 atomic_store_rel_int(&dd->dump_valid, 1); 149unlock_vsc: 150 mlx5_vsc_unlock(mdev); 151failed: 152 mtx_unlock(&dd->dump_lock); 153} 154 155void 156mlx5_fwdump_clean(struct mlx5_core_dev *mdev) 157{ 158 struct mlx5_dump_data *dd; 159 160 for (;;) { 161 dd = mdev->dump_data; 162 if (dd == NULL) 163 return; 164 if (atomic_cmpset_ptr((uintptr_t *)&mdev->dump_data, 165 (uintptr_t)dd, 0) == 1) { 166 mlx5_fwdump_destroy_dd(dd); 167 return; 168 } 169 } 170} 171 172static int 173mlx5_dbsf_to_core(const struct mlx5_tool_addr *devaddr, 174 struct mlx5_core_dev **mdev) 175{ 176 device_t dev; 177 struct pci_dev *pdev; 178 179 dev = pci_find_dbsf(devaddr->domain, devaddr->bus, devaddr->slot, 180 devaddr->func); 181 if (dev == NULL) 182 return (ENOENT); 183 if (device_get_devclass(dev) != mlx5_core_driver.bsdclass) 184 return (EINVAL); 185 pdev = device_get_softc(dev); 186 *mdev = pci_get_drvdata(pdev); 187 if (*mdev == NULL) 188 return (ENOENT); 189 return (0); 190} 191 192static int 193mlx5_fwdump_copyout(struct mlx5_dump_data *dd, struct mlx5_fwdump_get *fwg) 194{ 195 const struct mlx5_crspace_regmap *r; 196 struct mlx5_fwdump_reg rv, *urv; 197 uint32_t i, ri; 198 int error; 199 200 if (dd == NULL) 201 return (ENOENT); 202 if (fwg->buf == NULL) { 203 fwg->reg_filled = dd->dump_size; 204 return (0); 205 } 206 if (atomic_load_acq_int(&dd->dump_valid) == 0) 207 return (ENOENT); 208 209 urv = fwg->buf; 210 for (i = 0, r = dd->rege; r->cnt != 0; r++) { 211 for (ri = 0; ri < r->cnt; ri++) { 212 if (i >= fwg->reg_cnt) 213 goto out; 214 rv.addr = r->addr + ri * 4; 215 rv.val = dd->dump[i]; 216 error = copyout(&rv, urv, sizeof(rv)); 217 if (error != 0) 218 return (error); 219 urv++; 220 i++; 221 } 222 } 223out: 224 fwg->reg_filled = i; 225 return (0); 226} 227 228static int 229mlx5_fwdump_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag, 230 struct thread *td) 231{ 232 struct mlx5_core_dev *mdev; 233 struct mlx5_fwdump_get *fwg; 234 struct mlx5_tool_addr *devaddr; 235 struct mlx5_dump_data *dd; 236 struct mlx5_fw_update *fu; 237 struct firmware fake_fw; 238 int error; 239 240 error = 0; 241 switch (cmd) { 242 case MLX5_FWDUMP_GET: 243 if ((fflag & FREAD) == 0) { 244 error = EBADF; 245 break; 246 } 247 fwg = (struct mlx5_fwdump_get *)data; 248 devaddr = &fwg->devaddr; 249 error = mlx5_dbsf_to_core(devaddr, &mdev); 250 if (error != 0) 251 break; 252 error = mlx5_fwdump_copyout(mdev->dump_data, fwg); 253 break; 254 case MLX5_FWDUMP_RESET: 255 if ((fflag & FWRITE) == 0) { 256 error = EBADF; 257 break; 258 } 259 devaddr = (struct mlx5_tool_addr *)data; 260 error = mlx5_dbsf_to_core(devaddr, &mdev); 261 if (error != 0) 262 break; 263 dd = mdev->dump_data; 264 if (dd != NULL) 265 atomic_store_rel_int(&dd->dump_valid, 0); 266 else 267 error = ENOENT; 268 break; 269 case MLX5_FWDUMP_FORCE: 270 if ((fflag & FWRITE) == 0) { 271 error = EBADF; 272 break; 273 } 274 devaddr = (struct mlx5_tool_addr *)data; 275 error = mlx5_dbsf_to_core(devaddr, &mdev); 276 if (error != 0) 277 break; 278 mlx5_fwdump(mdev); 279 break; 280 case MLX5_FW_UPDATE: 281 if ((fflag & FWRITE) == 0) { 282 error = EBADF; 283 break; 284 } 285 fu = (struct mlx5_fw_update *)data; 286 if (fu->img_fw_data_len > 10 * 1024 * 1024) { 287 error = EINVAL; 288 break; 289 } 290 devaddr = &fu->devaddr; 291 error = mlx5_dbsf_to_core(devaddr, &mdev); 292 if (error != 0) 293 break; 294 bzero(&fake_fw, sizeof(fake_fw)); 295 fake_fw.name = "umlx_fw_up"; 296 fake_fw.datasize = fu->img_fw_data_len; 297 fake_fw.version = 1; 298 fake_fw.data = (void *)kmem_malloc(fu->img_fw_data_len, 299 M_WAITOK); 300 if (fake_fw.data == NULL) { 301 error = ENOMEM; 302 break; 303 } 304 error = copyin(fu->img_fw_data, __DECONST(void *, fake_fw.data), 305 fu->img_fw_data_len); 306 if (error == 0) 307 error = -mlx5_firmware_flash(mdev, &fake_fw); 308 kmem_free((vm_offset_t)fake_fw.data, fu->img_fw_data_len); 309 break; 310 default: 311 error = ENOTTY; 312 break; 313 } 314 return (error); 315} 316 317static struct cdevsw mlx5_fwdump_devsw = { 318 .d_version = D_VERSION, 319 .d_ioctl = mlx5_fwdump_ioctl, 320}; 321 322static struct cdev *mlx5_fwdump_dev; 323 324int 325mlx5_fwdump_init(void) 326{ 327 struct make_dev_args mda; 328 int error; 329 330 make_dev_args_init(&mda); 331 mda.mda_flags = MAKEDEV_WAITOK | MAKEDEV_CHECKNAME; 332 mda.mda_devsw = &mlx5_fwdump_devsw; 333 mda.mda_uid = UID_ROOT; 334 mda.mda_gid = GID_OPERATOR; 335 mda.mda_mode = 0640; 336 error = make_dev_s(&mda, &mlx5_fwdump_dev, "mlx5ctl"); 337 return (-error); 338} 339 340void 341mlx5_fwdump_fini(void) 342{ 343 344 if (mlx5_fwdump_dev != NULL) 345 destroy_dev(mlx5_fwdump_dev); 346 347} 348