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