1 // SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
2 /* Copyright (c) 2019 Mellanox Technologies. */
3 
4 #include "dr_types.h"
5 
6 #define DR_ICM_MODIFY_HDR_ALIGN_BASE 64
7 #define DR_ICM_SYNC_THRESHOLD (64 * 1024 * 1024)
8 
9 struct mlx5dr_icm_pool {
10 	enum mlx5dr_icm_type icm_type;
11 	enum mlx5dr_icm_chunk_size max_log_chunk_sz;
12 	struct mlx5dr_domain *dmn;
13 	/* memory management */
14 	struct mutex mutex; /* protect the ICM pool and ICM buddy */
15 	struct list_head buddy_mem_list;
16 };
17 
18 struct mlx5dr_icm_dm {
19 	u32 obj_id;
20 	enum mlx5_sw_icm_type type;
21 	phys_addr_t addr;
22 	size_t length;
23 };
24 
25 struct mlx5dr_icm_mr {
26 	struct mlx5_core_mkey mkey;
27 	struct mlx5dr_icm_dm dm;
28 	struct mlx5dr_domain *dmn;
29 	size_t length;
30 	u64 icm_start_addr;
31 };
32 
33 static int dr_icm_create_dm_mkey(struct mlx5_core_dev *mdev,
34 				 u32 pd, u64 length, u64 start_addr, int mode,
35 				 struct mlx5_core_mkey *mkey)
36 {
37 	u32 inlen = MLX5_ST_SZ_BYTES(create_mkey_in);
38 	u32 in[MLX5_ST_SZ_DW(create_mkey_in)] = {};
39 	void *mkc;
40 
41 	mkc = MLX5_ADDR_OF(create_mkey_in, in, memory_key_mkey_entry);
42 
43 	MLX5_SET(mkc, mkc, access_mode_1_0, mode);
44 	MLX5_SET(mkc, mkc, access_mode_4_2, (mode >> 2) & 0x7);
45 	MLX5_SET(mkc, mkc, lw, 1);
46 	MLX5_SET(mkc, mkc, lr, 1);
47 	if (mode == MLX5_MKC_ACCESS_MODE_SW_ICM) {
48 		MLX5_SET(mkc, mkc, rw, 1);
49 		MLX5_SET(mkc, mkc, rr, 1);
50 	}
51 
52 	MLX5_SET64(mkc, mkc, len, length);
53 	MLX5_SET(mkc, mkc, pd, pd);
54 	MLX5_SET(mkc, mkc, qpn, 0xffffff);
55 	MLX5_SET64(mkc, mkc, start_addr, start_addr);
56 
57 	return mlx5_core_create_mkey(mdev, mkey, in, inlen);
58 }
59 
60 static struct mlx5dr_icm_mr *
61 dr_icm_pool_mr_create(struct mlx5dr_icm_pool *pool)
62 {
63 	struct mlx5_core_dev *mdev = pool->dmn->mdev;
64 	enum mlx5_sw_icm_type dm_type;
65 	struct mlx5dr_icm_mr *icm_mr;
66 	size_t log_align_base;
67 	int err;
68 
69 	icm_mr = kvzalloc(sizeof(*icm_mr), GFP_KERNEL);
70 	if (!icm_mr)
71 		return NULL;
72 
73 	icm_mr->dmn = pool->dmn;
74 
75 	icm_mr->dm.length = mlx5dr_icm_pool_chunk_size_to_byte(pool->max_log_chunk_sz,
76 							       pool->icm_type);
77 
78 	if (pool->icm_type == DR_ICM_TYPE_STE) {
79 		dm_type = MLX5_SW_ICM_TYPE_STEERING;
80 		log_align_base = ilog2(icm_mr->dm.length);
81 	} else {
82 		dm_type = MLX5_SW_ICM_TYPE_HEADER_MODIFY;
83 		/* Align base is 64B */
84 		log_align_base = ilog2(DR_ICM_MODIFY_HDR_ALIGN_BASE);
85 	}
86 	icm_mr->dm.type = dm_type;
87 
88 	err = mlx5_dm_sw_icm_alloc(mdev, icm_mr->dm.type, icm_mr->dm.length,
89 				   log_align_base, 0, &icm_mr->dm.addr,
90 				   &icm_mr->dm.obj_id);
91 	if (err) {
92 		mlx5dr_err(pool->dmn, "Failed to allocate SW ICM memory, err (%d)\n", err);
93 		goto free_icm_mr;
94 	}
95 
96 	/* Register device memory */
97 	err = dr_icm_create_dm_mkey(mdev, pool->dmn->pdn,
98 				    icm_mr->dm.length,
99 				    icm_mr->dm.addr,
100 				    MLX5_MKC_ACCESS_MODE_SW_ICM,
101 				    &icm_mr->mkey);
102 	if (err) {
103 		mlx5dr_err(pool->dmn, "Failed to create SW ICM MKEY, err (%d)\n", err);
104 		goto free_dm;
105 	}
106 
107 	icm_mr->icm_start_addr = icm_mr->dm.addr;
108 
109 	if (icm_mr->icm_start_addr & (BIT(log_align_base) - 1)) {
110 		mlx5dr_err(pool->dmn, "Failed to get Aligned ICM mem (asked: %zu)\n",
111 			   log_align_base);
112 		goto free_mkey;
113 	}
114 
115 	return icm_mr;
116 
117 free_mkey:
118 	mlx5_core_destroy_mkey(mdev, &icm_mr->mkey);
119 free_dm:
120 	mlx5_dm_sw_icm_dealloc(mdev, icm_mr->dm.type, icm_mr->dm.length, 0,
121 			       icm_mr->dm.addr, icm_mr->dm.obj_id);
122 free_icm_mr:
123 	kvfree(icm_mr);
124 	return NULL;
125 }
126 
127 static void dr_icm_pool_mr_destroy(struct mlx5dr_icm_mr *icm_mr)
128 {
129 	struct mlx5_core_dev *mdev = icm_mr->dmn->mdev;
130 	struct mlx5dr_icm_dm *dm = &icm_mr->dm;
131 
132 	mlx5_core_destroy_mkey(mdev, &icm_mr->mkey);
133 	mlx5_dm_sw_icm_dealloc(mdev, dm->type, dm->length, 0,
134 			       dm->addr, dm->obj_id);
135 	kvfree(icm_mr);
136 }
137 
138 static int dr_icm_chunk_ste_init(struct mlx5dr_icm_chunk *chunk)
139 {
140 	chunk->ste_arr = kvzalloc(chunk->num_of_entries *
141 				  sizeof(chunk->ste_arr[0]), GFP_KERNEL);
142 	if (!chunk->ste_arr)
143 		return -ENOMEM;
144 
145 	chunk->hw_ste_arr = kvzalloc(chunk->num_of_entries *
146 				     DR_STE_SIZE_REDUCED, GFP_KERNEL);
147 	if (!chunk->hw_ste_arr)
148 		goto out_free_ste_arr;
149 
150 	chunk->miss_list = kvmalloc(chunk->num_of_entries *
151 				    sizeof(chunk->miss_list[0]), GFP_KERNEL);
152 	if (!chunk->miss_list)
153 		goto out_free_hw_ste_arr;
154 
155 	return 0;
156 
157 out_free_hw_ste_arr:
158 	kvfree(chunk->hw_ste_arr);
159 out_free_ste_arr:
160 	kvfree(chunk->ste_arr);
161 	return -ENOMEM;
162 }
163 
164 static void dr_icm_chunk_ste_cleanup(struct mlx5dr_icm_chunk *chunk)
165 {
166 	kvfree(chunk->miss_list);
167 	kvfree(chunk->hw_ste_arr);
168 	kvfree(chunk->ste_arr);
169 }
170 
171 static enum mlx5dr_icm_type
172 get_chunk_icm_type(struct mlx5dr_icm_chunk *chunk)
173 {
174 	return chunk->buddy_mem->pool->icm_type;
175 }
176 
177 static void dr_icm_chunk_destroy(struct mlx5dr_icm_chunk *chunk)
178 {
179 	enum mlx5dr_icm_type icm_type = get_chunk_icm_type(chunk);
180 
181 	list_del(&chunk->chunk_list);
182 
183 	if (icm_type == DR_ICM_TYPE_STE)
184 		dr_icm_chunk_ste_cleanup(chunk);
185 
186 	kvfree(chunk);
187 }
188 
189 static int dr_icm_buddy_create(struct mlx5dr_icm_pool *pool)
190 {
191 	struct mlx5dr_icm_buddy_mem *buddy;
192 	struct mlx5dr_icm_mr *icm_mr;
193 
194 	icm_mr = dr_icm_pool_mr_create(pool);
195 	if (!icm_mr)
196 		return -ENOMEM;
197 
198 	buddy = kvzalloc(sizeof(*buddy), GFP_KERNEL);
199 	if (!buddy)
200 		goto free_mr;
201 
202 	if (mlx5dr_buddy_init(buddy, pool->max_log_chunk_sz))
203 		goto err_free_buddy;
204 
205 	buddy->icm_mr = icm_mr;
206 	buddy->pool = pool;
207 
208 	/* add it to the -start- of the list in order to search in it first */
209 	list_add(&buddy->list_node, &pool->buddy_mem_list);
210 
211 	return 0;
212 
213 err_free_buddy:
214 	kvfree(buddy);
215 free_mr:
216 	dr_icm_pool_mr_destroy(icm_mr);
217 	return -ENOMEM;
218 }
219 
220 static void dr_icm_buddy_destroy(struct mlx5dr_icm_buddy_mem *buddy)
221 {
222 	struct mlx5dr_icm_chunk *chunk, *next;
223 
224 	list_for_each_entry_safe(chunk, next, &buddy->hot_list, chunk_list)
225 		dr_icm_chunk_destroy(chunk);
226 
227 	list_for_each_entry_safe(chunk, next, &buddy->used_list, chunk_list)
228 		dr_icm_chunk_destroy(chunk);
229 
230 	dr_icm_pool_mr_destroy(buddy->icm_mr);
231 
232 	mlx5dr_buddy_cleanup(buddy);
233 
234 	kvfree(buddy);
235 }
236 
237 static struct mlx5dr_icm_chunk *
238 dr_icm_chunk_create(struct mlx5dr_icm_pool *pool,
239 		    enum mlx5dr_icm_chunk_size chunk_size,
240 		    struct mlx5dr_icm_buddy_mem *buddy_mem_pool,
241 		    unsigned int seg)
242 {
243 	struct mlx5dr_icm_chunk *chunk;
244 	int offset;
245 
246 	chunk = kvzalloc(sizeof(*chunk), GFP_KERNEL);
247 	if (!chunk)
248 		return NULL;
249 
250 	offset = mlx5dr_icm_pool_dm_type_to_entry_size(pool->icm_type) * seg;
251 
252 	chunk->rkey = buddy_mem_pool->icm_mr->mkey.key;
253 	chunk->mr_addr = offset;
254 	chunk->icm_addr =
255 		(uintptr_t)buddy_mem_pool->icm_mr->icm_start_addr + offset;
256 	chunk->num_of_entries =
257 		mlx5dr_icm_pool_chunk_size_to_entries(chunk_size);
258 	chunk->byte_size =
259 		mlx5dr_icm_pool_chunk_size_to_byte(chunk_size, pool->icm_type);
260 	chunk->seg = seg;
261 
262 	if (pool->icm_type == DR_ICM_TYPE_STE && dr_icm_chunk_ste_init(chunk)) {
263 		mlx5dr_err(pool->dmn,
264 			   "Failed to init ste arrays (order: %d)\n",
265 			   chunk_size);
266 		goto out_free_chunk;
267 	}
268 
269 	chunk->buddy_mem = buddy_mem_pool;
270 	INIT_LIST_HEAD(&chunk->chunk_list);
271 
272 	/* chunk now is part of the used_list */
273 	list_add_tail(&chunk->chunk_list, &buddy_mem_pool->used_list);
274 
275 	return chunk;
276 
277 out_free_chunk:
278 	kvfree(chunk);
279 	return NULL;
280 }
281 
282 static bool dr_icm_pool_is_sync_required(struct mlx5dr_icm_pool *pool)
283 {
284 	u64 allow_hot_size, all_hot_mem = 0;
285 	struct mlx5dr_icm_buddy_mem *buddy;
286 
287 	list_for_each_entry(buddy, &pool->buddy_mem_list, list_node) {
288 		allow_hot_size =
289 			mlx5dr_icm_pool_chunk_size_to_byte((buddy->max_order - 2),
290 							   pool->icm_type);
291 		all_hot_mem += buddy->hot_memory_size;
292 
293 		if (buddy->hot_memory_size > allow_hot_size ||
294 		    all_hot_mem > DR_ICM_SYNC_THRESHOLD)
295 			return true;
296 	}
297 
298 	return false;
299 }
300 
301 static int dr_icm_pool_sync_all_buddy_pools(struct mlx5dr_icm_pool *pool)
302 {
303 	struct mlx5dr_icm_buddy_mem *buddy, *tmp_buddy;
304 	int err;
305 
306 	err = mlx5dr_cmd_sync_steering(pool->dmn->mdev);
307 	if (err) {
308 		mlx5dr_err(pool->dmn, "Failed to sync to HW (err: %d)\n", err);
309 		return err;
310 	}
311 
312 	list_for_each_entry_safe(buddy, tmp_buddy, &pool->buddy_mem_list, list_node) {
313 		struct mlx5dr_icm_chunk *chunk, *tmp_chunk;
314 
315 		list_for_each_entry_safe(chunk, tmp_chunk, &buddy->hot_list, chunk_list) {
316 			mlx5dr_buddy_free_mem(buddy, chunk->seg,
317 					      ilog2(chunk->num_of_entries));
318 			buddy->hot_memory_size -= chunk->byte_size;
319 			dr_icm_chunk_destroy(chunk);
320 		}
321 	}
322 
323 	return 0;
324 }
325 
326 static int dr_icm_handle_buddies_get_mem(struct mlx5dr_icm_pool *pool,
327 					 enum mlx5dr_icm_chunk_size chunk_size,
328 					 struct mlx5dr_icm_buddy_mem **buddy,
329 					 unsigned int *seg)
330 {
331 	struct mlx5dr_icm_buddy_mem *buddy_mem_pool;
332 	bool new_mem = false;
333 	int err;
334 
335 	/* Check if we have chunks that are waiting for sync-ste */
336 	if (dr_icm_pool_is_sync_required(pool))
337 		dr_icm_pool_sync_all_buddy_pools(pool);
338 
339 alloc_buddy_mem:
340 	/* find the next free place from the buddy list */
341 	list_for_each_entry(buddy_mem_pool, &pool->buddy_mem_list, list_node) {
342 		err = mlx5dr_buddy_alloc_mem(buddy_mem_pool,
343 					     chunk_size, seg);
344 		if (!err)
345 			goto found;
346 
347 		if (WARN_ON(new_mem)) {
348 			/* We have new memory pool, first in the list */
349 			mlx5dr_err(pool->dmn,
350 				   "No memory for order: %d\n",
351 				   chunk_size);
352 			goto out;
353 		}
354 	}
355 
356 	/* no more available allocators in that pool, create new */
357 	err = dr_icm_buddy_create(pool);
358 	if (err) {
359 		mlx5dr_err(pool->dmn,
360 			   "Failed creating buddy for order %d\n",
361 			   chunk_size);
362 		goto out;
363 	}
364 
365 	/* mark we have new memory, first in list */
366 	new_mem = true;
367 	goto alloc_buddy_mem;
368 
369 found:
370 	*buddy = buddy_mem_pool;
371 out:
372 	return err;
373 }
374 
375 /* Allocate an ICM chunk, each chunk holds a piece of ICM memory and
376  * also memory used for HW STE management for optimizations.
377  */
378 struct mlx5dr_icm_chunk *
379 mlx5dr_icm_alloc_chunk(struct mlx5dr_icm_pool *pool,
380 		       enum mlx5dr_icm_chunk_size chunk_size)
381 {
382 	struct mlx5dr_icm_chunk *chunk = NULL;
383 	struct mlx5dr_icm_buddy_mem *buddy;
384 	unsigned int seg;
385 	int ret;
386 
387 	if (chunk_size > pool->max_log_chunk_sz)
388 		return NULL;
389 
390 	mutex_lock(&pool->mutex);
391 	/* find mem, get back the relevant buddy pool and seg in that mem */
392 	ret = dr_icm_handle_buddies_get_mem(pool, chunk_size, &buddy, &seg);
393 	if (ret)
394 		goto out;
395 
396 	chunk = dr_icm_chunk_create(pool, chunk_size, buddy, seg);
397 	if (!chunk)
398 		goto out_err;
399 
400 	goto out;
401 
402 out_err:
403 	mlx5dr_buddy_free_mem(buddy, seg, chunk_size);
404 out:
405 	mutex_unlock(&pool->mutex);
406 	return chunk;
407 }
408 
409 void mlx5dr_icm_free_chunk(struct mlx5dr_icm_chunk *chunk)
410 {
411 	struct mlx5dr_icm_buddy_mem *buddy = chunk->buddy_mem;
412 
413 	/* move the memory to the waiting list AKA "hot" */
414 	mutex_lock(&buddy->pool->mutex);
415 	list_move_tail(&chunk->chunk_list, &buddy->hot_list);
416 	buddy->hot_memory_size += chunk->byte_size;
417 	mutex_unlock(&buddy->pool->mutex);
418 }
419 
420 struct mlx5dr_icm_pool *mlx5dr_icm_pool_create(struct mlx5dr_domain *dmn,
421 					       enum mlx5dr_icm_type icm_type)
422 {
423 	enum mlx5dr_icm_chunk_size max_log_chunk_sz;
424 	struct mlx5dr_icm_pool *pool;
425 
426 	if (icm_type == DR_ICM_TYPE_STE)
427 		max_log_chunk_sz = dmn->info.max_log_sw_icm_sz;
428 	else
429 		max_log_chunk_sz = dmn->info.max_log_action_icm_sz;
430 
431 	pool = kvzalloc(sizeof(*pool), GFP_KERNEL);
432 	if (!pool)
433 		return NULL;
434 
435 	pool->dmn = dmn;
436 	pool->icm_type = icm_type;
437 	pool->max_log_chunk_sz = max_log_chunk_sz;
438 
439 	INIT_LIST_HEAD(&pool->buddy_mem_list);
440 
441 	mutex_init(&pool->mutex);
442 
443 	return pool;
444 }
445 
446 void mlx5dr_icm_pool_destroy(struct mlx5dr_icm_pool *pool)
447 {
448 	struct mlx5dr_icm_buddy_mem *buddy, *tmp_buddy;
449 
450 	list_for_each_entry_safe(buddy, tmp_buddy, &pool->buddy_mem_list, list_node)
451 		dr_icm_buddy_destroy(buddy);
452 
453 	mutex_destroy(&pool->mutex);
454 	kvfree(pool);
455 }
456