int serial_server_client_connect(seL4_CPtr badged_server_ep_cap, vka_t *client_vka, vspace_t *client_vspace, serial_client_context_t *conn) { seL4_Error error; int shmem_n_pages; uintptr_t shmem_tmp_vaddr; seL4_MessageInfo_t tag; cspacepath_t frame_cspath; if (badged_server_ep_cap == 0 || client_vka == NULL || client_vspace == NULL || conn == NULL) { return seL4_InvalidArgument; } memset(conn, 0, sizeof(serial_client_context_t)); shmem_n_pages = BYTES_TO_4K_PAGES(SERIAL_SERVER_SHMEM_MAX_SIZE); if (shmem_n_pages > seL4_MsgMaxExtraCaps) { ZF_LOGE(SERSERVC"connect: Currently unsupported shared memory size: " "IPC cap transfer capability is inadequate."); return seL4_RangeError; } conn->shmem = vspace_new_pages(client_vspace, seL4_AllRights, shmem_n_pages, seL4_PageBits); if (conn->shmem == NULL) { ZF_LOGE(SERSERVC"connect: Failed to alloc shmem."); return seL4_NotEnoughMemory; } assert(IS_ALIGNED((uintptr_t)conn->shmem, seL4_PageBits)); /* Look up the Frame cap behind each page in the shmem range, and marshal * all of those Frame caps to the parent. The parent will then map those * Frames into its VSpace and establish a shmem link. */ shmem_tmp_vaddr = (uintptr_t)conn->shmem; for (int i = 0; i < shmem_n_pages; i++) { vka_cspace_make_path(client_vka, vspace_get_cap(client_vspace, (void *)shmem_tmp_vaddr), &frame_cspath); seL4_SetCap(i, frame_cspath.capPtr); shmem_tmp_vaddr += BIT(seL4_PageBits); } /* Call the server asking it to establish the shmem mapping with us, and * get us connected up. */ seL4_SetMR(SSMSGREG_FUNC, FUNC_CONNECT_REQ); seL4_SetMR(SSMSGREG_CONNECT_REQ_SHMEM_SIZE, SERIAL_SERVER_SHMEM_MAX_SIZE); /* extraCaps doubles up as the number of shmem pages. */ tag = seL4_MessageInfo_new(0, 0, shmem_n_pages, SSMSGREG_CONNECT_REQ_END); tag = seL4_Call(badged_server_ep_cap, tag); /* It makes sense to verify that the message we're getting back is an * ACK response to our request message. */ if (seL4_GetMR(SSMSGREG_FUNC) != FUNC_CONNECT_ACK) { error = seL4_IllegalOperation; ZF_LOGE(SERSERVC"connect: Reply message was not a CONNECT_ACK as " "expected."); goto out; } /* When the parent replies, we check to see if it was successful, etc. */ error = seL4_MessageInfo_get_label(tag); if (error != (int)SERIAL_SERVER_NOERROR) { ZF_LOGE(SERSERVC"connect ERR %d: Failed to connect to the server.", error); if (error == (int)SERIAL_SERVER_ERROR_SHMEM_TOO_LARGE) { ZF_LOGE(SERSERVC"connect: Your requested shmem mapping size is too " "large.\n\tServer's max shmem size is %luB.", (long)seL4_GetMR(SSMSGREG_CONNECT_ACK_MAX_SHMEM_SIZE)); } goto out; } conn->shmem_size = SERIAL_SERVER_SHMEM_MAX_SIZE; vka_cspace_make_path(client_vka, badged_server_ep_cap, &conn->badged_server_ep_cspath); return seL4_NoError; out: if (conn->shmem != NULL) { vspace_unmap_pages(client_vspace, (void *)conn->shmem, shmem_n_pages, seL4_PageBits, VSPACE_FREE); } return error; }
seL4_Error serial_server_parent_spawn_thread(simple_t *parent_simple, vka_t *parent_vka, vspace_t *parent_vspace, uint8_t priority) { const size_t shmem_max_size = SERIAL_SERVER_SHMEM_MAX_SIZE; seL4_Error error; size_t shmem_max_n_pages; cspacepath_t parent_cspace_cspath; seL4_MessageInfo_t tag; if (parent_simple == NULL || parent_vka == NULL || parent_vspace == NULL) { return seL4_InvalidArgument; } memset(get_serial_server(), 0, sizeof(serial_server_context_t)); /* Get a CPtr to the parent's root cnode. */ shmem_max_n_pages = BYTES_TO_4K_PAGES(shmem_max_size); vka_cspace_make_path(parent_vka, 0, &parent_cspace_cspath); get_serial_server()->server_vka = parent_vka; get_serial_server()->server_vspace = parent_vspace; get_serial_server()->server_cspace = parent_cspace_cspath.root; get_serial_server()->server_simple = parent_simple; /* Allocate the Endpoint that the server will be listening on. */ error = vka_alloc_endpoint(parent_vka, &get_serial_server()->server_ep_obj); if (error != 0) { ZF_LOGE(SERSERVP"spawn_thread: failed to alloc endpoint, err=%d.", error); return error; } /* And also allocate a badged copy of the Server's endpoint that the Parent * can use to send to the Server. This is used to allow the Server to report * back to the Parent on whether or not the Server successfully bound to a * platform serial driver. * * This badged endpoint will be reused by the library as the Parent's badged * Endpoint cap, if the Parent itself ever chooses to connect() to the * Server later on. */ get_serial_server()->parent_badge_value = serial_server_badge_value_alloc(); if (get_serial_server()->parent_badge_value == SERIAL_SERVER_BADGE_VALUE_EMPTY) { error = seL4_NotEnoughMemory; goto out; } error = vka_mint_object(parent_vka, &get_serial_server()->server_ep_obj, &get_serial_server()->_badged_server_ep_cspath, seL4_AllRights, seL4_CapData_Badge_new(get_serial_server()->parent_badge_value)); if (error != 0) { ZF_LOGE(SERSERVP"spawn_thread: Failed to mint badged Endpoint cap to " "server.\n" "\tParent cannot confirm Server thread successfully spawned."); goto out; } /* Allocate enough Cnode slots in our CSpace to enable us to receive * frame caps from our clients, sufficient to cover "shmem_max_size". * The problem here is that we're sort of forced to assume that we get * these slots contiguously. If they're not, we have a problem. * * If a client tries to send us too many frames, we respond with an error, * and indicate our shmem_max_size in the SSMSGREG_RESPONSE * message register. */ get_serial_server()->frame_cap_recv_cspaths = calloc(shmem_max_n_pages, sizeof(cspacepath_t)); if (get_serial_server()->frame_cap_recv_cspaths == NULL) { error = seL4_NotEnoughMemory; goto out; } for (size_t i = 0; i < shmem_max_n_pages; i++) { error = vka_cspace_alloc_path(parent_vka, &get_serial_server()->frame_cap_recv_cspaths[i]); if (error != 0) { ZF_LOGE(SERSERVP"spawn_thread: Failed to alloc enough cnode slots " "to receive shmem frame caps equal to %d bytes.", shmem_max_size); goto out; } } error = sel4utils_configure_thread(parent_vka, parent_vspace, parent_vspace, get_serial_server()->server_ep_obj.cptr, priority, parent_cspace_cspath.root, seL4_NilData, &get_serial_server()->server_thread); if (error != 0) { ZF_LOGE(SERSERVP"spawn_thread: sel4utils_configure_thread failed " "with %d.", error); goto out; } error = sel4utils_start_thread(&get_serial_server()->server_thread, &serial_server_main, NULL, NULL, 1); if (error != 0) { ZF_LOGE(SERSERVP"spawn_thread: sel4utils_start_thread failed with " "%d.", error); goto out; } /* When the Server is spawned, it will reply to tell us whether or not it * successfully bound itself to the platform serial device. Block here * and wait for that reply. */ seL4_SetMR(SSMSGREG_FUNC, FUNC_SERVER_SPAWN_SYNC_REQ); tag = seL4_MessageInfo_new(0, 0, 0, SSMSGREG_SPAWN_SYNC_REQ_END); tag = seL4_Call(get_serial_server()->_badged_server_ep_cspath.capPtr, tag); /* Did all go well with the server? */ if (seL4_GetMR(SSMSGREG_FUNC) != FUNC_SERVER_SPAWN_SYNC_ACK) { ZF_LOGE(SERSERVP"spawn_thread: Server thread sync message after spawn " "was not a SYNC_ACK as expected."); error = seL4_InvalidArgument; goto out; } error = seL4_MessageInfo_get_label(tag); if (error != 0) { ZF_LOGE(SERSERVP"spawn_thread: Server thread failed to bind to the " "platform serial device."); goto out; } get_serial_server()->shmem_max_size = shmem_max_size; get_serial_server()->shmem_max_n_pages = shmem_max_n_pages; return 0; out: if (get_serial_server()->frame_cap_recv_cspaths != NULL) { for (size_t i = 0; i < shmem_max_n_pages; i++) { /* Since the array was allocated with calloc(), it was zero'd out. So * those indexes that didn't get allocated will have NULL in them. * Break early on the first index that has NULL. */ if (get_serial_server()->frame_cap_recv_cspaths[i].capPtr == 0) { break; } vka_cspace_free_path(parent_vka, get_serial_server()->frame_cap_recv_cspaths[i]); } } free(get_serial_server()->frame_cap_recv_cspaths); if (get_serial_server()->_badged_server_ep_cspath.capPtr != 0) { vka_cspace_free_path(parent_vka, get_serial_server()->_badged_server_ep_cspath); } if (get_serial_server()->parent_badge_value != SERIAL_SERVER_BADGE_VALUE_EMPTY) { serial_server_badge_value_free(get_serial_server()->parent_badge_value); } vka_free_object(parent_vka, &get_serial_server()->server_ep_obj); return error; }