root/usr.sbin/mlx5tool/mlx5tool.c
/*-
 * 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 <sys/param.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <dev/mlx5/mlx5io.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* stolen from pciconf.c: parsesel() */
static int
parse_pci_addr(const char *addrstr, struct mlx5_tool_addr *addr)
{
        char *eppos;
        unsigned long selarr[4];
        int i;

        if (addrstr == NULL) {
                warnx("no pci address specified");
                return (1);
        }
        if (strncmp(addrstr, "pci", 3) == 0) {
                addrstr += 3;
                i = 0;
                while (isdigit(*addrstr) && i < 4) {
                        selarr[i++] = strtoul(addrstr, &eppos, 10);
                        addrstr = eppos;
                        if (*addrstr == ':')
                                addrstr++;
                }
                if (i > 0 && *addrstr == '\0') {
                        addr->func = (i > 2) ? selarr[--i] : 0;
                        addr->slot = (i > 0) ? selarr[--i] : 0;
                        addr->bus = (i > 0) ? selarr[--i] : 0;
                        addr->domain = (i > 0) ? selarr[--i] : 0;
                        return (0);
                }
        }
        warnx("invalid pci address %s", addrstr);
        return (1);
}

static int
mlx5tool_save_dump(int ctldev, const struct mlx5_tool_addr *addr,
    const char *dumpname)
{
        struct mlx5_fwdump_get fdg;
        struct mlx5_fwdump_reg *rege;
        FILE *dump;
        size_t cnt;
        int error, res;

        if (dumpname == NULL)
                dump = stdout;
        else
                dump = fopen(dumpname, "w");
        if (dump == NULL) {
                warn("open %s", dumpname);
                return (1);
        }
        res = 1;
        memset(&fdg, 0, sizeof(fdg));
        fdg.devaddr = *addr;
        error = ioctl(ctldev, MLX5_FWDUMP_GET, &fdg);
        if (error != 0) {
                warn("MLX5_FWDUMP_GET dumpsize");
                goto out;
        }
        rege = calloc(fdg.reg_filled, sizeof(*rege));
        if (rege == NULL) {
                warn("alloc rege");
                goto out;
        }
        fdg.buf = rege;
        fdg.reg_cnt = fdg.reg_filled;
        error = ioctl(ctldev, MLX5_FWDUMP_GET, &fdg);
        if (error != 0) {
                if (errno == ENOENT)
                        warnx("no dump recorded");
                else
                        warn("MLX5_FWDUMP_GET dump fetch");
                goto out;
        }
        for (cnt = 0; cnt < fdg.reg_cnt; cnt++, rege++)
                fprintf(dump, "0x%08x\t0x%08x\n", rege->addr, rege->val);
        res = 0;
out:
        if (dump != stdout)
                fclose(dump);
        return (res);
}

static int
mlx5tool_dump_reset(int ctldev, const struct mlx5_tool_addr *addr)
{

        if (ioctl(ctldev, MLX5_FWDUMP_RESET, addr) == -1) {
                warn("MLX5_FWDUMP_RESET");
                return (1);
        }
        return (0);
}

static int
mlx5tool_dump_force(int ctldev, const struct mlx5_tool_addr *addr)
{

        if (ioctl(ctldev, MLX5_FWDUMP_FORCE, addr) == -1) {
                warn("MLX5_FWDUMP_FORCE");
                return (1);
        }
        return (0);
}

static int
mlx5tool_fw_update(int ctldev, const struct mlx5_tool_addr *addr,
    const char *img_fw_path)
{
        struct stat st;
        struct mlx5_fw_update fwup;
        int error, fd, res;

        res = 0;
        fd = open(img_fw_path, O_RDONLY);
        if (fd == -1) {
                warn("Unable to open %s", img_fw_path);
                res = 1;
                goto close_fd;
        }
        error = fstat(fd, &st);
        if (error != 0) {
                warn("Unable to stat %s", img_fw_path);
                res = 1;
                goto close_fd;
        }
        memset(&fwup, 0, sizeof(fwup));
        memcpy(&fwup.devaddr, addr, sizeof(fwup.devaddr));
        fwup.img_fw_data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE,
            fd, 0);
        if (fwup.img_fw_data == MAP_FAILED) {
                warn("Unable to mmap %s", img_fw_path);
                res = 1;
                goto close_fd;
        }
        fwup.img_fw_data_len = st.st_size;

        error = ioctl(ctldev, MLX5_FW_UPDATE, &fwup);
        if (error == -1) {
                warn("MLX5_FW_UPDATE");
        }

        munmap(fwup.img_fw_data, st.st_size);
close_fd:
        close(fd);
        return (res);
}

static int
mlx5tool_fw_reset(int ctldev, const struct mlx5_tool_addr *addr)
{

        if (ioctl(ctldev, MLX5_FW_RESET, addr) == -1) {
                warn("MLX5_FW_RESET");
                return (1);
        }
        return (0);
}

#define MLX5_EEPROM_HIGH_PAGE_OFFSET            128
#define MLX5_EEPROM_PAGE_LENGTH                 256

