1 // SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
2 /* Copyright (c) 2018 Mellanox Technologies */
3 
4 #include <linux/mlx5/vport.h>
5 #include "lib/devcom.h"
6 #include "mlx5_core.h"
7 
8 static LIST_HEAD(devcom_list);
9 
10 #define devcom_for_each_component(priv, comp, iter) \
11 	for (iter = 0; \
12 	     comp = &(priv)->components[iter], iter < MLX5_DEVCOM_NUM_COMPONENTS; \
13 	     iter++)
14 
15 struct mlx5_devcom_component {
16 	struct {
17 		void __rcu *data;
18 	} device[MLX5_DEVCOM_PORTS_SUPPORTED];
19 
20 	mlx5_devcom_event_handler_t handler;
21 	struct rw_semaphore sem;
22 	bool paired;
23 };
24 
25 struct mlx5_devcom_list {
26 	struct list_head list;
27 
28 	struct mlx5_devcom_component components[MLX5_DEVCOM_NUM_COMPONENTS];
29 	struct mlx5_core_dev *devs[MLX5_DEVCOM_PORTS_SUPPORTED];
30 };
31 
32 struct mlx5_devcom {
33 	struct mlx5_devcom_list *priv;
34 	int idx;
35 };
36 
37 static struct mlx5_devcom_list *mlx5_devcom_list_alloc(void)
38 {
39 	struct mlx5_devcom_component *comp;
40 	struct mlx5_devcom_list *priv;
41 	int i;
42 
43 	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
44 	if (!priv)
45 		return NULL;
46 
47 	devcom_for_each_component(priv, comp, i)
48 		init_rwsem(&comp->sem);
49 
50 	return priv;
51 }
52 
53 static struct mlx5_devcom *mlx5_devcom_alloc(struct mlx5_devcom_list *priv,
54 					     u8 idx)
55 {
56 	struct mlx5_devcom *devcom;
57 
58 	devcom = kzalloc(sizeof(*devcom), GFP_KERNEL);
59 	if (!devcom)
60 		return NULL;
61 
62 	devcom->priv = priv;
63 	devcom->idx = idx;
64 	return devcom;
65 }
66 
67 /* Must be called with intf_mutex held */
68 struct mlx5_devcom *mlx5_devcom_register_device(struct mlx5_core_dev *dev)
69 {
70 	struct mlx5_devcom_list *priv = NULL, *iter;
71 	struct mlx5_devcom *devcom = NULL;
72 	bool new_priv = false;
73 	u64 sguid0, sguid1;
74 	int idx, i;
75 
76 	if (!mlx5_core_is_pf(dev))
77 		return NULL;
78 	if (MLX5_CAP_GEN(dev, num_lag_ports) != MLX5_DEVCOM_PORTS_SUPPORTED)
79 		return NULL;
80 
81 	mlx5_dev_list_lock();
82 	sguid0 = mlx5_query_nic_system_image_guid(dev);
83 	list_for_each_entry(iter, &devcom_list, list) {
84 		struct mlx5_core_dev *tmp_dev = NULL;
85 
86 		idx = -1;
87 		for (i = 0; i < MLX5_DEVCOM_PORTS_SUPPORTED; i++) {
88 			if (iter->devs[i])
89 				tmp_dev = iter->devs[i];
90 			else
91 				idx = i;
92 		}
93 
94 		if (idx == -1)
95 			continue;
96 
97 		sguid1 = mlx5_query_nic_system_image_guid(tmp_dev);
98 		if (sguid0 != sguid1)
99 			continue;
100 
101 		priv = iter;
102 		break;
103 	}
104 
105 	if (!priv) {
106 		priv = mlx5_devcom_list_alloc();
107 		if (!priv) {
108 			devcom = ERR_PTR(-ENOMEM);
109 			goto out;
110 		}
111 
112 		idx = 0;
113 		new_priv = true;
114 	}
115 
116 	priv->devs[idx] = dev;
117 	devcom = mlx5_devcom_alloc(priv, idx);
118 	if (!devcom) {
119 		if (new_priv)
120 			kfree(priv);
121 		devcom = ERR_PTR(-ENOMEM);
122 		goto out;
123 	}
124 
125 	if (new_priv)
126 		list_add(&priv->list, &devcom_list);
127 out:
128 	mlx5_dev_list_unlock();
129 	return devcom;
130 }
131 
132 /* Must be called with intf_mutex held */
133 void mlx5_devcom_unregister_device(struct mlx5_devcom *devcom)
134 {
135 	struct mlx5_devcom_list *priv;
136 	int i;
137 
138 	if (IS_ERR_OR_NULL(devcom))
139 		return;
140 
141 	mlx5_dev_list_lock();
142 	priv = devcom->priv;
143 	priv->devs[devcom->idx] = NULL;
144 
145 	kfree(devcom);
146 
147 	for (i = 0; i < MLX5_DEVCOM_PORTS_SUPPORTED; i++)
148 		if (priv->devs[i])
149 			break;
150 
151 	if (i != MLX5_DEVCOM_PORTS_SUPPORTED)
152 		goto out;
153 
154 	list_del(&priv->list);
155 	kfree(priv);
156 out:
157 	mlx5_dev_list_unlock();
158 }
159 
160 void mlx5_devcom_register_component(struct mlx5_devcom *devcom,
161 				    enum mlx5_devcom_components id,
162 				    mlx5_devcom_event_handler_t handler,
163 				    void *data)
164 {
165 	struct mlx5_devcom_component *comp;
166 
167 	if (IS_ERR_OR_NULL(devcom))
168 		return;
169 
170 	WARN_ON(!data);
171 
172 	comp = &devcom->priv->components[id];
173 	down_write(&comp->sem);
174 	comp->handler = handler;
175 	rcu_assign_pointer(comp->device[devcom->idx].data, data);
176 	up_write(&comp->sem);
177 }
178 
179 void mlx5_devcom_unregister_component(struct mlx5_devcom *devcom,
180 				      enum mlx5_devcom_components id)
181 {
182 	struct mlx5_devcom_component *comp;
183 
184 	if (IS_ERR_OR_NULL(devcom))
185 		return;
186 
187 	comp = &devcom->priv->components[id];
188 	down_write(&comp->sem);
189 	RCU_INIT_POINTER(comp->device[devcom->idx].data, NULL);
190 	up_write(&comp->sem);
191 	synchronize_rcu();
192 }
193 
194 int mlx5_devcom_send_event(struct mlx5_devcom *devcom,
195 			   enum mlx5_devcom_components id,
196 			   int event,
197 			   void *event_data)
198 {
199 	struct mlx5_devcom_component *comp;
200 	int err = -ENODEV, i;
201 
202 	if (IS_ERR_OR_NULL(devcom))
203 		return err;
204 
205 	comp = &devcom->priv->components[id];
206 	down_write(&comp->sem);
207 	for (i = 0; i < MLX5_DEVCOM_PORTS_SUPPORTED; i++) {
208 		void *data = rcu_dereference_protected(comp->device[i].data,
209 						       lockdep_is_held(&comp->sem));
210 
211 		if (i != devcom->idx && data) {
212 			err = comp->handler(event, data, event_data);
213 			break;
214 		}
215 	}
216 
217 	up_write(&comp->sem);
218 	return err;
219 }
220 
221 void mlx5_devcom_set_paired(struct mlx5_devcom *devcom,
222 			    enum mlx5_devcom_components id,
223 			    bool paired)
224 {
225 	struct mlx5_devcom_component *comp;
226 
227 	comp = &devcom->priv->components[id];
228 	WARN_ON(!rwsem_is_locked(&comp->sem));
229 
230 	WRITE_ONCE(comp->paired, paired);
231 }
232 
233 bool mlx5_devcom_is_paired(struct mlx5_devcom *devcom,
234 			   enum mlx5_devcom_components id)
235 {
236 	if (IS_ERR_OR_NULL(devcom))
237 		return false;
238 
239 	return READ_ONCE(devcom->priv->components[id].paired);
240 }
241 
242 void *mlx5_devcom_get_peer_data(struct mlx5_devcom *devcom,
243 				enum mlx5_devcom_components id)
244 {
245 	struct mlx5_devcom_component *comp;
246 	int i;
247 
248 	if (IS_ERR_OR_NULL(devcom))
249 		return NULL;
250 
251 	comp = &devcom->priv->components[id];
252 	down_read(&comp->sem);
253 	if (!READ_ONCE(comp->paired)) {
254 		up_read(&comp->sem);
255 		return NULL;
256 	}
257 
258 	for (i = 0; i < MLX5_DEVCOM_PORTS_SUPPORTED; i++)
259 		if (i != devcom->idx)
260 			break;
261 
262 	return rcu_dereference_protected(comp->device[i].data, lockdep_is_held(&comp->sem));
263 }
264 
265 void *mlx5_devcom_get_peer_data_rcu(struct mlx5_devcom *devcom, enum mlx5_devcom_components id)
266 {
267 	struct mlx5_devcom_component *comp;
268 	int i;
269 
270 	if (IS_ERR_OR_NULL(devcom))
271 		return NULL;
272 
273 	for (i = 0; i < MLX5_DEVCOM_PORTS_SUPPORTED; i++)
274 		if (i != devcom->idx)
275 			break;
276 
277 	comp = &devcom->priv->components[id];
278 	/* This can change concurrently, however 'data' pointer will remain
279 	 * valid for the duration of RCU read section.
280 	 */
281 	if (!READ_ONCE(comp->paired))
282 		return NULL;
283 
284 	return rcu_dereference(comp->device[i].data);
285 }
286 
287 void mlx5_devcom_release_peer_data(struct mlx5_devcom *devcom,
288 				   enum mlx5_devcom_components id)
289 {
290 	struct mlx5_devcom_component *comp = &devcom->priv->components[id];
291 
292 	up_read(&comp->sem);
293 }
294