xref: /linux/drivers/misc/issei/host_client.c (revision 93e4b3076b5f2d853462b9777d083c77fc0b7b23)
1 // SPDX-License-Identifier: GPL-2.0
2 /* Copyright (C) 2023-2026 Intel Corporation */
3 #include <linux/cleanup.h>
4 #include <linux/dev_printk.h>
5 #include <linux/err.h>
6 #include <linux/fs.h>
7 #include <linux/kobject.h>
8 #include <linux/list.h>
9 #include <linux/overflow.h>
10 #include <linux/slab.h>
11 #include <linux/types.h>
12 #include <linux/wait.h>
13 #include <linux/uuid.h>
14 
15 #include "fw_client.h"
16 #include "host_client.h"
17 #include "issei_dev.h"
18 
19 static inline u8 __issei_cl_fw_id(const struct issei_host_client *cl)
20 {
21 	return cl->fw_cl ? cl->fw_cl->id : 0;
22 }
23 
24 #define ISSEI_CL_FMT "cl:host=%02d fw=%02d "
25 
26 #define cl_dbg(_dev_, _cl_, format, arg...) do { \
27 	struct issei_host_client *_l_cl_ = _cl_; \
28 	dev_dbg(&(_dev_)->dev, ISSEI_CL_FMT format, _l_cl_->id, \
29 		__issei_cl_fw_id(_l_cl_), ##arg); \
30 } while (0)
31 
32 #define cl_warn(_dev_, _cl_, format, arg...) do { \
33 	struct issei_host_client *_l_cl_ = _cl_; \
34 	dev_warn(&(_dev_)->dev, ISSEI_CL_FMT format, _l_cl_->id, \
35 		__issei_cl_fw_id(_l_cl_), ##arg); \
36 } while (0)
37 
38 #define cl_err(_dev_, _cl_, format, arg...) do { \
39 	struct issei_host_client *_l_cl_ = _cl_; \
40 	dev_err(&(_dev_)->dev, ISSEI_CL_FMT format, _l_cl_->id, \
41 		__issei_cl_fw_id(_l_cl_), ##arg); \
42 } while (0)
43 
44 static void __issei_cl_clean_wbuf(struct issei_write_buf *wbuf)
45 {
46 	list_del(&wbuf->list);
47 	kfree(wbuf->data);
48 	kfree(wbuf);
49 }
50 
51 static void __issei_cl_release_rbuf(struct issei_host_client *cl)
52 {
53 	cl->read_data = NULL;
54 	cl->read_data_size = 0;
55 }
56 
57 static void __issei_cl_clean_rbuf(struct issei_host_client *cl)
58 {
59 	kfree(cl->read_data);
60 	__issei_cl_release_rbuf(cl);
61 }
62 
63 static void __issei_cl_clean_all_wbuf(struct issei_device *idev, struct issei_host_client *cl)
64 {
65 	struct issei_write_buf *wbuf, *next;
66 
67 	if (!cl->write_in_progress)
68 		return;
69 	list_for_each_entry_safe(wbuf, next, &idev->write_queue, list) {
70 		if (wbuf->cl == cl) {
71 			__issei_cl_clean_wbuf(wbuf);
72 			break;
73 		}
74 	}
75 	cl->write_in_progress = false;
76 	/* synchronized under host client mutex */
77 	if (waitqueue_active(&cl->write_wait))
78 		wake_up_interruptible(&cl->write_wait);
79 }
80 
81 static struct issei_host_client *__issei_cl_by_id(struct issei_device *idev, u16 id)
82 {
83 	struct issei_host_client *cl;
84 
85 	list_for_each_entry(cl, &idev->host_client_list, list) {
86 		if (cl->id == id)
87 			return cl;
88 	}
89 	return NULL;
90 }
91 
92 static void __issei_cl_disconnect(struct issei_device *idev, struct issei_host_client *cl)
93 {
94 	if (cl->state == ISSEI_HOST_CL_STATE_DISCONNECTED)
95 		return;
96 
97 	__issei_cl_clean_all_wbuf(idev, cl);
98 
99 	if (!WARN_ON(!cl->fw_cl)) {
100 		issei_fw_cl_disconnect(cl->fw_cl);
101 		cl->fw_cl = NULL;
102 	}
103 	cl->state = ISSEI_HOST_CL_STATE_DISCONNECTED;
104 
105 	if (cl->read_data)
106 		__issei_cl_clean_rbuf(cl);
107 	/* synchronized under host client mutex */
108 	if (waitqueue_active(&cl->read_wait))
109 		wake_up_interruptible(&cl->read_wait);
110 	cl_dbg(idev, cl, "Disconnected\n");
111 }
112 
113 static void __issei_cl_init(struct issei_host_client *cl, struct issei_device *idev,
114 			    u16 id, struct file *fp)
115 {
116 	INIT_LIST_HEAD(&cl->list);
117 	cl->idev = idev;
118 	cl->id = id;
119 	cl->state = ISSEI_HOST_CL_STATE_DISCONNECTED;
120 	cl->fp = fp;
121 	init_waitqueue_head(&cl->write_wait);
122 	init_waitqueue_head(&cl->read_wait);
123 	__issei_cl_release_rbuf(cl);
124 }
125 
126 /**
127  * issei_cl_create - create the host client
128  * @idev: issei device
129  * @fp: file pointer to associate with host client
130  *
131  * Return: client pointer on success, ERR_PTR on error
132  */
133 struct issei_host_client *issei_cl_create(struct issei_device *idev, struct file *fp)
134 {
135 	struct issei_host_client *cl;
136 	u16 id;
137 
138 	guard(mutex)(&idev->client_lock);
139 
140 	if (idev->host_client_count == ISSEI_HOST_CLIENTS_MAX) {
141 		dev_err(&idev->dev, "Maximum open clients %d is reached.\n",
142 			ISSEI_HOST_CLIENTS_MAX);
143 		return ERR_PTR(-EMFILE);
144 	}
145 
146 	do {
147 		if (check_add_overflow(idev->host_client_last_id, 1, &id)) /* overflow */
148 			id = 1;
149 		idev->host_client_last_id = id;
150 		/* Not an endless loop as we have less clients then id's */
151 	} while (__issei_cl_by_id(idev, id));
152 
153 	cl = kzalloc_obj(*cl);
154 	if (!cl)
155 		return ERR_PTR(-ENOMEM);
156 
157 	__issei_cl_init(cl, idev, id, fp);
158 	list_add_tail(&cl->list, &idev->host_client_list);
159 	idev->host_client_count++;
160 
161 	cl_dbg(idev, cl, "Created\n");
162 	return cl;
163 }
164 
165 /**
166  * issei_cl_remove - disconnect and free the host client
167  * @cl: host client
168  */
169 void issei_cl_remove(struct issei_host_client *cl)
170 {
171 	struct issei_device *idev;
172 
173 	/* don't shout on error exit path */
174 	if (!cl)
175 		return;
176 
177 	idev = cl->idev;
178 
179 	guard(mutex)(&idev->client_lock);
180 
181 	idev->host_client_count--;
182 	list_del(&cl->list);
183 
184 	__issei_cl_disconnect(idev, cl);
185 
186 	cl_dbg(idev, cl, "Removed\n");
187 	kfree(cl);
188 }
189 
190 /**
191  * issei_cl_connect - connect between FW and host client
192  * @cl: host client
193  * @uuid: FW client unique ID
194  * @mtu: memory for FW client max message size
195  * @ver: memory for FW client version
196  * @flags: memory for FW client flags
197  *
198  * Search for firmware client by UUID and connect it to provided
199  * host client, if not already connected to some client.
200  *
201  * Return: 0 on success, <0 on error
202  */
203 int issei_cl_connect(struct issei_host_client *cl, const uuid_t *uuid, u32 *mtu, u8 *ver,
204 		     u32 *flags)
205 {
206 	struct issei_device *idev = cl->idev;
207 	struct issei_fw_client *fw_cl;
208 	int ret;
209 
210 	guard(mutex)(&idev->client_lock);
211 
212 	if (cl->state == ISSEI_HOST_CL_STATE_CONNECTED) {
213 		cl_err(idev, cl, "Already connected\n");
214 		return -EISCONN;
215 	}
216 
217 	fw_cl = issei_fw_cl_find_by_uuid(idev, uuid);
218 	if (!fw_cl) {
219 		cl_dbg(idev, cl, "FW client %pUb not found\n", uuid);
220 		return -ENOTTY;
221 	}
222 
223 	ret = issei_fw_cl_connect(fw_cl, cl); /* calls kobject_get for fw_cl on success */
224 	kobject_put(&fw_cl->kobj);
225 	if (ret) {
226 		cl_err(idev, cl, "FW client is already connected ret = %d\n", ret);
227 		return ret;
228 	}
229 
230 	cl->fw_cl = fw_cl;
231 	cl->state = ISSEI_HOST_CL_STATE_CONNECTED;
232 
233 	*mtu = fw_cl->mtu;
234 	*ver = fw_cl->ver;
235 	*flags = fw_cl->flags;
236 	cl_dbg(idev, cl, "Connected\n");
237 	return 0;
238 }
239 
240 /**
241  * issei_cl_disconnect - disconnect between FW and host client
242  * @cl: host client
243  *
244  * Return: 0 on success, -ENOTCONN if not connected
245  */
246 int issei_cl_disconnect(struct issei_host_client *cl)
247 {
248 	struct issei_device *idev = cl->idev;
249 
250 	guard(mutex)(&idev->client_lock);
251 
252 	if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED)
253 		return -ENOTCONN;
254 	__issei_cl_disconnect(idev, cl);
255 	return 0;
256 }
257 
258 /**
259  * issei_cl_all_disconnect - disconnect all FW clients
260  * @idev: issei device
261  */
262 void issei_cl_all_disconnect(struct issei_device *idev)
263 {
264 	struct issei_host_client *cl;
265 
266 	guard(mutex)(&idev->client_lock);
267 
268 	list_for_each_entry(cl, &idev->host_client_list, list)
269 		__issei_cl_disconnect(idev, cl);
270 }
271 
272 /**
273  * issei_cl_write - enqueue write request
274  * @cl: host client
275  * @buf: buffer to write
276  * @buf_size: buffer size
277  *
278  * Add write request to the write queue and wakes working thread.
279  * This call takes ownership of buf memory, if succeeded.
280  *
281  * Return: size of data on success, <0 on error
282  */
283 ssize_t issei_cl_write(struct issei_host_client *cl, const u8 *buf, size_t buf_size)
284 {
285 	struct issei_device *idev = cl->idev;
286 	struct issei_write_buf *wbuf;
287 
288 	guard(mutex)(&idev->client_lock);
289 
290 	if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED)
291 		return -ENOTCONN;
292 
293 	if (cl->write_in_progress) {
294 		cl_dbg(idev, cl, "Another write is in progress\n");
295 		return -EAGAIN;
296 	}
297 
298 	if (buf_size > cl->fw_cl->mtu) {
299 		cl_err(idev, cl, "Write is too big %zu > %u\n", buf_size, cl->fw_cl->mtu);
300 		return -EFBIG;
301 	}
302 
303 	wbuf = kmalloc_obj(*wbuf);
304 	if (!wbuf)
305 		return -ENOMEM;
306 	wbuf->cl = cl;
307 	wbuf->data = buf;
308 	wbuf->data_size = buf_size;
309 	list_add_tail(&wbuf->list, &idev->write_queue);
310 	cl->write_in_progress = true;
311 	cl_dbg(idev, cl, "Write queued %zu bytes\n", buf_size);
312 
313 	issei_poke_process_thread(idev);
314 
315 	return buf_size;
316 }
317 
318 /**
319  * issei_cl_write_from_queue - writes first request from queue to firmware
320  * @idev: issei device
321  *
322  * Tries to write first request from the write queue to firmware.
323  * Releases buf memory, if succeeded.
324  *
325  * Return: 0 on success, <0 on error
326  */
327 int issei_cl_write_from_queue(struct issei_device *idev)
328 {
329 	struct issei_write_buf *wbuf;
330 	struct issei_dma_data data;
331 	struct issei_host_client *cl;
332 	int ret;
333 
334 	guard(mutex)(&idev->client_lock);
335 
336 	wbuf = list_first_entry_or_null(&idev->write_queue, struct issei_write_buf, list);
337 	if (!wbuf)
338 		return 0;
339 
340 	cl = wbuf->cl;
341 
342 	data.fw_id = cl->fw_cl->id;
343 	data.host_id = cl->id;
344 	data.flags = 0;
345 	data.status = 0;
346 	data.length = wbuf->data_size;
347 	data.buf = (void *)wbuf->data;
348 	ret = issei_dma_write(idev, &data);
349 	if (ret == -EBUSY)
350 		return 0;
351 	if (ret == -EIO)
352 		return ret;
353 	if (ret >= 0)
354 		idev->ops->irq_write_generate(idev);
355 	cl->write_in_progress = false;
356 	/* synchronized under host client mutex */
357 	if (waitqueue_active(&cl->write_wait))
358 		wake_up_interruptible(&cl->write_wait);
359 	cl_dbg(idev, cl, "Write %zu bytes\n", wbuf->data_size);
360 	__issei_cl_clean_wbuf(wbuf);
361 	return 0;
362 }
363 
364 static struct issei_host_client *__issei_cl_read_buf_check(struct issei_device *idev, u16 fw_id,
365 							   u16 host_id, size_t buf_size)
366 {
367 	struct issei_host_client *cl;
368 
369 	cl = __issei_cl_by_id(idev, host_id);
370 	if (!cl) {
371 		dev_dbg(&idev->dev, "No client %u\n", host_id);
372 		return ERR_PTR(-ENOTTY);
373 	}
374 
375 	if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED) {
376 		cl_dbg(idev, cl, "Not connected\n");
377 		return ERR_PTR(-ENODEV);
378 	}
379 	if (cl->fw_cl->id != fw_id) {
380 		cl_dbg(idev, cl, "Wrong firmware client %u ?= %u\n", cl->fw_cl->id, fw_id);
381 		return ERR_PTR(-ENODEV);
382 	}
383 
384 	if (buf_size > cl->fw_cl->mtu) {
385 		cl_err(idev, cl, "Read is too big %zu > %u\n", buf_size, cl->fw_cl->mtu);
386 		__issei_cl_disconnect(idev, cl);
387 		return NULL;
388 	}
389 
390 	if (cl->read_data) {
391 		cl_err(idev, cl, "Previous data was not read by user-space, disconnecting\n");
392 		__issei_cl_disconnect(idev, cl);
393 		return NULL;
394 	}
395 
396 	return cl;
397 }
398 
399 /**
400  * issei_cl_read_buf - process data from firmware
401  * @idev: issei device
402  * @fw_id: firmware client id
403  * @host_id: host client id
404  * @buf: buffer with data
405  * @buf_size: buffer size
406  *
407  * Puts data from firmware into provided host client storage.
408  * Free buffer or consume it.
409  *
410  * Return: 0 on success or recoverable error, <0 on unrecoverable error
411  */
412 int issei_cl_read_buf(struct issei_device *idev, u16 fw_id, u16 host_id, u8 *buf, size_t buf_size)
413 {
414 	struct issei_host_client *cl;
415 
416 	guard(mutex)(&idev->client_lock);
417 
418 	cl = __issei_cl_read_buf_check(idev, fw_id, host_id, buf_size);
419 	if (IS_ERR_OR_NULL(cl)) {
420 		kfree(buf);
421 		return PTR_ERR(cl);
422 	}
423 
424 	cl->read_data = buf;
425 	cl->read_data_size = buf_size;
426 
427 	/* synchronized under host client mutex */
428 	if (waitqueue_active(&cl->read_wait))
429 		wake_up_interruptible(&cl->read_wait);
430 	cl_dbg(idev, cl, "Read %zu bytes\n", buf_size);
431 
432 	return 0;
433 }
434 
435 /**
436  * issei_cl_read - read data from queue to provided buffer
437  * @cl: host client
438  * @buf: buffer to store data
439  * @buf_size: buffer size
440  *
441  * Tries to take data buffer from client and return it to caller.
442  * The caller receives ownership of the data buffer.
443  *
444  * Return: read data size on success, <0 on error
445  */
446 ssize_t issei_cl_read(struct issei_host_client *cl, u8 **buf, size_t buf_size)
447 {
448 	struct issei_device *idev = cl->idev;
449 	size_t read_data_size;
450 
451 	guard(mutex)(&idev->client_lock);
452 
453 	if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED)
454 		return -ENOTCONN;
455 
456 	if (!cl->read_data)
457 		return -ENOENT;
458 
459 	if (cl->read_data_size > buf_size) {
460 		cl_err(idev, cl, "Buffer is too small %zu > %zu\n",
461 		       cl->read_data_size, buf_size);
462 		return -EFBIG;
463 	}
464 
465 	*buf = cl->read_data;
466 	read_data_size = cl->read_data_size;
467 	cl_dbg(idev, cl, "Read by client %zu bytes\n", read_data_size);
468 	__issei_cl_release_rbuf(cl);
469 	return read_data_size;
470 }
471 
472 /**
473  * issei_cl_check_read - check if client has data to read
474  *
475  * @cl: host client
476  *
477  * Return: 1 - data available, 0 - no data, < 0 on error
478  */
479 int issei_cl_check_read(struct issei_host_client *cl)
480 {
481 	struct issei_device *idev = cl->idev;
482 
483 	guard(mutex)(&idev->client_lock);
484 
485 	if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED)
486 		return -ENOTCONN;
487 	if (!cl->read_data)
488 		return 0;
489 	return 1;
490 }
491 
492 /**
493  * issei_cl_check_write - check if client is ready to write
494  *
495  * @cl: host client
496  *
497  * Return: 1 - can not write, 0 - can write, < 0 on error
498  */
499 int issei_cl_check_write(struct issei_host_client *cl)
500 {
501 	struct issei_device *idev = cl->idev;
502 
503 	guard(mutex)(&idev->client_lock);
504 
505 	if (cl->state != ISSEI_HOST_CL_STATE_CONNECTED)
506 		return -ENOTCONN;
507 	if (cl->write_in_progress)
508 		return 1;
509 	return 0;
510 }
511 
512 void issei_cl_clean_all_wbuf(struct issei_host_client *cl)
513 {
514 	struct issei_device *idev = cl->idev;
515 
516 	guard(mutex)(&idev->client_lock);
517 
518 	__issei_cl_clean_all_wbuf(idev, cl);
519 }
520