diff options
| author | Michael Biebl <biebl@debian.org> | 2024-02-22 17:21:11 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2024-02-22 17:21:11 +0100 |
| commit | bba2e4b4de668db525cbfdfc35292e5a0b51671a (patch) | |
| tree | 38d20cddfcc6f71572b9e169deefab5fa96e8d0c /src/libnm-platform/devlink/nm-devlink.c | |
| parent | 6681f77b757bbc42ce5c8868ee9142b7ebc8c059 (diff) | |
New upstream version 1.46.0 upstream/1.46.0
Diffstat (limited to 'src/libnm-platform/devlink/nm-devlink.c')
| -rw-r--r-- | src/libnm-platform/devlink/nm-devlink.c | 365 |
1 files changed, 365 insertions, 0 deletions
diff --git a/src/libnm-platform/devlink/nm-devlink.c b/src/libnm-platform/devlink/nm-devlink.c new file mode 100644 index 00000000..f06697cf --- /dev/null +++ b/src/libnm-platform/devlink/nm-devlink.c @@ -0,0 +1,365 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Red Hat, Inc. + */ + +#include "libnm-glib-aux/nm-default-glib-i18n-lib.h" + +#include "nm-devlink.h" + +#include <linux/if.h> +#include <linux/devlink.h> + +#include "libnm-log-core/nm-logging.h" +#include "libnm-platform/nm-netlink.h" +#include "libnm-platform/nm-platform.h" +#include "libnm-platform/nm-platform-utils.h" + +#define _NMLOG_PREFIX_NAME "devlink" +#define _NMLOG_DOMAIN LOGD_PLATFORM | LOGD_DEVICE +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + char _ifname_buf[IFNAMSIZ]; \ + const char *_ifname = self ? nmp_utils_if_indextoname(self->ifindex, _ifname_buf) : NULL; \ + \ + nm_log((level), \ + _NMLOG_DOMAIN, \ + _ifname ?: NULL, \ + NULL, \ + "%s%s%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _NMLOG_PREFIX_NAME, \ + NM_PRINT_FMT_QUOTED(_ifname, " (", _ifname, ")", "") \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +#define CB_RESULT_PENDING 0 +#define CB_RESULT_OK 1 + +struct _NMDevlink { + NMPlatform *plat; + struct nl_sock *genl_sock_sync; + guint16 genl_family_id; + int ifindex; +}; + +/** + * nm_devlink_new: + * @platform: the #NMPlatform that will use this #NMDevlink instance + * @genl_sock_sync: the netlink socket (will be used synchronously) + * @ifindex: the kernel's netdev ifindex corresponding to the devlink device + * + * Create a new #NMDevlink instance to make devlink queries regarding a specific + * device. + * + * Returns: (transfer full): the allocated new #NMDevlink + */ +NMDevlink * +nm_devlink_new(NMPlatform *platform, struct nl_sock *genl_sock_sync, int ifindex) +{ + NMDevlink *self = g_new(NMDevlink, 1); + + self->plat = platform; + self->genl_sock_sync = genl_sock_sync; + self->genl_family_id = nm_platform_genl_get_family_id(platform, NMP_GENL_FAMILY_TYPE_DEVLINK); + self->ifindex = ifindex; + return self; +} + +/** + * nm_devlink_get_dev_identifier: + * @self: the #NMDevlink + * @out_bus: (out): the "bus_name" part of the devlink device identifier + * @out_addr: (out): the "bus_addr" part of the devlink device identifier + * @error: (optional): the error location + * + * Get the devlink device identifier of the device for which the #NMDevlink was + * created (with the @ifindex argument of nm_devlink_get_new()). A devlink device + * is identified as "bus_name/bus_addr" (i.e. "pci/0000:65:00.0"). This function + * provides both parts separately. + * + * Note that here we only get the potential devlink device identifier. The real devlink + * device might not even exist if the hw doesn't implement devlink or the netdev + * doesn't have a 1-1 corresponding devlink device (i.e. because it's a VF or + * because the hw uses a "one eswitch for many ports" model). + * + * Also note that currently only PCI devices are supported, an error will be + * returned for other kind of devices. + * + * Returns: FALSE in case of error, TRUE otherwise + */ +gboolean +nm_devlink_get_dev_identifier(NMDevlink *self, char **out_bus, char **out_addr, GError **error) +{ + const char *bus; + char sbuf[IFNAMSIZ]; + NMPUtilsEthtoolDriverInfo ethtool_driver_info; + + nm_assert(out_bus != NULL && out_addr != NULL); + nm_assert(!error || !*error); + + if (!nm_platform_link_get_udev_property(self->plat, self->ifindex, "ID_BUS", &bus)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "Can't get udev info for device '%s'", + nmp_utils_if_indextoname(self->ifindex, sbuf)); + return FALSE; + } + + if (!nm_streq0(bus, "pci")) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "Devlink is only supported for PCI but device '%s' has bus name '%s'", + nmp_utils_if_indextoname(self->ifindex, sbuf), + bus); + return FALSE; + } + + if (!nmp_utils_ethtool_get_driver_info(self->ifindex, ðtool_driver_info)) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "Can't get ethtool driver info for device '%s'", + nmp_utils_if_indextoname(self->ifindex, sbuf)); + return FALSE; + } + + *out_bus = g_strdup("pci"); + *out_addr = g_strdup(ethtool_driver_info._private_bus_info); + return TRUE; +} + +static struct nl_msg * +devlink_alloc_msg(NMDevlink *self, uint8_t cmd, uint16_t flags) +{ + nm_auto_nlmsg struct nl_msg *msg = nlmsg_alloc(0); + if (!msg) + return NULL; + + genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, self->genl_family_id, 0, flags, cmd, 0); + return g_steal_pointer(&msg); +} + +static int +ack_cb_handler(const struct nl_msg *msg, void *data) +{ + int *result = data; + *result = CB_RESULT_OK; + return NL_STOP; +} + +static int +finish_cb_handler(const struct nl_msg *msg, void *data) +{ + int *result = data; + *result = CB_RESULT_OK; + return NL_SKIP; +} + +static int +err_cb_handler(const struct sockaddr_nl *nla, const struct nlmsgerr *err, void *data) +{ + void **args = data; + NMDevlink *self = args[0]; + int *result = args[1]; + char **err_msg = args[2]; + const char *extack_msg = NULL; + + *result = err->error; + nlmsg_parse_error(nlmsg_undata(err), &extack_msg); + + _LOGT("error response (%d - %s)", err->error, extack_msg ?: nm_strerror(err->error)); + + if (err_msg) + *err_msg = g_strdup(extack_msg ?: nm_strerror(err->error)); + + return NL_SKIP; +} + +static int +devlink_send_and_recv(NMDevlink *self, + struct nl_msg *msg, + int (*valid_handler)(const struct nl_msg *, void *), + void *valid_data, + char **err_msg) +{ + int nle; + int cb_result = CB_RESULT_PENDING; + void *err_arg[] = {self, &cb_result, err_msg}; + const struct nl_cb cb = { + .err_cb = err_cb_handler, + .err_arg = err_arg, + .finish_cb = finish_cb_handler, + .finish_arg = &cb_result, + .ack_cb = ack_cb_handler, + .ack_arg = &cb_result, + .valid_cb = valid_handler, + .valid_arg = valid_data, + }; + + g_return_val_if_fail(msg != NULL, -ENOMEM); + + if (err_msg) + *err_msg = NULL; + + nle = nl_send_auto(self->genl_sock_sync, msg); + if (nle < 0) + goto out; + + while (cb_result == CB_RESULT_PENDING) { + nle = nl_recvmsgs(self->genl_sock_sync, &cb); + if (nle < 0 && nle != -EAGAIN) { + _LOGW("nl_recvmsgs() error (%d - %s)", nle, nm_strerror(nle)); + break; + } + } + +out: + if (nle < 0 && err_msg && *err_msg == NULL) + *err_msg = strdup(nm_strerror(nle)); + + if (nle >= 0 && cb_result < 0) + nle = cb_result; + return nle; +} + +static int +devlink_parse_eswitch_mode(const struct nl_msg *msg, void *data) +{ + static const struct nla_policy eswitch_policy[] = { + [DEVLINK_ATTR_ESWITCH_MODE] = {.type = NLA_U16}, + [DEVLINK_ATTR_ESWITCH_INLINE_MODE] = {.type = NLA_U8}, + [DEVLINK_ATTR_ESWITCH_ENCAP_MODE] = {.type = NLA_U8}, + }; + NMDevlinkEswitchParams *params = data; + struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg)); + struct nlattr *tb[G_N_ELEMENTS(eswitch_policy)]; + struct nlattr *nla; + + if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), eswitch_policy) < 0) + return NL_SKIP; + + nla = tb[DEVLINK_ATTR_ESWITCH_MODE]; + params->mode = nla ? (_NMSriovEswitchMode) nla_get_u16(nla) : _NM_SRIOV_ESWITCH_MODE_UNKNOWN; + + nla = tb[DEVLINK_ATTR_ESWITCH_INLINE_MODE]; + params->inline_mode = + nla ? (_NMSriovEswitchInlineMode) nla_get_u8(nla) : _NM_SRIOV_ESWITCH_INLINE_MODE_UNKNOWN; + + nla = tb[DEVLINK_ATTR_ESWITCH_ENCAP_MODE]; + params->encap_mode = + nla ? (_NMSriovEswitchEncapMode) nla_get_u8(nla) : _NM_SRIOV_ESWITCH_ENCAP_MODE_UNKNOWN; + + return NL_OK; +} + +/* + * nm_devlink_get_eswitch_params: + * @self: the #NMDevlink + * @out_params: the eswitch parameters read via Devlink + * @error: the error location + * + * Get the eswitch configuration of the device related to the #NMDevlink instance. Note + * that this might be unsupported by the device (see nm_devlink_get_dev()). + * + * Returns: FALSE in case of error, TRUE otherwise + */ +gboolean +nm_devlink_get_eswitch_params(NMDevlink *self, NMDevlinkEswitchParams *out_params, GError **error) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + gs_free char *bus = NULL; + gs_free char *addr = NULL; + gs_free char *err_msg = NULL; + int rc; + + nm_assert(out_params); + + if (!nm_devlink_get_dev_identifier(self, &bus, &addr, error)) + return FALSE; + + msg = devlink_alloc_msg(self, DEVLINK_CMD_ESWITCH_GET, 0); + NLA_PUT_STRING(msg, DEVLINK_ATTR_BUS_NAME, bus); + NLA_PUT_STRING(msg, DEVLINK_ATTR_DEV_NAME, addr); + + rc = devlink_send_and_recv(self, msg, devlink_parse_eswitch_mode, out_params, &err_msg); + if (rc < 0) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "devlink: eswitch get failed (%d - %s)", + rc, + err_msg); + return FALSE; + } + + _LOGD("eswitch get success"); + + return TRUE; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} + +/* + * nm_devlink_set_eswitch_params: + * @self: the #NMDevlink + * @params: the eswitch parameters to set + * @error: the error location + * + * Set the eswitch configuration of the device related to the #NMDevlink instance. Note + * that this might be unsupported by the device (see nm_devlink_get_dev()). + * + * If any of the eswitch parameters is set to "preserve" it won't be modified. + * + * Returns: FALSE in case of error, TRUE otherwise + */ +gboolean +nm_devlink_set_eswitch_params(NMDevlink *self, NMDevlinkEswitchParams params, GError **error) +{ + nm_auto_nlmsg struct nl_msg *msg = NULL; + gs_free char *bus = NULL; + gs_free char *addr = NULL; + gs_free char *err_msg = NULL; + int rc; + + if (params.mode == _NM_SRIOV_ESWITCH_MODE_PRESERVE + && params.inline_mode == _NM_SRIOV_ESWITCH_INLINE_MODE_PRESERVE + && params.encap_mode == _NM_SRIOV_ESWITCH_ENCAP_MODE_PRESERVE) + return TRUE; + + if (!nm_devlink_get_dev_identifier(self, &bus, &addr, error)) + return FALSE; + + msg = devlink_alloc_msg(self, DEVLINK_CMD_ESWITCH_SET, 0); + NLA_PUT_STRING(msg, DEVLINK_ATTR_BUS_NAME, bus); + NLA_PUT_STRING(msg, DEVLINK_ATTR_DEV_NAME, addr); + + if (params.mode != _NM_SRIOV_ESWITCH_MODE_PRESERVE) + NLA_PUT_U16(msg, DEVLINK_ATTR_ESWITCH_MODE, params.mode); + if (params.inline_mode != _NM_SRIOV_ESWITCH_INLINE_MODE_PRESERVE) + NLA_PUT_U8(msg, DEVLINK_ATTR_ESWITCH_INLINE_MODE, params.inline_mode); + if (params.encap_mode != _NM_SRIOV_ESWITCH_ENCAP_MODE_PRESERVE) + NLA_PUT_U8(msg, DEVLINK_ATTR_ESWITCH_ENCAP_MODE, params.encap_mode); + + rc = devlink_send_and_recv(self, msg, NULL, NULL, &err_msg); + if (rc < 0) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "devlink: eswitch set failed (%d - %s)", + rc, + err_msg); + return FALSE; + } + + _LOGD("eswitch set success"); + + return TRUE; + +nla_put_failure: + g_return_val_if_reached(FALSE); +} |