1 | // SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB |
2 | /* Copyright (c) 2017 - 2021 Intel Corporation */ |
3 | #include "osdep.h" |
4 | #include "hmc.h" |
5 | #include "defs.h" |
6 | #include "type.h" |
7 | #include "protos.h" |
8 | |
9 | #include "ws.h" |
10 | |
11 | /** |
12 | * irdma_alloc_node - Allocate a WS node and init |
13 | * @vsi: vsi pointer |
14 | * @user_pri: user priority |
15 | * @node_type: Type of node, leaf or parent |
16 | * @parent: parent node pointer |
17 | */ |
18 | static struct irdma_ws_node *irdma_alloc_node(struct irdma_sc_vsi *vsi, |
19 | u8 user_pri, |
20 | enum irdma_ws_node_type node_type, |
21 | struct irdma_ws_node *parent) |
22 | { |
23 | struct irdma_virt_mem ws_mem; |
24 | struct irdma_ws_node *node; |
25 | u16 node_index = 0; |
26 | |
27 | ws_mem.size = sizeof(struct irdma_ws_node); |
28 | ws_mem.va = kzalloc(size: ws_mem.size, GFP_KERNEL); |
29 | if (!ws_mem.va) |
30 | return NULL; |
31 | |
32 | if (parent) { |
33 | node_index = irdma_alloc_ws_node_id(dev: vsi->dev); |
34 | if (node_index == IRDMA_WS_NODE_INVALID) { |
35 | kfree(objp: ws_mem.va); |
36 | return NULL; |
37 | } |
38 | } |
39 | |
40 | node = ws_mem.va; |
41 | node->index = node_index; |
42 | node->vsi_index = vsi->vsi_idx; |
43 | INIT_LIST_HEAD(list: &node->child_list_head); |
44 | if (node_type == WS_NODE_TYPE_LEAF) { |
45 | node->type_leaf = true; |
46 | node->traffic_class = vsi->qos[user_pri].traffic_class; |
47 | node->user_pri = user_pri; |
48 | node->rel_bw = vsi->qos[user_pri].rel_bw; |
49 | if (!node->rel_bw) |
50 | node->rel_bw = 1; |
51 | |
52 | node->lan_qs_handle = vsi->qos[user_pri].lan_qos_handle; |
53 | node->prio_type = IRDMA_PRIO_WEIGHTED_RR; |
54 | } else { |
55 | node->rel_bw = 1; |
56 | node->prio_type = IRDMA_PRIO_WEIGHTED_RR; |
57 | node->enable = true; |
58 | } |
59 | |
60 | node->parent = parent; |
61 | |
62 | return node; |
63 | } |
64 | |
65 | /** |
66 | * irdma_free_node - Free a WS node |
67 | * @vsi: VSI stricture of device |
68 | * @node: Pointer to node to free |
69 | */ |
70 | static void irdma_free_node(struct irdma_sc_vsi *vsi, |
71 | struct irdma_ws_node *node) |
72 | { |
73 | struct irdma_virt_mem ws_mem; |
74 | |
75 | if (node->index) |
76 | irdma_free_ws_node_id(dev: vsi->dev, node_id: node->index); |
77 | |
78 | ws_mem.va = node; |
79 | ws_mem.size = sizeof(struct irdma_ws_node); |
80 | kfree(objp: ws_mem.va); |
81 | } |
82 | |
83 | /** |
84 | * irdma_ws_cqp_cmd - Post CQP work scheduler node cmd |
85 | * @vsi: vsi pointer |
86 | * @node: pointer to node |
87 | * @cmd: add, remove or modify |
88 | */ |
89 | static int irdma_ws_cqp_cmd(struct irdma_sc_vsi *vsi, |
90 | struct irdma_ws_node *node, u8 cmd) |
91 | { |
92 | struct irdma_ws_node_info node_info = {}; |
93 | |
94 | node_info.id = node->index; |
95 | node_info.vsi = node->vsi_index; |
96 | if (node->parent) |
97 | node_info.parent_id = node->parent->index; |
98 | else |
99 | node_info.parent_id = node_info.id; |
100 | |
101 | node_info.weight = node->rel_bw; |
102 | node_info.tc = node->traffic_class; |
103 | node_info.prio_type = node->prio_type; |
104 | node_info.type_leaf = node->type_leaf; |
105 | node_info.enable = node->enable; |
106 | if (irdma_cqp_ws_node_cmd(dev: vsi->dev, cmd, node_info: &node_info)) { |
107 | ibdev_dbg(to_ibdev(vsi->dev), "WS: CQP WS CMD failed\n" ); |
108 | return -ENOMEM; |
109 | } |
110 | |
111 | if (node->type_leaf && cmd == IRDMA_OP_WS_ADD_NODE) { |
112 | node->qs_handle = node_info.qs_handle; |
113 | vsi->qos[node->user_pri].qs_handle = node_info.qs_handle; |
114 | } |
115 | |
116 | return 0; |
117 | } |
118 | |
119 | /** |
120 | * ws_find_node - Find SC WS node based on VSI id or TC |
121 | * @parent: parent node of First VSI or TC node |
122 | * @match_val: value to match |
123 | * @type: match type VSI/TC |
124 | */ |
125 | static struct irdma_ws_node *ws_find_node(struct irdma_ws_node *parent, |
126 | u16 match_val, |
127 | enum irdma_ws_match_type type) |
128 | { |
129 | struct irdma_ws_node *node; |
130 | |
131 | switch (type) { |
132 | case WS_MATCH_TYPE_VSI: |
133 | list_for_each_entry(node, &parent->child_list_head, siblings) { |
134 | if (node->vsi_index == match_val) |
135 | return node; |
136 | } |
137 | break; |
138 | case WS_MATCH_TYPE_TC: |
139 | list_for_each_entry(node, &parent->child_list_head, siblings) { |
140 | if (node->traffic_class == match_val) |
141 | return node; |
142 | } |
143 | break; |
144 | default: |
145 | break; |
146 | } |
147 | |
148 | return NULL; |
149 | } |
150 | |
151 | /** |
152 | * irdma_tc_in_use - Checks to see if a leaf node is in use |
153 | * @vsi: vsi pointer |
154 | * @user_pri: user priority |
155 | */ |
156 | static bool irdma_tc_in_use(struct irdma_sc_vsi *vsi, u8 user_pri) |
157 | { |
158 | int i; |
159 | |
160 | mutex_lock(&vsi->qos[user_pri].qos_mutex); |
161 | if (!list_empty(head: &vsi->qos[user_pri].qplist)) { |
162 | mutex_unlock(lock: &vsi->qos[user_pri].qos_mutex); |
163 | return true; |
164 | } |
165 | |
166 | /* Check if the traffic class associated with the given user priority |
167 | * is in use by any other user priority. If so, nothing left to do |
168 | */ |
169 | for (i = 0; i < IRDMA_MAX_USER_PRIORITY; i++) { |
170 | if (vsi->qos[i].traffic_class == vsi->qos[user_pri].traffic_class && |
171 | !list_empty(head: &vsi->qos[i].qplist)) { |
172 | mutex_unlock(lock: &vsi->qos[user_pri].qos_mutex); |
173 | return true; |
174 | } |
175 | } |
176 | mutex_unlock(lock: &vsi->qos[user_pri].qos_mutex); |
177 | |
178 | return false; |
179 | } |
180 | |
181 | /** |
182 | * irdma_remove_leaf - Remove leaf node unconditionally |
183 | * @vsi: vsi pointer |
184 | * @user_pri: user priority |
185 | */ |
186 | static void irdma_remove_leaf(struct irdma_sc_vsi *vsi, u8 user_pri) |
187 | { |
188 | struct irdma_ws_node *ws_tree_root, *vsi_node, *tc_node; |
189 | int i; |
190 | u16 traffic_class; |
191 | |
192 | traffic_class = vsi->qos[user_pri].traffic_class; |
193 | for (i = 0; i < IRDMA_MAX_USER_PRIORITY; i++) |
194 | if (vsi->qos[i].traffic_class == traffic_class) |
195 | vsi->qos[i].valid = false; |
196 | |
197 | ws_tree_root = vsi->dev->ws_tree_root; |
198 | if (!ws_tree_root) |
199 | return; |
200 | |
201 | vsi_node = ws_find_node(parent: ws_tree_root, match_val: vsi->vsi_idx, |
202 | type: WS_MATCH_TYPE_VSI); |
203 | if (!vsi_node) |
204 | return; |
205 | |
206 | tc_node = ws_find_node(parent: vsi_node, |
207 | match_val: vsi->qos[user_pri].traffic_class, |
208 | type: WS_MATCH_TYPE_TC); |
209 | if (!tc_node) |
210 | return; |
211 | |
212 | irdma_ws_cqp_cmd(vsi, node: tc_node, cmd: IRDMA_OP_WS_DELETE_NODE); |
213 | vsi->unregister_qset(vsi, tc_node); |
214 | list_del(entry: &tc_node->siblings); |
215 | irdma_free_node(vsi, node: tc_node); |
216 | /* Check if VSI node can be freed */ |
217 | if (list_empty(head: &vsi_node->child_list_head)) { |
218 | irdma_ws_cqp_cmd(vsi, node: vsi_node, cmd: IRDMA_OP_WS_DELETE_NODE); |
219 | list_del(entry: &vsi_node->siblings); |
220 | irdma_free_node(vsi, node: vsi_node); |
221 | /* Free head node there are no remaining VSI nodes */ |
222 | if (list_empty(head: &ws_tree_root->child_list_head)) { |
223 | irdma_ws_cqp_cmd(vsi, node: ws_tree_root, |
224 | cmd: IRDMA_OP_WS_DELETE_NODE); |
225 | irdma_free_node(vsi, node: ws_tree_root); |
226 | vsi->dev->ws_tree_root = NULL; |
227 | } |
228 | } |
229 | } |
230 | |
231 | /** |
232 | * irdma_ws_add - Build work scheduler tree, set RDMA qs_handle |
233 | * @vsi: vsi pointer |
234 | * @user_pri: user priority |
235 | */ |
236 | int irdma_ws_add(struct irdma_sc_vsi *vsi, u8 user_pri) |
237 | { |
238 | struct irdma_ws_node *ws_tree_root; |
239 | struct irdma_ws_node *vsi_node; |
240 | struct irdma_ws_node *tc_node; |
241 | u16 traffic_class; |
242 | int ret = 0; |
243 | int i; |
244 | |
245 | mutex_lock(&vsi->dev->ws_mutex); |
246 | if (vsi->tc_change_pending) { |
247 | ret = -EBUSY; |
248 | goto exit; |
249 | } |
250 | |
251 | if (vsi->qos[user_pri].valid) |
252 | goto exit; |
253 | |
254 | ws_tree_root = vsi->dev->ws_tree_root; |
255 | if (!ws_tree_root) { |
256 | ibdev_dbg(to_ibdev(vsi->dev), "WS: Creating root node\n" ); |
257 | ws_tree_root = irdma_alloc_node(vsi, user_pri, |
258 | node_type: WS_NODE_TYPE_PARENT, NULL); |
259 | if (!ws_tree_root) { |
260 | ret = -ENOMEM; |
261 | goto exit; |
262 | } |
263 | |
264 | ret = irdma_ws_cqp_cmd(vsi, node: ws_tree_root, cmd: IRDMA_OP_WS_ADD_NODE); |
265 | if (ret) { |
266 | irdma_free_node(vsi, node: ws_tree_root); |
267 | goto exit; |
268 | } |
269 | |
270 | vsi->dev->ws_tree_root = ws_tree_root; |
271 | } |
272 | |
273 | /* Find a second tier node that matches the VSI */ |
274 | vsi_node = ws_find_node(parent: ws_tree_root, match_val: vsi->vsi_idx, |
275 | type: WS_MATCH_TYPE_VSI); |
276 | |
277 | /* If VSI node doesn't exist, add one */ |
278 | if (!vsi_node) { |
279 | ibdev_dbg(to_ibdev(vsi->dev), |
280 | "WS: Node not found matching VSI %d\n" , |
281 | vsi->vsi_idx); |
282 | vsi_node = irdma_alloc_node(vsi, user_pri, node_type: WS_NODE_TYPE_PARENT, |
283 | parent: ws_tree_root); |
284 | if (!vsi_node) { |
285 | ret = -ENOMEM; |
286 | goto vsi_add_err; |
287 | } |
288 | |
289 | ret = irdma_ws_cqp_cmd(vsi, node: vsi_node, cmd: IRDMA_OP_WS_ADD_NODE); |
290 | if (ret) { |
291 | irdma_free_node(vsi, node: vsi_node); |
292 | goto vsi_add_err; |
293 | } |
294 | |
295 | list_add(new: &vsi_node->siblings, head: &ws_tree_root->child_list_head); |
296 | } |
297 | |
298 | ibdev_dbg(to_ibdev(vsi->dev), |
299 | "WS: Using node %d which represents VSI %d\n" , |
300 | vsi_node->index, vsi->vsi_idx); |
301 | traffic_class = vsi->qos[user_pri].traffic_class; |
302 | tc_node = ws_find_node(parent: vsi_node, match_val: traffic_class, |
303 | type: WS_MATCH_TYPE_TC); |
304 | if (!tc_node) { |
305 | /* Add leaf node */ |
306 | ibdev_dbg(to_ibdev(vsi->dev), |
307 | "WS: Node not found matching VSI %d and TC %d\n" , |
308 | vsi->vsi_idx, traffic_class); |
309 | tc_node = irdma_alloc_node(vsi, user_pri, node_type: WS_NODE_TYPE_LEAF, |
310 | parent: vsi_node); |
311 | if (!tc_node) { |
312 | ret = -ENOMEM; |
313 | goto leaf_add_err; |
314 | } |
315 | |
316 | ret = irdma_ws_cqp_cmd(vsi, node: tc_node, cmd: IRDMA_OP_WS_ADD_NODE); |
317 | if (ret) { |
318 | irdma_free_node(vsi, node: tc_node); |
319 | goto leaf_add_err; |
320 | } |
321 | |
322 | list_add(new: &tc_node->siblings, head: &vsi_node->child_list_head); |
323 | /* |
324 | * callback to LAN to update the LAN tree with our node |
325 | */ |
326 | ret = vsi->register_qset(vsi, tc_node); |
327 | if (ret) |
328 | goto reg_err; |
329 | |
330 | tc_node->enable = true; |
331 | ret = irdma_ws_cqp_cmd(vsi, node: tc_node, cmd: IRDMA_OP_WS_MODIFY_NODE); |
332 | if (ret) { |
333 | vsi->unregister_qset(vsi, tc_node); |
334 | goto reg_err; |
335 | } |
336 | } |
337 | ibdev_dbg(to_ibdev(vsi->dev), |
338 | "WS: Using node %d which represents VSI %d TC %d\n" , |
339 | tc_node->index, vsi->vsi_idx, traffic_class); |
340 | /* |
341 | * Iterate through other UPs and update the QS handle if they have |
342 | * a matching traffic class. |
343 | */ |
344 | for (i = 0; i < IRDMA_MAX_USER_PRIORITY; i++) { |
345 | if (vsi->qos[i].traffic_class == traffic_class) { |
346 | vsi->qos[i].qs_handle = tc_node->qs_handle; |
347 | vsi->qos[i].lan_qos_handle = tc_node->lan_qs_handle; |
348 | vsi->qos[i].l2_sched_node_id = tc_node->l2_sched_node_id; |
349 | vsi->qos[i].valid = true; |
350 | } |
351 | } |
352 | goto exit; |
353 | |
354 | reg_err: |
355 | irdma_ws_cqp_cmd(vsi, node: tc_node, cmd: IRDMA_OP_WS_DELETE_NODE); |
356 | list_del(entry: &tc_node->siblings); |
357 | irdma_free_node(vsi, node: tc_node); |
358 | leaf_add_err: |
359 | if (list_empty(head: &vsi_node->child_list_head)) { |
360 | if (irdma_ws_cqp_cmd(vsi, node: vsi_node, cmd: IRDMA_OP_WS_DELETE_NODE)) |
361 | goto exit; |
362 | list_del(entry: &vsi_node->siblings); |
363 | irdma_free_node(vsi, node: vsi_node); |
364 | } |
365 | |
366 | vsi_add_err: |
367 | /* Free head node there are no remaining VSI nodes */ |
368 | if (list_empty(head: &ws_tree_root->child_list_head)) { |
369 | irdma_ws_cqp_cmd(vsi, node: ws_tree_root, cmd: IRDMA_OP_WS_DELETE_NODE); |
370 | vsi->dev->ws_tree_root = NULL; |
371 | irdma_free_node(vsi, node: ws_tree_root); |
372 | } |
373 | |
374 | exit: |
375 | mutex_unlock(lock: &vsi->dev->ws_mutex); |
376 | return ret; |
377 | } |
378 | |
379 | /** |
380 | * irdma_ws_remove - Free WS scheduler node, update WS tree |
381 | * @vsi: vsi pointer |
382 | * @user_pri: user priority |
383 | */ |
384 | void irdma_ws_remove(struct irdma_sc_vsi *vsi, u8 user_pri) |
385 | { |
386 | mutex_lock(&vsi->dev->ws_mutex); |
387 | if (irdma_tc_in_use(vsi, user_pri)) |
388 | goto exit; |
389 | irdma_remove_leaf(vsi, user_pri); |
390 | exit: |
391 | mutex_unlock(lock: &vsi->dev->ws_mutex); |
392 | } |
393 | |
394 | /** |
395 | * irdma_ws_reset - Reset entire WS tree |
396 | * @vsi: vsi pointer |
397 | */ |
398 | void irdma_ws_reset(struct irdma_sc_vsi *vsi) |
399 | { |
400 | u8 i; |
401 | |
402 | mutex_lock(&vsi->dev->ws_mutex); |
403 | for (i = 0; i < IRDMA_MAX_USER_PRIORITY; ++i) |
404 | irdma_remove_leaf(vsi, user_pri: i); |
405 | mutex_unlock(lock: &vsi->dev->ws_mutex); |
406 | } |
407 | |