| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (c) 2018, Intel Corporation. */ |
| |
| #include "ice_sched.h" |
| |
| /** |
| * ice_aq_delete_sched_elems - delete scheduler elements |
| * @hw: pointer to the hw struct |
| * @grps_req: number of groups to delete |
| * @buf: pointer to buffer |
| * @buf_size: buffer size in bytes |
| * @grps_del: returns total number of elements deleted |
| * @cd: pointer to command details structure or NULL |
| * |
| * Delete scheduling elements (0x040F) |
| */ |
| static enum ice_status |
| ice_aq_delete_sched_elems(struct ice_hw *hw, u16 grps_req, |
| struct ice_aqc_delete_elem *buf, u16 buf_size, |
| u16 *grps_del, struct ice_sq_cd *cd) |
| { |
| struct ice_aqc_add_move_delete_elem *cmd; |
| struct ice_aq_desc desc; |
| enum ice_status status; |
| |
| cmd = &desc.params.add_move_delete_elem; |
| ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_delete_sched_elems); |
| desc.flags |= cpu_to_le16(ICE_AQ_FLAG_RD); |
| cmd->num_grps_req = cpu_to_le16(grps_req); |
| |
| status = ice_aq_send_cmd(hw, &desc, buf, buf_size, cd); |
| if (!status && grps_del) |
| *grps_del = le16_to_cpu(cmd->num_grps_updated); |
| |
| return status; |
| } |
| |
| /** |
| * ice_sched_remove_elems - remove nodes from hw |
| * @hw: pointer to the hw struct |
| * @parent: pointer to the parent node |
| * @num_nodes: number of nodes |
| * @node_teids: array of node teids to be deleted |
| * |
| * This function remove nodes from hw |
| */ |
| static enum ice_status |
| ice_sched_remove_elems(struct ice_hw *hw, struct ice_sched_node *parent, |
| u16 num_nodes, u32 *node_teids) |
| { |
| struct ice_aqc_delete_elem *buf; |
| u16 i, num_groups_removed = 0; |
| enum ice_status status; |
| u16 buf_size; |
| |
| buf_size = sizeof(*buf) + sizeof(u32) * (num_nodes - 1); |
| buf = devm_kzalloc(ice_hw_to_dev(hw), buf_size, GFP_KERNEL); |
| if (!buf) |
| return ICE_ERR_NO_MEMORY; |
| buf->hdr.parent_teid = parent->info.node_teid; |
| buf->hdr.num_elems = cpu_to_le16(num_nodes); |
| for (i = 0; i < num_nodes; i++) |
| buf->teid[i] = cpu_to_le32(node_teids[i]); |
| status = ice_aq_delete_sched_elems(hw, 1, buf, buf_size, |
| &num_groups_removed, NULL); |
| if (status || num_groups_removed != 1) |
| ice_debug(hw, ICE_DBG_SCHED, "remove elements failed\n"); |
| devm_kfree(ice_hw_to_dev(hw), buf); |
| return status; |
| } |
| |
| /** |
| * ice_sched_get_first_node - get the first node of the given layer |
| * @hw: pointer to the hw struct |
| * @parent: pointer the base node of the subtree |
| * @layer: layer number |
| * |
| * This function retrieves the first node of the given layer from the subtree |
| */ |
| static struct ice_sched_node * |
| ice_sched_get_first_node(struct ice_hw *hw, struct ice_sched_node *parent, |
| u8 layer) |
| { |
| u8 i; |
| |
| if (layer < hw->sw_entry_point_layer) |
| return NULL; |
| for (i = 0; i < parent->num_children; i++) { |
| struct ice_sched_node *node = parent->children[i]; |
| |
| if (node) { |
| if (node->tx_sched_layer == layer) |
| return node; |
| /* this recursion is intentional, and wouldn't |
| * go more than 9 calls |
| */ |
| return ice_sched_get_first_node(hw, node, layer); |
| } |
| } |
| return NULL; |
| } |
| |
| /** |
| * ice_sched_get_tc_node - get pointer to TC node |
| * @pi: port information structure |
| * @tc: TC number |
| * |
| * This function returns the TC node pointer |
| */ |
| struct ice_sched_node *ice_sched_get_tc_node(struct ice_port_info *pi, u8 tc) |
| { |
| u8 i; |
| |
| if (!pi) |
| return NULL; |
| for (i = 0; i < pi->root->num_children; i++) |
| if (pi->root->children[i]->tc_num == tc) |
| return pi->root->children[i]; |
| return NULL; |
| } |
| |
| /** |
| * ice_free_sched_node - Free a Tx scheduler node from SW DB |
| * @pi: port information structure |
| * @node: pointer to the ice_sched_node struct |
| * |
| * This function frees up a node from SW DB as well as from HW |
| * |
| * This function needs to be called with the port_info->sched_lock held |
| */ |
| void ice_free_sched_node(struct ice_port_info *pi, struct ice_sched_node *node) |
| { |
| struct ice_sched_node *parent; |
| struct ice_hw *hw = pi->hw; |
| u8 i, j; |
| |
| /* Free the children before freeing up the parent node |
| * The parent array is updated below and that shifts the nodes |
| * in the array. So always pick the first child if num children > 0 |
| */ |
| while (node->num_children) |
| ice_free_sched_node(pi, node->children[0]); |
| |
| /* Leaf, TC and root nodes can't be deleted by SW */ |
| if (node->tx_sched_layer >= hw->sw_entry_point_layer && |
| node->info.data.elem_type != ICE_AQC_ELEM_TYPE_TC && |
| node->info.data.elem_type != ICE_AQC_ELEM_TYPE_ROOT_PORT && |
| node->info.data.elem_type != ICE_AQC_ELEM_TYPE_LEAF) { |
| u32 teid = le32_to_cpu(node->info.node_teid); |
| enum ice_status status; |
| |
| status = ice_sched_remove_elems(hw, node->parent, 1, &teid); |
| if (status) |
| ice_debug(hw, ICE_DBG_SCHED, |
| "remove element failed %d\n", status); |
| } |
| parent = node->parent; |
| /* root has no parent */ |
| if (parent) { |
| struct ice_sched_node *p, *tc_node; |
| |
| /* update the parent */ |
| for (i = 0; i < parent->num_children; i++) |
| if (parent->children[i] == node) { |
| for (j = i + 1; j < parent->num_children; j++) |
| parent->children[j - 1] = |
| parent->children[j]; |
| parent->num_children--; |
| break; |
| } |
| |
| /* search for previous sibling that points to this node and |
| * remove the reference |
| */ |
| tc_node = ice_sched_get_tc_node(pi, node->tc_num); |
| if (!tc_node) { |
| ice_debug(hw, ICE_DBG_SCHED, |
| "Invalid TC number %d\n", node->tc_num); |
| goto err_exit; |
| } |
| p = ice_sched_get_first_node(hw, tc_node, node->tx_sched_layer); |
| while (p) { |
| if (p->sibling == node) { |
| p->sibling = node->sibling; |
| break; |
| } |
| p = p->sibling; |
| } |
| } |
| err_exit: |
| /* leaf nodes have no children */ |
| if (node->children) |
| devm_kfree(ice_hw_to_dev(hw), node->children); |
| devm_kfree(ice_hw_to_dev(hw), node); |
| } |
| |
| /** |
| * ice_aq_query_sched_res - query scheduler resource |
| * @hw: pointer to the hw struct |
| * @buf_size: buffer size in bytes |
| * @buf: pointer to buffer |
| * @cd: pointer to command details structure or NULL |
| * |
| * Query scheduler resource allocation (0x0412) |
| */ |
| static enum ice_status |
| ice_aq_query_sched_res(struct ice_hw *hw, u16 buf_size, |
| struct ice_aqc_query_txsched_res_resp *buf, |
| struct ice_sq_cd *cd) |
| { |
| struct ice_aq_desc desc; |
| |
| ice_fill_dflt_direct_cmd_desc(&desc, ice_aqc_opc_query_sched_res); |
| return ice_aq_send_cmd(hw, &desc, buf, buf_size, cd); |
| } |
| |
| /** |
| * ice_sched_clear_tx_topo - clears the schduler tree nodes |
| * @pi: port information structure |
| * |
| * This function removes all the nodes from HW as well as from SW DB. |
| */ |
| static void ice_sched_clear_tx_topo(struct ice_port_info *pi) |
| { |
| struct ice_sched_agg_info *agg_info; |
| struct ice_sched_vsi_info *vsi_elem; |
| struct ice_sched_agg_info *atmp; |
| struct ice_sched_vsi_info *tmp; |
| struct ice_hw *hw; |
| |
| if (!pi) |
| return; |
| |
| hw = pi->hw; |
| |
| list_for_each_entry_safe(agg_info, atmp, &pi->agg_list, list_entry) { |
| struct ice_sched_agg_vsi_info *agg_vsi_info; |
| struct ice_sched_agg_vsi_info *vtmp; |
| |
| list_for_each_entry_safe(agg_vsi_info, vtmp, |
| &agg_info->agg_vsi_list, list_entry) { |
| list_del(&agg_vsi_info->list_entry); |
| devm_kfree(ice_hw_to_dev(hw), agg_vsi_info); |
| } |
| } |
| |
| /* remove the vsi list */ |
| list_for_each_entry_safe(vsi_elem, tmp, &pi->vsi_info_list, |
| list_entry) { |
| list_del(&vsi_elem->list_entry); |
| devm_kfree(ice_hw_to_dev(hw), vsi_elem); |
| } |
| |
| if (pi->root) { |
| ice_free_sched_node(pi, pi->root); |
| pi->root = NULL; |
| } |
| } |
| |
| /** |
| * ice_sched_clear_port - clear the scheduler elements from SW DB for a port |
| * @pi: port information structure |
| * |
| * Cleanup scheduling elements from SW DB |
| */ |
| static void ice_sched_clear_port(struct ice_port_info *pi) |
| { |
| if (!pi || pi->port_state != ICE_SCHED_PORT_STATE_READY) |
| return; |
| |
| pi->port_state = ICE_SCHED_PORT_STATE_INIT; |
| mutex_lock(&pi->sched_lock); |
| ice_sched_clear_tx_topo(pi); |
| mutex_unlock(&pi->sched_lock); |
| mutex_destroy(&pi->sched_lock); |
| } |
| |
| /** |
| * ice_sched_cleanup_all - cleanup scheduler elements from SW DB for all ports |
| * @hw: pointer to the hw struct |
| * |
| * Cleanup scheduling elements from SW DB for all the ports |
| */ |
| void ice_sched_cleanup_all(struct ice_hw *hw) |
| { |
| if (!hw || !hw->port_info) |
| return; |
| |
| if (hw->layer_info) |
| devm_kfree(ice_hw_to_dev(hw), hw->layer_info); |
| |
| ice_sched_clear_port(hw->port_info); |
| |
| hw->num_tx_sched_layers = 0; |
| hw->num_tx_sched_phys_layers = 0; |
| hw->flattened_layers = 0; |
| hw->max_cgds = 0; |
| } |
| |
| /** |
| * ice_sched_query_res_alloc - query the FW for num of logical sched layers |
| * @hw: pointer to the HW struct |
| * |
| * query FW for allocated scheduler resources and store in HW struct |
| */ |
| enum ice_status ice_sched_query_res_alloc(struct ice_hw *hw) |
| { |
| struct ice_aqc_query_txsched_res_resp *buf; |
| enum ice_status status = 0; |
| |
| if (hw->layer_info) |
| return status; |
| |
| buf = devm_kzalloc(ice_hw_to_dev(hw), sizeof(*buf), GFP_KERNEL); |
| if (!buf) |
| return ICE_ERR_NO_MEMORY; |
| |
| status = ice_aq_query_sched_res(hw, sizeof(*buf), buf, NULL); |
| if (status) |
| goto sched_query_out; |
| |
| hw->num_tx_sched_layers = le16_to_cpu(buf->sched_props.logical_levels); |
| hw->num_tx_sched_phys_layers = |
| le16_to_cpu(buf->sched_props.phys_levels); |
| hw->flattened_layers = buf->sched_props.flattening_bitmap; |
| hw->max_cgds = buf->sched_props.max_pf_cgds; |
| |
| hw->layer_info = devm_kmemdup(ice_hw_to_dev(hw), buf->layer_props, |
| (hw->num_tx_sched_layers * |
| sizeof(*hw->layer_info)), |
| GFP_KERNEL); |
| if (!hw->layer_info) { |
| status = ICE_ERR_NO_MEMORY; |
| goto sched_query_out; |
| } |
| |
| sched_query_out: |
| devm_kfree(ice_hw_to_dev(hw), buf); |
| return status; |
| } |