openbcm

Git mirror of https://github.com/Broadcom-Network-Switching-Software/OpenBCM
git clone git://git.finwo.net/mirror/broadcom/openbcm
Log | Files | Refs | README

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);