1 // SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
2 // Copyright (c) 2019 Mellanox Technologies
3 
4 #include <linux/mlx5/driver.h>
5 #include <linux/mlx5/device.h>
6 
7 #include "mlx5_core.h"
8 #include "lib/mlx5.h"
9 
10 struct mlx5_dm {
11 	/* protect access to icm bitmask */
12 	spinlock_t lock;
13 	unsigned long *steering_sw_icm_alloc_blocks;
14 	unsigned long *header_modify_sw_icm_alloc_blocks;
15 };
16 
17 struct mlx5_dm *mlx5_dm_create(struct mlx5_core_dev *dev)
18 {
19 	u64 header_modify_icm_blocks = 0;
20 	u64 steering_icm_blocks = 0;
21 	struct mlx5_dm *dm;
22 
23 	if (!(MLX5_CAP_GEN_64(dev, general_obj_types) & MLX5_GENERAL_OBJ_TYPES_CAP_SW_ICM))
24 		return NULL;
25 
26 	dm = kzalloc(sizeof(*dm), GFP_KERNEL);
27 	if (!dm)
28 		return ERR_PTR(-ENOMEM);
29 
30 	spin_lock_init(&dm->lock);
31 
32 	if (MLX5_CAP64_DEV_MEM(dev, steering_sw_icm_start_address)) {
33 		steering_icm_blocks =
34 			BIT(MLX5_CAP_DEV_MEM(dev, log_steering_sw_icm_size) -
35 			    MLX5_LOG_SW_ICM_BLOCK_SIZE(dev));
36 
37 		dm->steering_sw_icm_alloc_blocks =
38 			kcalloc(BITS_TO_LONGS(steering_icm_blocks),
39 				sizeof(unsigned long), GFP_KERNEL);
40 		if (!dm->steering_sw_icm_alloc_blocks)
41 			goto err_steering;
42 	}
43 
44 	if (MLX5_CAP64_DEV_MEM(dev, header_modify_sw_icm_start_address)) {
45 		header_modify_icm_blocks =
46 			BIT(MLX5_CAP_DEV_MEM(dev, log_header_modify_sw_icm_size) -
47 			    MLX5_LOG_SW_ICM_BLOCK_SIZE(dev));
48 
49 		dm->header_modify_sw_icm_alloc_blocks =
50 			kcalloc(BITS_TO_LONGS(header_modify_icm_blocks),
51 				sizeof(unsigned long), GFP_KERNEL);
52 		if (!dm->header_modify_sw_icm_alloc_blocks)
53 			goto err_modify_hdr;
54 	}
55 
56 	return dm;
57 
58 err_modify_hdr:
59 	kfree(dm->steering_sw_icm_alloc_blocks);
60 
61 err_steering:
62 	kfree(dm);
63 
64 	return ERR_PTR(-ENOMEM);
65 }
66 
67 void mlx5_dm_cleanup(struct mlx5_core_dev *dev)
68 {
69 	struct mlx5_dm *dm = dev->dm;
70 
71 	if (!dev->dm)
72 		return;
73 
74 	if (dm->steering_sw_icm_alloc_blocks) {
75 		WARN_ON(!bitmap_empty(dm->steering_sw_icm_alloc_blocks,
76 				      BIT(MLX5_CAP_DEV_MEM(dev, log_steering_sw_icm_size) -
77 					  MLX5_LOG_SW_ICM_BLOCK_SIZE(dev))));
78 		kfree(dm->steering_sw_icm_alloc_blocks);
79 	}
80 
81 	if (dm->header_modify_sw_icm_alloc_blocks) {
82 		WARN_ON(!bitmap_empty(dm->header_modify_sw_icm_alloc_blocks,
83 				      BIT(MLX5_CAP_DEV_MEM(dev,
84 							   log_header_modify_sw_icm_size) -
85 				      MLX5_LOG_SW_ICM_BLOCK_SIZE(dev))));
86 		kfree(dm->header_modify_sw_icm_alloc_blocks);
87 	}
88 
89 	kfree(dm);
90 }
91 
92 int mlx5_dm_sw_icm_alloc(struct mlx5_core_dev *dev, enum mlx5_sw_icm_type type,
93 			 u64 length, u32 log_alignment, u16 uid,
94 			 phys_addr_t *addr, u32 *obj_id)
95 {
96 	u32 num_blocks = DIV_ROUND_UP_ULL(length, MLX5_SW_ICM_BLOCK_SIZE(dev));
97 	u32 out[MLX5_ST_SZ_DW(general_obj_out_cmd_hdr)] = {};
98 	u32 in[MLX5_ST_SZ_DW(create_sw_icm_in)] = {};
99 	struct mlx5_dm *dm = dev->dm;
100 	unsigned long *block_map;
101 	u64 icm_start_addr;
102 	u32 log_icm_size;
103 	u64 align_mask;
104 	u32 max_blocks;
105 	u64 block_idx;
106 	void *sw_icm;
107 	int ret;
108 
109 	if (!dev->dm)
110 		return -EOPNOTSUPP;
111 
112 	if (!length || (length & (length - 1)) ||
113 	    length & (MLX5_SW_ICM_BLOCK_SIZE(dev) - 1))
114 		return -EINVAL;
115 
116 	MLX5_SET(general_obj_in_cmd_hdr, in, opcode,
117 		 MLX5_CMD_OP_CREATE_GENERAL_OBJECT);
118 	MLX5_SET(general_obj_in_cmd_hdr, in, obj_type, MLX5_OBJ_TYPE_SW_ICM);
119 	MLX5_SET(general_obj_in_cmd_hdr, in, uid, uid);
120 
121 	switch (type) {
122 	case MLX5_SW_ICM_TYPE_STEERING:
123 		icm_start_addr = MLX5_CAP64_DEV_MEM(dev, steering_sw_icm_start_address);
124 		log_icm_size = MLX5_CAP_DEV_MEM(dev, log_steering_sw_icm_size);
125 		block_map = dm->steering_sw_icm_alloc_blocks;
126 		break;
127 	case MLX5_SW_ICM_TYPE_HEADER_MODIFY:
128 		icm_start_addr = MLX5_CAP64_DEV_MEM(dev, header_modify_sw_icm_start_address);
129 		log_icm_size = MLX5_CAP_DEV_MEM(dev,
130 						log_header_modify_sw_icm_size);
131 		block_map = dm->header_modify_sw_icm_alloc_blocks;
132 		break;
133 	default:
134 		return -EINVAL;
135 	}
136 
137 	if (!block_map)
138 		return -EOPNOTSUPP;
139 
140 	max_blocks = BIT(log_icm_size - MLX5_LOG_SW_ICM_BLOCK_SIZE(dev));
141 
142 	if (log_alignment < MLX5_LOG_SW_ICM_BLOCK_SIZE(dev))
143 		log_alignment = MLX5_LOG_SW_ICM_BLOCK_SIZE(dev);
144 	align_mask = BIT(log_alignment - MLX5_LOG_SW_ICM_BLOCK_SIZE(dev)) - 1;
145 
146 	spin_lock(&dm->lock);
147 	block_idx = bitmap_find_next_zero_area(block_map, max_blocks, 0,
148 					       num_blocks, align_mask);
149 
150 	if (block_idx < max_blocks)
151 		bitmap_set(block_map,
152 			   block_idx, num_blocks);
153 
154 	spin_unlock(&dm->lock);
155 
156 	if (block_idx >= max_blocks)
157 		return -ENOMEM;
158 
159 	sw_icm = MLX5_ADDR_OF(create_sw_icm_in, in, sw_icm);
160 	icm_start_addr += block_idx << MLX5_LOG_SW_ICM_BLOCK_SIZE(dev);
161 	MLX5_SET64(sw_icm, sw_icm, sw_icm_start_addr,
162 		   icm_start_addr);
163 	MLX5_SET(sw_icm, sw_icm, log_sw_icm_size, ilog2(length));
164 
165 	ret = mlx5_cmd_exec(dev, in, sizeof(in), out, sizeof(out));
166 	if (ret) {
167 		spin_lock(&dm->lock);
168 		bitmap_clear(block_map,
169 			     block_idx, num_blocks);
170 		spin_unlock(&dm->lock);
171 
172 		return ret;
173 	}
174 
175 	*addr = icm_start_addr;
176 	*obj_id = MLX5_GET(general_obj_out_cmd_hdr, out, obj_id);
177 
178 	return 0;
179 }
180 EXPORT_SYMBOL_GPL(mlx5_dm_sw_icm_alloc);
181 
182 int mlx5_dm_sw_icm_dealloc(struct mlx5_core_dev *dev, enum mlx5_sw_icm_type type,
183 			   u64 length, u16 uid, phys_addr_t addr, u32 obj_id)
184 {
185 	u32 num_blocks = DIV_ROUND_UP_ULL(length, MLX5_SW_ICM_BLOCK_SIZE(dev));
186 	u32 out[MLX5_ST_SZ_DW(general_obj_out_cmd_hdr)] = {};
187 	u32 in[MLX5_ST_SZ_DW(general_obj_in_cmd_hdr)] = {};
188 	struct mlx5_dm *dm = dev->dm;
189 	unsigned long *block_map;
190 	u64 icm_start_addr;
191 	u64 start_idx;
192 	int err;
193 
194 	if (!dev->dm)
195 		return -EOPNOTSUPP;
196 
197 	switch (type) {
198 	case MLX5_SW_ICM_TYPE_STEERING:
199 		icm_start_addr = MLX5_CAP64_DEV_MEM(dev, steering_sw_icm_start_address);
200 		block_map = dm->steering_sw_icm_alloc_blocks;
201 		break;
202 	case MLX5_SW_ICM_TYPE_HEADER_MODIFY:
203 		icm_start_addr = MLX5_CAP64_DEV_MEM(dev, header_modify_sw_icm_start_address);
204 		block_map = dm->header_modify_sw_icm_alloc_blocks;
205 		break;
206 	default:
207 		return -EINVAL;
208 	}
209 
210 	MLX5_SET(general_obj_in_cmd_hdr, in, opcode,
211 		 MLX5_CMD_OP_DESTROY_GENERAL_OBJECT);
212 	MLX5_SET(general_obj_in_cmd_hdr, in, obj_type, MLX5_OBJ_TYPE_SW_ICM);
213 	MLX5_SET(general_obj_in_cmd_hdr, in, obj_id, obj_id);
214 	MLX5_SET(general_obj_in_cmd_hdr, in, uid, uid);
215 
216 	err =  mlx5_cmd_exec(dev, in, sizeof(in), out, sizeof(out));
217 	if (err)
218 		return err;
219 
220 	start_idx = (addr - icm_start_addr) >> MLX5_LOG_SW_ICM_BLOCK_SIZE(dev);
221 	spin_lock(&dm->lock);
222 	bitmap_clear(block_map,
223 		     start_idx, num_blocks);
224 	spin_unlock(&dm->lock);
225 
226 	return 0;
227 }
228 EXPORT_SYMBOL_GPL(mlx5_dm_sw_icm_dealloc);
229