1 // Copyright 2016 The Fuchsia Authors
2 //
3 // Use of this source code is governed by a MIT-style
4 // license that can be found in the LICENSE file or at
5 // https://opensource.org/licenses/MIT
6 
7 #include <object/futex_node.h>
8 #include <object/thread_dispatcher.h>
9 
10 #include <assert.h>
11 #include <err.h>
12 #include <fbl/mutex.h>
13 #include <platform.h>
14 #include <trace.h>
15 #include <kernel/thread_lock.h>
16 #include <zircon/types.h>
17 
18 #define LOCAL_TRACE 0
19 
FutexNode()20 FutexNode::FutexNode() {
21     LTRACE_ENTRY;
22 }
23 
~FutexNode()24 FutexNode::~FutexNode() {
25     LTRACE_ENTRY;
26 
27     DEBUG_ASSERT(!IsInQueue());
28 }
29 
IsInQueue() const30 bool FutexNode::IsInQueue() const {
31     DEBUG_ASSERT((queue_next_ == nullptr) == (queue_prev_ == nullptr));
32     return queue_next_ != nullptr;
33 }
34 
SetAsSingletonList()35 void FutexNode::SetAsSingletonList() {
36     DEBUG_ASSERT(!IsInQueue());
37     queue_prev_ = this;
38     queue_next_ = this;
39 }
40 
AppendList(FutexNode * head)41 void FutexNode::AppendList(FutexNode* head) {
42     SpliceNodes(this, head);
43 }
44 
45 // This removes |node| from the list whose first node is |list_head|.  This
46 // returns the new list head, or nullptr if the list has become empty.
RemoveNodeFromList(FutexNode * list_head,FutexNode * node)47 FutexNode* FutexNode::RemoveNodeFromList(FutexNode* list_head,
48                                          FutexNode* node) {
49     if (node->queue_next_ == node) {
50         DEBUG_ASSERT(node->queue_prev_ == node);
51         // This list is a singleton, so after removing the node, the list
52         // becomes empty.
53         list_head = nullptr;
54     } else {
55         if (node == list_head) {
56             // This node is the list head, so adjust the list head to be
57             // the next node.
58             list_head = node->queue_next_;
59         }
60 
61         // Remove the node from the list.
62         node->queue_next_->queue_prev_ = node->queue_prev_;
63         node->queue_prev_->queue_next_ = node->queue_next_;
64     }
65     node->MarkAsNotInQueue();
66     return list_head;
67 }
68 
69 // This removes up to |count| threads from the list specified by |node|,
70 // and it wakes those threads.  It returns the new list head (i.e. the list
71 // of remaining nodes), which may be null (empty).
72 //
73 // This will always remove at least one node, because it requires that
74 // |count| is non-zero and |list_head| is a non-empty list.
75 //
76 // RemoveFromHead() is similar, except that it produces a list of removed
77 // threads without waking them.
WakeThreads(FutexNode * node,uint32_t count,uintptr_t old_hash_key)78 FutexNode* FutexNode::WakeThreads(FutexNode* node, uint32_t count,
79                                   uintptr_t old_hash_key) {
80     ASSERT(node);
81     ASSERT(count != 0);
82 
83     FutexNode* const list_end = node->queue_prev_;
84     for (uint32_t i = 0; i < count; i++) {
85         DEBUG_ASSERT(node->GetKey() == old_hash_key);
86         // Clear this field to avoid any possible confusion.
87         node->set_hash_key(0);
88 
89         const bool is_last_node = (node == list_end);
90         FutexNode* next = node->queue_next_;
91         // This call can cause |node| to be freed, so we must not
92         // dereference |node| after this.
93         node->WakeThread();
94 
95         if (is_last_node) {
96             // We have reached the end of the list, so we are removing all
97             // the entries from the list.  Return an empty list of
98             // remaining nodes.
99             return nullptr;
100         }
101         node = next;
102     }
103 
104     // Restore the list invariant for the list of remaining waiter nodes.
105     RelinkAsAdjacent(list_end, node);
106     return node;
107 }
108 
109 // This removes up to |count| nodes from |list_head|.  It returns the new
110 // list head (i.e. the list of remaining nodes), which may be null (empty).
111 // On return, |list_head| is the list of nodes that were removed --
112 // |list_head| remains a valid list.
113 //
114 // This will always remove at least one node, because it requires that
115 // |count| is non-zero and |list_head| is a non-empty list.
116 //
117 // WakeThreads() is similar, except that it wakes the threads that it
118 // removes from the list.
RemoveFromHead(FutexNode * list_head,uint32_t count,uintptr_t old_hash_key,uintptr_t new_hash_key)119 FutexNode* FutexNode::RemoveFromHead(FutexNode* list_head, uint32_t count,
120                                      uintptr_t old_hash_key,
121                                      uintptr_t new_hash_key) {
122     ASSERT(list_head);
123     ASSERT(count != 0);
124 
125     FutexNode* node = list_head;
126     for (uint32_t i = 0; i < count; i++) {
127         DEBUG_ASSERT(node->GetKey() == old_hash_key);
128         // For requeuing, update the key so that FutexWait() can remove the
129         // thread from its current queue if the wait operation times out.
130         node->set_hash_key(new_hash_key);
131 
132         node = node->queue_next_;
133         if (node == list_head) {
134             // We have reached the end of the list, so we are removing all
135             // the entries from the list.  Return an empty list of
136             // remaining nodes.
137             return nullptr;
138         }
139     }
140 
141     // Split the list into two lists.
142     SpliceNodes(list_head, node);
143     return node;
144 }
145 
146 // This blocks the current thread.  This releases the given mutex (which
147 // must be held when BlockThread() is called).  To reduce contention, it
148 // does not reclaim the mutex on return.
BlockThread(Guard<fbl::Mutex> && adopt_guard,zx_time_t deadline,TimerSlack slack)149 zx_status_t FutexNode::BlockThread(Guard<fbl::Mutex>&& adopt_guard,
150                                    zx_time_t deadline,
151                                    TimerSlack slack) {
152     // Adopt the guarded lock from the caller. This could happen before or after
153     // the following locks because the underlying lock is held from the caller's
154     // frame. The runtime validator state is not affected by the adoption.
155     Guard<fbl::Mutex> guard{AdoptLock, ktl::move(adopt_guard)};
156 
157     Guard<spin_lock_t, IrqSave> thread_lock_guard{ThreadLock::Get()};
158     ThreadDispatcher::AutoBlocked by(ThreadDispatcher::Blocked::FUTEX);
159 
160     // We specifically want reschedule=MutexPolicy::NoReschedule here, otherwise
161     // the combination of releasing the mutex and enqueuing the current thread
162     // would not be atomic, which would mean that we could miss wakeups.
163     guard.Release(MutexPolicy::ThreadLockHeld, MutexPolicy::NoReschedule);
164 
165     thread_t* current_thread = get_current_thread();
166     zx_status_t result;
167     current_thread->interruptable = true;
168     result = wait_queue_.Block(deadline, slack);
169     current_thread->interruptable = false;
170 
171     return result;
172 }
173 
WakeThread()174 void FutexNode::WakeThread() {
175     // We must be careful to correctly handle the case where the thread
176     // for |this| wakes and exits, deleting |this|.  There are two
177     // cases to consider:
178     //  1) The thread's wait times out, or the thread is killed or
179     //     suspended.  In those cases, FutexWait() will reacquire the
180     //     FutexContext lock.  We are currently holding that lock, so
181     //     FutexWait() will not race with us.
182     //  2) The thread is woken by our wait_queue_wake_one() call.  In
183     //     this case, FutexWait() will *not* reacquire the FutexContext
184     //     lock.  To handle this correctly, we must not access |this|
185     //     after wait_queue_wake_one().
186 
187     // We must do this before we wake the thread, to handle case 2.
188     MarkAsNotInQueue();
189 
190     Guard<spin_lock_t, IrqSave> thread_lock_guard{ThreadLock::Get()};
191     wait_queue_.WakeOne(/* reschedule */ true, ZX_OK);
192 }
193 
194 // Set |node1| and |node2|'s list pointers so that |node1| is immediately
195 // before |node2| in the linked list.
RelinkAsAdjacent(FutexNode * node1,FutexNode * node2)196 void FutexNode::RelinkAsAdjacent(FutexNode* node1, FutexNode* node2) {
197     node1->queue_next_ = node2;
198     node2->queue_prev_ = node1;
199 }
200 
201 // If |node1| and |node2| are separate lists, this combines them into one
202 // list.  If |node1| and |node2| are different nodes in the same list, this
203 // splits them into two separate lists.  (This operation happens to be a
204 // self-inverse.)
SpliceNodes(FutexNode * node1,FutexNode * node2)205 void FutexNode::SpliceNodes(FutexNode* node1, FutexNode* node2) {
206     FutexNode* node1_prev = node1->queue_prev_;
207     FutexNode* node2_prev = node2->queue_prev_;
208     RelinkAsAdjacent(node1_prev, node2);
209     RelinkAsAdjacent(node2_prev, node1);
210 }
211 
MarkAsNotInQueue()212 void FutexNode::MarkAsNotInQueue() {
213     queue_next_ = nullptr;
214     // Unsetting queue_prev_ stops us from following an outdated pointer in
215     // case we make a mistake with list manipulation.  Otherwise, it is
216     // only required by the assertion in IsInQueue().
217     queue_prev_ = nullptr;
218 }
219