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 = 0;
54         }
55     }
56 
57     return dataQueue;
58 }
59 
60 IOSharedDataQueue *IOSharedDataQueue::withEntries(UInt32 numEntries, UInt32 entrySize)
61 {
62     IOSharedDataQueue *dataQueue = new IOSharedDataQueue;
63 
64     if (dataQueue) {
65         if (!dataQueue->initWithEntries(numEntries, entrySize)) {
66             dataQueue->release();
67             dataQueue = 0;
68         }
69     }
70 
71     return dataQueue;
72 }
73 
74 Boolean IOSharedDataQueue::initWithCapacity(UInt32 size)
75 {
76     IODataQueueAppendix *   appendix;
77     vm_size_t               allocSize;
78 
79     if (!super::init()) {
80         return false;
81     }
82 
83     _reserved = (ExpansionData *)IOMalloc(sizeof(struct ExpansionData));
84     if (!_reserved) {
85         return false;
86     }
87 
88     if (size > UINT32_MAX - DATA_QUEUE_MEMORY_HEADER_SIZE - DATA_QUEUE_MEMORY_APPENDIX_SIZE) {
89         return false;
90     }
91 
92     allocSize = round_page(size + DATA_QUEUE_MEMORY_HEADER_SIZE + DATA_QUEUE_MEMORY_APPENDIX_SIZE);
93 
94     if (allocSize < size) {
95         return false;
96     }
97 
98     dataQueue = (IODataQueueMemory *)IOMallocAligned(allocSize, PAGE_SIZE);
99     if (dataQueue == 0) {
100         return false;
101     }
102     bzero(dataQueue, allocSize);
103 
104     dataQueue->queueSize    = size;
105 //  dataQueue->head         = 0;
106 //  dataQueue->tail         = 0;
107 
108     if (!setQueueSize(size)) {
109         return false;
110     }
111 
112     appendix            = (IODataQueueAppendix *)((UInt8 *)dataQueue + size + DATA_QUEUE_MEMORY_HEADER_SIZE);
113     appendix->version   = 0;
114 
115     if (!notifyMsg) {
116         notifyMsg = IOMalloc(sizeof(mach_msg_header_t));
117         if (!notifyMsg)
118             return false;
119     }
120     bzero(notifyMsg, sizeof(mach_msg_header_t));
121 
122     setNotificationPort(MACH_PORT_NULL);
123 
124     return true;
125 }
126 
127 void IOSharedDataQueue::free()
128 {
129     if (dataQueue) {
130         IOFreeAligned(dataQueue, round_page(getQueueSize() + DATA_QUEUE_MEMORY_HEADER_SIZE + DATA_QUEUE_MEMORY_APPENDIX_SIZE));
131         dataQueue = NULL;
132         if (notifyMsg) {
133             IOFree(notifyMsg, sizeof(mach_msg_header_t));
134             notifyMsg = NULL;
135         }
136     }
137 
138     if (_reserved) {
139         IOFree (_reserved, sizeof(struct ExpansionData));
140         _reserved = NULL;
141     }
142 
143     super::free();
144 }
145 
146 IOMemoryDescriptor *IOSharedDataQueue::getMemoryDescriptor()
147 {
148     IOMemoryDescriptor *descriptor = 0;
149 
150     if (dataQueue != 0) {
151         descriptor = IOMemoryDescriptor::withAddress(dataQueue, getQueueSize() + DATA_QUEUE_MEMORY_HEADER_SIZE + DATA_QUEUE_MEMORY_APPENDIX_SIZE, kIODirectionOutIn);
152     }
153 
154     return descriptor;
155 }
156 
157 
158 IODataQueueEntry * IOSharedDataQueue::peek()
159 {
160     IODataQueueEntry *entry = 0;
161 
162     if (dataQueue && (dataQueue->head != dataQueue->tail)) {
163         IODataQueueEntry *  head        = 0;
164         UInt32              headSize    = 0;
165         UInt32              headOffset  = dataQueue->head;
166         UInt32              queueSize   = getQueueSize();
167 
168         if (headOffset >= queueSize) {
169             return NULL;
170         }
171 
172         head         = (IODataQueueEntry *)((char *)dataQueue->queue + headOffset);
173         headSize     = head->size;
174 
175         // Check if there's enough room before the end of the queue for a header.
176         // If there is room, check if there's enough room to hold the header and
177         // the data.
178 
179         if ((headOffset > UINT32_MAX - DATA_QUEUE_ENTRY_HEADER_SIZE) ||
180             (headOffset + DATA_QUEUE_ENTRY_HEADER_SIZE > queueSize) ||
181             (headOffset + DATA_QUEUE_ENTRY_HEADER_SIZE > UINT32_MAX - headSize) ||
182             (headOffset + headSize + DATA_QUEUE_ENTRY_HEADER_SIZE > queueSize)) {
183             // No room for the header or the data, wrap to the beginning of the queue.
184             // Note: wrapping even with the UINT32_MAX checks, as we have to support
185             // queueSize of UINT32_MAX
186             entry = dataQueue->queue;
187         } else {
188             entry = head;
189         }
190     }
191 
192     return entry;
193 }
194 
195 Boolean IOSharedDataQueue::enqueue(void * data, UInt32 dataSize)
196 {
197     const UInt32       head      = dataQueue->head;  // volatile
198     const UInt32       tail      = dataQueue->tail;
199     const UInt32       entrySize = dataSize + DATA_QUEUE_ENTRY_HEADER_SIZE;
200     IODataQueueEntry * entry;
201 
202     // Check for overflow of entrySize
203     if (dataSize > UINT32_MAX - DATA_QUEUE_ENTRY_HEADER_SIZE) {
204         return false;
205     }
206     // Check for underflow of (getQueueSize() - tail)
207     if (getQueueSize() < tail || getQueueSize() < head) {
208         return false;
209     }
210 
211     if ( tail >= head )
212     {
213         // Is there enough room at the end for the entry?
214         if ((entrySize <= UINT32_MAX - tail) &&
215             ((tail + entrySize) <= getQueueSize()) )
216         {
217             entry = (IODataQueueEntry *)((UInt8 *)dataQueue->queue + tail);
218 
219             entry->size = dataSize;
220             memcpy(&entry->data, data, dataSize);
221 
222             // The tail can be out of bound when the size of the new entry
223             // exactly matches the available space at the end of the queue.
224             // The tail can range from 0 to dataQueue->queueSize inclusive.
225 
226             OSAddAtomic(entrySize, (SInt32 *)&dataQueue->tail);
227         }
228         else if ( head > entrySize )     // Is there enough room at the beginning?
229         {
230             // Wrap around to the beginning, but do not allow the tail to catch
231             // up to the head.
232 
233             dataQueue->queue->size = dataSize;
234 
235             // We need to make sure that there is enough room to set the size before
236             // doing this. The user client checks for this and will look for the size
237             // at the beginning if there isn't room for it at the end.
238 
239             if ( ( getQueueSize() - tail ) >= DATA_QUEUE_ENTRY_HEADER_SIZE )
240             {
241                 ((IODataQueueEntry *)((UInt8 *)dataQueue->queue + tail))->size = dataSize;
242             }
243 
244             memcpy(&dataQueue->queue->data, data, dataSize);
245             OSCompareAndSwap(dataQueue->tail, entrySize, &dataQueue->tail);
246         }
247         else
248         {
249             return false;    // queue is full
250         }
251     }
252     else
253     {
254         // Do not allow the tail to catch up to the head when the queue is full.
255         // That's why the comparison uses a '>' rather than '>='.
256 
257         if ( (head - tail) > entrySize )
258         {
259             entry = (IODataQueueEntry *)((UInt8 *)dataQueue->queue + tail);
260 
261             entry->size = dataSize;
262             memcpy(&entry->data, data, dataSize);
263             OSAddAtomic(entrySize, (SInt32 *)&dataQueue->tail);
264         }
265         else
266         {
267             return false;    // queue is full
268         }
269     }
270 
271     // Send notification (via mach message) that data is available.
272 
273     if ( ( head == tail )                                                   /* queue was empty prior to enqueue() */
274         ||   ( dataQueue->head == tail ) )   /* queue was emptied during enqueue() */
275     {
276         sendDataAvailableNotification();
277     }
278 
279     return true;
280 }
281 
282 Boolean IOSharedDataQueue::dequeue(void *data, UInt32 *dataSize)
283 {
284     Boolean             retVal          = TRUE;
285     IODataQueueEntry *  entry           = 0;
286     UInt32              entrySize       = 0;
287     UInt32              newHeadOffset   = 0;
288 
289     if (dataQueue) {
290         if (dataQueue->head != dataQueue->tail) {
291             IODataQueueEntry *  head        = 0;
292             UInt32              headSize    = 0;
293             UInt32              headOffset  = dataQueue->head;
294             UInt32              queueSize   = getQueueSize();
295 
296             if (headOffset > queueSize) {
297                 return false;
298             }
299 
300             head         = (IODataQueueEntry *)((char *)dataQueue->queue + headOffset);
301             headSize     = head->size;
302 
303             // we wrapped around to beginning, so read from there
304             // either there was not even room for the header
305             if ((headOffset > UINT32_MAX - DATA_QUEUE_ENTRY_HEADER_SIZE) ||
306                 (headOffset + DATA_QUEUE_ENTRY_HEADER_SIZE > queueSize) ||
307                 // or there was room for the header, but not for the data
308                 (headOffset + DATA_QUEUE_ENTRY_HEADER_SIZE > UINT32_MAX - headSize) ||
309                 (headOffset + headSize + DATA_QUEUE_ENTRY_HEADER_SIZE > queueSize)) {
310                 // Note: we have to wrap to the beginning even with the UINT32_MAX checks
311                 // because we have to support a queueSize of UINT32_MAX.
312                 entry           = dataQueue->queue;
313                 entrySize       = entry->size;
314                 if ((entrySize > UINT32_MAX - DATA_QUEUE_ENTRY_HEADER_SIZE) ||
315                     (entrySize + DATA_QUEUE_ENTRY_HEADER_SIZE > queueSize)) {
316                     return false;
317                 }
318                 newHeadOffset   = entrySize + DATA_QUEUE_ENTRY_HEADER_SIZE;
319                 // else it is at the end
320             } else {
321                 entry           = head;
322                 entrySize       = entry->size;
323                 if ((entrySize > UINT32_MAX - DATA_QUEUE_ENTRY_HEADER_SIZE) ||
324                     (entrySize + DATA_QUEUE_ENTRY_HEADER_SIZE > UINT32_MAX - headOffset) ||
325                     (entrySize + DATA_QUEUE_ENTRY_HEADER_SIZE + headOffset > queueSize)) {
326                     return false;
327                 }
328                 newHeadOffset   = headOffset + entrySize + DATA_QUEUE_ENTRY_HEADER_SIZE;
329             }
330         }
331 
332         if (entry) {
333             if (data) {
334                 if (dataSize) {
335                     if (entrySize <= *dataSize) {
336                         memcpy(data, &(entry->data), entrySize);
337                         OSCompareAndSwap( dataQueue->head, newHeadOffset, (SInt32 *)&dataQueue->head);
338                     } else {
339                         retVal = FALSE;
340                     }
341                 } else {
342                     retVal = FALSE;
343                 }
344             } else {
345                 OSCompareAndSwap( dataQueue->head, newHeadOffset, (SInt32 *)&dataQueue->head);
346             }
347 
348             if (dataSize) {
349                 *dataSize = entrySize;
350             }
351         } else {
352             retVal = FALSE;
353         }
354     } else {
355         retVal = FALSE;
356     }
357 
358     return retVal;
359 }
360 
361 UInt32 IOSharedDataQueue::getQueueSize()
362 {
363     if (!_reserved) {
364         return 0;
365     }
366     return _reserved->queueSize;
367 }
368 
369 Boolean IOSharedDataQueue::setQueueSize(UInt32 size)
370 {
371     if (!_reserved) {
372         return false;
373     }
374     _reserved->queueSize = size;
375     return true;
376 }
377 
378 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 0);
379 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 1);
380 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 2);
381 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 3);
382 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 4);
383 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 5);
384 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 6);
385 OSMetaClassDefineReservedUnused(IOSharedDataQueue, 7);
386