1 /*
2  * Copyright 2016 Patrick Rudolph <siro@das-labor.org>
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * on the rights to use, copy, modify, merge, publish, distribute, sub
8  * license, and/or sell copies of the Software, and to permit persons to whom
9  * the Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
18  * THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
21  * USE OR OTHER DEALINGS IN THE SOFTWARE. */
22 
23 #include "nine_queue.h"
24 #include "os/os_thread.h"
25 #include "util/macros.h"
26 #include "nine_helpers.h"
27 
28 #define NINE_CMD_BUF_INSTR (256)
29 
30 #define NINE_CMD_BUFS (32)
31 #define NINE_CMD_BUFS_MASK (NINE_CMD_BUFS - 1)
32 
33 #define NINE_QUEUE_SIZE (8192 * 16 + 128)
34 
35 #define DBG_CHANNEL DBG_DEVICE
36 
37 /*
38  * Single producer - single consumer pool queue
39  *
40  * Producer:
41  * Calls nine_queue_alloc to get a slice of memory in current cmdbuf.
42  * Calls nine_queue_flush to flush the queue by request.
43  * The queue is flushed automatically on insufficient space or once the
44  * cmdbuf contains NINE_CMD_BUF_INSTR instructions.
45  *
46  * nine_queue_flush does block, while nine_queue_alloc doesn't block.
47  *
48  * nine_queue_alloc returns NULL on insufficent space.
49  *
50  * Consumer:
51  * Calls nine_queue_wait_flush to wait for a cmdbuf.
52  * After waiting for a cmdbuf it calls nine_queue_get until NULL is returned.
53  *
54  * nine_queue_wait_flush does block, while nine_queue_get doesn't block.
55  *
56  * Constrains:
57  * Only a single consumer and a single producer are supported.
58  *
59  */
60 
61 struct nine_cmdbuf {
62     unsigned instr_size[NINE_CMD_BUF_INSTR];
63     unsigned num_instr;
64     unsigned offset;
65     void *mem_pool;
66     BOOL full;
67 };
68 
69 struct nine_queue_pool {
70     struct nine_cmdbuf pool[NINE_CMD_BUFS];
71     unsigned head;
72     unsigned tail;
73     unsigned cur_instr;
74     BOOL worker_wait;
75     pipe_condvar event_pop;
76     pipe_condvar event_push;
77     pipe_mutex mutex_pop;
78     pipe_mutex mutex_push;
79 };
80 
81 /* Consumer functions: */
82 void
83 nine_queue_wait_flush(struct nine_queue_pool* ctx)
84 {
85     struct nine_cmdbuf *cmdbuf = &ctx->pool[ctx->tail];
86 
87     /* wait for cmdbuf full */
88     pipe_mutex_lock(ctx->mutex_push);
89     while (!cmdbuf->full)
90     {
91         DBG("waiting for full cmdbuf\n");
92         pipe_condvar_wait(ctx->event_push, ctx->mutex_push);
93     }
94     DBG("got cmdbuf=%p\n", cmdbuf);
95     pipe_mutex_unlock(ctx->mutex_push);
96 
97     cmdbuf->offset = 0;
98     ctx->cur_instr = 0;
99 }
100 
101 /* Gets a pointer to the next memory slice.
102  * Does not block.
103  * Returns NULL on empty cmdbuf. */
104 void *
105 nine_queue_get(struct nine_queue_pool* ctx)
106 {
107     struct nine_cmdbuf *cmdbuf = &ctx->pool[ctx->tail];
108     unsigned offset;
109 
110     /* At this pointer there's always a cmdbuf. */
111 
112     if (ctx->cur_instr == cmdbuf->num_instr) {
113         /* signal waiting producer */
114         pipe_mutex_lock(ctx->mutex_pop);
115         DBG("freeing cmdbuf=%p\n", cmdbuf);
116         cmdbuf->full = 0;
117         pipe_condvar_signal(ctx->event_pop);
118         pipe_mutex_unlock(ctx->mutex_pop);
119 
120         ctx->tail = (ctx->tail + 1) & NINE_CMD_BUFS_MASK;
121 
122         return NULL;
123     }
124 
125     /* At this pointer there's always a cmdbuf with instruction to process. */
126     offset = cmdbuf->offset;
127     cmdbuf->offset += cmdbuf->instr_size[ctx->cur_instr];
128     ctx->cur_instr ++;
129 
130     return cmdbuf->mem_pool + offset;
131 }
132 
133 /* Producer functions: */
134 
135 /* Flushes the queue.
136  * Moves the current cmdbuf to worker thread.
137  * Blocks until next cmdbuf is free. */
138 void
139 nine_queue_flush(struct nine_queue_pool* ctx)
140 {
141     struct nine_cmdbuf *cmdbuf = &ctx->pool[ctx->head];
142 
143     DBG("flushing cmdbuf=%p instr=%d size=%d\n",
144            cmdbuf, cmdbuf->num_instr, cmdbuf->offset);
145 
146     /* Nothing to flush */
147     if (!cmdbuf->num_instr)
148         return;
149 
150     /* signal waiting worker */
151     pipe_mutex_lock(ctx->mutex_push);
152     cmdbuf->full = 1;
153     pipe_condvar_signal(ctx->event_push);
154     pipe_mutex_unlock(ctx->mutex_push);
155 
156     ctx->head = (ctx->head + 1) & NINE_CMD_BUFS_MASK;
157 
158     cmdbuf = &ctx->pool[ctx->head];
159 
160     /* wait for queue empty */
161     pipe_mutex_lock(ctx->mutex_pop);
162     while (cmdbuf->full)
163     {
164         DBG("waiting for empty cmdbuf\n");
165         pipe_condvar_wait(ctx->event_pop, ctx->mutex_pop);
166     }
167     DBG("got empty cmdbuf=%p\n", cmdbuf);
168     pipe_mutex_unlock(ctx->mutex_pop);
169     cmdbuf->offset = 0;
170     cmdbuf->num_instr = 0;
171 }
172 
173 /* Gets a a pointer to slice of memory with size @space.
174  * Does block if queue is full.
175  * Returns NULL on @space > NINE_QUEUE_SIZE. */
176 void *
177 nine_queue_alloc(struct nine_queue_pool* ctx, unsigned space)
178 {
179     unsigned offset;
180     struct nine_cmdbuf *cmdbuf = &ctx->pool[ctx->head];
181 
182     if (space > NINE_QUEUE_SIZE)
183         return NULL;
184 
185     /* at this pointer there's always a free queue available */
186 
187     if ((cmdbuf->offset + space > NINE_QUEUE_SIZE) ||
188         (cmdbuf->num_instr == NINE_CMD_BUF_INSTR)) {
189 
190         nine_queue_flush(ctx);
191 
192         cmdbuf = &ctx->pool[ctx->head];
193     }
194 
195     DBG("cmdbuf=%p space=%d\n", cmdbuf, space);
196 
197     /* at this pointer there's always a free queue with sufficient space available */
198 
199     offset = cmdbuf->offset;
200     cmdbuf->offset += space;
201     cmdbuf->instr_size[cmdbuf->num_instr] = space;
202     cmdbuf->num_instr ++;
203 
204     return cmdbuf->mem_pool + offset;
205 }
206 
207 /* Returns the current queue flush state.
208  * TRUE nothing flushed
209  * FALSE one ore more instructions queued flushed. */
210 bool
211 nine_queue_no_flushed_work(struct nine_queue_pool* ctx)
212 {
213     return (ctx->tail == ctx->head);
214 }
215 
216 /* Returns the current queue empty state.
217  * TRUE no instructions queued.
218  * FALSE one ore more instructions queued. */
219 bool
220 nine_queue_isempty(struct nine_queue_pool* ctx)
221 {
222     struct nine_cmdbuf *cmdbuf = &ctx->pool[ctx->head];
223 
224     return (ctx->tail == ctx->head) && !cmdbuf->num_instr;
225 }
226 
227 struct nine_queue_pool*
228 nine_queue_create(void)
229 {
230     unsigned i;
231     struct nine_queue_pool *ctx;
232 
233     ctx = CALLOC_STRUCT(nine_queue_pool);
234     if (!ctx)
235         goto failed;
236 
237     for (i = 0; i < NINE_CMD_BUFS; i++) {
238         ctx->pool[i].mem_pool = MALLOC(NINE_QUEUE_SIZE);
239         if (!ctx->pool[i].mem_pool)
240             goto failed;
241     }
242 
243     pipe_condvar_init(ctx->event_pop);
244     pipe_mutex_init(ctx->mutex_pop);
245 
246     pipe_condvar_init(ctx->event_push);
247     pipe_mutex_init(ctx->mutex_push);
248 
249     /* Block until first cmdbuf has been flushed. */
250     ctx->worker_wait = TRUE;
251 
252     return ctx;
253 failed:
254     if (ctx) {
255         for (i = 0; i < NINE_CMD_BUFS; i++) {
256             if (ctx->pool[i].mem_pool)
257                 FREE(ctx->pool[i].mem_pool);
258         }
259         FREE(ctx);
260     }
261     return NULL;
262 }
263 
264 void
265 nine_queue_delete(struct nine_queue_pool *ctx)
266 {
267     unsigned i;
268     pipe_mutex_destroy(ctx->mutex_pop);
269     pipe_mutex_destroy(ctx->mutex_push);
270 
271     for (i = 0; i < NINE_CMD_BUFS; i++)
272         FREE(ctx->pool[i].mem_pool);
273 
274     FREE(ctx);
275 }
276