linux-uk-proxy.c (16469B)
1 2 /* 3 * 4 * This license is set out in https://raw.githubusercontent.com/Broadcom-Network-Switching-Software/OpenBCM/master/Legal/LICENSE file. 5 * 6 * Copyright 2007-2019 Broadcom Inc. All rights reserved. 7 */ 8 9 #include <gmodule.h> /* Must be included first */ 10 11 #include <linux/list.h> 12 #include <linux-uk-proxy.h> 13 14 #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,12,0) 15 #include <linux/uaccess.h> 16 #endif 17 18 MODULE_AUTHOR("Broadcom Corporation"); 19 MODULE_DESCRIPTION("User/Kernel Mode Proxy"); 20 MODULE_LICENSE("Proprietary"); 21 22 23 #define LUK_MODULE_NAME "linux-uk-proxy" 24 #define LUK_SERVICE_QUEUE_MAX 8 25 26 /* Service data packet */ 27 typedef struct luk_proxy_data_s { 28 struct list_head list; 29 unsigned char data[LUK_MAX_DATA_SIZE]; 30 uint32_t len; 31 } luk_proxy_data_t; 32 33 typedef struct luk_proxy_list_s { 34 struct list_head list; 35 struct semaphore access_s; 36 struct semaphore count_s; 37 const char *service; 38 char name[32]; 39 } luk_proxy_list_t; 40 41 /* Service data queue */ 42 typedef struct luk_proxy_data_q_s { 43 luk_proxy_list_t data; 44 luk_proxy_list_t free; 45 } luk_proxy_data_q_t; 46 47 /* Service Entry */ 48 typedef struct luk_proxy_service_s { 49 struct list_head list; 50 51 char service[LUK_MAX_SERVICE_NAME]; 52 53 #define LUK_SERVICE_F_LOOPBACK 0x1 /* Service is in loopback mode */ 54 #define LUK_SERVICE_F_INACTIVE 0x2 /* Service has been deleted */ 55 #define LUK_SERVICE_F_SUSPENDED 0x4 /* Service has been suspended */ 56 57 int flags; 58 59 luk_proxy_data_q_t recv_q; 60 luk_proxy_data_q_t send_q; 61 62 } luk_proxy_service_t; 63 64 /* We have a list of services */ 65 static struct list_head _service_list; 66 67 #define DATA_ENTRY(l) list_entry(l, luk_proxy_data_t, list) 68 #define SERVICE_ENTRY(l) list_entry(l, luk_proxy_service_t, list) 69 70 /* 71 * Function: _find_service 72 * 73 * Purpose: 74 * Find proxy service in list of active proxy services. 75 * Parameters: 76 * service - proxy service name 77 * Returns: 78 * Proxy service if found, otherwise NULL 79 */ 80 static luk_proxy_service_t * 81 _find_service(const char *service) 82 { 83 struct list_head* list; 84 list_for_each(list, &_service_list) { 85 luk_proxy_service_t *s = SERVICE_ENTRY(list); 86 if (!strcmp(service, s->service) && !(s->flags & LUK_SERVICE_F_INACTIVE)) { 87 return s; 88 } 89 } 90 return NULL; 91 } 92 93 /* 94 * Function: _init_data_q 95 * 96 * Purpose: 97 * Initialize proxy service data queue. 98 * Parameters: 99 * q - data queue 100 * service - proxy service name associated with this queue 101 * q_name - name of data queue 102 * q_size - maximum number of packets in data queue 103 * Returns: 104 * 0 if success, otherwise -1 105 */ 106 static int 107 _init_data_q(luk_proxy_data_q_t *q, const char *service, const char *q_name, int q_size) 108 { 109 int i; 110 111 INIT_LIST_HEAD(&q->free.list); 112 sema_init(&q->free.access_s, 1); 113 sema_init(&q->free.count_s, q_size); 114 q->free.service = service; 115 sprintf(q->free.name, "%s.free", q_name); 116 117 INIT_LIST_HEAD(&q->data.list); 118 sema_init(&q->data.access_s, 1); 119 sema_init(&q->data.count_s, 0); 120 q->data.service = service; 121 sprintf(q->data.name, "%s.data", q_name); 122 123 for (i = 0; i < q_size; i++) { 124 luk_proxy_data_t *data = kmalloc(sizeof(*data), GFP_KERNEL); 125 126 if (!data) { 127 return -1; 128 } 129 list_add_tail(&data->list, &q->free.list); 130 } 131 return 0; 132 } 133 134 /* 135 * Function: _list_op 136 * 137 * Purpose: 138 * Move data in and out of proxy service queues. 139 * Parameters: 140 * source_list - source queue list (free or data) 141 * dest_list - destination queue list (free or data) 142 * data - buffer to copy data to/from 143 * len - length of data 144 * flags - data direction and context 145 * _LIST_OP_F_PUT: copy data from buffer to queue 146 * _LIST_OP_F_GET: copy data from from queue to buffer 147 * _LIST_OP_F_USER: buffer is in user space 148 * _LIST_OP_F_KERN: buffer is in kernel space 149 * Returns: 150 * 0 if success, otherwise -1 151 */ 152 #define _LIST_OP_F_PUT 0x1 153 #define _LIST_OP_F_GET 0x0 154 #define _LIST_OP_F_USER 0x2 155 #define _LIST_OP_F_KERN 0x0 156 157 static int 158 _list_op(luk_proxy_list_t *source_list, luk_proxy_list_t *dest_list, void *data, uint32_t *len, int flags) 159 { 160 luk_proxy_data_t *dp; 161 int err; 162 163 #if 1 164 #define LDBG(l, ptr) 165 #else 166 #define LDBG(l, str) gprintk("%d:%s:%s - %s\n", current->pid, l->service, l->name, str) 167 #endif 168 169 /* We need to take an entry from the source list */ 170 LDBG(source_list, "waiting for an entry"); 171 if ((err = down_interruptible(&source_list->count_s)) < 0) { 172 return err; 173 } 174 LDBG(source_list, "entry ready"); 175 176 /* We know there is at least one entry for us now. Wait to modify the list */ 177 LDBG(source_list, "locking..."); 178 if ((err = down_interruptible(&source_list->access_s)) < 0) { 179 up(&source_list->count_s); 180 return err; 181 } 182 183 LDBG(source_list, "locked - pulling off the entry..."); 184 185 /* Entry is now available from the source list, Remove it. */ 186 dp = DATA_ENTRY(source_list->list.next); 187 list_del(&dp->list); 188 up(&source_list->access_s); 189 LDBG(source_list, "unlocked"); 190 191 /* Molest it at will */ 192 193 if (flags & _LIST_OP_F_PUT) { 194 /* The given data should be pushed into this entry */ 195 if (flags & _LIST_OP_F_USER) { 196 /* The data is from user space */ 197 if (copy_from_user((void *)(unsigned long)dp->data, data, *len)) { 198 return -EFAULT; 199 } 200 } else { 201 /* The data is from the kernel */ 202 memcpy((void *)(unsigned long)dp->data, data, *len); 203 } 204 dp->len = *len; 205 } else { 206 /* Deliver the data in this entry */ 207 if (flags & _LIST_OP_F_USER) { 208 /* Data is going to user space */ 209 if (copy_to_user(data, (void *)(unsigned long)dp->data, dp->len)) { 210 return -EFAULT; 211 } 212 } else { 213 /* The data is going to the kernel */ 214 memcpy(data, (void *)(unsigned long)dp->data, dp->len); 215 } 216 *len = dp->len; 217 } 218 219 /* Now we want to add it to the dest list. Just need the access sem... */ 220 LDBG(dest_list, "locking..."); 221 if (down_interruptible(&dest_list->access_s)); 222 LDBG(dest_list, "locked - adding entry"); 223 list_add_tail(&dp->list, &dest_list->list); 224 LDBG(dest_list, "unlocked"); 225 up(&dest_list->access_s); 226 LDBG(dest_list, "incrementing count..."); 227 up(&dest_list->count_s); 228 return 0; 229 } 230 231 /* 232 * Function: _linux_uk_proxy_recv_from_user 233 * 234 * Purpose: 235 * Queue data packet from user space. 236 * Parameters: 237 * service - proxy service name 238 * data - data received 239 * len - length of data 240 * Returns: 241 * 0 if success, otherwise -1 242 * Notes: 243 * This function will block if the kernel receive queue is full. 244 */ 245 static int 246 _linux_uk_proxy_recv_from_user(const char *service, void *data, uint32_t len) 247 { 248 int rc; 249 luk_proxy_service_t *s; 250 char service0[LUK_MAX_SERVICE_NAME] = { 0 }; 251 char service1[LUK_MAX_SERVICE_NAME] = { 0 }; 252 253 strcpy(service0, service); 254 255 if (strchr(service, '|')) { 256 /* This data should be piped between the given services */ 257 *strchr(service0, '|') = 0; 258 strcpy(service1, strchr(service, '|') + 1); 259 } 260 261 /* Should this be piped to another service? */ 262 263 if (!(s = _find_service(service0))) { 264 return -1; 265 } 266 267 if (s->flags & LUK_SERVICE_F_LOOPBACK) { 268 strcpy(service1, service0); 269 } 270 271 /* We want to push this incoming data to this service's RX queue */ 272 rc = _list_op(&s->recv_q.free, &s->recv_q.data, data, &len, 273 _LIST_OP_F_PUT | _LIST_OP_F_USER); 274 if (rc == -ERESTARTSYS || rc == -EINTR) { 275 return -2; 276 } 277 278 if (service1[0]) { 279 /* This should be piped to the given service */ 280 unsigned char d[LUK_MAX_DATA_SIZE]; 281 uint32_t len = LUK_MAX_DATA_SIZE; 282 283 /* Get the packet from the incoming service */ 284 if (linux_uk_proxy_recv(service0, d, &len) < 0) { 285 return 0; 286 } 287 288 /* Try to pipe the data. If the service doesn't exist, it will just drop. */ 289 linux_uk_proxy_send(service1, d, len); 290 } 291 return 0; 292 } 293 294 /* 295 * Function: _linux_uk_proxy_send_to_user 296 * 297 * Purpose: 298 * Get data packet for user space. 299 * Parameters: 300 * service - proxy service name 301 * data - buffer to write data to 302 * len - length of data returned 303 * Returns: 304 * 0 if success, otherwise -1 305 * Notes: 306 * This function will block if no data is ready. 307 */ 308 static int 309 _linux_uk_proxy_send_to_user(const char *service, void *data, uint32_t *len) 310 { 311 luk_proxy_service_t *s; 312 int rc; 313 314 if (!(s = _find_service(service))) { 315 return -1; 316 } 317 318 /* We want to get data from this service's tx queue */ 319 rc = _list_op(&s->send_q.data, &s->send_q.free, data, len, 320 _LIST_OP_F_GET | _LIST_OP_F_USER); 321 if (rc == -ERESTARTSYS || rc == -EINTR) { 322 return -2; 323 } 324 return 0; 325 } 326 327 /* 328 * Function: _linux_uk_proxy_suspend 329 * 330 * Purpose: 331 * Suspend service and flush queues. 332 * Parameters: 333 * service - proxy service name 334 * Returns: 335 * 0 if success, otherwise -1 336 */ 337 static int 338 _linux_uk_proxy_suspend(const char *service) 339 { 340 luk_proxy_service_t *s; 341 342 if (!(s = _find_service(service))) { 343 return -1; 344 } 345 346 s->flags |= LUK_SERVICE_F_SUSPENDED; 347 348 return 0; 349 } 350 351 /* 352 * Function: _linux_uk_proxy_resume 353 * 354 * Purpose: 355 * Resume a suspended service. 356 * Parameters: 357 * service - proxy service name 358 * Returns: 359 * 0 if success, otherwise -1 360 */ 361 static int 362 _linux_uk_proxy_resume(const char *service) 363 { 364 luk_proxy_service_t *s; 365 366 if (!(s = _find_service(service))) { 367 return -1; 368 } 369 370 s->flags &= ~LUK_SERVICE_F_SUSPENDED; 371 372 return 0; 373 } 374 375 /* 376 * Generic module functions 377 */ 378 379 /* 380 * Function: _init 381 * 382 * Purpose: 383 * Module initialization. 384 * Parameters: 385 * None 386 * Returns: 387 * Always 0 388 */ 389 static int 390 _init(void) 391 { 392 INIT_LIST_HEAD(&_service_list); 393 return 0; 394 } 395 396 /* 397 * Function: _cleanup 398 * 399 * Purpose: 400 * Module cleanup function. 401 * Parameters: 402 * None 403 * Returns: 404 * Always 0 405 */ 406 static int 407 _cleanup(void) 408 { 409 struct list_head *list, *tmplist; 410 list_for_each_safe(list, tmplist, &_service_list) { 411 luk_proxy_service_t *s = SERVICE_ENTRY(list); 412 linux_uk_proxy_service_destroy(s->service); 413 list_del(list); 414 kfree(s); 415 } 416 return 0; 417 } 418 419 /* 420 * Function: _pprint 421 * 422 * Purpose: 423 * Print proc filesystem information. 424 * Parameters: 425 * None 426 * Returns: 427 * Always 0 428 */ 429 static int 430 _pprint(void) 431 { 432 int i = 0; 433 struct list_head *list; 434 435 pprintf("Broadcom Linux User/Kernel Mode Proxy\n"); 436 pprintf("\tCurrent Services:\n"); 437 438 /* Current Service List */ 439 list_for_each(list, &_service_list) { 440 luk_proxy_service_t *s = SERVICE_ENTRY(list); 441 pprintf("\t\t%s ", s->service); 442 pprintf(": %s%s%s\n", 443 (s->flags & LUK_SERVICE_F_INACTIVE) ? "INACTIVE" : "ACTIVE", 444 (s->flags & LUK_SERVICE_F_LOOPBACK) ? " LOOPBACK" : "", 445 (s->flags & LUK_SERVICE_F_SUSPENDED) ? " SUSPENDED" : ""); 446 i++; 447 } 448 449 if (i == 0) { 450 pprintf("None\n"); 451 } 452 453 return 0; 454 } 455 456 /* 457 * Function: _ioctl 458 * 459 * Purpose: 460 * Handle IOCTL commands from user mode. 461 * Parameters: 462 * cmd - IOCTL cmd 463 * arg - IOCTL parameters 464 * Returns: 465 * Always 0 466 */ 467 static int 468 _ioctl(unsigned int cmd, unsigned long arg) 469 { 470 luk_ioctl_t io; 471 472 if (copy_from_user(&io, (void*)arg, sizeof(io))) { 473 return -EFAULT; 474 } 475 476 switch(cmd) { 477 case LUK_VERSION: 478 io.rc = 1; 479 break; 480 case LUK_SERVICE_CREATE: 481 /* Client wants to create a service */ 482 io.rc = linux_uk_proxy_service_create(io.service, 483 io.args.create.q_size, 484 io.args.create.flags); 485 break; 486 case LUK_SERVICE_DESTROY: 487 /* Client wants to destroy a service */ 488 io.rc = linux_uk_proxy_service_destroy(io.service); 489 break; 490 case LUK_SERVICE_SEND: 491 /* Client is sending data to a service */ 492 io.rc = _linux_uk_proxy_recv_from_user(io.service, 493 (void *)(unsigned long)io.args.recv.data, 494 io.args.recv.len); 495 break; 496 case LUK_SERVICE_RECV: 497 /* Client wants data from a service */ 498 io.rc = _linux_uk_proxy_send_to_user(io.service, 499 (void *)(unsigned long)io.args.recv.data, 500 &io.args.recv.len); 501 break; 502 case LUK_SERVICE_SUSPEND: 503 /* Client wants to suspend a service */ 504 io.rc = _linux_uk_proxy_suspend(io.service); 505 break; 506 case LUK_SERVICE_RESUME: 507 /* Client wants to resume a service */ 508 io.rc = _linux_uk_proxy_resume(io.service); 509 break; 510 default: 511 GDBG("INVALID IOCTL"); 512 io.rc = -1; 513 break; 514 } 515 516 if (copy_to_user((void*)arg, &io, sizeof(io))) { 517 return -EFAULT; 518 } 519 520 return 0; 521 } 522 523 static gmodule_t _gmodule = { 524 name: LUK_MODULE_NAME, 525 major: LUK_DEVICE_MAJOR, 526 init: _init, 527 cleanup: _cleanup, 528 pprint: _pprint, 529 ioctl: _ioctl, 530 open: NULL, 531 close: NULL, 532 }; 533 534 535 gmodule_t * 536 gmodule_get(void) 537 { 538 return &_gmodule; 539 } 540 541 /* 542 * Exported functions 543 */ 544 545 /* 546 * Function: linux_uk_proxy_service_create 547 * 548 * Purpose: 549 * Create new proxy service 550 * Parameters: 551 * service - proxy service name 552 * q_size - maximum number of packets in I/O data queues 553 * Returns: 554 * 0 if success, otherwise -1 555 * Notes: 556 * If the service exists already, no error is reported. 557 */ 558 int 559 linux_uk_proxy_service_create(const char *service, unsigned int q_size, unsigned int flags) 560 { 561 luk_proxy_service_t *ns; 562 563 /* Service already active? */ 564 if (_find_service(service)) { 565 return 0; 566 } 567 568 ns = kmalloc(sizeof(luk_proxy_service_t), GFP_KERNEL); 569 570 if (!ns) { 571 return -1; 572 } 573 574 strcpy(ns->service, service); 575 _init_data_q(&ns->recv_q, ns->service, "recv_q", q_size); 576 _init_data_q(&ns->send_q, ns->service, "send_q", q_size); 577 578 ns->flags = flags; 579 580 list_add_tail(&ns->list, &_service_list); 581 582 return 0; 583 } 584 585 /* 586 * Function: linux_uk_proxy_service_destroy 587 * 588 * Purpose: 589 * Destroy active proxy service 590 * Parameters: 591 * service - proxy service name 592 * Returns: 593 * Always 0 594 * Notes: 595 * The destroyed proxy service is just marked as inactive and 596 * resources are not freed until the module is unloaded. 597 */ 598 int 599 linux_uk_proxy_service_destroy(const char* service) 600 { 601 luk_proxy_service_t *s; 602 603 if ((s = _find_service(service))) { 604 /* Mark it as inactive */ 605 s->flags |= LUK_SERVICE_F_INACTIVE; 606 } 607 return 0; 608 } 609 610 /* 611 * Function: linux_uk_proxy_recv 612 * 613 * Purpose: 614 * Used by a kernel process to receive data from user space. 615 * Parameters: 616 * service - proxy service name 617 * data - buffer to write data to 618 * len - length of data returned 619 * Returns: 620 * 0 if success, otherwise -1 621 * Notes: 622 * This function will block if no data is ready. 623 */ 624 int 625 linux_uk_proxy_recv(const char *service, void *data, uint32_t *len) 626 { 627 luk_proxy_service_t *s; 628 629 if (!(s = _find_service(service))) { 630 return -1; 631 } 632 633 /* We want to get data from this service's rx queue */ 634 return _list_op(&s->recv_q.data, &s->recv_q.free, data, len, _LIST_OP_F_GET | _LIST_OP_F_KERN); 635 } 636 637 /* 638 * Function: linux_uk_proxy_send 639 * 640 * Purpose: 641 * Used by a kernel process to send data to user space. 642 * Parameters: 643 * service - proxy service name 644 * data - data to send 645 * len - length of data 646 * Returns: 647 * 0 if success, otherwise -1 648 * Notes: 649 * This function will block if the user receive queue is full. 650 */ 651 int 652 linux_uk_proxy_send(const char *service, void *data, uint32_t len) 653 { 654 luk_proxy_service_t *s; 655 656 if (!(s = _find_service(service))) { 657 return -1; 658 } 659 660 if (s->flags & LUK_SERVICE_F_SUSPENDED) { 661 return 0; 662 } 663 664 /* We want to push this data to the services's tx queue */ 665 return _list_op(&s->send_q.free, &s->send_q.data, data, &len, _LIST_OP_F_PUT | _LIST_OP_F_KERN); 666 } 667 668 669 LKM_EXPORT_SYM(linux_uk_proxy_service_create); 670 LKM_EXPORT_SYM(linux_uk_proxy_service_destroy); 671 LKM_EXPORT_SYM(linux_uk_proxy_recv); 672 LKM_EXPORT_SYM(linux_uk_proxy_send);