• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023 Institute of Parallel And Distributed Systems (IPADS), Shanghai Jiao Tong University (SJTU)
3  * Licensed under the Mulan PSL v2.
4  * You can use this software according to the terms and conditions of the Mulan PSL v2.
5  * You may obtain a copy of Mulan PSL v2 at:
6  *     http://license.coscl.org.cn/MulanPSL2
7  * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
8  * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
9  * PURPOSE.
10  * See the Mulan PSL v2 for more details.
11  */
12 
13 #ifndef _GNU_SOURCE
14 #define _GNU_SOURCE
15 #include "unistd.h"
16 #endif
17 
18 #include <chcore/syscall.h>
19 #include <chcore/thread.h>
20 #include <pthread.h>
21 #include <sched.h>
22 #include <string.h>
23 #include <chcore/ipc.h>
24 #include <chcore/defs.h>
25 #include <chcore/memory.h>
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <chcore/bug.h>
29 #include <debug_lock.h>
30 #include <errno.h>
31 #include <assert.h>
32 #include "pthread_impl.h"
33 #include "fs_client_defs.h"
34 #include <chcore/pthread.h>
35 
36 /*
37  * **fsm_ipc_struct** is an address that points to the per-thread
38  * system_ipc_fsm in the pthread_t struct.
39  */
__fsm_ipc_struct_location(void)40 ipc_struct_t *__fsm_ipc_struct_location(void)
41 {
42     return &__pthread_self()->system_ipc_fsm;
43 }
44 
45 /*
46  * **lwip_ipc_struct** is an address that points to the per-thread
47  * system_ipc_net in the pthread_t struct.
48  */
__net_ipc_struct_location(void)49 ipc_struct_t *__net_ipc_struct_location(void)
50 {
51     return &__pthread_self()->system_ipc_net;
52 }
53 
__procmgr_ipc_struct_location(void)54 ipc_struct_t *__procmgr_ipc_struct_location(void)
55 {
56     return &__pthread_self()->system_ipc_procmgr;
57 }
58 
59 static int connect_system_server(ipc_struct_t *ipc_struct);
60 static int disconnect_system_servers();
61 
62 /* Interfaces for operate the ipc message (begin here) */
63 
ipc_create_msg(ipc_struct_t * icb,unsigned int data_len)64 ipc_msg_t *ipc_create_msg(ipc_struct_t *icb, unsigned int data_len)
65 {
66     return ipc_create_msg_with_cap(icb, data_len, 0);
67 }
68 /*
69  * ipc_msg_t is constructed on the shm pointed by
70  * ipc_struct_t->shared_buf.
71  * A new ips_msg will override the old one.
72  */
ipc_create_msg_with_cap(ipc_struct_t * icb,unsigned int data_len,unsigned int cap_slot_number)73 ipc_msg_t *ipc_create_msg_with_cap(ipc_struct_t *icb, unsigned int data_len,
74                                    unsigned int cap_slot_number)
75 {
76     ipc_msg_t *ipc_msg;
77     unsigned long buf_len;
78 
79     if (unlikely(icb->conn_cap == 0)) {
80         /* Create the IPC connection on demand */
81         if (connect_system_server(icb) != 0)
82             return NULL;
83     }
84 
85     /* Grab the ipc lock before setting ipc msg */
86     chcore_spin_lock(&(icb->lock));
87 
88     /* The ips_msg metadata is at the beginning of the memory */
89     buf_len = icb->shared_buf_len - sizeof(ipc_msg_t);
90 
91     /*
92      * Check the total length of data and caps.
93      *
94      * The checks at client side is not for security but for preventing
95      * unintended errors made by benign clients.
96      * The server has to validate the ipc msg by itself.
97      */
98     if (((data_len + sizeof(unsigned int) * cap_slot_number) > buf_len)
99         || ((data_len + sizeof(unsigned int) * cap_slot_number) < data_len)) {
100         printf("%s failed: too long msg (the usable shm size is 0x%lx)\n",
101                __func__,
102                buf_len);
103         chcore_spin_unlock(&(icb->lock));
104         return NULL;
105     }
106 
107     ipc_msg = (ipc_msg_t *)icb->shared_buf;
108     ipc_msg->icb = icb;
109 
110     ipc_msg->data_len = data_len;
111     ipc_msg->cap_slot_number = cap_slot_number;
112     ipc_msg->data_offset = sizeof(*ipc_msg);
113     ipc_msg->cap_slots_offset = ipc_msg->data_offset + data_len;
114 
115     /*
116      * Zeroing is not that meaningful for shared memory.
117      * If necessary, the client can explict clear the shm by itself.
118      */
119     return ipc_msg;
120 }
121 
ipc_get_msg_data(ipc_msg_t * ipc_msg)122 char *ipc_get_msg_data(ipc_msg_t *ipc_msg)
123 {
124     return (char *)ipc_msg + ipc_msg->data_offset;
125 }
126 
ipc_set_msg_data(ipc_msg_t * ipc_msg,void * data,unsigned int offset,unsigned int len)127 int ipc_set_msg_data(ipc_msg_t *ipc_msg, void *data, unsigned int offset,
128                      unsigned int len)
129 {
130     if ((offset + len < offset) || (offset + len > ipc_msg->data_len)) {
131         printf("%s failed due to overflow.\n", __func__);
132         return -1;
133     }
134 
135     memcpy(ipc_get_msg_data(ipc_msg) + offset, data, len);
136     return 0;
137 }
138 
ipc_get_msg_cap_ptr(ipc_msg_t * ipc_msg,unsigned int cap_id)139 static unsigned int *ipc_get_msg_cap_ptr(ipc_msg_t *ipc_msg,
140                                          unsigned int cap_id)
141 {
142     return (unsigned int *)((char *)ipc_msg + ipc_msg->cap_slots_offset)
143            + cap_id;
144 }
145 
ipc_get_msg_cap(ipc_msg_t * ipc_msg,unsigned int cap_slot_index)146 cap_t ipc_get_msg_cap(ipc_msg_t *ipc_msg, unsigned int cap_slot_index)
147 {
148     if (cap_slot_index >= ipc_msg->cap_slot_number) {
149         printf("%s failed due to overflow.\n", __func__);
150         return -1;
151     }
152     return *ipc_get_msg_cap_ptr(ipc_msg, cap_slot_index);
153 }
154 
ipc_set_msg_cap(ipc_msg_t * ipc_msg,unsigned int cap_slot_index,cap_t cap)155 int ipc_set_msg_cap(ipc_msg_t *ipc_msg, unsigned int cap_slot_index, cap_t cap)
156 {
157     if (cap_slot_index >= ipc_msg->cap_slot_number) {
158         printf("%s failed due to overflow.\n", __func__);
159         return -1;
160     }
161 
162     *ipc_get_msg_cap_ptr(ipc_msg, cap_slot_index) = cap;
163     return 0;
164 }
165 
ipc_destroy_msg(ipc_msg_t * ipc_msg)166 int ipc_destroy_msg(ipc_msg_t *ipc_msg)
167 {
168     /* Release the ipc lock */
169     chcore_spin_unlock(&(ipc_msg->icb->lock));
170 
171     return 0;
172 }
173 
174 /* Interfaces for operate the ipc message (end here) */
175 
176 #ifdef CHCORE_ARCH_X86_64
177 /**
178  * In ChCore IPC, a server shadow thread **always** uses usys_ipc_return syscall
179  * to enter kernel, sleep and return to client thread. When recycling a
180  * connection, the kernel would wait for all its server shadow thread until they
181  * return to kernel through usys_ipc_return, then the kernel calls
182  * ipc_shadow_thread_exit_routine on those threads to recycle server state for
183  * this connection.
184  *
185  * On x86_64, the problem is that due to all shadow threads return to kernel
186  * using syscall, and the calling convention of syscall would clobber registers,
187  * so functions called by the kernel should also follow syscall calling
188  * convention. However, ipc_shadow_thread_exit_routine whose assembly code is
189  * generated by compiler is unaware of that and still follows SystemV calling
190  * convention. So we implement the necessary translation from syscall to SystemV
191  * calling convention using this naked function
192  */
ipc_shadow_thread_exit_routine_naked(void)193 __attribute__((naked)) void ipc_shadow_thread_exit_routine_naked(void)
194 {
195     __asm__ __volatile__("mov %r10, %rcx \n"
196                          "call ipc_shadow_thread_exit_routine \n");
197 }
198 #endif
199 
200 /* Shadow thread exit routine */
ipc_shadow_thread_exit_routine(server_destructor destructor_func,badge_t client_badge,unsigned long server_shm_addr,unsigned long shm_size)201 int ipc_shadow_thread_exit_routine(server_destructor destructor_func,
202                                    badge_t client_badge,
203                                    unsigned long server_shm_addr,
204                                    unsigned long shm_size)
205 {
206     if (destructor_func) {
207         destructor_func(client_badge);
208     }
209     chcore_free_vaddr(server_shm_addr, shm_size);
210     pthread_detach(pthread_self());
211     disconnect_system_servers();
212     pthread_exit(NULL);
213 }
214 
ipc_shadow_thread_exit_routine_single(server_destructor destructor_func,badge_t client_badge,unsigned long server_shm_addr,unsigned long shm_size)215 void ipc_shadow_thread_exit_routine_single(server_destructor destructor_func,
216                                            badge_t client_badge,
217                                            unsigned long server_shm_addr,
218                                            unsigned long shm_size)
219 {
220     if (destructor_func) {
221         destructor_func(client_badge);
222     }
223     chcore_free_vaddr(server_shm_addr, shm_size);
224     usys_ipc_exit_routine_return();
225 }
226 
227 /* A register_callback thread uses this to finish a registration */
ipc_register_cb_return(cap_t server_thread_cap,unsigned long server_thread_exit_routine,unsigned long server_shm_addr)228 void ipc_register_cb_return(cap_t server_thread_cap,
229                             unsigned long server_thread_exit_routine,
230                             unsigned long server_shm_addr)
231 {
232     usys_ipc_register_cb_return(
233         server_thread_cap, server_thread_exit_routine, server_shm_addr);
234 }
235 
236 /* A register_callback thread is passive (never proactively run) */
register_cb(void * ipc_handler)237 void *register_cb(void *ipc_handler)
238 {
239     cap_t server_thread_cap = 0;
240     unsigned long shm_addr;
241 
242     shm_addr = chcore_alloc_vaddr(IPC_PER_SHM_SIZE);
243 
244     // printf("[server]: A new client comes in! ipc_handler: 0x%lx\n",
245     // ipc_handler);
246 
247     /*
248      * Create a passive thread for serving IPC requests.
249      * Besides, reusing an existing thread is also supported.
250      */
251     pthread_t handler_tid;
252     server_thread_cap = chcore_pthread_create_shadow(
253         &handler_tid, NULL, ipc_handler, (void *)NO_ARG);
254     BUG_ON(server_thread_cap < 0);
255 #ifndef CHCORE_ARCH_X86_64
256     ipc_register_cb_return(server_thread_cap,
257                            (unsigned long)ipc_shadow_thread_exit_routine,
258                            shm_addr);
259 #else
260     ipc_register_cb_return(server_thread_cap,
261                            (unsigned long)ipc_shadow_thread_exit_routine_naked,
262                            shm_addr);
263 #endif
264 
265     return NULL;
266 }
267 
268 /* Register callback for single-handler-thread server */
register_cb_single(void * ipc_handler)269 void *register_cb_single(void *ipc_handler)
270 {
271     static cap_t single_handler_thread_cap = -1;
272     unsigned long shm_addr;
273 
274     /* alloc shm_addr */
275     shm_addr = chcore_alloc_vaddr(IPC_PER_SHM_SIZE);
276 
277     /* if single handler thread isn't created */
278     if (single_handler_thread_cap == -1) {
279         pthread_t single_handler_tid;
280         single_handler_thread_cap = chcore_pthread_create_shadow(
281             &single_handler_tid, NULL, ipc_handler, (void *)NO_ARG);
282     }
283 
284     assert(single_handler_thread_cap > 0);
285     ipc_register_cb_return(single_handler_thread_cap,
286                            (unsigned long)ipc_shadow_thread_exit_routine_single,
287                            shm_addr);
288 
289     return NULL;
290 }
291 
ipc_register_server(server_handler server_handler,void * (* client_register_handler)(void *))292 int ipc_register_server(server_handler server_handler,
293                         void *(*client_register_handler)(void *))
294 {
295     return ipc_register_server_with_destructor(
296         server_handler, client_register_handler, DEFAULT_DESTRUCTOR);
297 }
298 
299 /*
300  * Currently, a server thread can only invoke this interface once.
301  * But, a server can use another thread to register a new service.
302  */
ipc_register_server_with_destructor(server_handler server_handler,void * (* client_register_handler)(void *),server_destructor server_destructor)303 int ipc_register_server_with_destructor(server_handler server_handler,
304                                         void *(*client_register_handler)(void *),
305                                         server_destructor server_destructor)
306 {
307     cap_t register_cb_thread_cap;
308     int ret;
309 
310 /*
311  * Create a passive thread for handling IPC registration.
312  * - run after a client wants to register
313  * - be responsible for initializing the ipc connection
314  */
315 #define ARG_SET_BY_KERNEL 0
316     pthread_t handler_tid;
317     register_cb_thread_cap = chcore_pthread_create_register_cb(
318         &handler_tid, NULL, client_register_handler, (void *)ARG_SET_BY_KERNEL);
319     BUG_ON(register_cb_thread_cap < 0);
320     /*
321      * Kernel will pass server_handler as the argument for the
322      * register_cb_thread.
323      */
324     ret = usys_register_server((unsigned long)server_handler,
325                                (unsigned long)register_cb_thread_cap,
326                                (unsigned long)server_destructor);
327     if (ret != 0) {
328         printf("%s failed (retval is %d)\n", __func__, ret);
329     }
330     return ret;
331 }
332 
333 struct client_shm_config {
334     cap_t shm_cap;
335     unsigned long shm_addr;
336 };
337 
338 /*
339  * A client thread can register itself for multiple times.
340  *
341  * The returned ipc_struct_t is from heap,
342  * so the callee needs to free it.
343  */
ipc_register_client(cap_t server_thread_cap)344 ipc_struct_t *ipc_register_client(cap_t server_thread_cap)
345 {
346     cap_t conn_cap;
347     ipc_struct_t *client_ipc_struct;
348 
349     struct client_shm_config shm_config;
350     cap_t shm_cap;
351 
352     client_ipc_struct = malloc(sizeof(ipc_struct_t));
353     if (client_ipc_struct == NULL) {
354         return NULL;
355     }
356 
357     /*
358      * Before registering client on the server,
359      * the client allocates the shm (and shares it with
360      * the server later).
361      *
362      * Now we used PMO_DATA instead of PMO_SHM because:
363      * - SHM (IPC_PER_SHM_SIZE) only contains one page and
364      *   PMO_DATA is thus more efficient.
365      *
366      * If the SHM becomes larger, we can use PMO_SHM instead.
367      * Both types are tested and can work well.
368      */
369 
370     // shm_cap = usys_create_pmo(IPC_PER_SHM_SIZE, PMO_SHM);
371     shm_cap = usys_create_pmo(IPC_PER_SHM_SIZE, PMO_DATA);
372     if (shm_cap < 0) {
373         printf("usys_create_pmo ret %d\n", shm_cap);
374         goto out_free_client_ipc_struct;
375     }
376 
377     shm_config.shm_cap = shm_cap;
378     shm_config.shm_addr = chcore_alloc_vaddr(IPC_PER_SHM_SIZE);
379 
380     // printf("%s: register_client with shm_addr 0x%lx\n",
381     //      __func__, shm_config.shm_addr);
382 
383     while (1) {
384         conn_cap =
385             usys_register_client(server_thread_cap, (unsigned long)&shm_config);
386 
387         if (conn_cap == -EIPCRETRY) {
388             // printf("client: Try to connect again ...\n");
389             /* The server IPC may be not ready. */
390             usys_yield();
391         } else if (conn_cap < 0) {
392             printf("client: %s failed (return %d), server_thread_cap is %d\n",
393                    __func__,
394                    conn_cap,
395                    server_thread_cap);
396             goto out_free_vaddr;
397         } else {
398             /* Success */
399             break;
400         }
401     }
402 
403     client_ipc_struct->lock = 0;
404     client_ipc_struct->shared_buf = shm_config.shm_addr;
405     client_ipc_struct->shared_buf_len = IPC_PER_SHM_SIZE;
406     client_ipc_struct->conn_cap = conn_cap;
407 
408     return client_ipc_struct;
409 
410 out_free_vaddr:
411     usys_revoke_cap(shm_cap, false);
412     chcore_free_vaddr(shm_config.shm_addr, IPC_PER_SHM_SIZE);
413 
414 out_free_client_ipc_struct:
415     free(client_ipc_struct);
416 
417     return NULL;
418 }
419 
ipc_client_close_connection(ipc_struct_t * ipc_struct)420 int ipc_client_close_connection(ipc_struct_t *ipc_struct)
421 {
422     int ret;
423     while (1) {
424         ret = usys_ipc_close_connection(ipc_struct->conn_cap);
425 
426         if (ret == -EAGAIN) {
427             usys_yield();
428         } else if (ret < 0) {
429             goto out;
430         } else {
431             break;
432         }
433     }
434 
435     chcore_free_vaddr(ipc_struct->shared_buf, ipc_struct->shared_buf_len);
436     free(ipc_struct);
437 out:
438     return ret;
439 }
440 
441 /* Client uses **ipc_call** to issue an IPC request */
ipc_call(ipc_struct_t * icb,ipc_msg_t * ipc_msg)442 long ipc_call(ipc_struct_t *icb, ipc_msg_t *ipc_msg)
443 {
444     long ret;
445 
446     if (unlikely(icb->conn_cap == 0)) {
447         /* Create the IPC connection on demand */
448         if ((ret = connect_system_server(icb)) != 0)
449             return ret;
450     }
451 
452     do {
453         ret = usys_ipc_call(
454             icb->conn_cap, (unsigned long)ipc_msg, ipc_msg->cap_slot_number);
455     } while (ret == -EIPCRETRY);
456 
457     return ret;
458 }
459 
460 /* Server uses **ipc_return** to finish an IPC request */
ipc_return(ipc_msg_t * ipc_msg,long ret)461 void ipc_return(ipc_msg_t *ipc_msg, long ret)
462 {
463     if (ipc_msg != NULL)
464         ipc_msg->cap_slot_number = 0;
465     usys_ipc_return((unsigned long)ret, 0);
466 }
467 
468 /*
469  * IPC return and copy back capabilities.
470  * Use different ipc return interface because cap_slot_number
471  * is valid only when we have cap to return. So we need to reset it to
472  * 0 in ipc_return which has no cap to return.
473  */
ipc_return_with_cap(ipc_msg_t * ipc_msg,long ret)474 void ipc_return_with_cap(ipc_msg_t *ipc_msg, long ret)
475 {
476     usys_ipc_return((unsigned long)ret, ipc_msg->cap_slot_number);
477 }
478 
simple_ipc_forward(ipc_struct_t * ipc_struct,void * data,int len)479 int simple_ipc_forward(ipc_struct_t *ipc_struct, void *data, int len)
480 {
481     ipc_msg_t *ipc_msg;
482     int ret;
483 
484     ipc_msg = ipc_create_msg(ipc_struct, len);
485     ipc_set_msg_data(ipc_msg, data, 0, len);
486     ret = ipc_call(ipc_struct, ipc_msg);
487     ipc_destroy_msg(ipc_msg);
488 
489     return ret;
490 }
491 
ipc_struct_copy(ipc_struct_t * dst,ipc_struct_t * src)492 static void ipc_struct_copy(ipc_struct_t *dst, ipc_struct_t *src)
493 {
494     dst->conn_cap = src->conn_cap;
495     dst->shared_buf = src->shared_buf;
496     dst->shared_buf_len = src->shared_buf_len;
497     dst->lock = src->lock;
498 }
499 
connect_system_server(ipc_struct_t * ipc_struct)500 static int connect_system_server(ipc_struct_t *ipc_struct)
501 {
502     ipc_struct_t *tmp;
503 
504     switch (ipc_struct->server_id) {
505     case FS_MANAGER: {
506         tmp = ipc_register_client(fsm_server_cap);
507         if (tmp == NULL) {
508             printf("%s: failed to connect FS\n", __func__);
509             return -1;
510         }
511         break;
512     }
513     case NET_MANAGER: {
514         tmp = ipc_register_client(lwip_server_cap);
515         if (tmp == NULL) {
516             printf("%s: failed to connect NET\n", __func__);
517             return -1;
518         }
519         break;
520     }
521     case PROC_MANAGER: {
522         tmp = ipc_register_client(procmgr_server_cap);
523         if (tmp == NULL) {
524             printf("%s: failed to connect PROCMGR\n", __func__);
525             return -1;
526         }
527         break;
528     }
529     default:
530         printf("%s: unsupported system server id %d\n",
531                __func__,
532                ipc_struct->server_id);
533         return -1;
534     }
535 
536     /* Copy the newly allocated ipc_struct to the per_thread ipc_struct */
537     ipc_struct_copy(ipc_struct, tmp);
538     free(tmp);
539 
540     return 0;
541 }
542 
543 #define disconnect_server(server)                      \
544     do {                                               \
545         int ret;                                       \
546         if (!server) {                                 \
547             printf(#server "is NULL!\n");              \
548             break;                                     \
549         }                                              \
550         if ((server)->conn_cap) {                      \
551             ret = ipc_client_close_connection(server); \
552             if (ret < 0)                               \
553                 return ret;                            \
554         }                                              \
555     } while (0)
556 
disconnect_system_servers(void)557 int disconnect_system_servers(void)
558 {
559     disconnect_server(procmgr_ipc_struct);
560     disconnect_server(fsm_ipc_struct);
561     disconnect_server(lwip_ipc_struct);
562     disconnect_mounted_fs();
563 }
564