diff --git a/Documentation/netlink/specs/devlink.yaml b/Documentation/netlink/specs/devlink.yaml index 52ad1e7805d1..38b1190f3d26 100644 --- a/Documentation/netlink/specs/devlink.yaml +++ b/Documentation/netlink/specs/devlink.yaml @@ -895,6 +895,16 @@ attribute-sets: resource-dump response. Bit 0 (dev) selects device-level resources; bit 1 (port) selects port-level resources. When absent all classes are returned. + - + name: parent-dev + type: nest + nested-attributes: dl-parent-dev + doc: | + Identifies the devlink instance which owns the parent rate node. + Used with rate-set and rate-new to parent a rate object to a node on + a different devlink instance, enabling cross-device rate scheduling. + When absent, the parent node is resolved on the same instance. + - name: dl-dev-stats subset-of: devlink @@ -1317,6 +1327,16 @@ attribute-sets: Specifies the bandwidth share assigned to the Traffic Class. The bandwidth for the traffic class is determined in proportion to the sum of the shares of all configured classes. + - + name: dl-parent-dev + subset-of: devlink + attributes: + - + name: bus-name + - + name: dev-name + - + name: index operations: enum-model: directional @@ -2289,8 +2309,8 @@ operations: dont-validate: [strict] flags: [admin-perm] do: - pre: devlink-nl-pre-doit - post: devlink-nl-post-doit + pre: devlink-nl-pre-doit-parent-dev-optional + post: devlink-nl-post-doit-parent-dev-optional request: attributes: - bus-name @@ -2303,6 +2323,7 @@ operations: - rate-tx-weight - rate-parent-node-name - rate-tc-bws + - parent-dev - name: rate-new @@ -2311,8 +2332,8 @@ operations: dont-validate: [strict] flags: [admin-perm] do: - pre: devlink-nl-pre-doit - post: devlink-nl-post-doit + pre: devlink-nl-pre-doit-parent-dev-optional + post: devlink-nl-post-doit-parent-dev-optional request: attributes: - bus-name @@ -2325,6 +2346,7 @@ operations: - rate-tx-weight - rate-parent-node-name - rate-tc-bws + - parent-dev - name: rate-del diff --git a/Documentation/networking/devlink/devlink-port.rst b/Documentation/networking/devlink/devlink-port.rst index 9374ebe70f48..18aca77006d5 100644 --- a/Documentation/networking/devlink/devlink-port.rst +++ b/Documentation/networking/devlink/devlink-port.rst @@ -420,6 +420,8 @@ API allows to configure following rate object's parameters: Parent node name. Parent node rate limits are considered as additional limits to all node children limits. ``tx_max`` is an upper limit for children. ``tx_share`` is a total bandwidth distributed among children. + If the device supports cross-function scheduling, the parent can be from a + different function of the same underlying device. ``tc_bw`` Allow users to set the bandwidth allocation per traffic class on rate diff --git a/Documentation/networking/devlink/index.rst b/Documentation/networking/devlink/index.rst index 32f70879ddd0..4745148fecf4 100644 --- a/Documentation/networking/devlink/index.rst +++ b/Documentation/networking/devlink/index.rst @@ -31,10 +31,10 @@ sure to respect following rules: - Lock ordering should be maintained. If driver needs to take instance lock of both nested and parent instances at the same time, devlink - instance lock of the parent instance should be taken first, only then - instance lock of the nested instance could be taken. - - Driver should use object-specific helpers to setup the nested relationship - before registering the nested devlink instance: + instance lock of the nested instance should be taken first, only then + instance lock of the parent instance could be taken. + - Driver should use object-specific helpers to setup the + nested relationship: - ``devl_nested_devlink_set()`` - called to setup devlink -> nested devlink relationship (could be used for multiple nested instances). diff --git a/Documentation/networking/devlink/mlx5.rst b/Documentation/networking/devlink/mlx5.rst index 4bba4d780a4a..cf1dffa67669 100644 --- a/Documentation/networking/devlink/mlx5.rst +++ b/Documentation/networking/devlink/mlx5.rst @@ -419,3 +419,36 @@ User commands examples: .. note:: This command can run over all interfaces such as PF/VF and representor ports. + +Rates +===== + +mlx5 devices can limit transmission of individual VFs or a group of them via +the devlink-rate API in switchdev mode. + +User commands examples: + +- Print the existing rates:: + + $ devlink port function rate show + +- Set a max tx limit on traffic from VF0:: + + $ devlink port function rate set pci/0000:82:00.0/1 tx_max 10Gbit + +- Create a rate group with a max tx limit and add two VFs to it:: + + $ devlink port function rate add pci/0000:82:00.0/group1 tx_max 10Gbit + $ devlink port function rate set pci/0000:82:00.0/1 parent group1 + $ devlink port function rate set pci/0000:82:00.0/2 parent group1 + +- Same scenario, with a min guarantee of 20% of the bandwidth for the first VF:: + + $ devlink port function rate add pci/0000:82:00.0/group1 tx_max 10Gbit + $ devlink port function rate set pci/0000:82:00.0/1 parent group1 tx_share 2Gbit + $ devlink port function rate set pci/0000:82:00.0/2 parent group1 + +- Cross-device scheduling:: + + $ devlink port function rate add pci/0000:82:00.0/group1 tx_max 10Gbit + $ devlink port function rate set pci/0000:82:00.1/32769 parent pci/0000:82:00.0/group1 diff --git a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c index c31e05529fc4..b9026cc64383 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c @@ -383,6 +383,7 @@ static const struct devlink_ops mlx5_devlink_ops = { .rate_node_del = mlx5_esw_devlink_rate_node_del, .rate_leaf_parent_set = mlx5_esw_devlink_rate_leaf_parent_set, .rate_node_parent_set = mlx5_esw_devlink_rate_node_parent_set, + .supported_cross_device_rate_nodes = true, #endif #ifdef CONFIG_MLX5_SF_MANAGER .port_new = mlx5_devlink_sf_port_new, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c index 6e50311faa27..8c27a33f9d7b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c @@ -268,7 +268,6 @@ void mlx5_esw_offloads_devlink_port_unregister(struct mlx5_vport *vport) dl_port = vport->dl_port; mlx5_esw_devlink_port_res_unregister(&dl_port->dl_port); - mlx5_esw_qos_vport_update_parent(vport, NULL, NULL); devl_rate_leaf_destroy(&dl_port->dl_port); devl_port_unregister(&dl_port->dl_port); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c index faccc60fc93a..0d20f51b9702 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.c @@ -11,57 +11,8 @@ /* Minimum supported BW share value by the HW is 1 Mbit/sec */ #define MLX5_MIN_BW_SHARE 1 -/* Holds rate nodes associated with an E-Switch. */ -struct mlx5_qos_domain { - /* Serializes access to all qos changes in the qos domain. */ - struct mutex lock; - /* List of all mlx5_esw_sched_nodes. */ - struct list_head nodes; -}; - -static void esw_qos_lock(struct mlx5_eswitch *esw) -{ - mutex_lock(&esw->qos.domain->lock); -} - -static void esw_qos_unlock(struct mlx5_eswitch *esw) -{ - mutex_unlock(&esw->qos.domain->lock); -} - -static void esw_assert_qos_lock_held(struct mlx5_eswitch *esw) -{ - lockdep_assert_held(&esw->qos.domain->lock); -} - -static struct mlx5_qos_domain *esw_qos_domain_alloc(void) -{ - struct mlx5_qos_domain *qos_domain; - - qos_domain = kzalloc_obj(*qos_domain); - if (!qos_domain) - return NULL; - - mutex_init(&qos_domain->lock); - INIT_LIST_HEAD(&qos_domain->nodes); - - return qos_domain; -} - -static int esw_qos_domain_init(struct mlx5_eswitch *esw) -{ - esw->qos.domain = esw_qos_domain_alloc(); - - return esw->qos.domain ? 0 : -ENOMEM; -} - -static void esw_qos_domain_release(struct mlx5_eswitch *esw) -{ - kfree(esw->qos.domain); - esw->qos.domain = NULL; -} - enum sched_node_type { + SCHED_NODE_TYPE_ROOT, SCHED_NODE_TYPE_VPORTS_TSAR, SCHED_NODE_TYPE_VPORT, SCHED_NODE_TYPE_TC_ARBITER_TSAR, @@ -94,7 +45,9 @@ struct mlx5_esw_sched_node { enum sched_node_type type; /* The eswitch this node belongs to. */ struct mlx5_eswitch *esw; - /* The children nodes of this node, empty list for leaf nodes. */ + /* The children nodes of this node, empty list for leaf nodes. + * Can be from multiple E-Switches. + */ struct list_head children; /* Valid only if this node is associated with a vport. */ struct mlx5_vport *vport; @@ -106,16 +59,63 @@ struct mlx5_esw_sched_node { u32 tc_bw[DEVLINK_RATE_TCS_MAX]; }; -static void esw_qos_node_attach_to_parent(struct mlx5_esw_sched_node *node) +/* Locking notes: + * QoS changes are normally protected by the shd lock. But on older HW shd + * might not be created at all, so there needs to be a fallback serialization + * mechanism. This is esw->state_lock. + * Callers into QoS hold a combination of RTNL, devlink instance lock and + * esw->state_lock. Devlink rate ops additionally hold the shd lock if it + * exists. + * - VF rate ops use esw_qos_lock/esw_qos_unlock. + * - callers with esw->state_lock held use esw_qos_shd_lock/esw_qos_shd_unlock. + * - devlink callers use esw_qos_devlink_lock/esw_qos_devlink_unlock. + */ +static void esw_assert_qos_lock_held(struct mlx5_core_dev *dev) { - if (!node->parent) { - /* Root children are assigned a depth level of 2. */ - node->level = 2; - list_add_tail(&node->entry, &node->esw->qos.domain->nodes); - } else { - node->level = node->parent->level + 1; - list_add_tail(&node->entry, &node->parent->children); - } + if (dev->shd) + devl_assert_locked(dev->shd); + else + lockdep_assert_held(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_lock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_lock(dev->shd); + else + mutex_lock(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_unlock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_unlock(dev->shd); + else + mutex_unlock(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_shd_lock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_lock(dev->shd); +} + +static void esw_qos_shd_unlock(struct mlx5_core_dev *dev) +{ + if (dev->shd) + devl_unlock(dev->shd); +} + +static void esw_qos_devlink_lock(struct mlx5_core_dev *dev) +{ + if (!dev->shd) + mutex_lock(&dev->priv.eswitch->state_lock); +} + +static void esw_qos_devlink_unlock(struct mlx5_core_dev *dev) +{ + if (!dev->shd) + mutex_unlock(&dev->priv.eswitch->state_lock); } static int esw_qos_num_tcs(struct mlx5_core_dev *dev) @@ -125,14 +125,14 @@ static int esw_qos_num_tcs(struct mlx5_core_dev *dev) return num_tcs < DEVLINK_RATE_TCS_MAX ? num_tcs : DEVLINK_RATE_TCS_MAX; } -static void -esw_qos_node_set_parent(struct mlx5_esw_sched_node *node, struct mlx5_esw_sched_node *parent) +static void esw_qos_node_set_parent(struct mlx5_esw_sched_node *node, + struct mlx5_esw_sched_node *parent) { - list_del_init(&node->entry); node->parent = parent; - if (parent) - node->esw = parent->esw; - esw_qos_node_attach_to_parent(node); + node->esw = parent->esw; + node->level = parent->level + 1; + list_del(&node->entry); + list_add_tail(&node->entry, &parent->children); } static void esw_qos_nodes_set_parent(struct list_head *nodes, @@ -321,22 +321,19 @@ static int esw_qos_create_rate_limit_element(struct mlx5_esw_sched_node *node, return esw_qos_node_create_sched_element(node, sched_ctx, extack); } -static u32 esw_qos_calculate_min_rate_divider(struct mlx5_eswitch *esw, - struct mlx5_esw_sched_node *parent) +static u32 +esw_qos_calculate_min_rate_divider(struct mlx5_esw_sched_node *parent) { - struct list_head *nodes = parent ? &parent->children : &esw->qos.domain->nodes; - u32 fw_max_bw_share = MLX5_CAP_QOS(esw->dev, max_tsar_bw_share); + u32 fw_max_bw_share = MLX5_CAP_QOS(parent->esw->dev, max_tsar_bw_share); struct mlx5_esw_sched_node *node; u32 max_guarantee = 0; /* Find max min_rate across all nodes. * This will correspond to fw_max_bw_share in the final bw_share calculation. */ - list_for_each_entry(node, nodes, entry) { - if (node->esw == esw && node->ix != esw->qos.root_tsar_ix && - node->min_rate > max_guarantee) + list_for_each_entry(node, &parent->children, entry) + if (node->min_rate > max_guarantee) max_guarantee = node->min_rate; - } if (max_guarantee) return max_t(u32, max_guarantee / fw_max_bw_share, 1); @@ -368,18 +365,13 @@ static void esw_qos_update_sched_node_bw_share(struct mlx5_esw_sched_node *node, esw_qos_sched_elem_config(node, node->max_rate, bw_share, extack); } -static void esw_qos_normalize_min_rate(struct mlx5_eswitch *esw, - struct mlx5_esw_sched_node *parent, +static void esw_qos_normalize_min_rate(struct mlx5_esw_sched_node *parent, struct netlink_ext_ack *extack) { - struct list_head *nodes = parent ? &parent->children : &esw->qos.domain->nodes; - u32 divider = esw_qos_calculate_min_rate_divider(esw, parent); + u32 divider = esw_qos_calculate_min_rate_divider(parent); struct mlx5_esw_sched_node *node; - list_for_each_entry(node, nodes, entry) { - if (node->esw != esw || node->ix == esw->qos.root_tsar_ix) - continue; - + list_for_each_entry(node, &parent->children, entry) { /* Vports TC TSARs don't have a minimum rate configured, * so there's no need to update the bw_share on them. */ @@ -391,7 +383,7 @@ static void esw_qos_normalize_min_rate(struct mlx5_eswitch *esw, if (list_empty(&node->children)) continue; - esw_qos_normalize_min_rate(node->esw, node, extack); + esw_qos_normalize_min_rate(node, extack); } } @@ -412,14 +404,11 @@ static u32 esw_qos_calculate_tc_bw_divider(u32 *tc_bw) static int esw_qos_set_node_min_rate(struct mlx5_esw_sched_node *node, u32 min_rate, struct netlink_ext_ack *extack) { - struct mlx5_eswitch *esw = node->esw; - if (min_rate == node->min_rate) return 0; node->min_rate = min_rate; - esw_qos_normalize_min_rate(esw, node->parent, extack); - + esw_qos_normalize_min_rate(node->parent, extack); return 0; } @@ -460,6 +449,7 @@ esw_qos_vport_create_sched_element(struct mlx5_esw_sched_node *vport_node, struct mlx5_esw_sched_node *parent = vport_node->parent; u32 sched_ctx[MLX5_ST_SZ_DW(scheduling_context)] = {}; struct mlx5_core_dev *dev = vport_node->esw->dev; + struct mlx5_vport *vport = vport_node->vport; void *attr; if (!mlx5_qos_element_type_supported( @@ -471,11 +461,17 @@ esw_qos_vport_create_sched_element(struct mlx5_esw_sched_node *vport_node, MLX5_SET(scheduling_context, sched_ctx, element_type, SCHEDULING_CONTEXT_ELEMENT_TYPE_VPORT); attr = MLX5_ADDR_OF(scheduling_context, sched_ctx, element_attributes); - MLX5_SET(vport_element, attr, vport_number, vport_node->vport->vport); - MLX5_SET(scheduling_context, sched_ctx, parent_element_id, - parent ? parent->ix : vport_node->esw->qos.root_tsar_ix); + MLX5_SET(vport_element, attr, vport_number, vport->vport); + MLX5_SET(scheduling_context, sched_ctx, parent_element_id, parent->ix); MLX5_SET(scheduling_context, sched_ctx, max_average_bw, vport_node->max_rate); + if (vport->dev != dev) { + /* The port is assigned to a node on another eswitch. */ + MLX5_SET(vport_element, attr, eswitch_owner_vhca_id_valid, + true); + MLX5_SET(vport_element, attr, eswitch_owner_vhca_id, + MLX5_CAP_GEN(vport->dev, vhca_id)); + } return esw_qos_node_create_sched_element(vport_node, sched_ctx, extack); } @@ -487,6 +483,7 @@ esw_qos_vport_tc_create_sched_element(struct mlx5_esw_sched_node *vport_tc_node, { u32 sched_ctx[MLX5_ST_SZ_DW(scheduling_context)] = {}; struct mlx5_core_dev *dev = vport_tc_node->esw->dev; + struct mlx5_vport *vport = vport_tc_node->vport; void *attr; if (!mlx5_qos_element_type_supported( @@ -498,8 +495,7 @@ esw_qos_vport_tc_create_sched_element(struct mlx5_esw_sched_node *vport_tc_node, MLX5_SET(scheduling_context, sched_ctx, element_type, SCHEDULING_CONTEXT_ELEMENT_TYPE_VPORT_TC); attr = MLX5_ADDR_OF(scheduling_context, sched_ctx, element_attributes); - MLX5_SET(vport_tc_element, attr, vport_number, - vport_tc_node->vport->vport); + MLX5_SET(vport_tc_element, attr, vport_number, vport->vport); MLX5_SET(vport_tc_element, attr, traffic_class, vport_tc_node->tc); MLX5_SET(scheduling_context, sched_ctx, max_bw_obj_id, rate_limit_elem_ix); @@ -507,13 +503,20 @@ esw_qos_vport_tc_create_sched_element(struct mlx5_esw_sched_node *vport_tc_node, vport_tc_node->parent->ix); MLX5_SET(scheduling_context, sched_ctx, bw_share, vport_tc_node->bw_share); + if (vport->dev != dev) { + /* The port is assigned to a node on another eswitch. */ + MLX5_SET(vport_tc_element, attr, eswitch_owner_vhca_id_valid, + true); + MLX5_SET(vport_tc_element, attr, eswitch_owner_vhca_id, + MLX5_CAP_GEN(vport->dev, vhca_id)); + } return esw_qos_node_create_sched_element(vport_tc_node, sched_ctx, extack); } static struct mlx5_esw_sched_node * -__esw_qos_alloc_node(struct mlx5_eswitch *esw, u32 tsar_ix, enum sched_node_type type, +__esw_qos_alloc_node(u32 tsar_ix, enum sched_node_type type, struct mlx5_esw_sched_node *parent) { struct mlx5_esw_sched_node *node; @@ -522,20 +525,12 @@ __esw_qos_alloc_node(struct mlx5_eswitch *esw, u32 tsar_ix, enum sched_node_type if (!node) return NULL; - node->esw = esw; node->ix = tsar_ix; node->type = type; - node->parent = parent; INIT_LIST_HEAD(&node->children); - esw_qos_node_attach_to_parent(node); - if (!parent) { - /* The caller is responsible for inserting the node into the - * parent list if necessary. This function can also be used with - * a NULL parent, which doesn't necessarily indicate that it - * refers to the root scheduling element. - */ - list_del_init(&node->entry); - } + INIT_LIST_HEAD(&node->entry); + if (parent) + esw_qos_node_set_parent(node, parent); return node; } @@ -570,7 +565,7 @@ static int esw_qos_create_vports_tc_node(struct mlx5_esw_sched_node *parent, SCHEDULING_HIERARCHY_E_SWITCH)) return -EOPNOTSUPP; - vports_tc_node = __esw_qos_alloc_node(parent->esw, 0, + vports_tc_node = __esw_qos_alloc_node(0, SCHED_NODE_TYPE_VPORTS_TC_TSAR, parent); if (!vports_tc_node) { @@ -665,7 +660,6 @@ static int esw_qos_create_tc_arbiter_sched_elem( struct netlink_ext_ack *extack) { u32 tsar_ctx[MLX5_ST_SZ_DW(scheduling_context)] = {}; - u32 tsar_parent_ix; void *attr; if (!mlx5_qos_tsar_type_supported(tc_arbiter_node->esw->dev, @@ -678,10 +672,8 @@ static int esw_qos_create_tc_arbiter_sched_elem( attr = MLX5_ADDR_OF(scheduling_context, tsar_ctx, element_attributes); MLX5_SET(tsar_element, attr, tsar_type, TSAR_ELEMENT_TSAR_TYPE_TC_ARB); - tsar_parent_ix = tc_arbiter_node->parent ? tc_arbiter_node->parent->ix : - tc_arbiter_node->esw->qos.root_tsar_ix; MLX5_SET(scheduling_context, tsar_ctx, parent_element_id, - tsar_parent_ix); + tc_arbiter_node->parent->ix); MLX5_SET(scheduling_context, tsar_ctx, element_type, SCHEDULING_CONTEXT_ELEMENT_TYPE_TSAR); MLX5_SET(scheduling_context, tsar_ctx, max_average_bw, @@ -694,37 +686,36 @@ static int esw_qos_create_tc_arbiter_sched_elem( } static struct mlx5_esw_sched_node * -__esw_qos_create_vports_sched_node(struct mlx5_eswitch *esw, struct mlx5_esw_sched_node *parent, +__esw_qos_create_vports_sched_node(struct mlx5_esw_sched_node *parent, struct netlink_ext_ack *extack) { struct mlx5_esw_sched_node *node; - u32 tsar_ix; int err; + u32 ix; - err = esw_qos_create_node_sched_elem(esw->dev, esw->qos.root_tsar_ix, 0, - 0, &tsar_ix); + err = esw_qos_create_node_sched_elem(parent->esw->dev, parent->ix, 0, 0, + &ix); if (err) { NL_SET_ERR_MSG_MOD(extack, "E-Switch create TSAR for node failed"); return ERR_PTR(err); } - node = __esw_qos_alloc_node(esw, tsar_ix, SCHED_NODE_TYPE_VPORTS_TSAR, parent); + node = __esw_qos_alloc_node(ix, SCHED_NODE_TYPE_VPORTS_TSAR, parent); if (!node) { NL_SET_ERR_MSG_MOD(extack, "E-Switch alloc node failed"); err = -ENOMEM; goto err_alloc_node; } - list_add_tail(&node->entry, &esw->qos.domain->nodes); - esw_qos_normalize_min_rate(esw, NULL, extack); - trace_mlx5_esw_node_qos_create(esw->dev, node, node->ix); + esw_qos_normalize_min_rate(parent, extack); + trace_mlx5_esw_node_qos_create(parent->esw->dev, node, node->ix); return node; err_alloc_node: - if (mlx5_destroy_scheduling_element_cmd(esw->dev, + if (mlx5_destroy_scheduling_element_cmd(parent->esw->dev, SCHEDULING_HIERARCHY_E_SWITCH, - tsar_ix)) + ix)) NL_SET_ERR_MSG_MOD(extack, "E-Switch destroy TSAR for node failed"); return ERR_PTR(err); } @@ -738,7 +729,7 @@ esw_qos_create_vports_sched_node(struct mlx5_eswitch *esw, struct netlink_ext_ac struct mlx5_esw_sched_node *node; int err; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(esw->dev); if (!MLX5_CAP_QOS(esw->dev, log_esw_max_sched_depth)) return ERR_PTR(-EOPNOTSUPP); @@ -746,7 +737,7 @@ esw_qos_create_vports_sched_node(struct mlx5_eswitch *esw, struct netlink_ext_ac if (err) return ERR_PTR(err); - node = __esw_qos_create_vports_sched_node(esw, NULL, extack); + node = __esw_qos_create_vports_sched_node(esw->qos.root, extack); if (IS_ERR(node)) esw_qos_put(esw); @@ -762,45 +753,54 @@ static void __esw_qos_destroy_node(struct mlx5_esw_sched_node *node, struct netl trace_mlx5_esw_node_qos_destroy(esw->dev, node, node->ix); esw_qos_destroy_node(node, extack); - esw_qos_normalize_min_rate(esw, NULL, extack); + esw_qos_normalize_min_rate(esw->qos.root, extack); } static int esw_qos_create(struct mlx5_eswitch *esw, struct netlink_ext_ack *extack) { struct mlx5_core_dev *dev = esw->dev; + struct mlx5_esw_sched_node *root; + u32 root_ix; int err; if (!MLX5_CAP_GEN(dev, qos) || !MLX5_CAP_QOS(dev, esw_scheduling)) return -EOPNOTSUPP; - err = esw_qos_create_node_sched_elem(esw->dev, 0, 0, 0, - &esw->qos.root_tsar_ix); + err = esw_qos_create_node_sched_elem(esw->dev, 0, 0, 0, &root_ix); if (err) { esw_warn(dev, "E-Switch create root TSAR failed (%d)\n", err); return err; } + root = __esw_qos_alloc_node(root_ix, SCHED_NODE_TYPE_ROOT, NULL); + if (!root) { + esw_warn(dev, "E-Switch create root node failed\n"); + err = -ENOMEM; + goto out_err; + } + root->esw = esw; + root->level = 1; + esw->qos.root = root; refcount_set(&esw->qos.refcnt, 1); return 0; +out_err: + mlx5_destroy_scheduling_element_cmd(dev, SCHEDULING_HIERARCHY_E_SWITCH, + root_ix); + return err; } static void esw_qos_destroy(struct mlx5_eswitch *esw) { - int err; - - err = mlx5_destroy_scheduling_element_cmd(esw->dev, - SCHEDULING_HIERARCHY_E_SWITCH, - esw->qos.root_tsar_ix); - if (err) - esw_warn(esw->dev, "E-Switch destroy root TSAR failed (%d)\n", err); + esw_qos_destroy_node(esw->qos.root, NULL); + esw->qos.root = NULL; } static int esw_qos_get(struct mlx5_eswitch *esw, struct netlink_ext_ack *extack) { int err = 0; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(esw->dev); if (!refcount_inc_not_zero(&esw->qos.refcnt)) { /* esw_qos_create() set refcount to 1 only on success. * No need to decrement on failure. @@ -813,7 +813,7 @@ static int esw_qos_get(struct mlx5_eswitch *esw, struct netlink_ext_ack *extack) static void esw_qos_put(struct mlx5_eswitch *esw) { - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(esw->dev); if (refcount_dec_and_test(&esw->qos.refcnt)) esw_qos_destroy(esw); } @@ -866,8 +866,7 @@ esw_qos_create_vport_tc_sched_node(struct mlx5_vport *vport, u8 tc = vports_tc_node->tc; int err; - vport_tc_node = __esw_qos_alloc_node(vport_node->esw, 0, - SCHED_NODE_TYPE_VPORT_TC, + vport_tc_node = __esw_qos_alloc_node(0, SCHED_NODE_TYPE_VPORT_TC, vports_tc_node); if (!vport_tc_node) return -ENOMEM; @@ -959,7 +958,7 @@ esw_qos_vport_tc_enable(struct mlx5_vport *vport, enum sched_node_type type, /* Increase the parent's level by 2 to account for both the * TC arbiter and the vports TC scheduling element. */ - new_level = (parent ? parent->level : 2) + 2; + new_level = parent->level + 2; max_level = 1 << MLX5_CAP_QOS(vport_node->esw->dev, log_esw_max_sched_depth); if (new_level > max_level) { @@ -970,7 +969,7 @@ esw_qos_vport_tc_enable(struct mlx5_vport *vport, enum sched_node_type type, } } - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (type == SCHED_NODE_TYPE_RATE_LIMITER) err = esw_qos_create_rate_limit_element(vport_node, extack); @@ -997,7 +996,7 @@ esw_qos_vport_tc_enable(struct mlx5_vport *vport, enum sched_node_type type, err_sched_nodes: if (type == SCHED_NODE_TYPE_RATE_LIMITER) { esw_qos_node_destroy_sched_element(vport_node, NULL); - esw_qos_node_attach_to_parent(vport_node); + esw_qos_node_set_parent(vport_node, vport_node->parent); } else { esw_qos_tc_arbiter_scheduling_teardown(vport_node, NULL); } @@ -1055,7 +1054,7 @@ static void esw_qos_vport_disable(struct mlx5_vport *vport, struct netlink_ext_a vport_node->bw_share = 0; memset(vport_node->tc_bw, 0, sizeof(vport_node->tc_bw)); list_del_init(&vport_node->entry); - esw_qos_normalize_min_rate(vport_node->esw, vport_node->parent, extack); + esw_qos_normalize_min_rate(vport_node->parent, extack); trace_mlx5_esw_vport_qos_destroy(vport_node->esw->dev, vport); } @@ -1068,7 +1067,7 @@ static int esw_qos_vport_enable(struct mlx5_vport *vport, struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; int err; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); esw_qos_node_set_parent(vport_node, parent); if (type == SCHED_NODE_TYPE_VPORT) @@ -1079,9 +1078,10 @@ static int esw_qos_vport_enable(struct mlx5_vport *vport, return err; vport_node->type = type; - esw_qos_normalize_min_rate(vport_node->esw, parent, extack); - trace_mlx5_esw_vport_qos_create(vport->dev, vport, vport_node->max_rate, - vport_node->bw_share); + esw_qos_normalize_min_rate(parent, extack); + trace_mlx5_esw_vport_qos_create(vport_node->esw->dev, vport, + vport_node->bw_share, + vport_node->max_rate); return 0; } @@ -1092,22 +1092,20 @@ static int mlx5_esw_qos_vport_enable(struct mlx5_vport *vport, enum sched_node_t { struct mlx5_eswitch *esw = vport->dev->priv.eswitch; struct mlx5_esw_sched_node *sched_node; - struct mlx5_eswitch *parent_esw; int err; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(vport->dev); err = esw_qos_get(esw, extack); if (err) return err; - parent_esw = parent ? parent->esw : esw; - sched_node = __esw_qos_alloc_node(parent_esw, 0, type, parent); + if (!parent) + parent = esw->qos.root; + sched_node = __esw_qos_alloc_node(0, type, parent); if (!sched_node) { esw_qos_put(esw); return -ENOMEM; } - if (!parent) - list_add_tail(&sched_node->entry, &esw->qos.domain->nodes); sched_node->max_rate = max_rate; sched_node->min_rate = min_rate; @@ -1125,33 +1123,23 @@ static int mlx5_esw_qos_vport_enable(struct mlx5_vport *vport, enum sched_node_t static void mlx5_esw_qos_vport_disable_locked(struct mlx5_vport *vport) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(vport->dev); if (!vport->qos.sched_node) return; esw_qos_vport_disable(vport, NULL); mlx5_esw_qos_vport_qos_free(vport); - esw_qos_put(esw); + esw_qos_put(vport->dev->priv.eswitch); } void mlx5_esw_qos_vport_disable(struct mlx5_vport *vport) { struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - struct mlx5_esw_sched_node *parent; lockdep_assert_held(&esw->state_lock); - esw_qos_lock(esw); - if (!vport->qos.sched_node) - goto unlock; - - parent = vport->qos.sched_node->parent; - WARN(parent, "Disabling QoS on port before detaching it from node"); - + esw_qos_shd_lock(vport->dev); mlx5_esw_qos_vport_disable_locked(vport); -unlock: - esw_qos_unlock(esw); + esw_qos_shd_unlock(vport->dev); } static int mlx5_esw_qos_set_vport_max_rate(struct mlx5_vport *vport, u32 max_rate, @@ -1159,7 +1147,7 @@ static int mlx5_esw_qos_set_vport_max_rate(struct mlx5_vport *vport, u32 max_rat { struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (!vport_node) return mlx5_esw_qos_vport_enable(vport, SCHED_NODE_TYPE_VPORT, NULL, max_rate, 0, @@ -1174,7 +1162,7 @@ static int mlx5_esw_qos_set_vport_min_rate(struct mlx5_vport *vport, u32 min_rat { struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (!vport_node) return mlx5_esw_qos_vport_enable(vport, SCHED_NODE_TYPE_VPORT, NULL, 0, min_rate, @@ -1187,29 +1175,27 @@ static int mlx5_esw_qos_set_vport_min_rate(struct mlx5_vport *vport, u32 min_rat int mlx5_esw_qos_set_vport_rate(struct mlx5_vport *vport, u32 max_rate, u32 min_rate) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; int err; - esw_qos_lock(esw); + esw_qos_lock(vport->dev); err = mlx5_esw_qos_set_vport_min_rate(vport, min_rate, NULL); if (!err) err = mlx5_esw_qos_set_vport_max_rate(vport, max_rate, NULL); - esw_qos_unlock(esw); + esw_qos_unlock(vport->dev); return err; } bool mlx5_esw_qos_get_vport_rate(struct mlx5_vport *vport, u32 *max_rate, u32 *min_rate) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; bool enabled; - esw_qos_lock(esw); + esw_qos_shd_lock(vport->dev); enabled = !!vport->qos.sched_node; if (enabled) { *max_rate = vport->qos.sched_node->max_rate; *min_rate = vport->qos.sched_node->min_rate; } - esw_qos_unlock(esw); + esw_qos_shd_unlock(vport->dev); return enabled; } @@ -1234,6 +1220,28 @@ static int esw_qos_vport_tc_check_type(enum sched_node_type curr_type, return 0; } +static bool esw_qos_validate_unsupported_tc_bw(struct mlx5_eswitch *esw, + u32 *tc_bw) +{ + int i, num_tcs = esw_qos_num_tcs(esw->dev); + + for (i = num_tcs; i < DEVLINK_RATE_TCS_MAX; i++) + if (tc_bw[i]) + return false; + + return true; +} + +static bool esw_qos_vport_validate_unsupported_tc_bw(struct mlx5_vport *vport, + u32 *tc_bw) +{ + struct mlx5_esw_sched_node *node = vport->qos.sched_node; + struct mlx5_eswitch *esw = node ? + node->parent->esw : vport->dev->priv.eswitch; + + return esw_qos_validate_unsupported_tc_bw(esw, tc_bw); +} + static int esw_qos_vport_update(struct mlx5_vport *vport, enum sched_node_type type, struct mlx5_esw_sched_node *parent, @@ -1245,7 +1253,7 @@ static int esw_qos_vport_update(struct mlx5_vport *vport, u32 curr_tc_bw[DEVLINK_RATE_TCS_MAX] = {0}; int err; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (curr_type == type && curr_parent == parent) return 0; @@ -1253,8 +1261,15 @@ static int esw_qos_vport_update(struct mlx5_vport *vport, if (err) return err; - if (curr_type == SCHED_NODE_TYPE_TC_ARBITER_TSAR && curr_type == type) + if (curr_type == SCHED_NODE_TYPE_TC_ARBITER_TSAR && curr_type == type) { esw_qos_tc_arbiter_get_bw_shares(vport_node, curr_tc_bw); + if (!esw_qos_validate_unsupported_tc_bw(parent->esw, + curr_tc_bw)) { + NL_SET_ERR_MSG_MOD(extack, + "Unsupported traffic classes on the new device"); + return -EOPNOTSUPP; + } + } esw_qos_vport_disable(vport, extack); @@ -1275,11 +1290,10 @@ static int esw_qos_vport_update(struct mlx5_vport *vport, static int esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_sched_node *parent, struct netlink_ext_ack *extack) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; struct mlx5_esw_sched_node *curr_parent; enum sched_node_type type; - esw_assert_qos_lock_held(esw); + esw_assert_qos_lock_held(vport->dev); curr_parent = vport->qos.sched_node->parent; if (curr_parent == parent) return 0; @@ -1287,10 +1301,9 @@ static int esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw /* Set vport QoS type based on parent node type if different from * default QoS; otherwise, use the vport's current QoS type. */ - if (parent && parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) + if (parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) type = SCHED_NODE_TYPE_RATE_LIMITER; - else if (curr_parent && - curr_parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) + else if (curr_parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) type = SCHED_NODE_TYPE_VPORT; else type = vport->qos.sched_node->type; @@ -1319,11 +1332,9 @@ static int esw_qos_switch_tc_arbiter_node_to_vports( struct mlx5_esw_sched_node *node, struct netlink_ext_ack *extack) { - u32 parent_tsar_ix = node->parent ? - node->parent->ix : node->esw->qos.root_tsar_ix; int err; - err = esw_qos_create_node_sched_elem(node->esw->dev, parent_tsar_ix, + err = esw_qos_create_node_sched_elem(node->esw->dev, node->parent->ix, node->max_rate, node->bw_share, &node->ix); if (err) { @@ -1378,8 +1389,8 @@ esw_qos_move_node(struct mlx5_esw_sched_node *curr_node) { struct mlx5_esw_sched_node *new_node; - new_node = __esw_qos_alloc_node(curr_node->esw, curr_node->ix, - curr_node->type, NULL); + new_node = __esw_qos_alloc_node(curr_node->ix, curr_node->type, + curr_node->parent); if (!new_node) return ERR_PTR(-ENOMEM); @@ -1489,41 +1500,16 @@ static int esw_qos_node_enable_tc_arbitration(struct mlx5_esw_sched_node *node, return err; } -static u32 mlx5_esw_qos_lag_link_speed_get(struct mlx5_core_dev *mdev, - bool take_rtnl) -{ - struct ethtool_link_ksettings lksettings; - struct net_device *slave, *master; - u32 speed = SPEED_UNKNOWN; - - slave = mlx5_uplink_netdev_get(mdev); - if (!slave) - goto out; - - if (take_rtnl) - rtnl_lock(); - master = netdev_master_upper_dev_get(slave); - if (master && !__ethtool_get_link_ksettings(master, &lksettings)) - speed = lksettings.base.speed; - if (take_rtnl) - rtnl_unlock(); - -out: - mlx5_uplink_netdev_put(mdev, slave); - return speed; -} - static int mlx5_esw_qos_max_link_speed_get(struct mlx5_core_dev *mdev, u32 *link_speed_max, - bool take_rtnl, struct netlink_ext_ack *extack) { int err; - if (!mlx5_lag_is_active(mdev)) + if (!mlx5_lag_is_active(mdev) || + mlx5_lag_query_bond_speed(mdev, link_speed_max) < 0 || + *link_speed_max == 0) goto skip_lag; - *link_speed_max = mlx5_esw_qos_lag_link_speed_get(mdev, take_rtnl); - if (*link_speed_max != (u32)SPEED_UNKNOWN) return 0; @@ -1560,7 +1546,8 @@ int mlx5_esw_qos_modify_vport_rate(struct mlx5_eswitch *esw, u16 vport_num, u32 return PTR_ERR(vport); if (rate_mbps) { - err = mlx5_esw_qos_max_link_speed_get(esw->dev, &link_speed_max, false, NULL); + err = mlx5_esw_qos_max_link_speed_get(esw->dev, &link_speed_max, + NULL); if (err) return err; @@ -1570,9 +1557,9 @@ int mlx5_esw_qos_modify_vport_rate(struct mlx5_eswitch *esw, u16 vport_num, u32 return err; } - esw_qos_lock(esw); + esw_qos_lock(vport->dev); err = mlx5_esw_qos_set_vport_max_rate(vport, rate_mbps, NULL); - esw_qos_unlock(esw); + esw_qos_unlock(vport->dev); return err; } @@ -1598,7 +1585,7 @@ static int esw_qos_devlink_rate_to_mbps(struct mlx5_core_dev *mdev, const char * return -EINVAL; } - err = mlx5_esw_qos_max_link_speed_get(mdev, &link_speed_max, true, extack); + err = mlx5_esw_qos_max_link_speed_get(mdev, &link_speed_max, extack); if (err) return err; @@ -1610,30 +1597,6 @@ static int esw_qos_devlink_rate_to_mbps(struct mlx5_core_dev *mdev, const char * return 0; } -static bool esw_qos_validate_unsupported_tc_bw(struct mlx5_eswitch *esw, - u32 *tc_bw) -{ - int i, num_tcs = esw_qos_num_tcs(esw->dev); - - for (i = num_tcs; i < DEVLINK_RATE_TCS_MAX; i++) { - if (tc_bw[i]) - return false; - } - - return true; -} - -static bool esw_qos_vport_validate_unsupported_tc_bw(struct mlx5_vport *vport, - u32 *tc_bw) -{ - struct mlx5_esw_sched_node *node = vport->qos.sched_node; - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - - esw = (node && node->parent) ? node->parent->esw : esw; - - return esw_qos_validate_unsupported_tc_bw(esw, tc_bw); -} - static bool esw_qos_tc_bw_disabled(u32 *tc_bw) { int i; @@ -1650,55 +1613,38 @@ static void esw_vport_qos_prune_empty(struct mlx5_vport *vport) { struct mlx5_esw_sched_node *vport_node = vport->qos.sched_node; - esw_assert_qos_lock_held(vport->dev->priv.eswitch); + esw_assert_qos_lock_held(vport->dev); if (!vport_node) return; - if (vport_node->parent || vport_node->max_rate || - vport_node->min_rate || !esw_qos_tc_bw_disabled(vport_node->tc_bw)) + if (vport_node->parent != vport_node->esw->qos.root || + vport_node->max_rate || vport_node->min_rate || + !esw_qos_tc_bw_disabled(vport_node->tc_bw)) return; mlx5_esw_qos_vport_disable_locked(vport); } -int mlx5_esw_qos_init(struct mlx5_eswitch *esw) -{ - if (esw->qos.domain) - return 0; /* Nothing to change. */ - - return esw_qos_domain_init(esw); -} - -void mlx5_esw_qos_cleanup(struct mlx5_eswitch *esw) -{ - if (esw->qos.domain) - esw_qos_domain_release(esw); -} - /* Eswitch devlink rate API */ int mlx5_esw_devlink_rate_leaf_tx_share_set(struct devlink_rate *rate_leaf, void *priv, u64 tx_share, struct netlink_ext_ack *extack) { struct mlx5_vport *vport = priv; - struct mlx5_eswitch *esw; int err; - esw = vport->dev->priv.eswitch; - if (!mlx5_esw_allowed(esw)) + if (!mlx5_esw_allowed(vport->dev->priv.eswitch)) return -EPERM; err = esw_qos_devlink_rate_to_mbps(vport->dev, "tx_share", &tx_share, extack); if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(vport->dev); err = mlx5_esw_qos_set_vport_min_rate(vport, tx_share, extack); - if (err) - goto out; - esw_vport_qos_prune_empty(vport); -out: - esw_qos_unlock(esw); + if (!err) + esw_vport_qos_prune_empty(vport); + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1706,24 +1652,20 @@ int mlx5_esw_devlink_rate_leaf_tx_max_set(struct devlink_rate *rate_leaf, void * u64 tx_max, struct netlink_ext_ack *extack) { struct mlx5_vport *vport = priv; - struct mlx5_eswitch *esw; int err; - esw = vport->dev->priv.eswitch; - if (!mlx5_esw_allowed(esw)) + if (!mlx5_esw_allowed(vport->dev->priv.eswitch)) return -EPERM; err = esw_qos_devlink_rate_to_mbps(vport->dev, "tx_max", &tx_max, extack); if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(vport->dev); err = mlx5_esw_qos_set_vport_max_rate(vport, tx_max, extack); - if (err) - goto out; - esw_vport_qos_prune_empty(vport); -out: - esw_qos_unlock(esw); + if (!err) + esw_vport_qos_prune_empty(vport); + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1734,16 +1676,14 @@ int mlx5_esw_devlink_rate_leaf_tc_bw_set(struct devlink_rate *rate_leaf, { struct mlx5_esw_sched_node *vport_node; struct mlx5_vport *vport = priv; - struct mlx5_eswitch *esw; bool disable; int err = 0; - esw = vport->dev->priv.eswitch; - if (!mlx5_esw_allowed(esw)) + if (!mlx5_esw_allowed(vport->dev->priv.eswitch)) return -EPERM; disable = esw_qos_tc_bw_disabled(tc_bw); - esw_qos_lock(esw); + esw_qos_devlink_lock(vport->dev); if (!esw_qos_vport_validate_unsupported_tc_bw(vport, tc_bw)) { NL_SET_ERR_MSG_MOD(extack, @@ -1777,7 +1717,7 @@ int mlx5_esw_devlink_rate_leaf_tc_bw_set(struct devlink_rate *rate_leaf, if (!err) esw_qos_set_tc_arbiter_bw_shares(vport_node, tc_bw, extack); unlock: - esw_qos_unlock(esw); + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1787,18 +1727,17 @@ int mlx5_esw_devlink_rate_node_tc_bw_set(struct devlink_rate *rate_node, struct netlink_ext_ack *extack) { struct mlx5_esw_sched_node *node = priv; - struct mlx5_eswitch *esw = node->esw; bool disable; int err; - if (!esw_qos_validate_unsupported_tc_bw(esw, tc_bw)) { + if (!esw_qos_validate_unsupported_tc_bw(node->esw, tc_bw)) { NL_SET_ERR_MSG_MOD(extack, "E-Switch traffic classes number is not supported"); return -EOPNOTSUPP; } disable = esw_qos_tc_bw_disabled(tc_bw); - esw_qos_lock(esw); + esw_qos_devlink_lock(node->esw->dev); if (disable) { err = esw_qos_node_disable_tc_arbitration(node, extack); goto unlock; @@ -1808,7 +1747,7 @@ int mlx5_esw_devlink_rate_node_tc_bw_set(struct devlink_rate *rate_node, if (!err) esw_qos_set_tc_arbiter_bw_shares(node, tc_bw, extack); unlock: - esw_qos_unlock(esw); + esw_qos_devlink_unlock(node->esw->dev); return err; } @@ -1823,9 +1762,9 @@ int mlx5_esw_devlink_rate_node_tx_share_set(struct devlink_rate *rate_node, void if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); err = esw_qos_set_node_min_rate(node, tx_share, extack); - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); return err; } @@ -1840,9 +1779,9 @@ int mlx5_esw_devlink_rate_node_tx_max_set(struct devlink_rate *rate_node, void * if (err) return err; - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); err = esw_qos_sched_elem_config(node, tx_max, node->bw_share, extack); - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); return err; } @@ -1857,7 +1796,7 @@ int mlx5_esw_devlink_rate_node_new(struct devlink_rate *rate_node, void **priv, if (IS_ERR(esw)) return PTR_ERR(esw); - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); if (esw->mode != MLX5_ESWITCH_OFFLOADS) { NL_SET_ERR_MSG_MOD(extack, "Rate node creation supported only in switchdev mode"); @@ -1870,10 +1809,9 @@ int mlx5_esw_devlink_rate_node_new(struct devlink_rate *rate_node, void **priv, err = PTR_ERR(node); goto unlock; } - *priv = node; unlock: - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); return err; } @@ -1883,25 +1821,53 @@ int mlx5_esw_devlink_rate_node_del(struct devlink_rate *rate_node, void *priv, struct mlx5_esw_sched_node *node = priv; struct mlx5_eswitch *esw = node->esw; - esw_qos_lock(esw); + esw_qos_devlink_lock(esw->dev); __esw_qos_destroy_node(node, extack); esw_qos_put(esw); - esw_qos_unlock(esw); + esw_qos_devlink_unlock(esw->dev); + return 0; } -int mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_sched_node *parent, - struct netlink_ext_ack *extack) +static int +mlx5_esw_validate_cross_esw_scheduling(struct mlx5_eswitch *esw, + struct mlx5_esw_sched_node *parent, + struct netlink_ext_ack *extack) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - int err = 0; + if (!parent || esw == parent->esw) + return 0; - if (parent && parent->esw != esw) { - NL_SET_ERR_MSG_MOD(extack, "Cross E-Switch scheduling is not supported"); + if (!MLX5_CAP_QOS(esw->dev, esw_cross_esw_sched)) { + NL_SET_ERR_MSG_MOD(extack, + "Cross E-Switch scheduling is not supported"); + return -EOPNOTSUPP; + } + if (!esw->dev->shd || esw->dev->shd != parent->esw->dev->shd) { + NL_SET_ERR_MSG_MOD(extack, + "Cannot add vport to a parent belonging to a different device"); + return -EOPNOTSUPP; + } + if (!mlx5_lag_is_active(esw->dev)) { + NL_SET_ERR_MSG_MOD(extack, + "Cross E-Switch scheduling requires LAG to be activated"); return -EOPNOTSUPP; } - esw_qos_lock(esw); + return 0; +} + +static int +mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, + struct mlx5_esw_sched_node *parent, + struct netlink_ext_ack *extack) +{ + struct mlx5_eswitch *esw = vport->dev->priv.eswitch; + int err; + + err = mlx5_esw_validate_cross_esw_scheduling(esw, parent, extack); + if (err) + return err; + if (!vport->qos.sched_node && parent) { enum sched_node_type type; @@ -1910,9 +1876,11 @@ int mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_s err = mlx5_esw_qos_vport_enable(vport, type, parent, 0, 0, extack); } else if (vport->qos.sched_node) { - err = esw_qos_vport_update_parent(vport, parent, extack); + err = esw_qos_vport_update_parent(vport, + parent ? : esw->qos.root, + extack); } - esw_qos_unlock(esw); + return err; } @@ -1925,14 +1893,11 @@ int mlx5_esw_devlink_rate_leaf_parent_set(struct devlink_rate *devlink_rate, struct mlx5_vport *vport = priv; int err; + esw_qos_devlink_lock(vport->dev); err = mlx5_esw_qos_vport_update_parent(vport, node, extack); - if (!err) { - struct mlx5_eswitch *esw = vport->dev->priv.eswitch; - - esw_qos_lock(esw); + if (!err) esw_vport_qos_prune_empty(vport); - esw_qos_unlock(esw); - } + esw_qos_devlink_unlock(vport->dev); return err; } @@ -1958,7 +1923,7 @@ mlx5_esw_qos_node_validate_set_parent(struct mlx5_esw_sched_node *node, { u8 new_level, max_level; - if (parent && parent->esw != node->esw) { + if (parent->esw != node->esw) { NL_SET_ERR_MSG_MOD(extack, "Cannot assign node to another E-Switch"); return -EOPNOTSUPP; @@ -1970,13 +1935,13 @@ mlx5_esw_qos_node_validate_set_parent(struct mlx5_esw_sched_node *node, return -EOPNOTSUPP; } - if (parent && parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { + if (parent->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { NL_SET_ERR_MSG_MOD(extack, "Cannot attach a node to a parent with TC bandwidth configured"); return -EOPNOTSUPP; } - new_level = parent ? parent->level + 1 : 2; + new_level = parent->level + 1; if (node->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { /* Increase by one to account for the vports TC scheduling * element. @@ -2027,14 +1992,12 @@ static int esw_qos_vports_node_update_parent(struct mlx5_esw_sched_node *node, { struct mlx5_esw_sched_node *curr_parent = node->parent; struct mlx5_eswitch *esw = node->esw; - u32 parent_ix; int err; - parent_ix = parent ? parent->ix : node->esw->qos.root_tsar_ix; mlx5_destroy_scheduling_element_cmd(esw->dev, SCHEDULING_HIERARCHY_E_SWITCH, node->ix); - err = esw_qos_create_node_sched_elem(esw->dev, parent_ix, + err = esw_qos_create_node_sched_elem(esw->dev, parent->ix, node->max_rate, 0, &node->ix); if (err) { NL_SET_ERR_MSG_MOD(extack, @@ -2061,12 +2024,15 @@ static int mlx5_esw_qos_node_update_parent(struct mlx5_esw_sched_node *node, struct mlx5_eswitch *esw = node->esw; int err; + esw_qos_devlink_lock(esw->dev); + curr_parent = node->parent; + if (!parent) + parent = esw->qos.root; + err = mlx5_esw_qos_node_validate_set_parent(node, parent, extack); if (err) - return err; + goto out; - esw_qos_lock(esw); - curr_parent = node->parent; if (node->type == SCHED_NODE_TYPE_TC_ARBITER_TSAR) { err = esw_qos_tc_arbiter_node_update_parent(node, parent, extack); @@ -2077,12 +2043,11 @@ static int mlx5_esw_qos_node_update_parent(struct mlx5_esw_sched_node *node, if (err) goto out; - esw_qos_normalize_min_rate(esw, curr_parent, extack); - esw_qos_normalize_min_rate(esw, parent, extack); + esw_qos_normalize_min_rate(curr_parent, extack); + esw_qos_normalize_min_rate(parent, extack); out: - esw_qos_unlock(esw); - + esw_qos_devlink_unlock(esw->dev); return err; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h index 0a50982b0e27..f275e850d2c9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/qos.h @@ -6,9 +6,6 @@ #ifdef CONFIG_MLX5_ESWITCH -int mlx5_esw_qos_init(struct mlx5_eswitch *esw); -void mlx5_esw_qos_cleanup(struct mlx5_eswitch *esw); - int mlx5_esw_qos_set_vport_rate(struct mlx5_vport *evport, u32 max_rate, u32 min_rate); bool mlx5_esw_qos_get_vport_rate(struct mlx5_vport *vport, u32 *max_rate, u32 *min_rate); void mlx5_esw_qos_vport_disable(struct mlx5_vport *vport); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index a0e2ca87b8d8..b6e2c153b4f7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -1885,10 +1885,6 @@ int mlx5_eswitch_enable_locked(struct mlx5_eswitch *esw, int num_vfs) MLX5_NB_INIT(&esw->nb, eswitch_vport_event, NIC_VPORT_CHANGE); mlx5_eq_notifier_register(esw->dev, &esw->nb); - err = mlx5_esw_qos_init(esw); - if (err) - goto err_esw_init; - if (esw->mode == MLX5_ESWITCH_LEGACY) { err = esw_legacy_enable(esw); } else { @@ -1990,6 +1986,13 @@ void mlx5_eswitch_disable_sriov(struct mlx5_eswitch *esw, bool clear_vf) esw->esw_funcs.num_vfs, esw->esw_funcs.num_ec_vfs, esw->enabled_vports); mlx5_eswitch_invalidate_wq(esw); + + if (esw->mode == MLX5_ESWITCH_OFFLOADS) { + struct devlink *devlink = priv_to_devlink(esw->dev); + + devl_rate_nodes_destroy(devlink); + } + mlx5_esw_reps_block(esw); if (!mlx5_core_is_ecpf(esw->dev)) { @@ -2003,12 +2006,6 @@ void mlx5_eswitch_disable_sriov(struct mlx5_eswitch *esw, bool clear_vf) } mlx5_esw_reps_unblock(esw); - - if (esw->mode == MLX5_ESWITCH_OFFLOADS) { - struct devlink *devlink = priv_to_devlink(esw->dev); - - devl_rate_nodes_destroy(devlink); - } /* Destroy legacy fdb when disabling sriov in legacy mode. */ if (esw->mode == MLX5_ESWITCH_LEGACY) mlx5_eswitch_disable_locked(esw); @@ -2039,6 +2036,9 @@ void mlx5_eswitch_disable_locked(struct mlx5_eswitch *esw) esw->mode == MLX5_ESWITCH_LEGACY ? "LEGACY" : "OFFLOADS", esw->esw_funcs.num_vfs, esw->esw_funcs.num_ec_vfs, esw->enabled_vports); + if (esw->mode == MLX5_ESWITCH_OFFLOADS) + devl_rate_nodes_destroy(devlink); + if (esw->fdb_table.flags & MLX5_ESW_FDB_CREATED) { esw->fdb_table.flags &= ~MLX5_ESW_FDB_CREATED; if (esw->mode == MLX5_ESWITCH_OFFLOADS) @@ -2047,9 +2047,6 @@ void mlx5_eswitch_disable_locked(struct mlx5_eswitch *esw) esw_legacy_disable(esw); mlx5_esw_acls_ns_cleanup(esw); } - - if (esw->mode == MLX5_ESWITCH_OFFLOADS) - devl_rate_nodes_destroy(devlink); } void mlx5_eswitch_disable(struct mlx5_eswitch *esw) @@ -2554,9 +2551,6 @@ int mlx5_eswitch_init(struct mlx5_core_dev *dev) goto reps_err; esw->mode = MLX5_ESWITCH_LEGACY; - err = mlx5_esw_qos_init(esw); - if (err) - goto reps_err; mutex_init(&esw->offloads.encap_tbl_lock); hash_init(esw->offloads.encap_tbl); @@ -2611,7 +2605,6 @@ void mlx5_eswitch_cleanup(struct mlx5_eswitch *esw) mlx5_eswitch_invalidate_wq(esw); destroy_workqueue(esw->work_queue); - mlx5_esw_qos_cleanup(esw); WARN_ON(refcount_read(&esw->qos.refcnt)); mutex_destroy(&esw->state_lock); WARN_ON(!xa_empty(&esw->offloads.vhca_map)); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h index fea72b1dedab..c655f6e8da1c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h @@ -234,8 +234,10 @@ struct mlx5_vport { struct mlx5_vport_info info; - /* Protected with the E-Switch qos domain lock. The Vport QoS can - * either be disabled (sched_node is NULL) or in one of three states: + /* Protected by either the shared devlink (dev->shd) lock or by + * esw->state_lock. See esw_assert_qos_lock_held() for more details. + * The Vport QoS can either be disabled (sched_node is NULL) or in one + * of three states: * 1. Regular QoS (sched_node is a vport node). * 2. TC QoS enabled on the vport (sched_node is a TC arbiter). * 3. TC QoS enabled on the vport's parent node @@ -382,7 +384,6 @@ enum { }; struct dentry; -struct mlx5_qos_domain; struct mlx5_eswitch { struct mlx5_core_dev *dev; @@ -411,12 +412,15 @@ struct mlx5_eswitch { atomic64_t user_count; wait_queue_head_t work_queue_wait; - /* Protected with the E-Switch qos domain lock. */ + /* QoS changes are serialized by either the shared devlink (dev->shd) + * lock or by esw->state_lock. See esw_assert_qos_lock_held() for more + * details. + */ struct { /* Initially 0, meaning no QoS users and QoS is disabled. */ refcount_t refcnt; - u32 root_tsar_ix; - struct mlx5_qos_domain *domain; + /* The root node of the hierarchy. */ + struct mlx5_esw_sched_node *root; } qos; struct mlx5_esw_bridge_offloads *br_offloads; @@ -482,8 +486,6 @@ int mlx5_eswitch_set_vport_trust(struct mlx5_eswitch *esw, u16 vport_num, bool setting); int mlx5_eswitch_set_vport_rate(struct mlx5_eswitch *esw, u16 vport, u32 max_rate, u32 min_rate); -int mlx5_esw_qos_vport_update_parent(struct mlx5_vport *vport, struct mlx5_esw_sched_node *node, - struct netlink_ext_ack *extack); int mlx5_eswitch_set_vepa(struct mlx5_eswitch *esw, u8 setting); int mlx5_eswitch_get_vepa(struct mlx5_eswitch *esw, u8 *setting); int mlx5_eswitch_get_vport_config(struct mlx5_eswitch *esw, diff --git a/include/net/devlink.h b/include/net/devlink.h index dd546dbd57cf..ffe1ad5fb70b 100644 --- a/include/net/devlink.h +++ b/include/net/devlink.h @@ -1594,6 +1594,15 @@ struct devlink_ops { struct devlink_rate *parent, void *priv_child, void *priv_parent, struct netlink_ext_ack *extack); + /* Indicates if cross-device rate nodes are supported. + * This also requires a shared common ancestor object all devices that + * could share rate nodes are nested in. + * If enabled, rate operations may be called on an instance with only + * the common ancestor lock held and *without that instance lock held*. + * It is the driver's responsibility to ensure proper serialization + * with other operations. + */ + bool supported_cross_device_rate_nodes; /** * selftests_check() - queries if selftest is supported * @devlink: devlink instance diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h index ca713bcc47b9..a6801feb7744 100644 --- a/include/uapi/linux/devlink.h +++ b/include/uapi/linux/devlink.h @@ -648,6 +648,8 @@ enum devlink_attr { DEVLINK_ATTR_INDEX, /* uint */ DEVLINK_ATTR_RESOURCE_SCOPE_MASK, /* u32 */ + DEVLINK_ATTR_PARENT_DEV, /* nested */ + /* Add new attributes above here, update the spec in * Documentation/netlink/specs/devlink.yaml and re-generate * net/devlink/netlink_gen.c. diff --git a/net/devlink/core.c b/net/devlink/core.c index fe9f6a0a67d5..c53a42e17a58 100644 --- a/net/devlink/core.c +++ b/net/devlink/core.c @@ -67,6 +67,22 @@ static void __devlink_rel_put(struct devlink_rel *rel) devlink_rel_free(rel); } +struct devlink *__must_check devlink_nested_in_get_lock(struct devlink *devlink) +{ + devl_assert_locked(devlink); + if (!devlink->rel) + return NULL; + devlink = devlinks_xa_get(devlink->rel->nested_in.devlink_index); + if (!devlink) + return NULL; + devl_lock(devlink); + if (devl_is_registered(devlink)) + return devlink; + devl_unlock(devlink); + devlink_put(devlink); + return NULL; +} + static void devlink_rel_nested_in_notify_work(struct work_struct *work) { struct devlink_rel *rel = container_of(work, struct devlink_rel, @@ -518,6 +534,9 @@ void devlink_free(struct devlink *devlink) { ASSERT_DEVLINK_NOT_REGISTERED(devlink); + devl_lock(devlink); + WARN_ON(devlink_rates_check(devlink, NULL, NULL)); + devl_unlock(devlink); devlink_rel_put(devlink); WARN_ON(!list_empty(&devlink->trap_policer_list)); @@ -528,7 +547,6 @@ void devlink_free(struct devlink *devlink) WARN_ON(!list_empty(&devlink->resource_list)); WARN_ON(!list_empty(&devlink->dpipe_table_list)); WARN_ON(!list_empty(&devlink->sb_list)); - WARN_ON(devlink_rates_check(devlink, NULL, NULL)); WARN_ON(!list_empty(&devlink->linecard_list)); WARN_ON(!xa_empty(&devlink->ports)); diff --git a/net/devlink/dev.c b/net/devlink/dev.c index 57b2b8f03543..bcf001554e84 100644 --- a/net/devlink/dev.c +++ b/net/devlink/dev.c @@ -222,7 +222,7 @@ static void devlink_notify(struct devlink *devlink, enum devlink_command cmd) int devlink_nl_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -519,7 +519,7 @@ devlink_nl_reload_actions_performed_snd(struct devlink *devlink, u32 actions_per int devlink_nl_reload_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; enum devlink_reload_action action; enum devlink_reload_limit limit; struct net *dest_net = NULL; @@ -683,7 +683,7 @@ static int devlink_nl_eswitch_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_eswitch_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -704,7 +704,7 @@ int devlink_nl_eswitch_get_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_eswitch_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const struct devlink_ops *ops = devlink->ops; enum devlink_eswitch_encap_mode encap_mode; u8 inline_mode; @@ -906,7 +906,7 @@ devlink_nl_info_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_info_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -1134,7 +1134,7 @@ int devlink_nl_flash_update_doit(struct sk_buff *skb, struct genl_info *info) { struct nlattr *nla_overwrite_mask, *nla_file_name; struct devlink_flash_update_params params = {}; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const char *file_name; u32 supported_params; int ret; @@ -1302,7 +1302,7 @@ devlink_nl_selftests_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_selftests_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -1372,7 +1372,7 @@ static const struct nla_policy devlink_selftest_nl_policy[DEVLINK_ATTR_SELFTEST_ int devlink_nl_selftests_run_doit(struct sk_buff *skb, struct genl_info *info) { struct nlattr *tb[DEVLINK_ATTR_SELFTEST_ID_MAX + 1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct nlattr *attrs, *selftests; struct sk_buff *msg; void *hdr; diff --git a/net/devlink/devl_internal.h b/net/devlink/devl_internal.h index e4e48ee2da5a..cdf894ba5a9d 100644 --- a/net/devlink/devl_internal.h +++ b/net/devlink/devl_internal.h @@ -136,6 +136,10 @@ typedef void devlink_rel_notify_cb_t(struct devlink *devlink, u32 obj_index); typedef void devlink_rel_cleanup_cb_t(struct devlink *devlink, u32 obj_index, u32 rel_index); +/* Returns the locked+referenced nested-in instance or NULL. */ +struct devlink *__must_check +devlink_nested_in_get_lock(struct devlink *devlink); + void devlink_rel_nested_in_clear(u32 rel_index); int devlink_rel_nested_in_add(u32 *rel_index, u32 devlink_index, u32 obj_index, devlink_rel_notify_cb_t *notify_cb, @@ -147,6 +151,20 @@ int devlink_rel_devlink_handle_put(struct sk_buff *msg, struct devlink *devlink, bool *msg_updated); /* Netlink */ +struct devlink_nl_ctx { + struct devlink *devlink; + struct devlink_port *devlink_port; + struct devlink *parent_devlink; +}; + +static inline struct devlink_nl_ctx * +devlink_nl_ctx(struct genl_info *info) +{ + BUILD_BUG_ON(sizeof(struct devlink_nl_ctx) > + sizeof_field(struct genl_info, ctx)); + return (struct devlink_nl_ctx *)info->ctx; +} + enum devlink_multicast_groups { DEVLINK_MCGRP_CONFIG, }; @@ -180,6 +198,8 @@ typedef int devlink_nl_dump_one_func_t(struct sk_buff *msg, struct devlink * devlink_get_from_attrs_lock(struct net *net, struct nlattr **attrs, bool dev_lock); +struct devlink * +devlink_get_parent_from_attrs_lock(struct net *net, struct nlattr **attrs); int devlink_nl_dumpit(struct sk_buff *msg, struct netlink_callback *cb, devlink_nl_dump_one_func_t *dump_one); diff --git a/net/devlink/dpipe.c b/net/devlink/dpipe.c index c8d4a4374ae1..08c7b66fc3e8 100644 --- a/net/devlink/dpipe.c +++ b/net/devlink/dpipe.c @@ -213,7 +213,7 @@ static int devlink_dpipe_tables_fill(struct genl_info *info, struct list_head *dpipe_tables, const char *table_name) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_dpipe_table *table; struct nlattr *tables_attr; struct sk_buff *skb = NULL; @@ -290,7 +290,7 @@ static int devlink_dpipe_tables_fill(struct genl_info *info, int devlink_nl_dpipe_table_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const char *table_name = NULL; if (info->attrs[DEVLINK_ATTR_DPIPE_TABLE_NAME]) @@ -478,7 +478,7 @@ int devlink_dpipe_entry_ctx_prepare(struct devlink_dpipe_dump_ctx *dump_ctx) if (!dump_ctx->hdr) goto nla_put_failure; - devlink = dump_ctx->info->user_ptr[0]; + devlink = devlink_nl_ctx(dump_ctx->info)->devlink; if (devlink_nl_put_handle(dump_ctx->skb, devlink)) goto nla_put_failure; dump_ctx->nest = nla_nest_start_noflag(dump_ctx->skb, @@ -563,7 +563,7 @@ static int devlink_dpipe_entries_fill(struct genl_info *info, int devlink_nl_dpipe_entries_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_dpipe_table *table; const char *table_name; @@ -650,7 +650,7 @@ static int devlink_dpipe_headers_fill(struct genl_info *info, struct devlink_dpipe_headers * dpipe_headers) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct nlattr *headers_attr; struct sk_buff *skb = NULL; struct nlmsghdr *nlh; @@ -713,7 +713,7 @@ static int devlink_dpipe_headers_fill(struct genl_info *info, int devlink_nl_dpipe_headers_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (!devlink->dpipe_headers) return -EOPNOTSUPP; @@ -747,7 +747,7 @@ static int devlink_dpipe_table_counters_set(struct devlink *devlink, int devlink_nl_dpipe_table_counters_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const char *table_name; bool counters_enable; diff --git a/net/devlink/health.c b/net/devlink/health.c index ea7a334e939b..8ce6cd399cb7 100644 --- a/net/devlink/health.c +++ b/net/devlink/health.c @@ -358,7 +358,7 @@ devlink_health_reporter_get_from_info(struct devlink *devlink, int devlink_nl_health_reporter_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; struct sk_buff *msg; int err; @@ -456,7 +456,7 @@ int devlink_nl_health_reporter_get_dumpit(struct sk_buff *skb, int devlink_nl_health_reporter_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); @@ -715,7 +715,7 @@ EXPORT_SYMBOL_GPL(devlink_health_reporter_state_update); int devlink_nl_health_reporter_recover_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); @@ -1157,7 +1157,7 @@ static int devlink_fmsg_dumpit(struct devlink_fmsg *fmsg, struct sk_buff *skb, int devlink_nl_health_reporter_diagnose_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; struct devlink_fmsg *fmsg; int err; @@ -1252,7 +1252,7 @@ int devlink_nl_health_reporter_dump_get_dumpit(struct sk_buff *skb, int devlink_nl_health_reporter_dump_clear_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); @@ -1269,7 +1269,7 @@ int devlink_nl_health_reporter_dump_clear_doit(struct sk_buff *skb, int devlink_nl_health_reporter_test_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_health_reporter *reporter; reporter = devlink_health_reporter_get_from_info(devlink, info); diff --git a/net/devlink/linecard.c b/net/devlink/linecard.c index 8315d35cb91d..fd18f2759770 100644 --- a/net/devlink/linecard.c +++ b/net/devlink/linecard.c @@ -171,7 +171,7 @@ void devlink_linecards_notify_unregister(struct devlink *devlink) int devlink_nl_linecard_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_linecard *linecard; struct sk_buff *msg; int err; @@ -371,7 +371,7 @@ static int devlink_linecard_type_unset(struct devlink_linecard *linecard, int devlink_nl_linecard_set_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_linecard *linecard; int err; diff --git a/net/devlink/netlink.c b/net/devlink/netlink.c index ae4afc739678..300580c1a217 100644 --- a/net/devlink/netlink.c +++ b/net/devlink/netlink.c @@ -12,6 +12,7 @@ #define DEVLINK_NL_FLAG_NEED_PORT BIT(0) #define DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT BIT(1) #define DEVLINK_NL_FLAG_NEED_DEV_LOCK BIT(2) +#define DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV BIT(3) static const struct genl_multicast_group devlink_nl_mcgrps[] = { [DEVLINK_MCGRP_CONFIG] = { .name = DEVLINK_GENL_MCGRP_CONFIG_NAME }, @@ -239,37 +240,82 @@ devlink_get_from_attrs_lock(struct net *net, struct nlattr **attrs, return ERR_PTR(-ENODEV); } +struct devlink * +devlink_get_parent_from_attrs_lock(struct net *net, struct nlattr **attrs) +{ + unsigned int maxtype = ARRAY_SIZE(devlink_dl_parent_dev_nl_policy) - 1; + struct devlink *devlink; + struct nlattr **tb; + int err; + + if (!attrs[DEVLINK_ATTR_PARENT_DEV]) + return ERR_PTR(-EINVAL); + + tb = kcalloc(maxtype + 1, sizeof(*tb), GFP_KERNEL); + if (!tb) + return ERR_PTR(-ENOMEM); + + err = nla_parse_nested(tb, maxtype, attrs[DEVLINK_ATTR_PARENT_DEV], + devlink_dl_parent_dev_nl_policy, NULL); + if (err) + goto out; + + devlink = devlink_get_from_attrs_lock(net, tb, false); + kfree(tb); + return devlink; +out: + kfree(tb); + return ERR_PTR(err); +} + static int __devlink_nl_pre_doit(struct sk_buff *skb, struct genl_info *info, u8 flags) { + bool parent_dev = flags & DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV; bool dev_lock = flags & DEVLINK_NL_FLAG_NEED_DEV_LOCK; + struct devlink *devlink, *parent_devlink = NULL; + struct net *net = genl_info_net(info); + struct nlattr **attrs = info->attrs; struct devlink_port *devlink_port; - struct devlink *devlink; int err; - devlink = devlink_get_from_attrs_lock(genl_info_net(info), info->attrs, - dev_lock); - if (IS_ERR(devlink)) - return PTR_ERR(devlink); + if (parent_dev && attrs[DEVLINK_ATTR_PARENT_DEV]) { + parent_devlink = devlink_get_parent_from_attrs_lock(net, attrs); + if (IS_ERR(parent_devlink)) + return PTR_ERR(parent_devlink); + devlink_nl_ctx(info)->parent_devlink = parent_devlink; + /* Drop the parent devlink lock but don't release the reference. + * This will keep it alive until the end of the request. + */ + devl_unlock(parent_devlink); + } - info->user_ptr[0] = devlink; + devlink = devlink_get_from_attrs_lock(net, attrs, dev_lock); + if (IS_ERR(devlink)) { + err = PTR_ERR(devlink); + goto parent_put; + } + devlink_nl_ctx(info)->devlink = devlink; if (flags & DEVLINK_NL_FLAG_NEED_PORT) { devlink_port = devlink_port_get_from_info(devlink, info); if (IS_ERR(devlink_port)) { err = PTR_ERR(devlink_port); goto unlock; } - info->user_ptr[1] = devlink_port; + devlink_nl_ctx(info)->devlink_port = devlink_port; } else if (flags & DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT) { devlink_port = devlink_port_get_from_info(devlink, info); if (!IS_ERR(devlink_port)) - info->user_ptr[1] = devlink_port; + devlink_nl_ctx(info)->devlink_port = devlink_port; } return 0; unlock: devl_dev_unlock(devlink, dev_lock); devlink_put(devlink); +parent_put: + if (parent_dev && parent_devlink) + devlink_put(parent_devlink); return err; } @@ -298,15 +344,25 @@ int devlink_nl_pre_doit_port_optional(const struct genl_split_ops *ops, return __devlink_nl_pre_doit(skb, info, DEVLINK_NL_FLAG_NEED_DEVLINK_OR_PORT); } +int devlink_nl_pre_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info) +{ + return __devlink_nl_pre_doit(skb, info, + DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV); +} + static void __devlink_nl_post_doit(struct sk_buff *skb, struct genl_info *info, u8 flags) { bool dev_lock = flags & DEVLINK_NL_FLAG_NEED_DEV_LOCK; struct devlink *devlink; - devlink = info->user_ptr[0]; + devlink = devlink_nl_ctx(info)->devlink; devl_dev_unlock(devlink, dev_lock); devlink_put(devlink); + if (devlink_nl_ctx(info)->parent_devlink) + devlink_put(devlink_nl_ctx(info)->parent_devlink); } void devlink_nl_post_doit(const struct genl_split_ops *ops, @@ -322,6 +378,14 @@ devlink_nl_post_doit_dev_lock(const struct genl_split_ops *ops, __devlink_nl_post_doit(skb, info, DEVLINK_NL_FLAG_NEED_DEV_LOCK); } +void +devlink_nl_post_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info) +{ + __devlink_nl_post_doit(skb, info, DEVLINK_NL_FLAG_OPTIONAL_PARENT_DEV); +} + static int devlink_nl_inst_single_dumpit(struct sk_buff *msg, struct netlink_callback *cb, int flags, devlink_nl_dump_one_func_t *dump_one, diff --git a/net/devlink/netlink_gen.c b/net/devlink/netlink_gen.c index f52b0c2b19ed..dec00133178d 100644 --- a/net/devlink/netlink_gen.c +++ b/net/devlink/netlink_gen.c @@ -46,6 +46,12 @@ devlink_attr_param_type_validate(const struct nlattr *attr, } /* Common nested types */ +const struct nla_policy devlink_dl_parent_dev_nl_policy[DEVLINK_ATTR_INDEX + 1] = { + [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DEVLINK_ATTR_INDEX] = NLA_POLICY_FULL_RANGE(NLA_UINT, &devlink_attr_index_range), +}; + const struct nla_policy devlink_dl_port_function_nl_policy[DEVLINK_PORT_FN_ATTR_CAPS + 1] = { [DEVLINK_PORT_FUNCTION_ATTR_HW_ADDR] = { .type = NLA_BINARY, }, [DEVLINK_PORT_FN_ATTR_STATE] = NLA_POLICY_MAX(NLA_U8, 1), @@ -608,7 +614,7 @@ static const struct nla_policy devlink_rate_get_dump_nl_policy[DEVLINK_ATTR_INDE }; /* DEVLINK_CMD_RATE_SET - do */ -static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_INDEX + 1] = { +static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_PARENT_DEV + 1] = { [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_INDEX] = NLA_POLICY_FULL_RANGE(NLA_UINT, &devlink_attr_index_range), @@ -619,10 +625,11 @@ static const struct nla_policy devlink_rate_set_nl_policy[DEVLINK_ATTR_INDEX + 1 [DEVLINK_ATTR_RATE_TX_WEIGHT] = { .type = NLA_U32, }, [DEVLINK_ATTR_RATE_PARENT_NODE_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_RATE_TC_BWS] = NLA_POLICY_NESTED(devlink_dl_rate_tc_bws_nl_policy), + [DEVLINK_ATTR_PARENT_DEV] = NLA_POLICY_NESTED(devlink_dl_parent_dev_nl_policy), }; /* DEVLINK_CMD_RATE_NEW - do */ -static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_INDEX + 1] = { +static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_PARENT_DEV + 1] = { [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_INDEX] = NLA_POLICY_FULL_RANGE(NLA_UINT, &devlink_attr_index_range), @@ -633,6 +640,7 @@ static const struct nla_policy devlink_rate_new_nl_policy[DEVLINK_ATTR_INDEX + 1 [DEVLINK_ATTR_RATE_TX_WEIGHT] = { .type = NLA_U32, }, [DEVLINK_ATTR_RATE_PARENT_NODE_NAME] = { .type = NLA_NUL_STRING, }, [DEVLINK_ATTR_RATE_TC_BWS] = NLA_POLICY_NESTED(devlink_dl_rate_tc_bws_nl_policy), + [DEVLINK_ATTR_PARENT_DEV] = NLA_POLICY_NESTED(devlink_dl_parent_dev_nl_policy), }; /* DEVLINK_CMD_RATE_DEL - do */ @@ -1290,21 +1298,21 @@ const struct genl_split_ops devlink_nl_ops[75] = { { .cmd = DEVLINK_CMD_RATE_SET, .validate = GENL_DONT_VALIDATE_STRICT, - .pre_doit = devlink_nl_pre_doit, + .pre_doit = devlink_nl_pre_doit_parent_dev_optional, .doit = devlink_nl_rate_set_doit, - .post_doit = devlink_nl_post_doit, + .post_doit = devlink_nl_post_doit_parent_dev_optional, .policy = devlink_rate_set_nl_policy, - .maxattr = DEVLINK_ATTR_INDEX, + .maxattr = DEVLINK_ATTR_PARENT_DEV, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { .cmd = DEVLINK_CMD_RATE_NEW, .validate = GENL_DONT_VALIDATE_STRICT, - .pre_doit = devlink_nl_pre_doit, + .pre_doit = devlink_nl_pre_doit_parent_dev_optional, .doit = devlink_nl_rate_new_doit, - .post_doit = devlink_nl_post_doit, + .post_doit = devlink_nl_post_doit_parent_dev_optional, .policy = devlink_rate_new_nl_policy, - .maxattr = DEVLINK_ATTR_INDEX, + .maxattr = DEVLINK_ATTR_PARENT_DEV, .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, }, { diff --git a/net/devlink/netlink_gen.h b/net/devlink/netlink_gen.h index 20034b0929a8..a70e0e4769aa 100644 --- a/net/devlink/netlink_gen.h +++ b/net/devlink/netlink_gen.h @@ -13,6 +13,7 @@ #include /* Common nested types */ +extern const struct nla_policy devlink_dl_parent_dev_nl_policy[DEVLINK_ATTR_INDEX + 1]; extern const struct nla_policy devlink_dl_port_function_nl_policy[DEVLINK_PORT_FN_ATTR_CAPS + 1]; extern const struct nla_policy devlink_dl_rate_tc_bws_nl_policy[DEVLINK_RATE_TC_ATTR_BW + 1]; extern const struct nla_policy devlink_dl_selftest_id_nl_policy[DEVLINK_ATTR_SELFTEST_ID_FLASH + 1]; @@ -29,12 +30,19 @@ int devlink_nl_pre_doit_port_optional(const struct genl_split_ops *ops, struct genl_info *info); int devlink_nl_pre_doit_dev_lock(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); +int devlink_nl_pre_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info); void devlink_nl_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); void devlink_nl_post_doit_dev_lock(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); +void +devlink_nl_post_doit_parent_dev_optional(const struct genl_split_ops *ops, + struct sk_buff *skb, + struct genl_info *info); int devlink_nl_get_doit(struct sk_buff *skb, struct genl_info *info); int devlink_nl_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb); diff --git a/net/devlink/param.c b/net/devlink/param.c index 3e9d2e5750c2..1cc562a6ebfd 100644 --- a/net/devlink/param.c +++ b/net/devlink/param.c @@ -627,7 +627,7 @@ devlink_param_get_from_info(struct xarray *params, struct genl_info *info) int devlink_nl_param_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_param_item *param_item; struct sk_buff *msg; int err; @@ -728,7 +728,7 @@ static int __devlink_nl_cmd_param_set_doit(struct devlink *devlink, int devlink_nl_param_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; return __devlink_nl_cmd_param_set_doit(devlink, 0, &devlink->params, info, DEVLINK_CMD_PARAM_NEW); diff --git a/net/devlink/port.c b/net/devlink/port.c index 485029d43428..c268afefaed7 100644 --- a/net/devlink/port.c +++ b/net/devlink/port.c @@ -594,7 +594,7 @@ void devlink_ports_notify_unregister(struct devlink *devlink) int devlink_nl_port_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct sk_buff *msg; int err; @@ -830,7 +830,7 @@ static int devlink_port_function_set(struct devlink_port *port, int devlink_nl_port_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; int err; if (info->attrs[DEVLINK_ATTR_PORT_TYPE]) { @@ -856,8 +856,8 @@ int devlink_nl_port_set_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_port_split_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; u32 count; if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_PORT_SPLIT_COUNT)) @@ -887,8 +887,8 @@ int devlink_nl_port_split_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_port_unsplit_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (!devlink_port->ops->port_unsplit) return -EOPNOTSUPP; @@ -899,7 +899,7 @@ int devlink_nl_port_new_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; struct devlink_port_new_attrs new_attrs = {}; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_port *devlink_port; struct sk_buff *msg; int err; @@ -961,9 +961,9 @@ int devlink_nl_port_new_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_port_del_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (!devlink_port->ops->port_del) return -EOPNOTSUPP; diff --git a/net/devlink/rate.c b/net/devlink/rate.c index 533d21b028a7..e727c8b8b33e 100644 --- a/net/devlink/rate.c +++ b/net/devlink/rate.c @@ -30,13 +30,53 @@ devlink_rate_leaf_get_from_info(struct devlink *devlink, struct genl_info *info) return devlink_rate ?: ERR_PTR(-ENODEV); } +/* Repeatedly walks the nested devlink chain while cross device rate nodes are + * supported and finds the topmost instance where rates should be stored. + * That instance is locked, referenced and returned. + * When cross device rate nodes aren't supported the original devlink instance + * is returned. + */ +static struct devlink *devl_rate_lock(struct devlink *devlink) +{ + struct devlink *rate_devlink = devlink, *parent; + + devl_assert_locked(devlink); + + while (rate_devlink->ops && + rate_devlink->ops->supported_cross_device_rate_nodes) { + parent = devlink_nested_in_get_lock(rate_devlink); + if (!parent) + break; + if (rate_devlink != devlink) { + /* Unlock intermediate instances. */ + devl_unlock(rate_devlink); + devlink_put(rate_devlink); + } + rate_devlink = parent; + } + return rate_devlink; +} + +/* Unlocks and puts 'rate devlink' if different than 'devlink'. */ +static void devl_rate_unlock(struct devlink *devlink, + struct devlink *rate_devlink) +{ + if (devlink == rate_devlink) + return; + + devl_unlock(rate_devlink); + devlink_put(rate_devlink); +} + static struct devlink_rate * -devlink_rate_node_get_by_name(struct devlink *devlink, const char *node_name) +devlink_rate_node_get_by_name(struct devlink *rate_devlink, + struct devlink *devlink, const char *node_name) { struct devlink_rate *devlink_rate; - list_for_each_entry(devlink_rate, &devlink->rate_list, list) { - if (devlink_rate_is_node(devlink_rate) && + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) { + if (devlink_rate->devlink == devlink && + devlink_rate_is_node(devlink_rate) && !strcmp(node_name, devlink_rate->name)) return devlink_rate; } @@ -44,7 +84,8 @@ devlink_rate_node_get_by_name(struct devlink *devlink, const char *node_name) } static struct devlink_rate * -devlink_rate_node_get_from_attrs(struct devlink *devlink, struct nlattr **attrs) +devlink_rate_node_get_from_attrs(struct devlink *rate_devlink, + struct devlink *devlink, struct nlattr **attrs) { const char *rate_node_name; size_t len; @@ -57,24 +98,30 @@ devlink_rate_node_get_from_attrs(struct devlink *devlink, struct nlattr **attrs) if (!len || strspn(rate_node_name, "0123456789") == len) return ERR_PTR(-EINVAL); - return devlink_rate_node_get_by_name(devlink, rate_node_name); + return devlink_rate_node_get_by_name(rate_devlink, devlink, + rate_node_name); } static struct devlink_rate * -devlink_rate_node_get_from_info(struct devlink *devlink, struct genl_info *info) +devlink_rate_node_get_from_info(struct devlink *rate_devlink, + struct devlink *devlink, + struct genl_info *info) { - return devlink_rate_node_get_from_attrs(devlink, info->attrs); + return devlink_rate_node_get_from_attrs(rate_devlink, devlink, + info->attrs); } static struct devlink_rate * -devlink_rate_get_from_info(struct devlink *devlink, struct genl_info *info) +devlink_rate_get_from_info(struct devlink *rate_devlink, + struct devlink *devlink, struct genl_info *info) { struct nlattr **attrs = info->attrs; if (attrs[DEVLINK_ATTR_PORT_INDEX]) return devlink_rate_leaf_get_from_info(devlink, info); else if (attrs[DEVLINK_ATTR_RATE_NODE_NAME]) - return devlink_rate_node_get_from_info(devlink, info); + return devlink_rate_node_get_from_info(rate_devlink, devlink, + info); else return ERR_PTR(-EINVAL); } @@ -102,6 +149,25 @@ static int devlink_rate_put_tc_bws(struct sk_buff *msg, u32 *tc_bw) return -EMSGSIZE; } +static int devlink_nl_rate_parent_fill(struct sk_buff *msg, + struct devlink_rate *devlink_rate) +{ + struct devlink_rate *parent = devlink_rate->parent; + struct devlink *devlink = parent->devlink; + + if (nla_put_string(msg, DEVLINK_ATTR_RATE_PARENT_NODE_NAME, + parent->name)) + return -EMSGSIZE; + + if (devlink != devlink_rate->devlink && + devlink_nl_put_nested_handle(msg, + devlink_net(devlink_rate->devlink), + devlink, DEVLINK_ATTR_PARENT_DEV)) + return -EMSGSIZE; + + return 0; +} + static int devlink_nl_rate_fill(struct sk_buff *msg, struct devlink_rate *devlink_rate, enum devlink_command cmd, u32 portid, u32 seq, @@ -146,10 +212,9 @@ static int devlink_nl_rate_fill(struct sk_buff *msg, devlink_rate->tx_weight)) goto nla_put_failure; - if (devlink_rate->parent) - if (nla_put_string(msg, DEVLINK_ATTR_RATE_PARENT_NODE_NAME, - devlink_rate->parent->name)) - goto nla_put_failure; + if (devlink_rate->parent && + devlink_nl_rate_parent_fill(msg, devlink_rate)) + goto nla_put_failure; if (devlink_rate_put_tc_bws(msg, devlink_rate->tc_bw)) goto nla_put_failure; @@ -190,17 +255,25 @@ static void devlink_rate_notify(struct devlink_rate *devlink_rate, void devlink_rates_notify_register(struct devlink *devlink) { struct devlink_rate *rate_node; + struct devlink *rate_devlink; - list_for_each_entry(rate_node, &devlink->rate_list, list) - devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry(rate_node, &rate_devlink->rate_list, list) + if (rate_node->devlink == devlink) + devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); + devl_rate_unlock(devlink, rate_devlink); } void devlink_rates_notify_unregister(struct devlink *devlink) { struct devlink_rate *rate_node; + struct devlink *rate_devlink; - list_for_each_entry_reverse(rate_node, &devlink->rate_list, list) - devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry_reverse(rate_node, &rate_devlink->rate_list, list) + if (rate_node->devlink == devlink) + devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); + devl_rate_unlock(devlink, rate_devlink); } static int @@ -209,17 +282,20 @@ devlink_nl_rate_get_dump_one(struct sk_buff *msg, struct devlink *devlink, { struct devlink_nl_dump_state *state = devlink_dump_state(cb); struct devlink_rate *devlink_rate; + struct devlink *rate_devlink; int idx = 0; int err = 0; - list_for_each_entry(devlink_rate, &devlink->rate_list, list) { + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) { enum devlink_command cmd = DEVLINK_CMD_RATE_NEW; u32 id = NETLINK_CB(cb->skb).portid; - if (idx < state->idx) { + if (idx < state->idx || devlink_rate->devlink != devlink) { idx++; continue; } + err = devlink_nl_rate_fill(msg, devlink_rate, cmd, id, cb->nlh->nlmsg_seq, flags, NULL); if (err) { @@ -228,6 +304,7 @@ devlink_nl_rate_get_dump_one(struct sk_buff *msg, struct devlink *devlink, } idx++; } + devl_rate_unlock(devlink, rate_devlink); return err; } @@ -239,28 +316,38 @@ int devlink_nl_rate_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) int devlink_nl_rate_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *rate_devlink, *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *devlink_rate; struct sk_buff *msg; int err; - devlink_rate = devlink_rate_get_from_info(devlink, info); - if (IS_ERR(devlink_rate)) - return PTR_ERR(devlink_rate); + rate_devlink = devl_rate_lock(devlink); + devlink_rate = devlink_rate_get_from_info(rate_devlink, devlink, info); + if (IS_ERR(devlink_rate)) { + err = PTR_ERR(devlink_rate); + goto unlock; + } msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); - if (!msg) - return -ENOMEM; + if (!msg) { + err = -ENOMEM; + goto unlock; + } err = devlink_nl_rate_fill(msg, devlink_rate, DEVLINK_CMD_RATE_NEW, info->snd_portid, info->snd_seq, 0, info->extack); - if (err) { - nlmsg_free(msg); - return err; - } + if (err) + goto err_fill; + devl_rate_unlock(devlink, rate_devlink); return genlmsg_reply(msg, info); + +err_fill: + nlmsg_free(msg); +unlock: + devl_rate_unlock(devlink, rate_devlink); + return err; } static bool @@ -277,16 +364,18 @@ devlink_rate_is_parent_node(struct devlink_rate *devlink_rate, static int devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate, + struct devlink *rate_devlink, struct genl_info *info, struct nlattr *nla_parent) { - struct devlink *devlink = devlink_rate->devlink; + struct devlink *devlink = devlink_rate->devlink, *parent_devlink; const char *parent_name = nla_data(nla_parent); const struct devlink_ops *ops = devlink->ops; size_t len = strlen(parent_name); struct devlink_rate *parent; int err = -EOPNOTSUPP; + parent_devlink = devlink_nl_ctx(info)->parent_devlink ? : devlink; parent = devlink_rate->parent; if (parent && !len) { @@ -304,7 +393,14 @@ devlink_nl_rate_parent_node_set(struct devlink_rate *devlink_rate, refcount_dec(&parent->refcnt); devlink_rate->parent = NULL; } else if (len) { - parent = devlink_rate_node_get_by_name(devlink, parent_name); + /* parent_devlink (when different than devlink) isn't locked, + * but the rate node devlink instance is, so nobody from the + * same group of devices sharing rates could change the used + * fields or unregister the parent. + */ + parent = devlink_rate_node_get_by_name(rate_devlink, + parent_devlink, + parent_name); if (IS_ERR(parent)) return -ENODEV; @@ -423,6 +519,7 @@ static int devlink_nl_rate_tc_bw_set(struct devlink_rate *devlink_rate, } static int devlink_nl_rate_set(struct devlink_rate *devlink_rate, + struct devlink *rate_devlink, const struct devlink_ops *ops, struct genl_info *info) { @@ -497,7 +594,8 @@ static int devlink_nl_rate_set(struct devlink_rate *devlink_rate, */ nla_parent = attrs[DEVLINK_ATTR_RATE_PARENT_NODE_NAME]; if (nla_parent) { - err = devlink_nl_rate_parent_node_set(devlink_rate, info, + err = devlink_nl_rate_parent_node_set(devlink_rate, + rate_devlink, info, nla_parent); if (err) return err; @@ -588,31 +686,51 @@ static bool devlink_rate_set_ops_supported(const struct devlink_ops *ops, int devlink_nl_rate_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; struct devlink_rate *devlink_rate; const struct devlink_ops *ops; + struct devlink *rate_devlink; int err; - devlink_rate = devlink_rate_get_from_info(devlink, info); - if (IS_ERR(devlink_rate)) - return PTR_ERR(devlink_rate); + rate_devlink = devl_rate_lock(devlink); + devlink_rate = devlink_rate_get_from_info(rate_devlink, devlink, info); + if (IS_ERR(devlink_rate)) { + err = PTR_ERR(devlink_rate); + goto unlock; + } ops = devlink->ops; - if (!ops || !devlink_rate_set_ops_supported(ops, info, devlink_rate->type)) - return -EOPNOTSUPP; + if (!ops || + !devlink_rate_set_ops_supported(ops, info, devlink_rate->type)) { + err = -EOPNOTSUPP; + goto unlock; + } - err = devlink_nl_rate_set(devlink_rate, ops, info); + if (ctx->parent_devlink && ctx->parent_devlink != devlink && + !ops->supported_cross_device_rate_nodes) { + NL_SET_ERR_MSG(info->extack, + "Cross-device rate parents aren't supported"); + err = -EOPNOTSUPP; + goto unlock; + } + + err = devlink_nl_rate_set(devlink_rate, rate_devlink, ops, info); if (!err) devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_NEW); +unlock: + devl_rate_unlock(devlink, rate_devlink); return err; } int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; struct devlink_rate *rate_node; const struct devlink_ops *ops; + struct devlink *rate_devlink; int err; ops = devlink->ops; @@ -624,15 +742,29 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) if (!devlink_rate_set_ops_supported(ops, info, DEVLINK_RATE_TYPE_NODE)) return -EOPNOTSUPP; - rate_node = devlink_rate_node_get_from_attrs(devlink, info->attrs); - if (!IS_ERR(rate_node)) - return -EEXIST; - else if (rate_node == ERR_PTR(-EINVAL)) - return -EINVAL; + if (ctx->parent_devlink && ctx->parent_devlink != devlink && + !ops->supported_cross_device_rate_nodes) { + NL_SET_ERR_MSG(info->extack, + "Cross-device rate parents aren't supported"); + return -EOPNOTSUPP; + } + + rate_devlink = devl_rate_lock(devlink); + rate_node = devlink_rate_node_get_from_attrs(rate_devlink, devlink, + info->attrs); + if (!IS_ERR(rate_node)) { + err = -EEXIST; + goto unlock; + } else if (rate_node == ERR_PTR(-EINVAL)) { + err = -EINVAL; + goto unlock; + } rate_node = kzalloc_obj(*rate_node); - if (!rate_node) - return -ENOMEM; + if (!rate_node) { + err = -ENOMEM; + goto unlock; + } rate_node->devlink = devlink; rate_node->type = DEVLINK_RATE_TYPE_NODE; @@ -646,13 +778,14 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) if (err) goto err_node_new; - err = devlink_nl_rate_set(rate_node, ops, info); + err = devlink_nl_rate_set(rate_node, rate_devlink, ops, info); if (err) goto err_rate_set; refcount_set(&rate_node->refcnt, 1); - list_add(&rate_node->list, &devlink->rate_list); + list_add(&rate_node->list, &rate_devlink->rate_list); devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); + devl_rate_unlock(devlink, rate_devlink); return 0; err_rate_set: @@ -661,22 +794,29 @@ int devlink_nl_rate_new_doit(struct sk_buff *skb, struct genl_info *info) kfree(rate_node->name); err_strdup: kfree(rate_node); +unlock: + devl_rate_unlock(devlink, rate_devlink); return err; } int devlink_nl_rate_del_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *rate_devlink, *devlink = devlink_nl_ctx(info)->devlink; struct devlink_rate *rate_node; int err; - rate_node = devlink_rate_node_get_from_info(devlink, info); - if (IS_ERR(rate_node)) - return PTR_ERR(rate_node); + rate_devlink = devl_rate_lock(devlink); + rate_node = devlink_rate_node_get_from_info(rate_devlink, devlink, + info); + if (IS_ERR(rate_node)) { + err = PTR_ERR(rate_node); + goto unlock; + } if (refcount_read(&rate_node->refcnt) > 1) { NL_SET_ERR_MSG(info->extack, "Node has children. Cannot delete node."); - return -EBUSY; + err = -EBUSY; + goto unlock; } devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_DEL); @@ -687,6 +827,8 @@ int devlink_nl_rate_del_doit(struct sk_buff *skb, struct genl_info *info) list_del(&rate_node->list); kfree(rate_node->name); kfree(rate_node); +unlock: + devl_rate_unlock(devlink, rate_devlink); return err; } @@ -695,14 +837,20 @@ int devlink_rates_check(struct devlink *devlink, struct netlink_ext_ack *extack) { struct devlink_rate *devlink_rate; + struct devlink *rate_devlink; + int err = 0; - list_for_each_entry(devlink_rate, &devlink->rate_list, list) - if (!rate_filter || rate_filter(devlink_rate)) { + rate_devlink = devl_rate_lock(devlink); + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) + if (devlink_rate->devlink == devlink && + (!rate_filter || rate_filter(devlink_rate))) { if (extack) NL_SET_ERR_MSG(extack, "Rate node(s) exists."); - return -EBUSY; + err = -EBUSY; + break; } - return 0; + devl_rate_unlock(devlink, rate_devlink); + return err; } /** @@ -719,14 +867,21 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, struct devlink_rate *parent) { struct devlink_rate *rate_node; + struct devlink *rate_devlink; - rate_node = devlink_rate_node_get_by_name(devlink, node_name); - if (!IS_ERR(rate_node)) - return ERR_PTR(-EEXIST); + rate_devlink = devl_rate_lock(devlink); + rate_node = devlink_rate_node_get_by_name(rate_devlink, devlink, + node_name); + if (!IS_ERR(rate_node)) { + rate_node = ERR_PTR(-EEXIST); + goto unlock; + } rate_node = kzalloc_obj(*rate_node); - if (!rate_node) - return ERR_PTR(-ENOMEM); + if (!rate_node) { + rate_node = ERR_PTR(-ENOMEM); + goto unlock; + } rate_node->type = DEVLINK_RATE_TYPE_NODE; rate_node->devlink = devlink; @@ -735,7 +890,8 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, rate_node->name = kstrdup(node_name, GFP_KERNEL); if (!rate_node->name) { kfree(rate_node); - return ERR_PTR(-ENOMEM); + rate_node = ERR_PTR(-ENOMEM); + goto unlock; } if (parent) { @@ -744,8 +900,10 @@ devl_rate_node_create(struct devlink *devlink, void *priv, char *node_name, } refcount_set(&rate_node->refcnt, 1); - list_add(&rate_node->list, &devlink->rate_list); + list_add(&rate_node->list, &rate_devlink->rate_list); devlink_rate_notify(rate_node, DEVLINK_CMD_RATE_NEW); +unlock: + devl_rate_unlock(devlink, rate_devlink); return rate_node; } EXPORT_SYMBOL_GPL(devl_rate_node_create); @@ -761,10 +919,10 @@ EXPORT_SYMBOL_GPL(devl_rate_node_create); int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv, struct devlink_rate *parent) { - struct devlink *devlink = devlink_port->devlink; + struct devlink *rate_devlink, *devlink = devlink_port->devlink; struct devlink_rate *devlink_rate; - devl_assert_locked(devlink_port->devlink); + devl_assert_locked(devlink); if (WARN_ON(devlink_port->devlink_rate)) return -EBUSY; @@ -773,6 +931,7 @@ int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv, if (!devlink_rate) return -ENOMEM; + rate_devlink = devl_rate_lock(devlink); if (parent) { devlink_rate->parent = parent; refcount_inc(&devlink_rate->parent->refcnt); @@ -782,9 +941,10 @@ int devl_rate_leaf_create(struct devlink_port *devlink_port, void *priv, devlink_rate->devlink = devlink; devlink_rate->devlink_port = devlink_port; devlink_rate->priv = priv; - list_add_tail(&devlink_rate->list, &devlink->rate_list); + list_add_tail(&devlink_rate->list, &rate_devlink->rate_list); devlink_port->devlink_rate = devlink_rate; devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_NEW); + devl_rate_unlock(devlink, rate_devlink); return 0; } @@ -800,16 +960,19 @@ EXPORT_SYMBOL_GPL(devl_rate_leaf_create); void devl_rate_leaf_destroy(struct devlink_port *devlink_port) { struct devlink_rate *devlink_rate = devlink_port->devlink_rate; + struct devlink *rate_devlink, *devlink = devlink_port->devlink; - devl_assert_locked(devlink_port->devlink); + devl_assert_locked(devlink); if (!devlink_rate) return; + rate_devlink = devl_rate_lock(devlink); devlink_rate_notify(devlink_rate, DEVLINK_CMD_RATE_DEL); if (devlink_rate->parent) refcount_dec(&devlink_rate->parent->refcnt); list_del(&devlink_rate->list); devlink_port->devlink_rate = NULL; + devl_rate_unlock(devlink, rate_devlink); kfree(devlink_rate); } EXPORT_SYMBOL_GPL(devl_rate_leaf_destroy); @@ -818,20 +981,30 @@ EXPORT_SYMBOL_GPL(devl_rate_leaf_destroy); * devl_rate_nodes_destroy - destroy all devlink rate nodes on device * @devlink: devlink instance * - * Unset parent for all rate objects and destroy all rate nodes - * on specified device. + * Unset parent for all rate objects involving this device and destroy all rate + * nodes on it. */ void devl_rate_nodes_destroy(struct devlink *devlink) { - const struct devlink_ops *ops = devlink->ops; struct devlink_rate *devlink_rate, *tmp; + const struct devlink_ops *ops; + struct devlink *rate_devlink; devl_assert_locked(devlink); + rate_devlink = devl_rate_lock(devlink); - list_for_each_entry(devlink_rate, &devlink->rate_list, list) { - if (!devlink_rate->parent) + list_for_each_entry(devlink_rate, &rate_devlink->rate_list, list) { + if (!devlink_rate->parent || + (devlink_rate->devlink != devlink && + devlink_rate->parent->devlink != devlink)) continue; + /* This could destroy rate objects on other devlinks in the + * same hierarchy under 'rate_devlink'. This is safe because + * the shared common ancestor is locked so there can be no + * other concurrent rate operations on devlink_rate->devlink. + */ + ops = devlink_rate->devlink->ops; if (devlink_rate_is_leaf(devlink_rate)) ops->rate_leaf_parent_set(devlink_rate, NULL, devlink_rate->priv, NULL, NULL); @@ -842,13 +1015,17 @@ void devl_rate_nodes_destroy(struct devlink *devlink) refcount_dec(&devlink_rate->parent->refcnt); devlink_rate->parent = NULL; } - list_for_each_entry_safe(devlink_rate, tmp, &devlink->rate_list, list) { - if (devlink_rate_is_node(devlink_rate)) { + ops = devlink->ops; + list_for_each_entry_safe(devlink_rate, tmp, &rate_devlink->rate_list, + list) { + if (devlink_rate->devlink == devlink && + devlink_rate_is_node(devlink_rate)) { ops->rate_node_del(devlink_rate, devlink_rate->priv, NULL); list_del(&devlink_rate->list); kfree(devlink_rate->name); kfree(devlink_rate); } } + devl_rate_unlock(devlink, rate_devlink); } EXPORT_SYMBOL_GPL(devl_rate_nodes_destroy); diff --git a/net/devlink/region.c b/net/devlink/region.c index 5588e3d560b9..537779bbff07 100644 --- a/net/devlink/region.c +++ b/net/devlink/region.c @@ -469,7 +469,7 @@ static void devlink_region_snapshot_del(struct devlink_region *region, int devlink_nl_region_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_port *port = NULL; struct devlink_region *region; const char *region_name; @@ -588,7 +588,7 @@ int devlink_nl_region_get_dumpit(struct sk_buff *skb, int devlink_nl_region_del_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_snapshot *snapshot; struct devlink_port *port = NULL; struct devlink_region *region; @@ -633,7 +633,7 @@ int devlink_nl_region_del_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_region_new_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_snapshot *snapshot; struct devlink_port *port = NULL; struct nlattr *snapshot_id_attr; diff --git a/net/devlink/resource.c b/net/devlink/resource.c index 574108ccfe5d..c3cfda7ea070 100644 --- a/net/devlink/resource.c +++ b/net/devlink/resource.c @@ -117,7 +117,7 @@ devlink_resource_validate_size(struct devlink_resource *resource, u64 size, int devlink_nl_resource_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_resource *resource; u64 resource_id; u64 size; @@ -251,8 +251,9 @@ static int devlink_resource_list_fill(struct sk_buff *skb, static int devlink_resource_fill(struct genl_info *info, enum devlink_command cmd, int flags) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; + struct devlink_port *devlink_port; struct devlink_resource *resource; struct list_head *resource_list; struct nlattr *resources_attr; @@ -263,6 +264,7 @@ static int devlink_resource_fill(struct genl_info *info, int i; int err; + devlink_port = ctx->devlink_port; resource_list = devlink_port ? &devlink_port->resource_list : &devlink->resource_list; resource = list_first_entry(resource_list, @@ -326,10 +328,12 @@ static int devlink_resource_fill(struct genl_info *info, int devlink_nl_resource_dump_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_nl_ctx *ctx = devlink_nl_ctx(info); + struct devlink *devlink = ctx->devlink; + struct devlink_port *devlink_port; struct list_head *resource_list; + devlink_port = ctx->devlink_port; if (info->attrs[DEVLINK_ATTR_PORT_INDEX] && !devlink_port) return -ENODEV; diff --git a/net/devlink/sb.c b/net/devlink/sb.c index 49fcbfe08f15..129bd016e302 100644 --- a/net/devlink/sb.c +++ b/net/devlink/sb.c @@ -204,7 +204,7 @@ static int devlink_nl_sb_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_sb_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; int err; @@ -306,7 +306,7 @@ static int devlink_nl_sb_pool_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_sb_pool_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; u16 pool_index; @@ -415,7 +415,7 @@ static int devlink_sb_pool_set(struct devlink *devlink, unsigned int sb_index, int devlink_nl_sb_pool_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; enum devlink_sb_threshold_type threshold_type; struct devlink_sb *devlink_sb; u16 pool_index; @@ -506,7 +506,7 @@ static int devlink_nl_sb_port_pool_fill(struct sk_buff *msg, int devlink_nl_sb_port_pool_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct devlink *devlink = devlink_port->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; @@ -624,8 +624,8 @@ static int devlink_sb_port_pool_set(struct devlink_port *devlink_port, int devlink_nl_sb_port_pool_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_sb *devlink_sb; u16 pool_index; u32 threshold; @@ -716,7 +716,7 @@ devlink_nl_sb_tc_pool_bind_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_sb_tc_pool_bind_get_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; struct devlink *devlink = devlink_port->devlink; struct devlink_sb *devlink_sb; struct sk_buff *msg; @@ -864,8 +864,8 @@ static int devlink_sb_tc_pool_bind_set(struct devlink_port *devlink_port, int devlink_nl_sb_tc_pool_bind_set_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink_port *devlink_port = info->user_ptr[1]; - struct devlink *devlink = info->user_ptr[0]; + struct devlink_port *devlink_port = devlink_nl_ctx(info)->devlink_port; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; enum devlink_sb_pool_type pool_type; struct devlink_sb *devlink_sb; u16 tc_index; @@ -902,7 +902,7 @@ int devlink_nl_sb_tc_pool_bind_set_doit(struct sk_buff *skb, int devlink_nl_sb_occ_snapshot_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const struct devlink_ops *ops = devlink->ops; struct devlink_sb *devlink_sb; @@ -918,7 +918,7 @@ int devlink_nl_sb_occ_snapshot_doit(struct sk_buff *skb, struct genl_info *info) int devlink_nl_sb_occ_max_clear_doit(struct sk_buff *skb, struct genl_info *info) { - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; const struct devlink_ops *ops = devlink->ops; struct devlink_sb *devlink_sb; diff --git a/net/devlink/trap.c b/net/devlink/trap.c index 8edb31654a68..793ffc66dc11 100644 --- a/net/devlink/trap.c +++ b/net/devlink/trap.c @@ -302,7 +302,7 @@ static int devlink_nl_trap_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_trap_get_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_item *trap_item; struct sk_buff *msg; int err; @@ -412,7 +412,7 @@ static int devlink_trap_action_set(struct devlink *devlink, int devlink_nl_trap_set_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_item *trap_item; if (list_empty(&devlink->trap_list)) @@ -511,7 +511,7 @@ devlink_nl_trap_group_fill(struct sk_buff *msg, struct devlink *devlink, int devlink_nl_trap_group_get_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_group_item *group_item; struct sk_buff *msg; int err; @@ -682,7 +682,7 @@ static int devlink_trap_group_set(struct devlink *devlink, int devlink_nl_trap_group_set_doit(struct sk_buff *skb, struct genl_info *info) { struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct devlink_trap_group_item *group_item; bool modified = false; int err; @@ -804,7 +804,7 @@ int devlink_nl_trap_policer_get_doit(struct sk_buff *skb, { struct devlink_trap_policer_item *policer_item; struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; struct sk_buff *msg; int err; @@ -924,7 +924,7 @@ int devlink_nl_trap_policer_set_doit(struct sk_buff *skb, { struct devlink_trap_policer_item *policer_item; struct netlink_ext_ack *extack = info->extack; - struct devlink *devlink = info->user_ptr[0]; + struct devlink *devlink = devlink_nl_ctx(info)->devlink; if (list_empty(&devlink->trap_policer_list)) return -EOPNOTSUPP; diff --git a/tools/testing/selftests/drivers/net/hw/Makefile b/tools/testing/selftests/drivers/net/hw/Makefile index fd0535a96d84..234db5c2c90c 100644 --- a/tools/testing/selftests/drivers/net/hw/Makefile +++ b/tools/testing/selftests/drivers/net/hw/Makefile @@ -20,6 +20,7 @@ TEST_GEN_FILES := \ TEST_PROGS = \ csum.py \ devlink_port_split.py \ + devlink_rate_cross_esw.py \ devlink_rate_tc_bw.py \ devmem.py \ ethtool.sh \ diff --git a/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py b/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py new file mode 100755 index 000000000000..4416f024cb76 --- /dev/null +++ b/tools/testing/selftests/drivers/net/hw/devlink_rate_cross_esw.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +""" +Devlink Rate Cross-eswitch Scheduling Test Suite +================================================== + +Control-plane tests for cross-eswitch TX scheduling via devlink-rate. +Validates that VFs from different PFs on the same chip can share +rate groups using the cross-device parent-dev attribute. + +Preconditions: +- NETIF points to a bond device with exactly two interfaces. +- the interfaces must be two PFs from different devices sharing the same chip. +- (for mlx5): the two interfaces are in switchdev mode and configured in a LAG: + - devlink dev eswitch set $DEV1 mode switchdev + - devlink dev eswitch set $DEV2 mode switchdev + - devlink dev param set $DEV1 name esw_multiport value 1 cmode runtime + - devlink dev param set $DEV2 name esw_multiport value 1 cmode runtime +- test cases will be skipped if: + - the number of interfaces in the bond device is != 2. + - the kernel doesn't support devlink rates. + - the devlink API doesn't support cross-device parents (ENODEV). + - cross-esw rate scheduling returns EOPNOTSUPP. +""" + +import errno +import glob +import os +import time + +from lib.py import ksft_pr, ksft_eq, ksft_run, ksft_exit +from lib.py import KsftSkipEx, KsftFailEx +from lib.py import NetDrvEnv, DevlinkFamily +from lib.py import NlError +from lib.py import cmd, defer, ip, tool + + +# --- Discovery and setup --- + + +def get_bond_slaves(bond_ifname): + """Returns sorted list of slave netdev names for a bond.""" + pattern = f"/sys/class/net/{bond_ifname}/lower_*" + lowers = glob.glob(pattern) + if not lowers: + raise KsftSkipEx(f"No bond slaves for {bond_ifname}") + slaves = [] + for path in sorted(lowers): + name = os.path.basename(path) + if name.startswith("lower_"): + name = name[len("lower_"):] + slaves.append(name) + return slaves + + +def discover_pfs(cfg): + """Discovers both PFs from bond slaves.""" + slaves = get_bond_slaves(cfg.ifname) + if len(slaves) != 2: + raise KsftSkipEx(f"Need 2 bond slaves, found {len(slaves)}") + + pf0, pf1 = slaves[0], slaves[1] + ksft_pr(f"PF0: {pf0} PF1: {pf1}") + return pf0, pf1 + + +def get_pci_addr(ifname): + """Resolves PCI address for a network interface.""" + return os.path.basename(os.path.realpath(f"/sys/class/net/{ifname}/device")) + + +def get_vf_port_index(pf_pci): + """Finds devlink port-index for vf0 under pf_pci.""" + ports = tool("devlink", "port show", json=True)["port"] + for port_name, props in ports.items(): + if port_name.startswith(f"pci/{pf_pci}/") and props.get("vfnum") == 0: + return int(port_name.split("/")[-1]) + raise KsftSkipEx(f"VF port not found for {pf_pci}") + + +def cleanup_esw(pf): + """Removes VFs if created by tests.""" + cmd(f"echo 0 > /sys/class/net/{pf}/device/sriov_numvfs", shell=True, fail=False) + + +def setup_esw(pf): + """Creates 1 VF on 'pf'.""" + path = f"/sys/class/net/{pf}/device/sriov_numvfs" + cmd(f"echo 0 > {path}", shell=True) + cmd(f"echo 1 > {path}", shell=True) + defer(cleanup_esw, pf) + time.sleep(2) + + vf_dir = f"/sys/class/net/{pf}/device/virtfn0/net" + entries = os.listdir(vf_dir) if os.path.isdir(vf_dir) else [] + if not entries: + raise KsftSkipEx(f"VF not found for {pf}") + ip(f"link set dev {entries[0]} up") + + pf_pci = get_pci_addr(pf) + vf_idx = get_vf_port_index(pf_pci) + ksft_pr(f"Created VF {vf_idx} on PF {pf} ({pf_pci})") + return pf_pci, vf_idx + + +# --- Rate operation helpers --- + + +def rate_new(devnl, dev_pci, node_name, **kwargs): + """Creates rate node.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + } + params.update(kwargs) + try: + devnl.rate_new(params) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx("rate_new not supported") from e + raise KsftFailEx("rate_new failed") from e + + +def rate_get(devnl, dev_pci, node_name): + """Gets rate node.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + } + return devnl.rate_get(params) + + +def rate_get_leaf(devnl, dev_pci, port_index): + """Gets rate leaf (VF).""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "port-index": port_index, + } + return devnl.rate_get(params) + + +def rate_del(devnl, dev_pci, node_name): + """Deletes rate node.""" + devnl.rate_del({ + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + }) + + +def rate_set_leaf(devnl, dev_pci, port_index, **kwargs): + """Sets rate attributes on a leaf (VF).""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "port-index": port_index, + } + params.update(kwargs) + try: + devnl.rate_set(params) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx("rate_set not supported") from e + raise KsftFailEx("rate_set failed") from e + + +def rate_set_leaf_parent(devnl, dev_pci, port_index, + parent_name, parent_dev_pci=None): + """Sets a leaf's parent, optionally cross-esw.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "port-index": port_index, + "rate-parent-node-name": parent_name, + } + if parent_dev_pci: + params["parent-dev"] = { + "bus-name": "pci", + "dev-name": parent_dev_pci, + } + try: + devnl.rate_set(params) + except NlError as e: + if e.error == errno.EOPNOTSUPP: + raise KsftSkipEx("rate_set not supported") from e + if parent_dev_pci and e.error == errno.ENODEV: + raise KsftSkipEx("Cross-esw scheduling not supported") from e + raise KsftFailEx("rate_set failed") from e + + +def rate_clear_leaf_parent(devnl, dev_pci, port_index): + """Clears a leaf's parent.""" + rate_set_leaf_parent(devnl, dev_pci, port_index, "") + + +def rate_set_node(devnl, dev_pci, node_name, **kwargs): + """Sets rate attributes on a node.""" + params = { + "bus-name": "pci", + "dev-name": dev_pci, + "rate-node-name": node_name, + } + params.update(kwargs) + devnl.rate_set(params) + + +# --- Test cases --- + + +def test_same_esw_parent(cfg): + """Assigns PF0's VF to PF0's group (same esw baseline).""" + pf0, _ = discover_pfs(cfg) + pf0_pci, vf0_idx = setup_esw(pf0) + + rate_new(cfg.devnl, pf0_pci, "group0") + defer(rate_del, cfg.devnl, pf0_pci, "group0") + ksft_pr("rate-new succeeded") + + rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group0") + defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx) + + ksft_pr("Same-esw parent assignment succeeded") + + +def test_cross_esw_parent(cfg): + """Sets cross-esw parent, then clear it.""" + pf0, pf1 = discover_pfs(cfg) + pf0_pci, _ = setup_esw(pf0) + pf1_pci, vf1_idx = setup_esw(pf1) + + rate_new(cfg.devnl, pf0_pci, "group1") + defer(rate_del, cfg.devnl, pf0_pci, "group1") + ksft_pr("rate-new succeeded") + + rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx, + "group1", parent_dev_pci=pf0_pci) + defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx) + + ksft_pr("Cross-esw parent set and clear succeeded") + + +def test_tx_rates_on_cross_esw(cfg): + """Sets tx_max on group and tx_share on leaves in a cross-esw setup.""" + pf0, pf1 = discover_pfs(cfg) + pf0_pci, vf0_idx = setup_esw(pf0) + pf1_pci, vf1_idx = setup_esw(pf1) + + rate_new(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 10000000}) + defer(rate_del, cfg.devnl, pf0_pci, "group2") + ksft_pr("rate-new succeeded") + + rate_set_leaf_parent(cfg.devnl, pf1_pci, vf1_idx, + "group2", parent_dev_pci=pf0_pci) + defer(rate_clear_leaf_parent, cfg.devnl, pf1_pci, vf1_idx) + ksft_pr("set parent cross-esw succeeded") + + rate_set_leaf_parent(cfg.devnl, pf0_pci, vf0_idx, "group2") + defer(rate_clear_leaf_parent, cfg.devnl, pf0_pci, vf0_idx) + ksft_pr("set parent same esw succeeded") + + rate_set_leaf(cfg.devnl, pf0_pci, vf0_idx, **{"rate-tx-share": 1000000}) + rate = rate_get_leaf(cfg.devnl, pf0_pci, vf0_idx) + ksft_eq(rate["rate-tx-share"], 1000000) + rate_set_leaf(cfg.devnl, pf1_pci, vf1_idx, **{"rate-tx-share": 2000000}) + rate = rate_get_leaf(cfg.devnl, pf1_pci, vf1_idx) + ksft_eq(rate["rate-tx-share"], 2000000) + rate_set_node(cfg.devnl, pf0_pci, "group2", **{"rate-tx-max": 250000000}) + rate = rate_get(cfg.devnl, pf0_pci, "group2") + ksft_eq(rate["rate-tx-max"], 250000000) + + ksft_pr("tx_max and tx_share set on cross-esw group") + + +def main() -> None: + """Main function.""" + + with NetDrvEnv(__file__, nsim_test=False) as cfg: + cfg.devnl = DevlinkFamily() + + ksft_run( + cases=[ + test_same_esw_parent, + test_cross_esw_parent, + test_tx_rates_on_cross_esw, + ], + args=(cfg,), + ) + ksft_exit() + + +if __name__ == "__main__": + main()