]> git.saurik.com Git - apple/xnu.git/blob - iokit/Kernel/IOSharedDataQueue.cpp
xnu-6153.141.1.tar.gz
[apple/xnu.git] / iokit / Kernel / IOSharedDataQueue.cpp
1 /*
2 * Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29 #include <IOKit/IOSharedDataQueue.h>
30 #include <IOKit/IODataQueueShared.h>
31 #include <IOKit/IOLib.h>
32 #include <IOKit/IOMemoryDescriptor.h>
33
34 #ifdef enqueue
35 #undef enqueue
36 #endif
37
38 #ifdef dequeue
39 #undef dequeue
40 #endif
41
42 #define super IODataQueue
43
44 OSDefineMetaClassAndStructors(IOSharedDataQueue, IODataQueue)
45
46 IOSharedDataQueue *IOSharedDataQueue::withCapacity(UInt32 size)
47 {
48 IOSharedDataQueue *dataQueue = new IOSharedDataQueue;
49
50 if (dataQueue) {
51 if (!dataQueue->initWithCapacity(size)) {
52 dataQueue->release();
53 dataQueue = NULL;
54 }
55 }
56
57 return dataQueue;
58 }
59
60 IOSharedDataQueue *
61 IOSharedDataQueue::withEntries(UInt32 numEntries, UInt32 entrySize)
62 {
63 IOSharedDataQueue *dataQueue = new IOSharedDataQueue;
64
65 if (dataQueue) {
66 if (!dataQueue->initWithEntries(numEntries, entrySize)) {
67 dataQueue->release();
68 dataQueue = NULL;
69 }
70 }
71
72 return dataQueue;
73 }
74
75 Boolean
76 IOSharedDataQueue::initWithCapacity(UInt32 size)
77 {
78 IODataQueueAppendix * appendix;
79 vm_size_t allocSize;
80
81 if (!super::init()) {
82 return false;
83 }
84
85 _reserved = (ExpansionData *)IOMalloc(sizeof(struct ExpansionData));
86 if (!_reserved) {
87 return false;
88 }
89
90 if (size > UINT32_MAX - DATA_QUEUE_MEMORY_HEADER_SIZE - DATA_QUEUE_MEMORY_APPENDIX_SIZE) {
91 return false;
92 }
93
94 allocSize = round_page(size + DATA_QUEUE_MEMORY_HEADER_SIZE + DATA_QUEUE_MEMORY_APPENDIX_SIZE);
95
96 if (allocSize < size) {
97 return false;
98 }
99
100 dataQueue = (IODataQueueMemory *)IOMallocAligned(allocSize, PAGE_SIZE);
101 if (dataQueue == NULL) {
102 return false;
103 }
104 bzero(dataQueue, allocSize);
105
106 dataQueue->queueSize = size;
107 // dataQueue->head = 0;
108 // dataQueue->tail = 0;
109
110 if (!setQueueSize(size)) {
111 return false;
112 }
113
114 appendix = (IODataQueueAppendix *)((UInt8 *)dataQueue + size + DATA_QUEUE_MEMORY_HEADER_SIZE);
115 appendix->version = 0;
116
117 if (!notifyMsg) {
118 notifyMsg = IOMalloc(sizeof(mach_msg_header_t));
119 if (!notifyMsg) {
120 return false;
121 }
122 }
123 bzero(notifyMsg, sizeof(mach_msg_header_t));
124
125 setNotificationPort(MACH_PORT_NULL);
126
127 return true;
128 }
129
130 void
131 IOSharedDataQueue::free()
132 {
133 if (dataQueue) {
134 IOFreeAligned(dataQueue, round_page(getQueueSize() + DATA_QUEUE_MEMORY_HEADER_SIZE + DATA_QUEUE_MEMORY_APPENDIX_SIZE));
135 dataQueue = NULL;
136 if (notifyMsg) {
137 IOFree(notifyMsg, sizeof(mach_msg_header_t));
138 notifyMsg = NULL;
139 }
140 }
141
142 if (_reserved) {
143 IOFree(_reserved, sizeof(struct ExpansionData));
144 _reserved = NULL;
145 }
146
147 super::free();
148 }
149
150 IOMemoryDescriptor *
151 IOSharedDataQueue::getMemoryDescriptor()
152 {
153 IOMemoryDescriptor *descriptor = NULL;
154
155 if (dataQueue != NULL) {
156 descriptor = IOMemoryDescriptor::withAddress(dataQueue, getQueueSize() + DATA_QUEUE_MEMORY_HEADER_SIZE + DATA_QUEUE_MEMORY_APPENDIX_SIZE, kIODirectionOutIn);
157 }
158
159 return descriptor;
160 }
161
162
163 IODataQueueEntry *
164 IOSharedDataQueue::peek()
165 {
166 IODataQueueEntry *entry = NULL;
167 UInt32 headOffset;
168 UInt32 tailOffset;
169
170 if (!dataQueue) {
171 return NULL;
172 }
173
174 // Read head and tail with acquire barrier
175 // See rdar://problem/40780584 for an explanation of relaxed/acquire barriers
176 headOffset = __c11_atomic_load((_Atomic UInt32 *)&dataQueue->head, __ATOMIC_RELAXED);
177 tailOffset = __c11_atomic_load((_Atomic UInt32 *)&dataQueue->tail, __ATOMIC_ACQUIRE);
178
179 if (headOffset != tailOffset) {
180 volatile IODataQueueEntry * head = NULL;
181 UInt32 headSize = 0;
182 UInt32 headOffset = dataQueue->head;
183 UInt32 queueSize = getQueueSize();
184
185 if (headOffset >= queueSize) {
186 return NULL;
187 }
188
189 head = (IODataQueueEntry *)((char *)dataQueue->queue + headOffset);
190 headSize = head->size;
191
192 // Check if there's enough room before the end of the queue for a header.
193 // If there is room, check if there's enough room to hold the header and
194 // the data.
195
196 if ((headOffset > UINT32_MAX - DATA_QUEUE_ENTRY_HEADER_SIZE) ||
197 (headOffset + DATA_QUEUE_ENTRY_HEADER_SIZE > queueSize) ||
198 (headOffset + DATA_QUEUE_ENTRY_HEADER_SIZE > UINT32_MAX - headSize) ||
199 (headOffset + headSize + DATA_QUEUE_ENTRY_HEADER_SIZE > queueSize)) {
200 // No room for the header or the data, wrap to the beginning of the queue.
201 // Note: wrapping even with the UINT32_MAX checks, as we have to support
202 // queueSize of UINT32_MAX
203 entry = dataQueue->queue;
204 } else {
205 entry = (IODataQueueEntry *)head;
206 }
207 }
208
209 return entry;
210 }
211
212 Boolean
213 IOSharedDataQueue::enqueue(void * data, UInt32 dataSize)
214 {
215 UInt32 head;
216 UInt32 tail;
217 UInt32 newTail;
218 const UInt32 entrySize = dataSize + DATA_QUEUE_ENTRY_HEADER_SIZE;
219 IODataQueueEntry * entry;
220
221 // Force a single read of head and tail
222 // See rdar://problem/40780584 for an explanation of relaxed/acquire barriers
223 tail = __c11_atomic_load((_Atomic UInt32 *)&dataQueue->tail, __ATOMIC_RELAXED);
224 head = __c11_atomic_load((_Atomic UInt32 *)&dataQueue->head, __ATOMIC_ACQUIRE);
225
226 // Check for overflow of entrySize
227 if (dataSize > UINT32_MAX - DATA_QUEUE_ENTRY_HEADER_SIZE) {
228 return false;
229 }
230 // Check for underflow of (getQueueSize() - tail)
231 if (getQueueSize() < tail || getQueueSize() < head) {
232 return false;
233 }
234
235 if (tail >= head) {
236 // Is there enough room at the end for the entry?
237 if ((entrySize <= UINT32_MAX - tail) &&
238 ((tail + entrySize) <= getQueueSize())) {
239 entry = (IODataQueueEntry *)((UInt8 *)dataQueue->queue + tail);
240
241 entry->size = dataSize;
242 __nochk_memcpy(&entry->data, data, dataSize);
243
244 // The tail can be out of bound when the size of the new entry
245 // exactly matches the available space at the end of the queue.
246 // The tail can range from 0 to dataQueue->queueSize inclusive.
247
248 newTail = tail + entrySize;
249 } else if (head > entrySize) { // Is there enough room at the beginning?
250 // Wrap around to the beginning, but do not allow the tail to catch
251 // up to the head.
252
253 dataQueue->queue->size = dataSize;
254
255 // We need to make sure that there is enough room to set the size before
256 // doing this. The user client checks for this and will look for the size
257 // at the beginning if there isn't room for it at the end.
258
259 if ((getQueueSize() - tail) >= DATA_QUEUE_ENTRY_HEADER_SIZE) {
260 ((IODataQueueEntry *)((UInt8 *)dataQueue->queue + tail))->size = dataSize;
261 }
262
263 __nochk_memcpy(&dataQueue->queue->data, data, dataSize);
264 newTail = entrySize;
265 } else {
266 return false; // queue is full
267 }
268 } else {
269 // Do not allow the tail to catch up to the head when the queue is full.
270 // That's why the comparison uses a '>' rather than '>='.
271
272 if ((head - tail) > entrySize) {
273 entry = (IODataQueueEntry *)((UInt8 *)dataQueue->queue + tail);
274
275 entry->size = dataSize;
276 __nochk_memcpy(&entry->data, data, dataSize);
277 newTail = tail + entrySize;
278 } else {
279 return false; // queue is full
280 }
281 }
282
283 // Publish the data we just enqueued
284 __c11_atomic_store((_Atomic UInt32 *)&dataQueue->tail, newTail, __ATOMIC_RELEASE);
285
286 if (tail != head) {
287 //
288 // The memory barrier below paris with the one in ::dequeue
289 // so that either our store to the tail cannot be missed by
290 // the next dequeue attempt, or we will observe the dequeuer
291 // making the queue empty.
292 //
293 // Of course, if we already think the queue is empty,
294 // there's no point paying this extra cost.
295 //
296 __c11_atomic_thread_fence(__ATOMIC_SEQ_CST);
297 head = __c11_atomic_load((_Atomic UInt32 *)&dataQueue->head, __ATOMIC_RELAXED);
298 }
299
300 if (tail == head) {
301 // Send notification (via mach message) that data is now available.
302 sendDataAvailableNotification();
303 }
304 return true;
305 }
306
307 Boolean
308 IOSharedDataQueue::dequeue(void *data, UInt32 *dataSize)
309 {
310 Boolean retVal = TRUE;
311 volatile IODataQueueEntry * entry = NULL;
312 UInt32 entrySize = 0;
313 UInt32 headOffset = 0;
314 UInt32 tailOffset = 0;
315 UInt32 newHeadOffset = 0;
316
317 if (!dataQueue || (data && !dataSize)) {
318 return false;
319 }
320
321 // Read head and tail with acquire barrier
322 // See rdar://problem/40780584 for an explanation of relaxed/acquire barriers
323 headOffset = __c11_atomic_load((_Atomic UInt32 *)&dataQueue->head, __ATOMIC_RELAXED);
324 tailOffset = __c11_atomic_load((_Atomic UInt32 *)&dataQueue->tail, __ATOMIC_ACQUIRE);
325
326 if (headOffset != tailOffset) {
327 volatile IODataQueueEntry * head = NULL;
328 UInt32 headSize = 0;
329 UInt32 queueSize = getQueueSize();
330
331 if (headOffset > queueSize) {
332 return false;
333 }
334
335 head = (IODataQueueEntry *)((char *)dataQueue->queue + headOffset);
336 headSize = head->size;
337
338 // we wrapped around to beginning, so read from there
339 // either there was not even room for the header
340 if ((headOffset > UINT32_MAX - DATA_QUEUE_ENTRY_HEADER_SIZE) ||
341 (headOffset + DATA_QUEUE_ENTRY_HEADER_SIZE > queueSize) ||
342 // or there was room for the header, but not for the data
343 (headOffset + DATA_QUEUE_ENTRY_HEADER_SIZE > UINT32_MAX - headSize) ||
344 (headOffset + headSize + DATA_QUEUE_ENTRY_HEADER_SIZE > queueSize)) {
345 // Note: we have to wrap to the beginning even with the UINT32_MAX checks
346 // because we have to support a queueSize of UINT32_MAX.
347 entry = dataQueue->queue;
348 entrySize = entry->size;
349 if ((entrySize > UINT32_MAX - DATA_QUEUE_ENTRY_HEADER_SIZE) ||
350 (entrySize + DATA_QUEUE_ENTRY_HEADER_SIZE > queueSize)) {
351 return false;
352 }
353 newHeadOffset = entrySize + DATA_QUEUE_ENTRY_HEADER_SIZE;
354 // else it is at the end
355 } else {
356 entry = head;
357 entrySize = entry->size;
358 if ((entrySize > UINT32_MAX - DATA_QUEUE_ENTRY_HEADER_SIZE) ||
359 (entrySize + DATA_QUEUE_ENTRY_HEADER_SIZE > UINT32_MAX - headOffset) ||
360 (entrySize + DATA_QUEUE_ENTRY_HEADER_SIZE + headOffset > queueSize)) {
361 return false;
362 }
363 newHeadOffset = headOffset + entrySize + DATA_QUEUE_ENTRY_HEADER_SIZE;
364 }
365 } else {
366 // empty queue
367 return false;
368 }
369
370 if (data) {
371 if (entrySize > *dataSize) {
372 // not enough space
373 return false;
374 }
375 __nochk_memcpy(data, (void *)entry->data, entrySize);
376 *dataSize = entrySize;
377 }
378
379 __c11_atomic_store((_Atomic UInt32 *)&dataQueue->head, newHeadOffset, __ATOMIC_RELEASE);
380
381 if (newHeadOffset == tailOffset) {
382 //
383 // If we are making the queue empty, then we need to make sure
384 // that either the enqueuer notices, or we notice the enqueue
385 // that raced with our making of the queue empty.
386 //
387 __c11_atomic_thread_fence(__ATOMIC_SEQ_CST);
388 }
389
390 return retVal;
391 }
392
393 UInt32
394 IOSharedDataQueue::getQueueSize()
395 {
396 if (!_reserved) {
397 return 0;
398 }
399 return _reserved->queueSize;
400 }
401
402 Boolean
403 IOSharedDataQueue::setQueueSize(UInt32 size)
404 {
405 if (!_reserved) {
406 return false;
407 }
408 _reserved->queueSize = size;
409 return true;
410 }
411
412 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 0);
413 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 1);
414 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 2);
415 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 3);
416 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 4);
417 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 5);
418 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 6);
419 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 7);