/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2018 Red Hat, Inc.
*/
#include "libnm-core-impl/nm-default-libnm-core.h"
#include "nm-setting-sriov.h"
#include "nm-setting-private.h"
#include "nm-utils-private.h"
/**
* SECTION:nm-setting-sriov
* @short_description: Describes SR-IOV connection properties
* @include: nm-setting-sriov.h
**/
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE(NMSettingSriov, PROP_TOTAL_VFS, PROP_VFS, PROP_AUTOPROBE_DRIVERS, );
/**
* NMSettingSriov:
*
* SR-IOV settings
*
* Since: 1.14
*/
struct _NMSettingSriov {
NMSetting parent;
GPtrArray *vfs;
guint total_vfs;
NMTernary autoprobe_drivers;
};
struct _NMSettingSriovClass {
NMSettingClass parent;
};
G_DEFINE_TYPE(NMSettingSriov, nm_setting_sriov, NM_TYPE_SETTING)
/*****************************************************************************/
G_DEFINE_BOXED_TYPE(NMSriovVF, nm_sriov_vf, nm_sriov_vf_dup, nm_sriov_vf_unref)
struct _NMSriovVF {
guint refcount;
guint index;
GHashTable *attributes;
GHashTable *vlans;
guint * vlan_ids;
};
typedef struct {
guint id;
guint qos;
NMSriovVFVlanProtocol protocol;
} VFVlan;
static guint
_vf_vlan_hash(gconstpointer ptr)
{
return nm_hash_val(1348254767u, *((guint *) ptr));
}
static gboolean
_vf_vlan_equal(gconstpointer a, gconstpointer b)
{
return *((guint *) a) == *((guint *) b);
}
static GHashTable *
_vf_vlan_create_hash(void)
{
G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(VFVlan, id) == 0);
return g_hash_table_new_full(_vf_vlan_hash, _vf_vlan_equal, NULL, nm_g_slice_free_fcn(VFVlan));
}
/**
* nm_sriov_vf_new:
* @index: the VF index
*
* Creates a new #NMSriovVF object.
*
* Returns: (transfer full): the new #NMSriovVF object.
*
* Since: 1.14
**/
NMSriovVF *
nm_sriov_vf_new(guint index)
{
NMSriovVF *vf;
vf = g_slice_new(NMSriovVF);
*vf = (NMSriovVF){
.refcount = 1,
.index = index,
.attributes = g_hash_table_new_full(nm_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) g_variant_unref),
};
return vf;
}
/**
* nm_sriov_vf_ref:
* @vf: the #NMSriovVF
*
* Increases the reference count of the object.
*
* Since: 1.14
**/
void
nm_sriov_vf_ref(NMSriovVF *vf)
{
g_return_if_fail(vf);
g_return_if_fail(vf->refcount > 0);
vf->refcount++;
}
/**
* nm_sriov_vf_unref:
* @vf: the #NMSriovVF
*
* Decreases the reference count of the object. If the reference count
* reaches zero, the object will be destroyed.
*
* Since: 1.14
**/
void
nm_sriov_vf_unref(NMSriovVF *vf)
{
g_return_if_fail(vf);
g_return_if_fail(vf->refcount > 0);
vf->refcount--;
if (vf->refcount == 0) {
g_hash_table_unref(vf->attributes);
if (vf->vlans)
g_hash_table_unref(vf->vlans);
g_free(vf->vlan_ids);
nm_g_slice_free(vf);
}
}
/**
* nm_sriov_vf_equal:
* @vf: the #NMSriovVF
* @other: the #NMSriovVF to compare @vf to.
*
* Determines if two #NMSriovVF objects have the same index,
* attributes and VLANs.
*
* Returns: %TRUE if the objects contain the same values, %FALSE
* if they do not.
*
* Since: 1.14
**/
gboolean
nm_sriov_vf_equal(const NMSriovVF *vf, const NMSriovVF *other)
{
GHashTableIter iter;
const char * key;
GVariant * value, *value2;
VFVlan * vlan, *vlan2;
guint n_vlans;
g_return_val_if_fail(vf, FALSE);
g_return_val_if_fail(vf->refcount > 0, FALSE);
g_return_val_if_fail(other, FALSE);
g_return_val_if_fail(other->refcount > 0, FALSE);
if (vf == other)
return TRUE;
if (vf->index != other->index)
return FALSE;
if (g_hash_table_size(vf->attributes) != g_hash_table_size(other->attributes))
return FALSE;
g_hash_table_iter_init(&iter, vf->attributes);
while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &value)) {
value2 = g_hash_table_lookup(other->attributes, key);
if (!value2)
return FALSE;
if (!g_variant_equal(value, value2))
return FALSE;
}
n_vlans = vf->vlans ? g_hash_table_size(vf->vlans) : 0u;
if (n_vlans != (other->vlans ? g_hash_table_size(other->vlans) : 0u))
return FALSE;
if (n_vlans > 0) {
g_hash_table_iter_init(&iter, vf->vlans);
while (g_hash_table_iter_next(&iter, (gpointer *) &vlan, NULL)) {
vlan2 = g_hash_table_lookup(other->vlans, vlan);
if (!vlan2)
return FALSE;
if (vlan->qos != vlan2->qos || vlan->protocol != vlan2->protocol)
return FALSE;
}
}
return TRUE;
}
static void
vf_add_vlan(NMSriovVF *vf, guint vlan_id, guint qos, NMSriovVFVlanProtocol protocol)
{
VFVlan *vlan;
vlan = g_slice_new(VFVlan);
*vlan = (VFVlan){
.id = vlan_id,
.qos = qos,
.protocol = protocol,
};
if (!vf->vlans)
vf->vlans = _vf_vlan_create_hash();
g_hash_table_add(vf->vlans, vlan);
nm_clear_g_free(&vf->vlan_ids);
}
/**
* nm_sriov_vf_dup:
* @vf: the #NMSriovVF
*
* Creates a copy of @vf.
*
* Returns: (transfer full): a copy of @vf
*
* Since: 1.14
**/
NMSriovVF *
nm_sriov_vf_dup(const NMSriovVF *vf)
{
NMSriovVF * copy;
GHashTableIter iter;
const char * name;
GVariant * variant;
VFVlan * vlan;
g_return_val_if_fail(vf, NULL);
g_return_val_if_fail(vf->refcount > 0, NULL);
copy = nm_sriov_vf_new(vf->index);
g_hash_table_iter_init(&iter, vf->attributes);
while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant))
nm_sriov_vf_set_attribute(copy, name, variant);
if (vf->vlans) {
g_hash_table_iter_init(&iter, vf->vlans);
while (g_hash_table_iter_next(&iter, (gpointer *) &vlan, NULL))
vf_add_vlan(copy, vlan->id, vlan->qos, vlan->protocol);
}
return copy;
}
/**
* nm_sriov_vf_get_index:
* @vf: the #NMSriovVF
*
* Gets the index property of this VF object.
*
* Returns: the VF index
*
* Since: 1.14
**/
guint
nm_sriov_vf_get_index(const NMSriovVF *vf)
{
g_return_val_if_fail(vf, 0);
g_return_val_if_fail(vf->refcount > 0, 0);
return vf->index;
}
/**
* nm_sriov_vf_set_attribute:
* @vf: the #NMSriovVF
* @name: the name of a route attribute
* @value: (transfer none) (allow-none): the value
*
* Sets the named attribute on @vf to the given value.
*
* Since: 1.14
**/
void
nm_sriov_vf_set_attribute(NMSriovVF *vf, const char *name, GVariant *value)
{
g_return_if_fail(vf);
g_return_if_fail(vf->refcount > 0);
g_return_if_fail(name && *name != '\0');
g_return_if_fail(!nm_streq(name, "index"));
if (value) {
g_hash_table_insert(vf->attributes, g_strdup(name), g_variant_ref_sink(value));
} else
g_hash_table_remove(vf->attributes, name);
}
/**
* nm_sriov_vf_get_attribute_names:
* @vf: the #NMSriovVF
*
* Gets an array of attribute names defined on @vf.
*
* Returns: (transfer container): a %NULL-terminated array of attribute names
*
* Since: 1.14
**/
const char **
nm_sriov_vf_get_attribute_names(const NMSriovVF *vf)
{
g_return_val_if_fail(vf, NULL);
g_return_val_if_fail(vf->refcount > 0, NULL);
return nm_utils_strdict_get_keys(vf->attributes, TRUE, NULL);
}
/**
* nm_sriov_vf_get_attribute:
* @vf: the #NMSriovVF
* @name: the name of a VF attribute
*
* Gets the value of the attribute with name @name on @vf
*
* Returns: (transfer none): the value of the attribute with name @name on
* @vf, or %NULL if @vf has no such attribute.
*
* Since: 1.14
**/
GVariant *
nm_sriov_vf_get_attribute(const NMSriovVF *vf, const char *name)
{
g_return_val_if_fail(vf, NULL);
g_return_val_if_fail(vf->refcount > 0, NULL);
g_return_val_if_fail(name && *name != '\0', NULL);
return g_hash_table_lookup(vf->attributes, name);
}
const NMVariantAttributeSpec *const _nm_sriov_vf_attribute_spec[] = {
NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_SRIOV_VF_ATTRIBUTE_MAC,
G_VARIANT_TYPE_STRING,
.str_type = 'm', ),
NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_SRIOV_VF_ATTRIBUTE_SPOOF_CHECK, G_VARIANT_TYPE_BOOLEAN, ),
NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_SRIOV_VF_ATTRIBUTE_TRUST, G_VARIANT_TYPE_BOOLEAN, ),
NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_SRIOV_VF_ATTRIBUTE_MIN_TX_RATE, G_VARIANT_TYPE_UINT32, ),
NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(NM_SRIOV_VF_ATTRIBUTE_MAX_TX_RATE, G_VARIANT_TYPE_UINT32, ),
/* D-Bus only, synthetic attributes */
NM_VARIANT_ATTRIBUTE_SPEC_DEFINE("vlans", G_VARIANT_TYPE_STRING, .str_type = 'd', ),
NULL,
};
/**
* nm_sriov_vf_attribute_validate:
* @name: the attribute name
* @value: the attribute value
* @known: (out): on return, whether the attribute name is a known one
* @error: (allow-none): return location for a #GError, or %NULL
*
* Validates a VF attribute, i.e. checks that the attribute is a known one,
* the value is of the correct type and well-formed.
*
* Returns: %TRUE if the attribute is valid, %FALSE otherwise
*
* Since: 1.14
*/
gboolean
nm_sriov_vf_attribute_validate(const char *name, GVariant *value, gboolean *known, GError **error)
{
const NMVariantAttributeSpec *const *iter;
const NMVariantAttributeSpec * spec = NULL;
g_return_val_if_fail(name, FALSE);
g_return_val_if_fail(value, FALSE);
g_return_val_if_fail(!error || !*error, FALSE);
for (iter = _nm_sriov_vf_attribute_spec; *iter; iter++) {
if (nm_streq(name, (*iter)->name)) {
spec = *iter;
break;
}
}
if (!spec || spec->str_type == 'd') {
NM_SET_OUT(known, FALSE);
g_set_error_literal(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_FAILED,
_("unknown attribute"));
return FALSE;
}
NM_SET_OUT(known, TRUE);
if (!g_variant_is_of_type(value, spec->type)) {
g_set_error(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_FAILED,
_("invalid attribute type '%s'"),
g_variant_get_type_string(value));
return FALSE;
}
if (g_variant_type_equal(spec->type, G_VARIANT_TYPE_STRING)) {
const char *string;
switch (spec->str_type) {
case 'm': /* MAC address */
string = g_variant_get_string(value, NULL);
if (!nm_utils_hwaddr_valid(string, -1)) {
g_set_error(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_FAILED,
_("'%s' is not a valid MAC address"),
string);
return FALSE;
}
break;
default:
break;
}
}
return TRUE;
}
gboolean
_nm_sriov_vf_attribute_validate_all(const NMSriovVF *vf, GError **error)
{
GHashTableIter iter;
const char * name;
GVariant * variant;
GVariant * min, *max;
g_return_val_if_fail(vf, FALSE);
g_return_val_if_fail(vf->refcount > 0, FALSE);
g_hash_table_iter_init(&iter, vf->attributes);
while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) {
if (!nm_sriov_vf_attribute_validate(name, variant, NULL, error)) {
g_prefix_error(error, "attribute '%s':", name);
return FALSE;
}
}
min = g_hash_table_lookup(vf->attributes, NM_SRIOV_VF_ATTRIBUTE_MIN_TX_RATE);
max = g_hash_table_lookup(vf->attributes, NM_SRIOV_VF_ATTRIBUTE_MAX_TX_RATE);
if (min && max && g_variant_get_uint32(min) > g_variant_get_uint32(max)) {
g_set_error(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_FAILED,
"min_tx_rate is greater than max_tx_rate");
return FALSE;
}
return TRUE;
}
/**
* nm_sriov_vf_add_vlan:
* @vf: the #NMSriovVF
* @vlan_id: the VLAN id
*
* Adds a VLAN to the VF.
*
* Returns: %TRUE if the VLAN was added; %FALSE if it already existed
*
* Since: 1.14
**/
gboolean
nm_sriov_vf_add_vlan(NMSriovVF *vf, guint vlan_id)
{
g_return_val_if_fail(vf, FALSE);
g_return_val_if_fail(vf->refcount > 0, FALSE);
if (vf->vlans && g_hash_table_contains(vf->vlans, &vlan_id))
return FALSE;
vf_add_vlan(vf, vlan_id, 0, NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q);
return TRUE;
}
/**
* nm_sriov_vf_remove_vlan:
* @vf: the #NMSriovVF
* @vlan_id: the VLAN id
*
* Removes a VLAN from a VF.
*
* Returns: %TRUE if the VLAN was removed, %FALSE if the VLAN @vlan_id
* did not belong to the VF.
*
* Since: 1.14
*/
gboolean
nm_sriov_vf_remove_vlan(NMSriovVF *vf, guint vlan_id)
{
g_return_val_if_fail(vf, FALSE);
g_return_val_if_fail(vf->refcount > 0, FALSE);
if (!vf->vlans || !g_hash_table_remove(vf->vlans, &vlan_id))
return FALSE;
nm_clear_g_free(&vf->vlan_ids);
return TRUE;
}
static int
vlan_id_compare(gconstpointer a, gconstpointer b, gpointer user_data)
{
guint id_a = *(guint *) a;
guint id_b = *(guint *) b;
if (id_a < id_b)
return -1;
else if (id_a > id_b)
return 1;
else
return 0;
}
/**
* nm_sriov_vf_get_vlan_ids:
* @vf: the #NMSriovVF
* @length: (out) (allow-none): on return, the number of VLANs configured
*
* Returns the VLANs currently configured on the VF.
*
* Returns: (transfer none) (array length=length): a list of VLAN ids configured on the VF.
*
* Since: 1.14
*/
const guint *
nm_sriov_vf_get_vlan_ids(const NMSriovVF *vf, guint *length)
{
GHashTableIter iter;
VFVlan * vlan;
guint num, i;
g_return_val_if_fail(vf, NULL);
g_return_val_if_fail(vf->refcount > 0, NULL);
num = vf->vlans ? g_hash_table_size(vf->vlans) : 0u;
NM_SET_OUT(length, num);
if (vf->vlan_ids)
return vf->vlan_ids;
if (num == 0)
return NULL;
/* vf is const, however, vlan_ids is a mutable field caching the
* result ("mutable" in C++ terminology) */
((NMSriovVF *) vf)->vlan_ids = g_new0(guint, num);
i = 0;
g_hash_table_iter_init(&iter, vf->vlans);
while (g_hash_table_iter_next(&iter, (gpointer *) &vlan, NULL))
vf->vlan_ids[i++] = vlan->id;
nm_assert(num == i);
g_qsort_with_data(vf->vlan_ids, num, sizeof(guint), vlan_id_compare, NULL);
return vf->vlan_ids;
}
/**
* nm_sriov_vf_set_vlan_qos:
* @vf: the #NMSriovVF
* @vlan_id: the VLAN id
* @qos: a QoS (priority) value
*
* Sets a QoS value for the given VLAN.
*
* Since: 1.14
*/
void
nm_sriov_vf_set_vlan_qos(NMSriovVF *vf, guint vlan_id, guint32 qos)
{
VFVlan *vlan;
g_return_if_fail(vf);
g_return_if_fail(vf->refcount > 0);
if (!vf->vlans || !(vlan = g_hash_table_lookup(vf->vlans, &vlan_id)))
g_return_if_reached();
vlan->qos = qos;
}
/**
* nm_sriov_vf_set_vlan_protocol:
* @vf: the #NMSriovVF
* @vlan_id: the VLAN id
* @protocol: the VLAN protocol
*
* Sets the protocol for the given VLAN.
*
* Since: 1.14
*/
void
nm_sriov_vf_set_vlan_protocol(NMSriovVF *vf, guint vlan_id, NMSriovVFVlanProtocol protocol)
{
VFVlan *vlan;
g_return_if_fail(vf);
g_return_if_fail(vf->refcount > 0);
if (!vf->vlans || !(vlan = g_hash_table_lookup(vf->vlans, &vlan_id)))
g_return_if_reached();
vlan->protocol = protocol;
}
/**
* nm_sriov_vf_get_vlan_qos:
* @vf: the #NMSriovVF
* @vlan_id: the VLAN id
*
* Returns the QoS value for the given VLAN.
*
* Returns: the QoS value
*
* Since: 1.14
*/
guint32
nm_sriov_vf_get_vlan_qos(const NMSriovVF *vf, guint vlan_id)
{
VFVlan *vlan;
g_return_val_if_fail(vf, 0);
g_return_val_if_fail(vf->refcount > 0, 0);
if (!vf->vlans || !(vlan = g_hash_table_lookup(vf->vlans, &vlan_id)))
g_return_val_if_reached(0);
return vlan->qos;
}
/*
* nm_sriov_vf_get_vlan_protocol:
* @vf: the #NMSriovVF
* @vlan_id: the VLAN id
*
* Returns the configured protocol for the given VLAN.
*
* Returns: the configured protocol
*
* Since: 1.14
*/
NMSriovVFVlanProtocol
nm_sriov_vf_get_vlan_protocol(const NMSriovVF *vf, guint vlan_id)
{
VFVlan *vlan;
g_return_val_if_fail(vf, NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q);
g_return_val_if_fail(vf->refcount > 0, NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q);
if (!vf->vlans || !(vlan = g_hash_table_lookup(vf->vlans, &vlan_id)))
g_return_val_if_reached(NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q);
return vlan->protocol;
}
/*****************************************************************************/
/**
* nm_setting_sriov_get_total_vfs:
* @setting: the #NMSettingSriov
*
* Returns the value contained in the #NMSettingSriov:total-vfs
* property.
*
* Returns: the total number of SR-IOV virtual functions to create
*
* Since: 1.14
**/
guint
nm_setting_sriov_get_total_vfs(NMSettingSriov *setting)
{
g_return_val_if_fail(NM_IS_SETTING_SRIOV(setting), 0);
return setting->total_vfs;
}
/**
* nm_setting_sriov_get_num_vfs:
* @setting: the #NMSettingSriov
*
* Returns: the number of configured VFs
*
* Since: 1.14
**/
guint
nm_setting_sriov_get_num_vfs(NMSettingSriov *setting)
{
g_return_val_if_fail(NM_IS_SETTING_SRIOV(setting), 0);
return setting->vfs->len;
}
/**
* nm_setting_sriov_get_vf:
* @setting: the #NMSettingSriov
* @idx: index number of the VF to return
*
* Returns: (transfer none): the VF at index @idx
*
* Since: 1.14
**/
NMSriovVF *
nm_setting_sriov_get_vf(NMSettingSriov *setting, guint idx)
{
g_return_val_if_fail(NM_IS_SETTING_SRIOV(setting), NULL);
g_return_val_if_fail(idx < setting->vfs->len, NULL);
return setting->vfs->pdata[idx];
}
/**
* nm_setting_sriov_add_vf:
* @setting: the #NMSettingSriov
* @vf: the VF to add
*
* Appends a new VF and associated information to the setting. The
* given VF is duplicated internally and is not changed by this function.
*
* Since: 1.14
**/
void
nm_setting_sriov_add_vf(NMSettingSriov *setting, NMSriovVF *vf)
{
g_return_if_fail(NM_IS_SETTING_SRIOV(setting));
g_return_if_fail(vf);
g_return_if_fail(vf->refcount > 0);
g_ptr_array_add(setting->vfs, nm_sriov_vf_dup(vf));
_notify(setting, PROP_VFS);
}
/**
* nm_setting_sriov_remove_vf:
* @setting: the #NMSettingSriov
* @idx: index number of the VF
*
* Removes the VF at index @idx.
*
* Since: 1.14
**/
void
nm_setting_sriov_remove_vf(NMSettingSriov *setting, guint idx)
{
g_return_if_fail(NM_IS_SETTING_SRIOV(setting));
g_return_if_fail(idx < setting->vfs->len);
g_ptr_array_remove_index(setting->vfs, idx);
_notify(setting, PROP_VFS);
}
/**
* nm_setting_sriov_remove_vf_by_index:
* @setting: the #NMSettingSriov
* @index: the VF index of the VF to remove
*
* Removes the VF with VF index @index.
*
* Returns: %TRUE if the VF was found and removed; %FALSE if it was not
*
* Since: 1.14
**/
gboolean
nm_setting_sriov_remove_vf_by_index(NMSettingSriov *setting, guint index)
{
guint i;
g_return_val_if_fail(NM_IS_SETTING_SRIOV(setting), FALSE);
for (i = 0; i < setting->vfs->len; i++) {
if (nm_sriov_vf_get_index(setting->vfs->pdata[i]) == index) {
g_ptr_array_remove_index(setting->vfs, i);
_notify(setting, PROP_VFS);
return TRUE;
}
}
return FALSE;
}
/**
* nm_setting_sriov_clear_vfs:
* @setting: the #NMSettingSriov
*
* Removes all configured VFs.
*
* Since: 1.14
**/
void
nm_setting_sriov_clear_vfs(NMSettingSriov *setting)
{
g_return_if_fail(NM_IS_SETTING_SRIOV(setting));
if (setting->vfs->len != 0) {
g_ptr_array_set_size(setting->vfs, 0);
_notify(setting, PROP_VFS);
}
}
/**
* nm_setting_sriov_get_autoprobe_drivers:
* @setting: the #NMSettingSriov
*
* Returns the value contained in the #NMSettingSriov:autoprobe-drivers
* property.
*
* Returns: the autoprobe-drivers property value
*
* Since: 1.14
**/
NMTernary
nm_setting_sriov_get_autoprobe_drivers(NMSettingSriov *setting)
{
g_return_val_if_fail(NM_IS_SETTING_SRIOV(setting), NM_TERNARY_DEFAULT);
return setting->autoprobe_drivers;
}
static int
vf_index_compare(gconstpointer a, gconstpointer b)
{
NMSriovVF *vf_a = *(NMSriovVF **) a;
NMSriovVF *vf_b = *(NMSriovVF **) b;
if (vf_a->index < vf_b->index)
return -1;
else if (vf_a->index > vf_b->index)
return 1;
else
return 0;
}
gboolean
_nm_setting_sriov_sort_vfs(NMSettingSriov *setting)
{
gboolean need_sort = FALSE;
guint i;
for (i = 1; i < setting->vfs->len; i++) {
NMSriovVF *vf_prev = setting->vfs->pdata[i - 1];
NMSriovVF *vf = setting->vfs->pdata[i];
if (vf->index <= vf_prev->index) {
need_sort = TRUE;
break;
}
}
if (need_sort) {
g_ptr_array_sort(setting->vfs, vf_index_compare);
_notify(setting, PROP_VFS);
}
return need_sort;
}
/*****************************************************************************/
static GVariant *
vfs_to_dbus(const NMSettInfoSetting * sett_info,
guint property_idx,
NMConnection * connection,
NMSetting * setting,
NMConnectionSerializationFlags flags,
const NMConnectionSerializationOptions *options)
{
gs_unref_ptrarray GPtrArray *vfs = NULL;
GVariantBuilder builder;
guint i;
g_object_get(setting, NM_SETTING_SRIOV_VFS, &vfs, NULL);
g_variant_builder_init(&builder, G_VARIANT_TYPE("aa{sv}"));
if (vfs) {
for (i = 0; i < vfs->len; i++) {
gs_free const char **attr_names = NULL;
NMSriovVF * vf = vfs->pdata[i];
GVariantBuilder vf_builder;
const guint * vlan_ids;
const char ** name;
guint num_vlans = 0;
g_variant_builder_init(&vf_builder, G_VARIANT_TYPE_VARDICT);
g_variant_builder_add(&vf_builder,
"{sv}",
"index",
g_variant_new_uint32(nm_sriov_vf_get_index(vf)));
attr_names = nm_utils_strdict_get_keys(vf->attributes, TRUE, NULL);
if (attr_names) {
for (name = attr_names; *name; name++) {
g_variant_builder_add(&vf_builder,
"{sv}",
*name,
nm_sriov_vf_get_attribute(vf, *name));
}
}
/* VLANs are translated into an array of maps, where each map has
* keys 'id', 'qos' and 'proto'. This guarantees enough flexibility
* to accommodate any future new option. */
vlan_ids = nm_sriov_vf_get_vlan_ids(vf, &num_vlans);
if (num_vlans) {
GVariantBuilder vlans_builder;
guint j;
g_variant_builder_init(&vlans_builder, G_VARIANT_TYPE("aa{sv}"));
for (j = 0; j < num_vlans; j++) {
GVariantBuilder vlan_builder;
g_variant_builder_init(&vlan_builder, G_VARIANT_TYPE("a{sv}"));
g_variant_builder_add(&vlan_builder,
"{sv}",
"id",
g_variant_new_uint32(vlan_ids[j]));
g_variant_builder_add(
&vlan_builder,
"{sv}",
"qos",
g_variant_new_uint32(nm_sriov_vf_get_vlan_qos(vf, vlan_ids[j])));
g_variant_builder_add(
&vlan_builder,
"{sv}",
"protocol",
g_variant_new_uint32(nm_sriov_vf_get_vlan_protocol(vf, vlan_ids[j])));
g_variant_builder_add(&vlans_builder, "a{sv}", &vlan_builder);
}
g_variant_builder_add(&vf_builder,
"{sv}",
"vlans",
g_variant_builder_end(&vlans_builder));
}
g_variant_builder_add(&builder, "a{sv}", &vf_builder);
}
}
return g_variant_builder_end(&builder);
}
static gboolean
vfs_from_dbus(NMSetting * setting,
GVariant * connection_dict,
const char * property,
GVariant * value,
NMSettingParseFlags parse_flags,
GError ** error)
{
GPtrArray * vfs;
GVariantIter vf_iter;
GVariant * vf_var;
g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")), FALSE);
vfs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_sriov_vf_unref);
g_variant_iter_init(&vf_iter, value);
while (g_variant_iter_next(&vf_iter, "@a{sv}", &vf_var)) {
NMSriovVF * vf;
guint32 index;
GVariantIter attr_iter;
const char * attr_name;
GVariant * attr_var, *vlans_var;
if (!g_variant_lookup(vf_var, "index", "u", &index))
goto next;
vf = nm_sriov_vf_new(index);
g_variant_iter_init(&attr_iter, vf_var);
while (g_variant_iter_next(&attr_iter, "{&sv}", &attr_name, &attr_var)) {
if (!NM_IN_STRSET(attr_name, "index", "vlans"))
nm_sriov_vf_set_attribute(vf, attr_name, attr_var);
g_variant_unref(attr_var);
}
if (g_variant_lookup(vf_var, "vlans", "@aa{sv}", &vlans_var)) {
GVariantIter vlan_iter;
GVariant * vlan_var;
g_variant_iter_init(&vlan_iter, vlans_var);
while (g_variant_iter_next(&vlan_iter, "@a{sv}", &vlan_var)) {
NMSriovVFVlanProtocol proto = NM_SRIOV_VF_VLAN_PROTOCOL_802_1Q;
gint64 vlan_id = -1;
guint qos = 0;
g_variant_iter_init(&attr_iter, vlan_var);
while (g_variant_iter_next(&attr_iter, "{&sv}", &attr_name, &attr_var)) {
if (nm_streq(attr_name, "id")
&& g_variant_is_of_type(attr_var, G_VARIANT_TYPE_UINT32))
vlan_id = g_variant_get_uint32(attr_var);
else if (nm_streq(attr_name, "qos")
&& g_variant_is_of_type(attr_var, G_VARIANT_TYPE_UINT32))
qos = g_variant_get_uint32(attr_var);
else if (nm_streq(attr_name, "protocol")
&& g_variant_is_of_type(attr_var, G_VARIANT_TYPE_UINT32))
proto = g_variant_get_uint32(attr_var);
g_variant_unref(attr_var);
}
if (vlan_id != -1)
vf_add_vlan(vf, vlan_id, qos, proto);
g_variant_unref(vlan_var);
}
g_variant_unref(vlans_var);
}
g_ptr_array_add(vfs, vf);
next:
g_variant_unref(vf_var);
}
g_object_set(setting, NM_SETTING_SRIOV_VFS, vfs, NULL);
g_ptr_array_unref(vfs);
return TRUE;
}
/*****************************************************************************/
static gboolean
verify(NMSetting *setting, NMConnection *connection, GError **error)
{
NMSettingSriov *self = NM_SETTING_SRIOV(setting);
guint i;
if (self->vfs->len) {
gs_unref_hashtable GHashTable *h = NULL;
h = g_hash_table_new(nm_direct_hash, NULL);
for (i = 0; i < self->vfs->len; i++) {
NMSriovVF * vf = self->vfs->pdata[i];
gs_free_error GError *local = NULL;
if (vf->index >= self->total_vfs) {
g_set_error(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("VF with index %u, but the total number of VFs is %u"),
vf->index,
self->total_vfs);
g_prefix_error(error,
"%s.%s: ",
NM_SETTING_SRIOV_SETTING_NAME,
NM_SETTING_SRIOV_VFS);
return FALSE;
}
if (!_nm_sriov_vf_attribute_validate_all(vf, &local)) {
g_set_error(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("invalid VF %u: %s"),
vf->index,
local->message);
g_prefix_error(error,
"%s.%s: ",
NM_SETTING_SRIOV_SETTING_NAME,
NM_SETTING_SRIOV_VFS);
return FALSE;
}
if (g_hash_table_contains(h, GUINT_TO_POINTER(vf->index))) {
g_set_error(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("duplicate VF index %u"),
vf->index);
g_prefix_error(error,
"%s.%s: ",
NM_SETTING_SRIOV_SETTING_NAME,
NM_SETTING_SRIOV_VFS);
return FALSE;
}
g_hash_table_add(h, GUINT_TO_POINTER(vf->index));
}
}
/* Failures from here on are NORMALIZABLE... */
if (self->vfs->len) {
for (i = 1; i < self->vfs->len; i++) {
NMSriovVF *vf_prev = self->vfs->pdata[i - 1];
NMSriovVF *vf = self->vfs->pdata[i];
if (vf->index <= vf_prev->index) {
g_set_error(error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("VFs %d and %d are not sorted by ascending index"),
vf_prev->index,
vf->index);
g_prefix_error(error,
"%s.%s: ",
NM_SETTING_SRIOV_SETTING_NAME,
NM_SETTING_SRIOV_VFS);
return NM_SETTING_VERIFY_NORMALIZABLE;
}
}
}
return TRUE;
}
static NMTernary
compare_property(const NMSettInfoSetting *sett_info,
guint property_idx,
NMConnection * con_a,
NMSetting * set_a,
NMConnection * con_b,
NMSetting * set_b,
NMSettingCompareFlags flags)
{
NMSettingSriov *a;
NMSettingSriov *b;
guint i;
if (nm_streq(sett_info->property_infos[property_idx].name, NM_SETTING_SRIOV_VFS)) {
if (set_b) {
a = NM_SETTING_SRIOV(set_a);
b = NM_SETTING_SRIOV(set_b);
if (a->vfs->len != b->vfs->len)
return FALSE;
for (i = 0; i < a->vfs->len; i++) {
if (!nm_sriov_vf_equal(a->vfs->pdata[i], b->vfs->pdata[i]))
return FALSE;
}
}
return TRUE;
}
return NM_SETTING_CLASS(nm_setting_sriov_parent_class)
->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags);
}
/*****************************************************************************/
static void
get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
NMSettingSriov *self = NM_SETTING_SRIOV(object);
switch (prop_id) {
case PROP_TOTAL_VFS:
g_value_set_uint(value, self->total_vfs);
break;
case PROP_VFS:
g_value_take_boxed(value,
_nm_utils_copy_array(self->vfs,
(NMUtilsCopyFunc) nm_sriov_vf_dup,
(GDestroyNotify) nm_sriov_vf_unref));
break;
case PROP_AUTOPROBE_DRIVERS:
g_value_set_enum(value, self->autoprobe_drivers);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void
set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
NMSettingSriov *self = NM_SETTING_SRIOV(object);
switch (prop_id) {
case PROP_TOTAL_VFS:
self->total_vfs = g_value_get_uint(value);
break;
case PROP_VFS:
g_ptr_array_unref(self->vfs);
self->vfs = _nm_utils_copy_array(g_value_get_boxed(value),
(NMUtilsCopyFunc) nm_sriov_vf_dup,
(GDestroyNotify) nm_sriov_vf_unref);
break;
case PROP_AUTOPROBE_DRIVERS:
self->autoprobe_drivers = g_value_get_enum(value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
/*****************************************************************************/
static void
nm_setting_sriov_init(NMSettingSriov *setting)
{
setting->vfs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_sriov_vf_unref);
setting->autoprobe_drivers = NM_TERNARY_DEFAULT;
}
/**
* nm_setting_sriov_new:
*
* Creates a new #NMSettingSriov object with default values.
*
* Returns: (transfer full): the new empty #NMSettingSriov object
*
* Since: 1.14
**/
NMSetting *
nm_setting_sriov_new(void)
{
return g_object_new(NM_TYPE_SETTING_SRIOV, NULL);
}
static void
finalize(GObject *object)
{
NMSettingSriov *self = NM_SETTING_SRIOV(object);
g_ptr_array_unref(self->vfs);
G_OBJECT_CLASS(nm_setting_sriov_parent_class)->finalize(object);
}
static void
nm_setting_sriov_class_init(NMSettingSriovClass *klass)
{
GObjectClass * object_class = G_OBJECT_CLASS(klass);
NMSettingClass *setting_class = NM_SETTING_CLASS(klass);
GArray * properties_override = _nm_sett_info_property_override_create_array();
object_class->get_property = get_property;
object_class->set_property = set_property;
object_class->finalize = finalize;
setting_class->compare_property = compare_property;
setting_class->verify = verify;
/**
* NMSettingSriov:total-vfs
*
* The total number of virtual functions to create.
*
* Note that when the sriov setting is present NetworkManager
* enforces the number of virtual functions on the interface
* (also when it is zero) during activation and resets it
* upon deactivation. To prevent any changes to SR-IOV
* parameters don't add a sriov setting to the connection.
*
* Since: 1.14
**/
/* ---ifcfg-rh---
* property: total-vfs
* variable: SRIOV_TOTAL_VFS(+)
* description: The total number of virtual functions to create
* example: SRIOV_TOTAL_VFS=16
* ---end---
*/
obj_properties[PROP_TOTAL_VFS] = g_param_spec_uint(
NM_SETTING_SRIOV_TOTAL_VFS,
"",
"",
0,
G_MAXUINT32,
0,
NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
/**
* NMSettingSriov:vfs: (type GPtrArray(NMSriovVF))
*
* Array of virtual function descriptors.
*
* Each VF descriptor is a dictionary mapping attribute names
* to GVariant values. The 'index' entry is mandatory for
* each VF.
*
* When represented as string a VF is in the form:
*
* "INDEX [ATTR=VALUE[ ATTR=VALUE]...]".
*
* for example:
*
* "2 mac=00:11:22:33:44:55 spoof-check=true".
*
* Multiple VFs can be specified using a comma as separator.
* Currently, the following attributes are supported: mac,
* spoof-check, trust, min-tx-rate, max-tx-rate, vlans.
*
* The "vlans" attribute is represented as a semicolon-separated
* list of VLAN descriptors, where each descriptor has the form
*
* "ID[.PRIORITY[.PROTO]]".
*
* PROTO can be either 'q' for 802.1Q (the default) or 'ad' for
* 802.1ad.
*
* Since: 1.14
**/
/* ---ifcfg-rh---
* property: vfs
* variable: SRIOV_VF1(+), SRIOV_VF2(+), ...
* description: SR-IOV virtual function descriptors
* example: SRIOV_VF10="mac=00:11:22:33:44:55", ...
* ---end---
*/
obj_properties[PROP_VFS] = g_param_spec_boxed(NM_SETTING_SRIOV_VFS,
"",
"",
G_TYPE_PTR_ARRAY,
G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE
| G_PARAM_STATIC_STRINGS);
_nm_properties_override_gobj(properties_override,
obj_properties[PROP_VFS],
NM_SETT_INFO_PROPERT_TYPE(.dbus_type = NM_G_VARIANT_TYPE("aa{sv}"),
.to_dbus_fcn = vfs_to_dbus,
.from_dbus_fcn = vfs_from_dbus, ));
/**
* NMSettingSriov:autoprobe-drivers
*
* Whether to autoprobe virtual functions by a compatible driver.
*
* If set to %NM_TERNARY_TRUE, the kernel will try to bind VFs to
* a compatible driver and if this succeeds a new network
* interface will be instantiated for each VF.
*
* If set to %NM_TERNARY_FALSE, VFs will not be claimed and no
* network interfaces will be created for them.
*
* When set to %NM_TERNARY_DEFAULT, the global default is used; in
* case the global default is unspecified it is assumed to be
* %NM_TERNARY_TRUE.
*
* Since: 1.14
**/
/* ---ifcfg-rh---
* property: autoprobe-drivers
* variable: SRIOV_AUTOPROBE_DRIVERS(+)
* default: missing variable means global default
* description: Whether to autoprobe virtual functions by a compatible driver
* example: SRIOV_AUTOPROBE_DRIVERS=0,1
* ---end---
*/
obj_properties[PROP_AUTOPROBE_DRIVERS] = g_param_spec_enum(
NM_SETTING_SRIOV_AUTOPROBE_DRIVERS,
"",
"",
NM_TYPE_TERNARY,
NM_TERNARY_DEFAULT,
NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
_nm_setting_class_commit_full(setting_class,
NM_META_SETTING_TYPE_SRIOV,
NULL,
properties_override);
}