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