devlink: implement flash status monitoring

Listen to status notifications coming from kernel during flashing and
put them on stdout to inform user about the status.

Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: David Ahern <dsahern@gmail.com>
This commit is contained in:
Jiri Pirko 2019-09-12 13:29:38 +02:00 committed by David Ahern
parent 853be43f9e
commit 9b13cddfe2
4 changed files with 228 additions and 4 deletions

View File

@ -25,6 +25,7 @@
#include <linux/devlink.h>
#include <libmnl/libmnl.h>
#include <netinet/ether.h>
#include <sys/types.h>
#include "SNAPSHOT.h"
#include "list.h"
@ -96,6 +97,18 @@ pr_out_sp(unsigned int num, const char *fmt, ...)
g_new_line_count = 0; \
}
static void __attribute__((format(printf, 1, 2)))
pr_out_tty(const char *fmt, ...)
{
va_list ap;
if (!isatty(STDOUT_FILENO))
return;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
static void __pr_out_indent_inc(void)
{
if (g_indent_level + INDENT_STR_STEP > INDENT_STR_MAXLEN)
@ -135,9 +148,8 @@ static int _mnlg_socket_recv_run(struct mnlg_socket *nlg,
return 0;
}
static int _mnlg_socket_sndrcv(struct mnlg_socket *nlg,
const struct nlmsghdr *nlh,
mnl_cb_t data_cb, void *data)
static int _mnlg_socket_send(struct mnlg_socket *nlg,
const struct nlmsghdr *nlh)
{
int err;
@ -146,6 +158,18 @@ static int _mnlg_socket_sndrcv(struct mnlg_socket *nlg,
pr_err("Failed to call mnlg_socket_send\n");
return -errno;
}
return 0;
}
static int _mnlg_socket_sndrcv(struct mnlg_socket *nlg,
const struct nlmsghdr *nlh,
mnl_cb_t data_cb, void *data)
{
int err;
err = _mnlg_socket_send(nlg, nlh);
if (err)
return err;
return _mnlg_socket_recv_run(nlg, data_cb, data);
}
@ -2855,9 +2879,151 @@ static void cmd_dev_flash_help(void)
pr_err("Usage: devlink dev flash DEV file PATH [ component NAME ]\n");
}
struct cmd_dev_flash_status_ctx {
struct dl *dl;
char *last_msg;
char *last_component;
uint8_t not_first:1,
last_pc:1,
received_end:1,
flash_done:1;
};
static int nullstrcmp(const char *str1, const char *str2)
{
if (str1 && str2)
return strcmp(str1, str2);
if (!str1 && !str2)
return 0;
return str1 ? 1 : -1;
}
static int cmd_dev_flash_status_cb(const struct nlmsghdr *nlh, void *data)
{
struct cmd_dev_flash_status_ctx *ctx = data;
struct dl_opts *opts = &ctx->dl->opts;
struct genlmsghdr *genl = mnl_nlmsg_get_payload(nlh);
struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {};
const char *component = NULL;
uint64_t done = 0, total = 0;
const char *msg = NULL;
const char *bus_name;
const char *dev_name;
if (genl->cmd != DEVLINK_CMD_FLASH_UPDATE_STATUS &&
genl->cmd != DEVLINK_CMD_FLASH_UPDATE_END)
return MNL_CB_STOP;
mnl_attr_parse(nlh, sizeof(*genl), attr_cb, tb);
if (!tb[DEVLINK_ATTR_BUS_NAME] || !tb[DEVLINK_ATTR_DEV_NAME])
return MNL_CB_ERROR;
bus_name = mnl_attr_get_str(tb[DEVLINK_ATTR_BUS_NAME]);
dev_name = mnl_attr_get_str(tb[DEVLINK_ATTR_DEV_NAME]);
if (strcmp(bus_name, opts->bus_name) ||
strcmp(dev_name, opts->dev_name))
return MNL_CB_ERROR;
if (genl->cmd == DEVLINK_CMD_FLASH_UPDATE_END && ctx->not_first) {
pr_out("\n");
free(ctx->last_msg);
free(ctx->last_component);
ctx->received_end = 1;
return MNL_CB_STOP;
}
if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG])
msg = mnl_attr_get_str(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_MSG]);
if (tb[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT])
component = mnl_attr_get_str(tb[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT]);
if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE])
done = mnl_attr_get_u64(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_DONE]);
if (tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL])
total = mnl_attr_get_u64(tb[DEVLINK_ATTR_FLASH_UPDATE_STATUS_TOTAL]);
if (!nullstrcmp(msg, ctx->last_msg) &&
!nullstrcmp(component, ctx->last_component) &&
ctx->last_pc && ctx->not_first) {
pr_out_tty("\b\b\b\b\b"); /* clean percentage */
} else {
if (ctx->not_first)
pr_out("\n");
if (component) {
pr_out("[%s] ", component);
free(ctx->last_component);
ctx->last_component = strdup(component);
}
if (msg) {
pr_out("%s", msg);
free(ctx->last_msg);
ctx->last_msg = strdup(msg);
}
}
if (total) {
pr_out_tty(" %3lu%%", (done * 100) / total);
ctx->last_pc = 1;
} else {
ctx->last_pc = 0;
}
fflush(stdout);
ctx->not_first = 1;
return MNL_CB_STOP;
}
static int cmd_dev_flash_fds_process(struct cmd_dev_flash_status_ctx *ctx,
struct mnlg_socket *nlg_ntf,
int pipe_r)
{
int nlfd = mnlg_socket_get_fd(nlg_ntf);
fd_set fds[3];
int fdmax;
int i;
int err;
int err2;
for (i = 0; i < 3; i++)
FD_ZERO(&fds[i]);
FD_SET(pipe_r, &fds[0]);
fdmax = pipe_r + 1;
FD_SET(nlfd, &fds[0]);
if (nlfd >= fdmax)
fdmax = nlfd + 1;
while (select(fdmax, &fds[0], &fds[1], &fds[2], NULL) < 0) {
if (errno == EINTR)
continue;
pr_err("select() failed\n");
return -errno;
}
if (FD_ISSET(nlfd, &fds[0])) {
err = _mnlg_socket_recv_run(nlg_ntf,
cmd_dev_flash_status_cb, ctx);
if (err)
return err;
}
if (FD_ISSET(pipe_r, &fds[0])) {
err = read(pipe_r, &err2, sizeof(err2));
if (err == -1) {
pr_err("Failed to read pipe\n");
return -errno;
}
if (err2)
return err2;
ctx->flash_done = 1;
}
return 0;
}
static int cmd_dev_flash(struct dl *dl)
{
struct cmd_dev_flash_status_ctx ctx = {.dl = dl,};
struct mnlg_socket *nlg_ntf;
struct nlmsghdr *nlh;
int pipe_r, pipe_w;
int pipe_fds[2];
pid_t pid;
int err;
if (dl_argv_match(dl, "help") || dl_no_arg(dl)) {
@ -2873,7 +3039,48 @@ static int cmd_dev_flash(struct dl *dl)
if (err)
return err;
return _mnlg_socket_sndrcv(dl->nlg, nlh, NULL, NULL);
nlg_ntf = mnlg_socket_open(DEVLINK_GENL_NAME, DEVLINK_GENL_VERSION);
if (!nlg_ntf)
return err;
err = _mnlg_socket_group_add(nlg_ntf, DEVLINK_GENL_MCGRP_CONFIG_NAME);
if (err)
return err;
err = pipe(pipe_fds);
if (err == -1)
return -errno;
pipe_r = pipe_fds[0];
pipe_w = pipe_fds[1];
pid = fork();
if (pid == -1) {
close(pipe_r);
close(pipe_w);
return -errno;
} else if (!pid) {
/* In child, just execute the flash and pass returned
* value through pipe once it is done.
*/
close(pipe_r);
err = _mnlg_socket_send(dl->nlg, nlh);
write(pipe_w, &err, sizeof(err));
close(pipe_w);
exit(0);
}
close(pipe_w);
do {
err = cmd_dev_flash_fds_process(&ctx, nlg_ntf, pipe_r);
if (err)
goto out;
} while (!ctx.flash_done || (ctx.not_first && !ctx.received_end));
err = _mnlg_socket_recv_run(dl->nlg, NULL, NULL);
out:
close(pipe_r);
mnlg_socket_close(nlg_ntf);
return err;
}
static int cmd_dev(struct dl *dl)

View File

@ -320,3 +320,8 @@ void mnlg_socket_close(struct mnlg_socket *nlg)
free(nlg->buf);
free(nlg);
}
int mnlg_socket_get_fd(struct mnlg_socket *nlg)
{
return mnl_socket_get_fd(nlg->nl);
}

View File

@ -23,5 +23,6 @@ int mnlg_socket_recv_run(struct mnlg_socket *nlg, mnl_cb_t data_cb, void *data);
int mnlg_socket_group_add(struct mnlg_socket *nlg, const char *group_name);
struct mnlg_socket *mnlg_socket_open(const char *family_name, uint8_t version);
void mnlg_socket_close(struct mnlg_socket *nlg);
int mnlg_socket_get_fd(struct mnlg_socket *nlg);
#endif /* _MNLG_H_ */

View File

@ -244,6 +244,17 @@ Sets the parameter internal_error_reset of specified devlink device to true.
devlink dev reload pci/0000:01:00.0
.RS 4
Performs hot reload of specified devlink device.
.RE
.PP
devlink dev flash pci/0000:01:00.0 file firmware.bin
.RS 4
Flashes the specified devlink device with provided firmware file name. If the driver supports it, user gets updates about the flash status. For example:
.br
Preparing to flash
.br
Flashing 100%
.br
Flashing done
.SH SEE ALSO
.BR devlink (8),