mirror of
https://gitlab.freedesktop.org/NetworkManager/NetworkManager.git
synced 2025-12-25 16:30:07 +01:00
361 lines
12 KiB
C
361 lines
12 KiB
C
/* 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)];
|
|
|
|
if (nla_parse_arr(tb, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), eswitch_policy) < 0)
|
|
return NL_SKIP;
|
|
|
|
if (!tb[DEVLINK_ATTR_ESWITCH_MODE] || !tb[DEVLINK_ATTR_ESWITCH_INLINE_MODE]
|
|
|| !tb[DEVLINK_ATTR_ESWITCH_ENCAP_MODE])
|
|
return NL_SKIP;
|
|
|
|
params->mode = (_NMSriovEswitchMode) nla_get_u16(tb[DEVLINK_ATTR_ESWITCH_MODE]);
|
|
params->encap_mode = (_NMSriovEswitchEncapMode) nla_get_u8(tb[DEVLINK_ATTR_ESWITCH_ENCAP_MODE]);
|
|
params->inline_mode =
|
|
(_NMSriovEswitchInlineMode) nla_get_u8(tb[DEVLINK_ATTR_ESWITCH_INLINE_MODE]);
|
|
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);
|
|
}
|