(* * Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) * * SPDX-License-Identifier: GPL-2.0-only *) (* * Operations on endpoints. *) theory Endpoint_D imports Invocations_D CSpace_D Tcb_D begin (* Inject the reply cap into the target TCB *) definition inject_reply_cap :: "cdl_object_id \ cdl_object_id \ bool \ unit k_monad" where "inject_reply_cap src_tcb_id dst_tcb_id can_grant \ do set_cap (src_tcb_id, tcb_pending_op_slot) $ cdl_cap.PendingSyncRecvCap src_tcb_id True False; insert_cap_child (ReplyCap src_tcb_id (if can_grant then {Grant, Write} else {Write})) (src_tcb_id, tcb_replycap_slot) (dst_tcb_id, tcb_caller_slot); return () od" (* * Get the slot where we should place an incoming cap for a * particular thread. *) definition get_receive_slot :: "cdl_object_id \ cdl_cap_ref option k_monad" where "get_receive_slot thread \ do tcb \ get_thread thread; recv_slot \ (case (cdl_tcb_caps tcb tcb_ipcbuffer_slot) of (Some (FrameCap _ _ rights _ _ _)) \ if (Read \ rights \ Write \ rights) then return (cdl_intent_recv_slot (cdl_tcb_intent tcb)) else return None | _ \ return None); case ( recv_slot ) of None \ return None | Some (croot, index, depth) \ doE \ \Lookup the slot.\ cspace_root \ unify_failure $ lookup_cap thread croot; result \ unify_failure $ lookup_slot_for_cnode_op cspace_root index depth; \ \Ensure nothing is already in it.\ cap \ liftE $ get_cap result; whenE (cap \ NullCap) throw; returnOk $ Some result odE (\_. return None) od " (* Get the cptr's that the given thread wishes to transfer. *) definition get_send_slots :: "cdl_object_id \ cdl_cptr list k_monad" where "get_send_slots thread \ do tcb \ get_thread thread; return $ cdl_intent_extras (cdl_tcb_intent tcb) od " definition get_ipc_buffer :: "cdl_object_id \ bool \ cdl_object_id option k_monad" where "get_ipc_buffer oid in_receive \ do frame_cap \ get_cap (oid,tcb_ipcbuffer_slot); (case frame_cap of Types_D.FrameCap _ a rights _ _ _ \ if (Write \ rights \ Read \ rights) \ (Read\ rights \ \ in_receive) then return (Some a) else return None | _ \ return None) od" definition corrupt_ipc_buffer :: "cdl_object_id \ bool \ unit k_monad" where "corrupt_ipc_buffer oid in_receive \ do buffer \ get_ipc_buffer oid in_receive; (case buffer of Some a \ corrupt_frame a | None \ corrupt_tcb_intent oid) od" (* * Transfers at most one cap in addition to a number of endpoint badges. * * Endpoint badges are transferred if the cap to be transferred is to * the endpoint used in the transfer. *) fun transfer_caps_loop :: "cdl_object_id option \ cdl_object_id \ (cdl_cap \ cdl_cap_ref) list \ cdl_cap_ref option \ unit k_monad" where "transfer_caps_loop ep receiver [] dest = return ()" | "transfer_caps_loop ep receiver ((cap,slot)#caps) dest = \ \Transfer badge, transfer cap, or abort early if more than one cap to transfer\ (if is_ep_cap cap \ ep = Some (cap_object cap) then do \ \transfer badge\ corrupt_ipc_buffer receiver True; \ \transfer rest of badges or cap\ transfer_caps_loop ep receiver caps dest od else if dest \ None then doE new_cap \ returnOk (update_cap_rights (cap_rights cap - {Write}) cap) \ returnOk cap; \ \Target cap is derived. This may abort transfer early.\ target_cap \ derive_cap slot new_cap; whenE (target_cap = NullCap) throw; \ \Copy the cap across as either a child or sibling.\ liftE (insert_cap_child target_cap slot (the dest) \ insert_cap_sibling target_cap slot (the dest)); \ \Transfer rest of badges\ liftE $ transfer_caps_loop ep receiver caps None odE (\_. return ()) else return ())" (* * Transfer caps from src to dest. * * In theory, the source thread specifies a list of caps to send, and * the destination thread specifies a list of cap slots to put them in. * * In the true spirit of L4 Pistachio, what _actually_ occurs during the * IPC transfer is hard to determine without knowing intricate details * of the kernel's implementation. In particular: * * - Caps often just won't send, but still 'burn' the receive slot * (ZombieCaps, ReplyCaps, IrqControlCap); * * - Caps may not send, but still allow later caps to * use the receive slot (Unwrapped endpoints); * * - Cap sending may stop half way (cap lookup faults); * * - The new cap may either be a sibling or child of source cap, * depending on where in the CDT the source cap is. * * - In reality, no more than one cap will ever be sent, because only * one destination cap slot is supported elsewhere in the code. * * We remove some of the details, replacing them with nondeterminism. *) definition transfer_caps :: "cdl_object_id option \ (cdl_cap \ cdl_cap_ref) list \ cdl_object_id \ cdl_object_id \ unit k_monad" where "transfer_caps ep caps sender receiver \ do dest_slot \ get_receive_slot receiver \ return None; transfer_caps_loop ep receiver caps dest_slot od" (* * Get the set of threads waiting to receive on the given notification. *) definition get_waiting_ntfn_recv_threads :: "cdl_object_id \ cdl_state \ cdl_object_id set" where "get_waiting_ntfn_recv_threads target state \ {x. \a. (cdl_objects state) x = Some (Tcb a) \ (((cdl_tcb_caps a) tcb_pending_op_slot) = Some (PendingNtfnRecvCap target)) }" (* Get the set of threads waiting to receive on the given sync endpoint. *) definition get_waiting_sync_recv_threads :: "cdl_object_id \ cdl_state \ cdl_object_id set" where "get_waiting_sync_recv_threads target state \ {x. \a. (cdl_objects state) x = Some (Tcb a) \ (\can_grant. (cdl_tcb_caps a) tcb_pending_op_slot = Some (PendingSyncRecvCap target False can_grant)) }" (* * Get the set of threads waiting to send to the given sync endpoint. *) definition get_waiting_sync_send_threads :: "cdl_object_id \ cdl_state \ cdl_object_id set" where "get_waiting_sync_send_threads target state \ {t. \fault a b. (cdl_objects state) t = Some (Tcb a) \ (\call can_grant can_grant_reply. (cdl_tcb_caps a) tcb_pending_op_slot = Some (PendingSyncSendCap target b call can_grant can_grant_reply fault)) }" (* * Get the set of threads which are bound to the given ntfn, but are * also waiting on sync IPC *) definition get_waiting_sync_bound_ntfn_threads :: "cdl_object_id \ cdl_state \ cdl_object_id set" where "get_waiting_sync_bound_ntfn_threads ntfn_id state \ {x. \a ep_id. (cdl_objects state) x = Some (Tcb a) \ (\can_grant. (cdl_tcb_caps a) tcb_pending_op_slot = Some (PendingSyncRecvCap ep_id False can_grant)) \ ((cdl_tcb_caps a) tcb_boundntfn_slot = Some (BoundNotificationCap ntfn_id))}" (* * Mark a thread blocked on IPC. * * Theads get a new implicit "send once" or "receive once" capability * when they block on an IPC. This is because if the capability they * used to start the send/receive is revoked, the transfer will still be * allowed to proceed (even if it is at a much later point in time). *) definition block_thread_on_ipc :: "cdl_object_id \ cdl_cap \ unit k_monad" where "block_thread_on_ipc tcb cap \ set_cap (tcb, tcb_pending_op_slot) cap" (* Might need to do some check here *) definition lookup_extra_caps :: "cdl_object_id \ cdl_cptr list \ (cdl_cap \ cdl_cap_ref) list fault_monad" where "lookup_extra_caps thread cptrs \ mapME (\cptr. lookup_cap_and_slot thread cptr) cptrs" (* * Transfer a message from "sender" to "receiver", possibly copying caps * over in the process. If a fault is pending in receiver, send fault instead. *) definition do_ipc_transfer :: "cdl_object_id option \ cdl_object_id \ cdl_object_id \ bool \ unit k_monad" where "do_ipc_transfer ep_id sender_id receiver_id can_grant \ do \ \look up cap transfer\ src_slots \ get_send_slots sender_id; do \ \do normal transfer\ caps \ if can_grant then lookup_extra_caps sender_id src_slots (\_. return []) else return []; \ \copy registers, transfer message or fault\ corrupt_ipc_buffer receiver_id True; \ \transfer caps if no fault occured\ transfer_caps ep_id caps sender_id receiver_id od \ \ \fault transfer\ corrupt_ipc_buffer receiver_id True; \ \set message info\ corrupt_tcb_intent receiver_id od" (* * Transfer a message from "sender" to "receiver" using a reply capability. * * The sender may have the right to grant caps over the channel. * * If a fault is pending in the receiver, the fault is transferred. *) definition do_reply_transfer :: "cdl_object_id \ cdl_object_id \ cdl_cap_ref \ bool \ unit k_monad" where "do_reply_transfer sender_id receiver_id reply_cap_slot can_grant \ do has_fault \ get_thread_fault receiver_id; when (\ has_fault) $ do_ipc_transfer None sender_id receiver_id can_grant; \ \Clear out any pending operation caps.\ delete_cap_simple reply_cap_slot; when (has_fault) $ (do corrupt_tcb_intent receiver_id; update_thread_fault receiver_id (\_. False) od ); if ( \ has_fault) then set_cap (receiver_id, tcb_pending_op_slot) RunningCap else (set_cap (receiver_id,tcb_pending_op_slot) NullCap \ set_cap (receiver_id,tcb_pending_op_slot) RestartCap) od" (* Wake-up a thread waiting on an notification. *) definition do_notification_transfer :: "cdl_object_id \ unit k_monad" where "do_notification_transfer receiver_id \ do set_cap (receiver_id,tcb_pending_op_slot) RunningCap; corrupt_tcb_intent receiver_id od" (* * Signal on a notification. * * If someone is blocked on the notifications, we wake them up. Otherwise, * this is a no-(.) *) definition send_signal_bound :: "cdl_object_id \ unit k_monad" where "send_signal_bound ntfn_id \ do bound_tcbs \ gets $ get_waiting_sync_bound_ntfn_threads ntfn_id; if (bound_tcbs \ {}) then do t \ select bound_tcbs; set_cap (t, tcb_pending_op_slot) NullCap; do_notification_transfer t od else return () od" definition send_signal :: "cdl_object_id \ unit k_monad" where "send_signal ep_id \ (do waiters \ gets $ get_waiting_ntfn_recv_threads ep_id; t \ option_select waiters; case t of None \ return () | Some receiver \ do_notification_transfer receiver od) \ send_signal_bound ep_id" (* * Receive a signal (receive from a notification). * * We will either receive data or block waiting. *) definition recv_signal :: "cdl_object_id \ cdl_cap \ unit k_monad" where "recv_signal tcb_id_receiver ep_cap \ do ep_id \ return $ cap_object ep_cap; block_thread_on_ipc tcb_id_receiver (PendingNtfnRecvCap ep_id) \ corrupt_tcb_intent tcb_id_receiver od" (* * Send an IPC to the given endpoint. If someone is waiting, we wake * them up. Otherwise, we put the sender to sleep. *) definition send_ipc :: "bool \ bool \ cdl_badge \ bool \ bool \ cdl_object_id \ cdl_object_id \ unit k_monad" where "send_ipc block call badge can_grant can_grant_reply tcb_id_sender ep_id \ do waiters \ gets $ get_waiting_sync_recv_threads ep_id; t \ option_select waiters; case t of None \ if block then block_thread_on_ipc tcb_id_sender (PendingSyncSendCap ep_id badge call can_grant can_grant_reply False) else return () | Some tcb_id_receiver \ do \ \liftM instead of bind+return avoids early unfolding in send_ipc_corres\ recv_state \ liftM (\tcb. the (cdl_tcb_caps tcb tcb_pending_op_slot)) $ get_thread tcb_id_receiver; reply_can_grant \ (case recv_state of PendingSyncRecvCap target False receiver_grant \ do do_ipc_transfer (Some ep_id) tcb_id_sender tcb_id_receiver can_grant; return receiver_grant od | _ \ fail); set_cap (tcb_id_receiver,tcb_pending_op_slot) RunningCap; (when (can_grant \ can_grant_reply) $ (inject_reply_cap tcb_id_sender tcb_id_receiver reply_can_grant)) \ set_cap (tcb_id_sender,tcb_pending_op_slot) NullCap \ return () od od" (* * Receive an IPC from the given endpoint. If someone is waiting, we * wake them up. Otherwise, we put the receiver to sleep. *) definition receive_sync :: "cdl_object_id \ cdl_object_id \ bool \ unit k_monad" where "receive_sync thread ep_id receiver_can_grant \ do waiters \ gets $ get_waiting_sync_send_threads ep_id; waiter \ option_select waiters; (case waiter of None \ block_thread_on_ipc thread (PendingSyncRecvCap ep_id False receiver_can_grant) \ corrupt_tcb_intent thread | Some tcb_id_sender \ (do tcb \ get_thread tcb_id_sender; case ((cdl_tcb_caps tcb) tcb_pending_op_slot) of Some (PendingSyncSendCap target _ call can_grant can_grant_reply fault) \ (do do_ipc_transfer (Some ep_id) tcb_id_sender thread can_grant; (when (can_grant \ can_grant_reply) $ (inject_reply_cap tcb_id_sender thread receiver_can_grant)) \ set_cap (tcb_id_sender, tcb_pending_op_slot) RunningCap \ set_cap (tcb_id_sender, tcb_pending_op_slot) NullCap od) od) ) od" (* This is more nonderministic than is really required, but it makes the refinement proofs much easier *) definition receive_ipc :: "cdl_object_id \ cdl_object_id \ bool \ unit k_monad" where "receive_ipc thread ep_id can_grant \ corrupt_tcb_intent thread \ receive_sync thread ep_id can_grant" definition invoke_endpoint :: "bool \ bool \ cdl_endpoint_invocation \ unit k_monad" where "invoke_endpoint is_call can_block params \ case params of SyncMessage badge can_grant can_grant_reply ep_id \ do thread \ gets_the cdl_current_thread; send_ipc can_block is_call badge can_grant can_grant_reply thread ep_id od" definition invoke_notification :: "cdl_notification_invocation \ unit k_monad" where "invoke_notification params \ case params of Signal badge ep_id \ send_signal ep_id" definition invoke_reply :: "cdl_reply_invocation \ unit k_monad" where "invoke_reply params \ case params of ReplyMessage recv reply_cap_ref rights \ do send \ gets_the cdl_current_thread; do_reply_transfer send recv reply_cap_ref rights od" (* * Send a fault IPC to the given thread's fault handler. *) definition send_fault_ipc :: "cdl_object_id \ unit fault_monad" where "send_fault_ipc tcb_id \ doE \ \Lookup where we should send the fault IPC to.\ tcb \ liftE $ get_thread tcb_id; target_ep_cptr \ returnOk $ cdl_tcb_fault_endpoint tcb; handler_cap \ lookup_cap tcb_id target_ep_cptr; (case handler_cap of EndpointCap ref badge rights \ if Write \ rights \ (Grant \ rights \ GrantReply \ rights) then liftE $ do update_thread_fault tcb_id (\_. True); send_ipc True True badge (Grant \ rights) True tcb_id ref od else throw | _ \ throw) odE" (* * Handle a fault caused by the current thread. * * The abstract spec adds two additional parameters: * * 1. The fault type (which we abstract away); * * 2. The thread causing the fault (which always turns out * to be the current thread). *) definition handle_fault :: "unit k_monad" where "handle_fault \ do tcb_id \ gets_the cdl_current_thread; (send_fault_ipc tcb_id (\_. KHeap_D.set_cap (tcb_id,tcb_pending_op_slot) cdl_cap.NullCap)) od" end