static void
mlx5tool_eeprom_print(struct mlx5_eeprom_get *eeprom_info)
{
        int index_in_row, line_length, row;
        size_t byte_to_write;

        byte_to_write = 0;
        line_length = 16;

        printf("\nOffset\t\tValues\n");
        printf("------\t\t------");
        while (byte_to_write < eeprom_info->eeprom_info_out_len) {
                printf("\n0x%04zX\t\t", byte_to_write);
                for (index_in_row = 0; index_in_row < line_length;
                    index_in_row++) {
                        printf("%02X ",
                            ((uint8_t *)eeprom_info->eeprom_info_buf)[
                            byte_to_write]);
                        byte_to_write++;
                }
        }

        if (eeprom_info->eeprom_info_page_valid) {
                row = MLX5_EEPROM_HIGH_PAGE_OFFSET;
                printf("\n\nUpper Page 0x03\n");
                printf("\nOffset\t\tValues\n");
                printf("------\t\t------");
                for (row = MLX5_EEPROM_HIGH_PAGE_OFFSET;
                    row < MLX5_EEPROM_PAGE_LENGTH;) {
                        printf("\n0x%04X\t\t", row);
                        for (index_in_row = 0;
                             index_in_row < line_length;
                             index_in_row++) {
                                printf("%02X ",
                                    ((uint8_t *)eeprom_info->
                                    eeprom_info_buf)[byte_to_write]);
                                byte_to_write++;
                                row++;
                        }
                }
        }
        printf("\n");
}

static int
mlx5tool_get_eeprom_info(int ctldev, const struct mlx5_tool_addr *addr)
{
        struct mlx5_eeprom_get eeprom_info;
        int error;

        memset(&eeprom_info, 0, sizeof(eeprom_info));
        eeprom_info.devaddr = *addr;

        error = ioctl(ctldev, MLX5_EEPROM_GET, &eeprom_info);
        if (error != 0) {
                warn("MLX5_EEPROM_GET");
                return (error);
        }
        eeprom_info.eeprom_info_buf =
            malloc(eeprom_info.eeprom_info_out_len + MLX5_EEPROM_PAGE_LENGTH);
        if (eeprom_info.eeprom_info_buf == NULL) {
                warn("alloc eeprom_info.eeprom_info_buf ");
                return (ENOMEM);
        }
        error = ioctl(ctldev, MLX5_EEPROM_GET, &eeprom_info);
        if (error != 0) {
                warn("MLX5_EEPROM_GET");
                free(eeprom_info.eeprom_info_buf);
                return (error);
        }

        mlx5tool_eeprom_print(&eeprom_info);

        free(eeprom_info.eeprom_info_buf);
        return (0);
}

static void
usage(void)
{

        fprintf(stderr,
            "Usage: mlx5tool -d pci<d:b:s:f> [-w -o dump.file | -r |"
            " -e | -f fw.mfa2 | -z]\n");
        fprintf(stderr, "\t-w - write firmware dump to the specified file\n");
        fprintf(stderr, "\t-r - reset dump\n");
        fprintf(stderr, "\t-E - get eeprom info\n");
        fprintf(stderr, "\t-e - force dump\n");
        fprintf(stderr, "\t-f fw.img - flash firmware from fw.img\n");
        fprintf(stderr, "\t-z - initiate firmware reset\n");
        exit(1);
}

enum mlx5_action {
        ACTION_DUMP_GET,
        ACTION_DUMP_RESET,
        ACTION_DUMP_FORCE,
        ACTION_FW_UPDATE,
        ACTION_FW_RESET,
        ACTION_GET_EEPROM_INFO,
        ACTION_NONE,
};

int
main(int argc, char *argv[])
{
        struct mlx5_tool_addr addr;
        char *dumpname;
        char *addrstr;
        char *img_fw_path;
        int c, ctldev, res;
        enum mlx5_action act;

        act = ACTION_NONE;
        addrstr = NULL;
        dumpname = NULL;
        img_fw_path = NULL;
        while ((c = getopt(argc, argv, "d:Eef:ho:rwz")) != -1) {
                switch (c) {
                case 'd':
                        addrstr = optarg;
                        break;
                case 'w':
                        if (act != ACTION_NONE)
                                usage();
                        act = ACTION_DUMP_GET;
                        break;
                case 'E':
                        if (act != ACTION_NONE)
                                usage();
                        act = ACTION_GET_EEPROM_INFO;
                        break;
                case 'e':
                        if (act != ACTION_NONE)
                                usage();
                        act = ACTION_DUMP_FORCE;
                        break;
                case 'o':
                        dumpname = optarg;
                        break;
                case 'r':
                        if (act != ACTION_NONE)
                                usage();
                        act = ACTION_DUMP_RESET;
                        break;
                case 'f':
                        if (act != ACTION_NONE)
                                usage();
                        act = ACTION_FW_UPDATE;
                        img_fw_path = optarg;
                        break;
                case 'z':
                        if (act != ACTION_NONE)
                                usage();
                        act = ACTION_FW_RESET;
                        break;
                case 'h':
                default:
                        usage();
                }
        }
        if (act == ACTION_NONE || (dumpname != NULL &&
            act != ACTION_DUMP_GET) || (img_fw_path != NULL &&
            act != ACTION_FW_UPDATE))
                usage();
        if (parse_pci_addr(addrstr, &addr) != 0)
                exit(1);

        ctldev = open(MLX5_DEV_PATH, O_RDWR);
        if (ctldev == -1)
                err(1, "open "MLX5_DEV_PATH);
        switch (act) {
        case ACTION_DUMP_GET:
                res = mlx5tool_save_dump(ctldev, &addr, dumpname);
                break;
        case ACTION_DUMP_RESET:
                res = mlx5tool_dump_reset(ctldev, &addr);
                break;
        case ACTION_DUMP_FORCE:
                res = mlx5tool_dump_force(ctldev, &addr);
                break;
        case ACTION_FW_UPDATE:
                res = mlx5tool_fw_update(ctldev, &addr, img_fw_path);
                break;
        case ACTION_FW_RESET:
                res = mlx5tool_fw_reset(ctldev, &addr);
                break;
        case ACTION_GET_EEPROM_INFO:
                res = mlx5tool_get_eeprom_info(ctldev, &addr);
                break;
        default:
                res = 0;
                break;
        }
        close(ctldev);
        exit(res);
}