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