diff options
Diffstat (limited to 'src/libnm-core-impl/nm-setting-sriov.c')
| -rw-r--r-- | src/libnm-core-impl/nm-setting-sriov.c | 1372 |
1 files changed, 1372 insertions, 0 deletions
diff --git a/src/libnm-core-impl/nm-setting-sriov.c b/src/libnm-core-impl/nm-setting-sriov.c new file mode 100644 index 00000000..b18d184a --- /dev/null +++ b/src/libnm-core-impl/nm-setting-sriov.c @@ -0,0 +1,1372 @@ +/* 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(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); +} |