diff options
Diffstat (limited to 'src/libnm-platform')
| -rw-r--r-- | src/libnm-platform/devlink/nm-devlink.c | 365 | ||||
| -rw-r--r-- | src/libnm-platform/devlink/nm-devlink.h | 30 | ||||
| -rw-r--r-- | src/libnm-platform/meson.build | 1 | ||||
| -rw-r--r-- | src/libnm-platform/nm-linux-platform.c | 440 | ||||
| -rw-r--r-- | src/libnm-platform/nm-platform.c | 19 | ||||
| -rw-r--r-- | src/libnm-platform/nm-platform.h | 15 |
6 files changed, 768 insertions, 102 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); +} diff --git a/src/libnm-platform/devlink/nm-devlink.h b/src/libnm-platform/devlink/nm-devlink.h new file mode 100644 index 00000000..c626a120 --- /dev/null +++ b/src/libnm-platform/devlink/nm-devlink.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2024 Red Hat, Inc. + */ + +#ifndef __NMP_DEVLINK_H__ +#define __NMP_DEVLINK_H__ + +#include "libnm-base/nm-base.h" +#include <linux/devlink.h> + +struct nl_sock; +typedef struct _NMPlatform NMPlatform; +typedef struct _NMDevlink NMDevlink; + +typedef struct { + _NMSriovEswitchMode mode; + _NMSriovEswitchInlineMode inline_mode; + _NMSriovEswitchEncapMode encap_mode; +} NMDevlinkEswitchParams; + +NMDevlink *nm_devlink_new(NMPlatform *platform, struct nl_sock *genl_sock_sync, int ifindex); +gboolean +nm_devlink_get_dev_identifier(NMDevlink *self, char **out_bus, char **out_addr, GError **error); +gboolean +nm_devlink_get_eswitch_params(NMDevlink *self, NMDevlinkEswitchParams *out_params, GError **error); +gboolean +nm_devlink_set_eswitch_params(NMDevlink *self, NMDevlinkEswitchParams params, GError **error); + +#endif /* __NMP_DEVLINK_H__ */ \ No newline at end of file diff --git a/src/libnm-platform/meson.build b/src/libnm-platform/meson.build index 696ca1a6..7b6ad042 100644 --- a/src/libnm-platform/meson.build +++ b/src/libnm-platform/meson.build @@ -12,6 +12,7 @@ libnm_platform = static_library( 'nmp-netns.c', 'nmp-object.c', 'nmp-plobj.c', + 'devlink/nm-devlink.c', 'wifi/nm-wifi-utils-nl80211.c', 'wifi/nm-wifi-utils.c', 'wpan/nm-wpan-utils.c', diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index a7078280..9ecac2d9 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -41,6 +41,7 @@ #include "libnm-platform/nm-netlink.h" #include "libnm-platform/nm-platform-utils.h" #include "libnm-platform/nmp-netns.h" +#include "libnm-platform/devlink/nm-devlink.h" #include "libnm-platform/wifi/nm-wifi-utils-wext.h" #include "libnm-platform/wifi/nm-wifi-utils.h" #include "libnm-platform/wpan/nm-wpan-utils.h" @@ -8881,141 +8882,394 @@ nla_put_failure: g_return_val_if_reached(FALSE); } +static gint64 +sriov_read_sysctl_uint(NMPlatform *platform, + int dirfd, + const char *ifname, + const char *dev_file, + GError **error) +{ + const char *path; + gint64 val; + + nm_assert(NM_STRLEN("device/%s") + strlen(dev_file)); + + path = nm_sprintf_bufa(256, "device/%s", dev_file); + val = nm_platform_sysctl_get_int_checked(platform, + NMP_SYSCTL_PATHID_NETDIR_UNSAFE_A(dirfd, ifname, path), + 10, + 0, + G_MAXUINT, + -1); + + if (val < 0) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "couldn't read %s: %s", + dev_file, + nm_strerror_native(errno)); + return -errno; + } + + return val; +} + +static gboolean +sriov_set_autoprobe(NMPlatform *platform, + int dirfd, + const char *ifname, + NMOptionBool autoprobe, + GError **error) +{ + int current_autoprobe = + (int) sriov_read_sysctl_uint(platform, dirfd, ifname, "sriov_drivers_autoprobe", error); + + if (current_autoprobe == -ENOENT) { + /* older kernel versions don't have this sysctl. Assume the value is "1". */ + current_autoprobe = 1; + g_clear_error(error); + } + + if (current_autoprobe < 0) + return FALSE; + + if (autoprobe != NM_OPTION_BOOL_DEFAULT && current_autoprobe != autoprobe) { + if (!nm_platform_sysctl_set( + platform, + NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_drivers_autoprobe"), + autoprobe == 1 ? "1" : "0")) { + g_set_error(error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "couldn't set SR-IOV drivers-autoprobe to %d: %s", + (int) autoprobe, + nm_strerror_native(errno)); + return FALSE; + } + } + + return TRUE; +} + +#define _SRIOV_ASYNC_MAX_STEPS 4 + +typedef struct _SriovAsyncState { + NMPlatform *platform; + int ifindex; + NMPlatformSriovParams sriov_params; + void (*steps[_SRIOV_ASYNC_MAX_STEPS])(struct _SriovAsyncState *); + int current_step; + NMPlatformAsyncCallback callback; + gpointer data; + GCancellable *cancellable; +} SriovAsyncState; + static void -sriov_idle_cb(gpointer user_data, GCancellable *cancellable) +sriov_async_invoke_callback(gpointer user_data, GCancellable *cancellable) { - gs_unref_object NMPlatform *platform = NULL; - gs_free_error GError *cancelled_error = NULL; - gs_free_error GError *error = NULL; - NMPlatformAsyncCallback callback; - gpointer callback_data; + gs_free_error GError *cancelled_error = NULL; + gs_free_error GError *error = NULL; + NMPlatformAsyncCallback callback; + gpointer callback_data; g_cancellable_set_error_if_cancelled(cancellable, &cancelled_error); - nm_utils_user_data_unpack(user_data, &platform, &error, &callback, &callback_data); + nm_utils_user_data_unpack(user_data, &error, &callback, &callback_data); callback(cancelled_error ?: error, callback_data); } static void +sriov_async_finish_err(SriovAsyncState *async_state, GError *error) +{ + NMPlatform *platform = async_state->platform; + + _LOGD("finished configuring SR-IOV, error: %s", error ? error->message : "none"); + + if (async_state->callback) { + /* nm_platform_link_set_sriov_params() promises to always call the callback, + * and always asynchronously. We might have reached here without doing + * any asynchronous task, so invoke the user's callback in the idle task + * to make it asynchronous. Actually, let's make it simple and do it + * always in this way, even if asynchronous tasks were made. + */ + gpointer packed = nm_utils_user_data_pack(g_steal_pointer(&error), + async_state->callback, + async_state->data); + nm_utils_invoke_on_idle(async_state->cancellable, sriov_async_invoke_callback, packed); + } + + g_object_unref(async_state->platform); + g_object_unref(async_state->cancellable); + g_free(async_state); + g_free(error); +} + +static void +sriov_async_call_next_step(SriovAsyncState *async_state) +{ + if (g_cancellable_is_cancelled(async_state->cancellable)) { + sriov_async_finish_err(async_state, NULL); /* The error will be set later */ + return; + } + + async_state->current_step++; + + nm_assert(async_state->current_step >= 0); + nm_assert(async_state->current_step < _SRIOV_ASYNC_MAX_STEPS); + nm_assert(async_state->steps[async_state->current_step] != NULL); + + async_state->steps[async_state->current_step](async_state); +} + +static void +sriov_async_sysctl_done_cb(GError *error, gpointer data) +{ + SriovAsyncState *async_state = data; + + if (error) + sriov_async_finish_err(async_state, g_error_copy(error)); + else + sriov_async_call_next_step(async_state); +} + +static void +sriov_async_set_num_vfs(SriovAsyncState *async_state, const char *val) +{ + NMPlatform *platform = async_state->platform; + const char *values[] = {val, NULL}; + nm_auto_close int dirfd = -1; + char ifname[IFNAMSIZ]; + gs_free_error GError *error = NULL; + + dirfd = nm_platform_sysctl_open_netdir(platform, async_state->ifindex, ifname); + if (!dirfd) { + g_set_error(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "couldn't open netdir for device with ifindex %d", + async_state->ifindex); + sriov_async_finish_err(async_state, g_steal_pointer(&error)); + return; + } + + sysctl_set_async(platform, + NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_numvfs"), + values, + sriov_async_sysctl_done_cb, + async_state, + async_state->cancellable); +} + +static void +sriov_async_step1_destroy_vfs(SriovAsyncState *async_state) +{ + NMPlatform *platform = async_state->platform; + + _LOGD("destroying VFs before configuring SR-IOV"); + + sriov_async_set_num_vfs(async_state, "0"); +} + +static void +sriov_async_step2_set_eswitch_mode(SriovAsyncState *async_state) +{ + NMPlatform *platform = async_state->platform; + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + gs_free NMDevlink *devlink = NULL; + gs_free_error GError *error = NULL; + NMDevlinkEswitchParams eswitch_params = { + .mode = async_state->sriov_params.eswitch_mode, + .inline_mode = async_state->sriov_params.eswitch_inline_mode, + .encap_mode = async_state->sriov_params.eswitch_encap_mode, + }; + + _LOGD("setting eswitch params (mode=%d, inline-mode=%d, encap-mode=%d)", + (int) eswitch_params.mode, + (int) eswitch_params.inline_mode, + (int) eswitch_params.encap_mode); + + /* We set eswitch mode as a sriov_async step because it's in the middle of + * other steps that are async. However, this step itself is synchronous. */ + devlink = nm_devlink_new(platform, priv->sk_genl_sync, async_state->ifindex); + if (!nm_devlink_set_eswitch_params(devlink, eswitch_params, &error)) { + sriov_async_finish_err(async_state, g_steal_pointer(&error)); + return; + } + + sriov_async_call_next_step(async_state); +} + +static void +sriov_async_step3_create_vfs(SriovAsyncState *async_state) +{ + NMPlatform *platform = async_state->platform; + const char *val = nm_sprintf_bufa(32, "%u", async_state->sriov_params.num_vfs); + + _LOGD("setting sriov_numvfs to %u", async_state->sriov_params.num_vfs); + + sriov_async_set_num_vfs(async_state, val); +} + +static void +sriov_async_step_finish_ok(SriovAsyncState *async_state) +{ + sriov_async_finish_err(async_state, NULL); +} + +static int +sriov_eswitch_get_needs_change(SriovAsyncState *async_state, + gboolean *out_needs_change, + GError **error) +{ + NMPlatform *platform = async_state->platform; + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE(platform); + _NMSriovEswitchMode mode = async_state->sriov_params.eswitch_mode; + _NMSriovEswitchInlineMode inline_mode = async_state->sriov_params.eswitch_inline_mode; + _NMSriovEswitchEncapMode encap_mode = async_state->sriov_params.eswitch_encap_mode; + NMDevlinkEswitchParams current_params; + gs_free NMDevlink *devlink = NULL; + + nm_assert(out_needs_change); + + if (mode == _NM_SRIOV_ESWITCH_MODE_PRESERVE + && inline_mode == _NM_SRIOV_ESWITCH_INLINE_MODE_PRESERVE + && encap_mode == _NM_SRIOV_ESWITCH_ENCAP_MODE_PRESERVE) { + *out_needs_change = FALSE; + return 0; + } + + devlink = nm_devlink_new(platform, priv->sk_genl_sync, async_state->ifindex); + + if (!nm_devlink_get_eswitch_params(devlink, ¤t_params, error)) + return -1; + + *out_needs_change = (mode != _NM_SRIOV_ESWITCH_MODE_PRESERVE && mode != current_params.mode) + || (inline_mode != _NM_SRIOV_ESWITCH_INLINE_MODE_PRESERVE + && inline_mode != current_params.inline_mode) + || (encap_mode != _NM_SRIOV_ESWITCH_ENCAP_MODE_PRESERVE + && encap_mode != current_params.encap_mode); + return 0; +} + +/* + * Take special care when setting new values: + * - don't touch anything if the right values are already set + * - to change the number of VFs, eswitch mode or autoprobe we need to destroy existing VFs + * - the autoprobe setting is irrelevant when numvfs is zero + */ +static void link_set_sriov_params_async(NMPlatform *platform, int ifindex, - guint num_vfs, - NMOptionBool autoprobe, + NMPlatformSriovParams sriov_params, NMPlatformAsyncCallback callback, gpointer data, GCancellable *cancellable) { + SriovAsyncState *async_state; nm_auto_pop_netns NMPNetns *netns = NULL; gs_free_error GError *error = NULL; nm_auto_close int dirfd = -1; - int current_autoprobe; - guint i, total; - gint64 current_num; char ifname[IFNAMSIZ]; - gpointer packed; - const char *values[3]; - char buf[64]; + int max_vfs; + int current_num_vfs; + gboolean need_change_eswitch_params; + gboolean need_change_vfs; + gboolean need_destroy_vfs; + gboolean need_create_vfs; + int i; g_return_if_fail(callback || !data); g_return_if_fail(cancellable); + async_state = g_new0(SriovAsyncState, 1); + async_state->platform = g_object_ref(platform); + async_state->ifindex = ifindex; + async_state->sriov_params = sriov_params; + async_state->current_step = -1; + async_state->callback = callback; + async_state->data = data; + async_state->cancellable = g_object_ref(cancellable); + if (!nm_platform_netns_push(platform, &netns)) { g_set_error_literal(&error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, - "couldn't change namespace"); - goto out_idle; + "couldn't change network namespace"); + sriov_async_finish_err(async_state, g_steal_pointer(&error)); + return; } dirfd = nm_platform_sysctl_open_netdir(platform, ifindex, ifname); if (!dirfd) { - g_set_error_literal(&error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "couldn't open netdir"); - goto out_idle; + g_set_error(&error, + NM_UTILS_ERROR, + NM_UTILS_ERROR_UNKNOWN, + "couldn't open netdir for device with ifindex %d", + ifindex); + sriov_async_finish_err(async_state, g_steal_pointer(&error)); + return; } - total = nm_platform_sysctl_get_int_checked( - platform, - NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_totalvfs"), - 10, - 0, - G_MAXUINT, - 0); - if (!errno && num_vfs > total) { - _LOGW("link: %d only supports %u VFs (requested %u)", ifindex, total, num_vfs); - num_vfs = total; + current_num_vfs = sriov_read_sysctl_uint(platform, dirfd, ifname, "sriov_numvfs", &error); + if (current_num_vfs < 0) { + sriov_async_finish_err(async_state, g_steal_pointer(&error)); + return; } - /* - * Take special care when setting new values: - * - don't touch anything if the right values are already set - * - to change the number of VFs or autoprobe we need to destroy existing VFs - * - the autoprobe setting is irrelevant when numvfs is zero - */ - current_num = nm_platform_sysctl_get_int_checked( - platform, - NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_numvfs"), - 10, - 0, - G_MAXUINT, - -1); - current_autoprobe = nm_platform_sysctl_get_int_checked( - platform, - NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_drivers_autoprobe"), - 10, - 0, - 1, - -1); - - if (current_autoprobe == -1 && errno == ENOENT) { - /* older kernel versions don't have this sysctl. Assume the value is - * "1". */ - current_autoprobe = 1; + max_vfs = sriov_read_sysctl_uint(platform, dirfd, ifname, "sriov_totalvfs", &error); + if (max_vfs < 0) { + _LOGD("link: can't read max VFs (%s)", error->message); + g_clear_error(&error); + max_vfs = sriov_params.num_vfs; /* Try to create all */ } - if (current_num == num_vfs - && (autoprobe == NM_OPTION_BOOL_DEFAULT || current_autoprobe == autoprobe)) - goto out_idle; + if (sriov_params.num_vfs > max_vfs) { + _LOGW("link: device %d only supports %u VFs (requested %u)", + ifindex, + max_vfs, + sriov_params.num_vfs); + _LOGW("link: reducing num_vfs to %u for device %d", max_vfs, ifindex); + sriov_params.num_vfs = max_vfs; + async_state->sriov_params.num_vfs = max_vfs; + } - if (NM_IN_SET(autoprobe, NM_OPTION_BOOL_TRUE, NM_OPTION_BOOL_FALSE) - && current_autoprobe != autoprobe - && !nm_platform_sysctl_set( - platform, - NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_drivers_autoprobe"), - nm_sprintf_buf(buf, "%d", (int) autoprobe))) { - g_set_error(&error, - NM_UTILS_ERROR, - NM_UTILS_ERROR_UNKNOWN, - "couldn't set SR-IOV drivers-autoprobe to %d: %s", - (int) autoprobe, - nm_strerror_native(errno)); - goto out_idle; + /* Setting autoprobe goes first, we can do it synchronously */ + if (sriov_params.num_vfs > 0 + && !sriov_set_autoprobe(platform, dirfd, ifname, sriov_params.autoprobe, &error)) { + sriov_async_finish_err(async_state, g_steal_pointer(&error)); + return; } - if (current_num == 0 && num_vfs == 0) - goto out_idle; + /* Decide what actions we must do. Note that we might need to destroy the VFs even + * if num_vfs == current_num_vfs, for example to change the eswitch mode. Because of + * that, we might need to create VFs even if num_vfs == current_num_vfs. + * Steps in order (unnecessary steps are skipped): + * 1. Destroy VFs + * 2. Set eswitch mode + * 3. Create VFs + * 4. Invoke caller's callback + */ + if (sriov_eswitch_get_needs_change(async_state, &need_change_eswitch_params, &error) < 0) { + sriov_async_finish_err(async_state, g_steal_pointer(&error)); + return; + } + need_change_vfs = sriov_params.num_vfs != current_num_vfs; + need_destroy_vfs = current_num_vfs > 0 && (need_change_eswitch_params || need_change_vfs); + need_create_vfs = (current_num_vfs == 0 || need_destroy_vfs) && sriov_params.num_vfs > 0; i = 0; - if (current_num != 0) - values[i++] = "0"; - if (num_vfs != 0) - values[i++] = nm_sprintf_bufa(32, "%u", num_vfs); - values[i++] = NULL; + if (need_destroy_vfs) + async_state->steps[i++] = sriov_async_step1_destroy_vfs; + if (need_change_eswitch_params) + async_state->steps[i++] = sriov_async_step2_set_eswitch_mode; + if (need_create_vfs) + async_state->steps[i++] = sriov_async_step3_create_vfs; - sysctl_set_async(platform, - NMP_SYSCTL_PATHID_NETDIR_A(dirfd, ifname, "device/sriov_numvfs"), - values, - callback, - data, - cancellable); - return; - -out_idle: - if (callback) { - packed = nm_utils_user_data_pack(g_object_ref(platform), - g_steal_pointer(&error), - callback, - data); - nm_utils_invoke_on_idle(cancellable, sriov_idle_cb, packed); - } + nm_assert(i < _SRIOV_ASYNC_MAX_STEPS); + + async_state->steps[i] = sriov_async_step_finish_ok; + + sriov_async_call_next_step(async_state); } static gboolean diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index 1411fe9e..b89b0359 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -452,6 +452,10 @@ _nm_platform_kernel_support_init(NMPlatformKernelSupportType type, int value) /*****************************************************************************/ const NMPGenlFamilyInfo nmp_genl_family_infos[_NMP_GENL_FAMILY_TYPE_NUM] = { + [NMP_GENL_FAMILY_TYPE_DEVLINK] = + { + .name = "devlink", + }, [NMP_GENL_FAMILY_TYPE_ETHTOOL] = { .name = "ethtool", @@ -2018,8 +2022,7 @@ nm_platform_link_supports_sriov(NMPlatform *self, int ifindex) void nm_platform_link_set_sriov_params_async(NMPlatform *self, int ifindex, - guint num_vfs, - NMOptionBool autoprobe, + NMPlatformSriovParams sriov_params, NMPlatformAsyncCallback callback, gpointer callback_data, GCancellable *cancellable) @@ -2028,11 +2031,17 @@ nm_platform_link_set_sriov_params_async(NMPlatform *self, g_return_if_fail(ifindex > 0); - _LOG3D("link: setting %u total VFs and autoprobe %d", num_vfs, (int) autoprobe); + _LOG3D("link: setting SR-IOV params (numvfs=%u, autoprobe=%d, eswitch mode=%d inline-mode=%d " + "encap-mode=%d)", + sriov_params.num_vfs, + (int) sriov_params.autoprobe, + (int) sriov_params.eswitch_mode, + (int) sriov_params.eswitch_inline_mode, + (int) sriov_params.eswitch_encap_mode); + klass->link_set_sriov_params_async(self, ifindex, - num_vfs, - autoprobe, + sriov_params, callback, callback_data, cancellable); diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index a6e60bd4..f6a6ba08 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -993,6 +993,14 @@ typedef struct { guint8 public_key[NMP_WIREGUARD_PUBLIC_KEY_LEN]; } _nm_alignas(NMPlatformObject) NMPlatformLnkWireGuard; +typedef struct { + guint num_vfs; + NMOptionBool autoprobe; + _NMSriovEswitchMode eswitch_mode; + _NMSriovEswitchInlineMode eswitch_inline_mode; + _NMSriovEswitchEncapMode eswitch_encap_mode; +} NMPlatformSriovParams; + typedef enum { NM_PLATFORM_WIREGUARD_CHANGE_FLAG_NONE = 0, NM_PLATFORM_WIREGUARD_CHANGE_FLAG_REPLACE_PEERS = (1LL << 0), @@ -1084,6 +1092,7 @@ nm_platform_kernel_support_get(NMPlatformKernelSupportType type) } typedef enum { + NMP_GENL_FAMILY_TYPE_DEVLINK, NMP_GENL_FAMILY_TYPE_ETHTOOL, NMP_GENL_FAMILY_TYPE_MPTCP_PM, NMP_GENL_FAMILY_TYPE_NL80211, @@ -1171,8 +1180,7 @@ typedef struct { gboolean (*link_set_name)(NMPlatform *self, int ifindex, const char *name); void (*link_set_sriov_params_async)(NMPlatform *self, int ifindex, - guint num_vfs, - NMOptionBool autoprobe, + NMPlatformSriovParams sriov_params, NMPlatformAsyncCallback callback, gpointer callback_data, GCancellable *cancellable); @@ -2034,8 +2042,7 @@ gboolean nm_platform_link_set_name(NMPlatform *self, int ifindex, const char *na void nm_platform_link_set_sriov_params_async(NMPlatform *self, int ifindex, - guint num_vfs, - NMOptionBool autoprobe, + NMPlatformSriovParams sriov_params, NMPlatformAsyncCallback callback, gpointer callback_data, GCancellable *cancellable); |