/** * \file * \brief Kernel management of dispatchers (implementation). */ /* * Copyright (c) 2007, 2008, 2009, 2010, 2011, 2013, ETH Zurich. * All rights reserved. * * This file is distributed under the terms in the attached LICENSE file. * If you do not find this file, copies can be found by writing to: * ETH Zurich D-INFK, Universitaetstrasse 6, CH-8092 Zurich. Attn: Systems Group. */ #include #include #include /* XXX wait_for_interrupt, resume, execute */ #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__x86_64__) || defined(__i386__) # include #endif #if defined(__x86_64__) && !defined(__k1om__) # include #endif /** * \brief The kernel timeslice given in system ticks */ systime_t kernel_timeslice; unsigned int config_timeslice = CONFIG_TIMESLICE; /// Counter for number of context switches uint64_t context_switch_counter = 0; /// Current execution dispatcher (when in system call or exception) struct dcb *dcb_current = NULL; #if CONFIG_TRACE && NETWORK_STACK_BENCHMARK #define TRACE_N_BM 1 #endif // CONFIG_TRACE && NETWORK_STACK_BENCHMARK void __attribute__ ((noreturn)) dispatch(struct dcb *dcb) { // XXX FIXME: Why is this null pointer check on the fast path ? // If we have nothing to do we should call something other than dispatch if (dcb == NULL) { dcb_current = NULL; wait_for_interrupt(); } // Don't context switch if we are current already if (dcb_current != dcb) { #ifdef TRACE_CSWITCH trace_event(TRACE_SUBSYS_KERNEL, TRACE_EVENT_KERNEL_CSWITCH, (uint32_t)(lvaddr_t)dcb & 0xFFFFFFFF); #endif context_switch(dcb); dcb_current = dcb; } assert(dcb != NULL); dispatcher_handle_t handle = dcb->disp; struct dispatcher_shared_generic *disp = get_dispatcher_shared_generic(handle); arch_registers_state_t *disabled_area = dispatcher_get_disabled_save_area(handle); if (disp != NULL) { disp->systime = systime_now() + kcb_current->kernel_off; } TRACE(KERNEL, SC_YIELD, 1); if (dcb->disabled) { if (disp != NULL) { debug(SUBSYS_DISPATCH, "resume %.*s at 0x%" PRIx64 ", %s\n", DISP_NAME_LEN, disp->name, (uint64_t)registers_get_ip(disabled_area), disp->disabled ? "disp->disabled" : "disp->enabled" ); } #if defined(__x86_64__) && !defined(__k1om__) if(!dcb->is_vm_guest) { resume(disabled_area); } else { vmkit_vmenter(dcb); } #else resume(disabled_area); #endif } else { if (disp != NULL) { debug(SUBSYS_DISPATCH, "dispatch %.*s\n", DISP_NAME_LEN, disp->name); assert(disp->dispatcher_run != 0); disp->disabled = true; } #if defined(__x86_64__) && !defined(__k1om__) if(!dcb->is_vm_guest) { execute(disp->dispatcher_run); } else { vmkit_vmexec(dcb, (disp) ? disp->dispatcher_run : 0); } #else execute(disp->dispatcher_run); #endif } } // end function: dispatch /** * \brief Transfer cap from 'send' to 'ep', according to 'msg'. * * Reads the cap transfer spec in the LMP message 'msg' and transfers * the cap from CSpace in DCB 'send' accordingly. * * \param ep Endpoint capability of destination * \param send Pointer to sending DCB. * \param send_cptr Address of capability in sender's cspace * \param send_level Depth/level of capability in sender's cspace * * \return Error code */ static errval_t lmp_transfer_cap(struct capability *ep, struct dcb *send, capaddr_t send_cptr, uint8_t send_level, bool give_away) { errval_t err; /* Parameter checking */ assert(send_cptr != CPTR_NULL); assert(send != NULL); assert(ep != NULL); assert(ep->type == ObjType_EndPointLMP); struct dcb *recv = ep->u.endpointlmp.listener; assert(recv != NULL); assert(ep->u.endpointlmp.epoffset != 0); // printk(LOG_NOTE, "%s: ep->u.endpointlmp.epoffset = %"PRIuLVADDR"\n", __FUNCTION__, ep->u.endpointlmp.epoffset); /* Look up the slot receiver can receive caps in */ struct lmp_endpoint_kern *recv_ep = (void *)((uint8_t *)recv->disp + ep->u.endpointlmp.epoffset); // Lookup cspace root for receiving struct capability *recv_cspace_cap; // XXX: do we want a level into receiver's cspace here? // printk(LOG_NOTE, "recv_cspace_ptr = %"PRIxCADDR"\n", recv_ep->recv_cspc); err = caps_lookup_cap(&recv->cspace.cap, recv_ep->recv_cspc, 2, &recv_cspace_cap, CAPRIGHTS_READ_WRITE); if (err_is_fail(err) || recv_cspace_cap->type != ObjType_L1CNode) { return SYS_ERR_LMP_CAPTRANSFER_DST_CNODE_INVALID; } // Check index into L1 cnode capaddr_t l1index = recv_ep->recv_cptr >> L2_CNODE_BITS; if (l1index >= cnode_get_slots(recv_cspace_cap)) { return SYS_ERR_LMP_CAPTRANSFER_DST_CNODE_INVALID; } // Get the cnode struct cte *recv_cnode_cte = caps_locate_slot(get_address(recv_cspace_cap), l1index); struct capability *recv_cnode_cap = &recv_cnode_cte->cap; // Check for cnode type if (recv_cnode_cap->type != ObjType_L2CNode) { return SYS_ERR_LMP_CAPTRANSFER_DST_CNODE_INVALID; } // The slot within the cnode struct cte *recv_cte; recv_cte = caps_locate_slot(get_address(recv_cnode_cap), recv_ep->recv_cptr & MASK(L2_CNODE_BITS)); /* Look up source slot in sender */ struct cte *send_cte; err = caps_lookup_slot(&send->cspace.cap, send_cptr, send_level, &send_cte, CAPRIGHTS_READ); if (err_is_fail(err)) { return err_push(err, SYS_ERR_LMP_CAPTRANSFER_SRC_LOOKUP); } /* Is destination empty */ if (recv_cte->cap.type != ObjType_Null) { debug(SUBSYS_DISPATCH, "%s: dest slot occupied\n", __FUNCTION__); return SYS_ERR_LMP_CAPTRANSFER_DST_SLOT_OCCUPIED; } //caps_trace(__func__, __LINE__, send_cte, "transferring"); //TRACE_CAP_MSG("transferring", send_cte); /* Insert send cap into recv cap */ err = caps_copy_to_cte(recv_cte, send_cte, false, 0, 0); assert(err_is_ok(err)); // Cannot fail after checking that slot is empty if (give_away) { err = caps_delete(send_cte); if (err_is_fail(err)) { printk(LOG_NOTE, "deleting source of lmp captransfer failed: %"PRIuERRV"\n", err); } assert(err_is_ok(err)); // A copy now exists in the recv slot, so this // should not fail } return SYS_ERR_OK; } /** * \brief Check if it would be possible to deliver LMP payload, but do not deliver it * * \param ep Endpoint capability to send to * \param payload_len Length (in number of words) of payload */ errval_t lmp_can_deliver_payload(struct capability *ep, size_t payload_len) { assert(ep != NULL); assert(ep->type == ObjType_EndPointLMP); struct dcb *recv = ep->u.endpointlmp.listener; assert(recv != NULL); /* check that receiver exists and has specified an endpoint buffer */ if (recv->disp == 0 || ep->u.endpointlmp.epoffset == 0) { return SYS_ERR_LMP_NO_TARGET; } /* locate receiver's endpoint buffer */ struct lmp_endpoint_kern *recv_ep = (void *)((uint8_t *)recv->disp + ep->u.endpointlmp.epoffset); /* check delivered/consumed state */ uint32_t epbuflen = ep->u.endpointlmp.epbuflen; uint32_t pos = recv_ep->delivered; uint32_t consumed = recv_ep->consumed; if (pos >= epbuflen || consumed >= epbuflen) { return SYS_ERR_LMP_EP_STATE_INVALID; } /* compute space available in endpoint */ uint32_t epspace; if (pos >= consumed) { epspace = epbuflen - (pos - consumed); } else { epspace = consumed - pos; } /* Check if there's enough space for another msg. * We always keep one word free, to avoid having the special case where * delivered == consumed may mean the buffer is both completely full and * completely empty */ if (epspace <= payload_len + LMP_RECV_HEADER_LENGTH) { return SYS_ERR_LMP_BUF_OVERFLOW; } return SYS_ERR_OK; } /** * \brief Deliver the payload of an LMP message to a dispatcher. * * \param ep Endpoint capability to send to * \param send DCB of the sender. Can be NULL for kernel-originated messages * \param payload Message payload * \param payload_len Length (in number of words) of payload * \param captransfer True iff a cap has also been delivered * * \return Error code */ errval_t lmp_deliver_payload(struct capability *ep, struct dcb *send, uintptr_t *payload, size_t payload_len, bool captransfer, bool now) { assert(ep != NULL); assert(ep->type == ObjType_EndPointLMP); struct dcb *recv = ep->u.endpointlmp.listener; assert(recv != NULL); assert(payload != NULL || payload_len == 0); errval_t err; err = lmp_can_deliver_payload(ep, payload_len); if (err_is_fail(err)) { return err; } /* locate receiver's endpoint buffer */ struct lmp_endpoint_kern *recv_ep = (void *)((uint8_t *)recv->disp + ep->u.endpointlmp.epoffset); /* read current pos and buflen */ uint32_t epbuflen = ep->u.endpointlmp.epbuflen; uint32_t pos = recv_ep->delivered; struct dispatcher_shared_generic *send_disp = send ? get_dispatcher_shared_generic(send->disp) : NULL; struct dispatcher_shared_generic *recv_disp = get_dispatcher_shared_generic(recv->disp); debug(SUBSYS_DISPATCH, "LMP %.*s -> %.*s\n", DISP_NAME_LEN, send ? send_disp->name : "kernel", DISP_NAME_LEN, recv_disp->name); // Setup receiver's message flags union lmp_recv_header recvheader = { .raw = 0 }; recvheader.x.flags.captransfer = captransfer; recvheader.x.length = payload_len; /* Deliver header */ recv_ep->buf[pos] = recvheader.raw; if (++pos == epbuflen) { pos = 0; } /* Transfer the msg */ for(int i = 0; i < payload_len; i++) { recv_ep->buf[pos] = payload[i]; if (++pos == epbuflen) { pos = 0; } } // update the delivered pos recv_ep->delivered = pos; // tell the dispatcher that it has an outstanding message in one of its EPs recv_disp->lmp_delivered += payload_len + LMP_RECV_HEADER_LENGTH; // ... and give it a hint which one to look at recv_disp->lmp_hint = ep->u.endpointlmp.epoffset; // Make target runnable make_runnable(recv); if (now) schedule_now(recv); return SYS_ERR_OK; } /** * \brief Deliver an LMP message to a dispatcher. * * \param ep Endpoint capability to send to * \param send DCB of the sender. Can be NULL for kernel-originated messages * \param payload Buffer containing message payload * \param len Length of message payload, as number of words * \param send_cptr Capability to be transferred with LMP * \param send_level CSpace level of cptr */ errval_t lmp_deliver(struct capability *ep, struct dcb *send, uintptr_t *payload, size_t len, capaddr_t send_cptr, uint8_t send_level, bool give_away) { bool captransfer; assert(ep != NULL); assert(ep->type == ObjType_EndPointLMP); struct dcb *recv = ep->u.endpointlmp.listener; assert(recv != NULL); assert(payload != NULL); errval_t err; /* Is the sender trying to send a cap? */ if (send_cptr != CPTR_NULL) { /* Don't attempt to transfer the cap if we can't send the payload */ err = lmp_can_deliver_payload(ep, len); if (err_is_fail(err)) { return err; } err = lmp_transfer_cap(ep, send, send_cptr, send_level, give_away); if (err_is_fail(err)) { return err; } captransfer = true; } else { captransfer = false; } /* Send msg */ err = lmp_deliver_payload(ep, send, payload, len, captransfer, false); // shouldn't fail, if we delivered the cap successfully assert(!(captransfer && err_is_fail(err))); return err; }