c47b170b02
Sponsored by: Mellanox Technologies MFC after: 1 week
509 lines
12 KiB
C
509 lines
12 KiB
C
/*-
|
|
* Copyright (c) 2018, 2019 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 <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/fcntl.h>
|
|
#include <dev/mlx5/driver.h>
|
|
#include <dev/mlx5/device.h>
|
|
#include <dev/mlx5/port.h>
|
|
#include <dev/mlx5/mlx5_core/mlx5_core.h>
|
|
#include <dev/mlx5/mlx5io.h>
|
|
#include <dev/mlx5/diagnostics.h>
|
|
|
|
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_core_dev *mdev)
|
|
{
|
|
|
|
mtx_assert(&mdev->dump_lock, MA_OWNED);
|
|
free(mdev->dump_data, M_MLX5_DUMP);
|
|
mdev->dump_data = NULL;
|
|
}
|
|
|
|
void
|
|
mlx5_fwdump_prep(struct mlx5_core_dev *mdev)
|
|
{
|
|
device_t dev;
|
|
int error, vsc_addr;
|
|
unsigned i, sz;
|
|
u32 addr, in, out, next_addr;
|
|
|
|
mdev->dump_data = NULL;
|
|
error = mlx5_vsc_find_cap(mdev);
|
|
if (error != 0) {
|
|
/* Inability to create a firmware dump is not fatal. */
|
|
mlx5_core_warn(mdev,
|
|
"Failed to find vendor-specific capability, error %d\n",
|
|
error);
|
|
return;
|
|
}
|
|
error = mlx5_vsc_lock(mdev);
|
|
if (error != 0)
|
|
return;
|
|
error = mlx5_vsc_set_space(mdev, MLX5_VSC_DOMAIN_SCAN_CRSPACE);
|
|
if (error != 0) {
|
|
mlx5_core_warn(mdev, "VSC scan space is not supported\n");
|
|
goto unlock_vsc;
|
|
}
|
|
dev = mdev->pdev->dev.bsddev;
|
|
vsc_addr = mdev->vsc_addr;
|
|
if (vsc_addr == 0) {
|
|
mlx5_core_warn(mdev, "Cannot read VSC, no address\n");
|
|
goto unlock_vsc;
|
|
}
|
|
|
|
in = 0;
|
|
for (sz = 1, addr = 0;;) {
|
|
MLX5_VSC_SET(vsc_addr, &in, address, addr);
|
|
pci_write_config(dev, vsc_addr + MLX5_VSC_ADDR_OFFSET, in, 4);
|
|
error = mlx5_vsc_wait_on_flag(mdev, 1);
|
|
if (error != 0) {
|
|
mlx5_core_warn(mdev,
|
|
"Failed waiting for read complete flag, error %d addr %#x\n",
|
|
error, addr);
|
|
goto unlock_vsc;
|
|
}
|
|
pci_read_config(dev, vsc_addr + MLX5_VSC_DATA_OFFSET, 4);
|
|
out = pci_read_config(dev, vsc_addr + MLX5_VSC_ADDR_OFFSET, 4);
|
|
next_addr = MLX5_VSC_GET(vsc_addr, &out, address);
|
|
if (next_addr == 0 || next_addr == addr)
|
|
break;
|
|
if (next_addr != addr + 4)
|
|
sz++;
|
|
addr = next_addr;
|
|
}
|
|
if (sz == 1) {
|
|
mlx5_core_warn(mdev, "no output from scan space\n");
|
|
goto unlock_vsc;
|
|
}
|
|
mdev->dump_rege = malloc(sz * sizeof(struct mlx5_crspace_regmap),
|
|
M_MLX5_DUMP, M_WAITOK | M_ZERO);
|
|
|
|
for (i = 0, addr = 0;;) {
|
|
MPASS(i < sz);
|
|
mdev->dump_rege[i].cnt++;
|
|
MLX5_VSC_SET(vsc_addr, &in, address, addr);
|
|
pci_write_config(dev, vsc_addr + MLX5_VSC_ADDR_OFFSET, in, 4);
|
|
error = mlx5_vsc_wait_on_flag(mdev, 1);
|
|
if (error != 0) {
|
|
mlx5_core_warn(mdev,
|
|
"Failed waiting for read complete flag, error %d addr %#x\n",
|
|
error, addr);
|
|
free(mdev->dump_rege, M_MLX5_DUMP);
|
|
mdev->dump_rege = NULL;
|
|
goto unlock_vsc;
|
|
}
|
|
pci_read_config(dev, vsc_addr + MLX5_VSC_DATA_OFFSET, 4);
|
|
out = pci_read_config(dev, vsc_addr + MLX5_VSC_ADDR_OFFSET, 4);
|
|
next_addr = MLX5_VSC_GET(vsc_addr, &out, address);
|
|
if (next_addr == 0 || next_addr == addr)
|
|
break;
|
|
if (next_addr != addr + 4)
|
|
mdev->dump_rege[++i].addr = next_addr;
|
|
addr = next_addr;
|
|
}
|
|
if (i + 1 != sz) {
|
|
mlx5_core_err(mdev,
|
|
"Inconsistent hw crspace reads: sz %u i %u addr %#lx",
|
|
sz, i, (unsigned long)addr);
|
|
}
|
|
|
|
mdev->dump_size = mlx5_fwdump_getsize(mdev->dump_rege);
|
|
mdev->dump_data = malloc(mdev->dump_size * sizeof(uint32_t),
|
|
M_MLX5_DUMP, M_WAITOK | M_ZERO);
|
|
mdev->dump_valid = false;
|
|
mdev->dump_copyout = false;
|
|
|
|
unlock_vsc:
|
|
mlx5_vsc_unlock(mdev);
|
|
}
|
|
|
|
int
|
|
mlx5_fwdump(struct mlx5_core_dev *mdev)
|
|
{
|
|
const struct mlx5_crspace_regmap *r;
|
|
uint32_t i, ri;
|
|
int error;
|
|
|
|
mlx5_core_info(mdev, "Issuing FW dump\n");
|
|
mtx_lock(&mdev->dump_lock);
|
|
if (mdev->dump_data == NULL) {
|
|
error = EIO;
|
|
goto failed;
|
|
}
|
|
if (mdev->dump_valid) {
|
|
/* only one dump */
|
|
mlx5_core_warn(mdev,
|
|
"Only one FW dump can be captured aborting FW dump\n");
|
|
error = EEXIST;
|
|
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 = mdev->dump_rege; r->cnt != 0; r++) {
|
|
for (ri = 0; ri < r->cnt; ri++) {
|
|
error = mlx5_vsc_read(mdev, r->addr + ri * 4,
|
|
&mdev->dump_data[i]);
|
|
if (error != 0)
|
|
goto unlock_vsc;
|
|
i++;
|
|
}
|
|
}
|
|
mdev->dump_valid = true;
|
|
unlock_vsc:
|
|
mlx5_vsc_unlock(mdev);
|
|
failed:
|
|
mtx_unlock(&mdev->dump_lock);
|
|
return (error);
|
|
}
|
|
|
|
void
|
|
mlx5_fwdump_clean(struct mlx5_core_dev *mdev)
|
|
{
|
|
|
|
mtx_lock(&mdev->dump_lock);
|
|
while (mdev->dump_copyout)
|
|
msleep(&mdev->dump_copyout, &mdev->dump_lock, 0, "mlx5fwc", 0);
|
|
mlx5_fwdump_destroy_dd(mdev);
|
|
mtx_unlock(&mdev->dump_lock);
|
|
free(mdev->dump_rege, M_MLX5_DUMP);
|
|
}
|
|
|
|
static int
|
|
mlx5_fwdump_reset(struct mlx5_core_dev *mdev)
|
|
{
|
|
int error;
|
|
|
|
error = 0;
|
|
mtx_lock(&mdev->dump_lock);
|
|
if (mdev->dump_data != NULL) {
|
|
while (mdev->dump_copyout) {
|
|
msleep(&mdev->dump_copyout, &mdev->dump_lock,
|
|
0, "mlx5fwr", 0);
|
|
}
|
|
mdev->dump_valid = false;
|
|
} else {
|
|
error = ENOENT;
|
|
}
|
|
mtx_unlock(&mdev->dump_lock);
|
|
return (error);
|
|
}
|
|
|
|
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_core_dev *mdev, struct mlx5_fwdump_get *fwg)
|
|
{
|
|
const struct mlx5_crspace_regmap *r;
|
|
struct mlx5_fwdump_reg rv, *urv;
|
|
uint32_t i, ri;
|
|
int error;
|
|
|
|
mtx_lock(&mdev->dump_lock);
|
|
if (mdev->dump_data == NULL) {
|
|
mtx_unlock(&mdev->dump_lock);
|
|
return (ENOENT);
|
|
}
|
|
if (fwg->buf == NULL) {
|
|
fwg->reg_filled = mdev->dump_size;
|
|
mtx_unlock(&mdev->dump_lock);
|
|
return (0);
|
|
}
|
|
if (!mdev->dump_valid) {
|
|
mtx_unlock(&mdev->dump_lock);
|
|
return (ENOENT);
|
|
}
|
|
mdev->dump_copyout = true;
|
|
mtx_unlock(&mdev->dump_lock);
|
|
|
|
urv = fwg->buf;
|
|
for (i = 0, r = mdev->dump_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 = mdev->dump_data[i];
|
|
error = copyout(&rv, urv, sizeof(rv));
|
|
if (error != 0)
|
|
return (error);
|
|
urv++;
|
|
i++;
|
|
}
|
|
}
|
|
out:
|
|
fwg->reg_filled = i;
|
|
mtx_lock(&mdev->dump_lock);
|
|
mdev->dump_copyout = false;
|
|
wakeup(&mdev->dump_copyout);
|
|
mtx_unlock(&mdev->dump_lock);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
mlx5_fw_reset(struct mlx5_core_dev *mdev)
|
|
{
|
|
device_t dev, bus;
|
|
int error;
|
|
|
|
error = -mlx5_set_mfrl_reg(mdev, MLX5_FRL_LEVEL3);
|
|
if (error == 0) {
|
|
dev = mdev->pdev->dev.bsddev;
|
|
mtx_lock(&Giant);
|
|
bus = device_get_parent(dev);
|
|
error = BUS_RESET_CHILD(device_get_parent(bus), bus,
|
|
DEVF_RESET_DETACH);
|
|
mtx_unlock(&Giant);
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
mlx5_eeprom_copyout(struct mlx5_core_dev *dev, struct mlx5_eeprom_get *eeprom_info)
|
|
{
|
|
struct mlx5_eeprom eeprom;
|
|
int error;
|
|
|
|
eeprom.i2c_addr = MLX5_I2C_ADDR_LOW;
|
|
eeprom.device_addr = 0;
|
|
eeprom.page_num = MLX5_EEPROM_LOW_PAGE;
|
|
eeprom.page_valid = 0;
|
|
|
|
/* Read three first bytes to get important info */
|
|
error = mlx5_get_eeprom_info(dev, &eeprom);
|
|
if (error != 0) {
|
|
mlx5_core_err(dev,
|
|
"Failed reading EEPROM initial information\n");
|
|
return (error);
|
|
}
|
|
eeprom_info->eeprom_info_page_valid = eeprom.page_valid;
|
|
eeprom_info->eeprom_info_out_len = eeprom.len;
|
|
|
|
if (eeprom_info->eeprom_info_buf == NULL)
|
|
return (0);
|
|
/*
|
|
* Allocate needed length buffer and additional space for
|
|
* page 0x03
|
|
*/
|
|
eeprom.data = malloc(eeprom.len + MLX5_EEPROM_PAGE_LENGTH,
|
|
M_MLX5_EEPROM, M_WAITOK | M_ZERO);
|
|
|
|
/* Read the whole eeprom information */
|
|
error = mlx5_get_eeprom(dev, &eeprom);
|
|
if (error != 0) {
|
|
mlx5_core_err(dev, "Failed reading EEPROM error = %d\n",
|
|
error);
|
|
error = 0;
|
|
/*
|
|
* Continue printing partial information in case of
|
|
* an error
|
|
*/
|
|
}
|
|
error = copyout(eeprom.data, eeprom_info->eeprom_info_buf,
|
|
eeprom.len);
|
|
free(eeprom.data, M_MLX5_EEPROM);
|
|
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
mlx5_ctl_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_fw_update *fu;
|
|
struct firmware fake_fw;
|
|
struct mlx5_eeprom_get *eeprom_info;
|
|
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, 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)
|
|
error = mlx5_fwdump_reset(mdev);
|
|
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;
|
|
error = mlx5_fwdump(mdev);
|
|
break;
|
|
case MLX5_FW_UPDATE:
|
|
if ((fflag & FWRITE) == 0) {
|
|
error = EBADF;
|
|
break;
|
|
}
|
|
fu = (struct mlx5_fw_update *)data;
|
|
if (fu->img_fw_data_len > 10 * 1024 * 1024) {
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
devaddr = &fu->devaddr;
|
|
error = mlx5_dbsf_to_core(devaddr, &mdev);
|
|
if (error != 0)
|
|
break;
|
|
bzero(&fake_fw, sizeof(fake_fw));
|
|
fake_fw.name = "umlx_fw_up";
|
|
fake_fw.datasize = fu->img_fw_data_len;
|
|
fake_fw.version = 1;
|
|
fake_fw.data = (void *)kmem_malloc(fu->img_fw_data_len,
|
|
M_WAITOK);
|
|
if (fake_fw.data == NULL) {
|
|
error = ENOMEM;
|
|
break;
|
|
}
|
|
error = copyin(fu->img_fw_data, __DECONST(void *, fake_fw.data),
|
|
fu->img_fw_data_len);
|
|
if (error == 0)
|
|
error = -mlx5_firmware_flash(mdev, &fake_fw);
|
|
kmem_free((vm_offset_t)fake_fw.data, fu->img_fw_data_len);
|
|
break;
|
|
case MLX5_FW_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;
|
|
error = mlx5_fw_reset(mdev);
|
|
break;
|
|
case MLX5_EEPROM_GET:
|
|
if ((fflag & FREAD) == 0) {
|
|
error = EBADF;
|
|
break;
|
|
}
|
|
eeprom_info = (struct mlx5_eeprom_get *)data;
|
|
devaddr = &eeprom_info->devaddr;
|
|
error = mlx5_dbsf_to_core(devaddr, &mdev);
|
|
if (error != 0)
|
|
break;
|
|
error = mlx5_eeprom_copyout(mdev, eeprom_info);
|
|
break;
|
|
default:
|
|
error = ENOTTY;
|
|
break;
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
static struct cdevsw mlx5_ctl_devsw = {
|
|
.d_version = D_VERSION,
|
|
.d_ioctl = mlx5_ctl_ioctl,
|
|
};
|
|
|
|
static struct cdev *mlx5_ctl_dev;
|
|
|
|
int
|
|
mlx5_ctl_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_ctl_devsw;
|
|
mda.mda_uid = UID_ROOT;
|
|
mda.mda_gid = GID_OPERATOR;
|
|
mda.mda_mode = 0640;
|
|
error = make_dev_s(&mda, &mlx5_ctl_dev, "mlx5ctl");
|
|
return (-error);
|
|
}
|
|
|
|
void
|
|
mlx5_ctl_fini(void)
|
|
{
|
|
|
|
if (mlx5_ctl_dev != NULL)
|
|
destroy_dev(mlx5_ctl_dev);
|
|
|
|
}
|