1 | /* |
2 | Copyright (c) 2012, Broadcom Europe Ltd |
3 | All rights reserved. |
4 | |
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the following conditions are met: |
7 | * Redistributions of source code must retain the above copyright |
8 | notice, this list of conditions and the following disclaimer. |
9 | * Redistributions in binary form must reproduce the above copyright |
10 | notice, this list of conditions and the following disclaimer in the |
11 | documentation and/or other materials provided with the distribution. |
12 | * Neither the name of the copyright holder nor the |
13 | names of its contributors may be used to endorse or promote products |
14 | derived from this software without specific prior written permission. |
15 | |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY |
20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ |
27 | |
28 | #include "interface/khronos/wf/wfc_client_ipc.h" |
29 | #include "interface/khronos/wf/wfc_ipc.h" |
30 | #include "interface/vcos/vcos.h" |
31 | #include "interface/vchiq_arm/vchiq.h" |
32 | |
33 | #define VCOS_LOG_CATEGORY (&wfc_client_ipc_log_category) |
34 | |
35 | //#define WFC_FULL_LOGGING |
36 | #ifdef WFC_FULL_LOGGING |
37 | #define WFC_CLIENT_IPC_LOGLEVEL VCOS_LOG_TRACE |
38 | #else |
39 | #define WFC_CLIENT_IPC_LOGLEVEL VCOS_LOG_WARN |
40 | #endif |
41 | |
42 | #define WFC_CLIENT_IPC_MAX_WAITERS 16 |
43 | static VCOS_ONCE_T wfc_client_ipc_once = VCOS_ONCE_INIT; |
44 | static VCHIQ_INSTANCE_T wfc_client_ipc_vchiq_instance; |
45 | static VCOS_LOG_CAT_T wfc_client_ipc_log_category; |
46 | |
47 | /** Client threads use one of these to wait for |
48 | * a reply from VideoCore. |
49 | */ |
50 | typedef struct WFC_WAITER_T |
51 | { |
52 | VCOS_SEMAPHORE_T sem; |
53 | unsigned inuse; |
54 | void *dest; /**< Where to write reply */ |
55 | size_t destlen; /**< Max length for reply */ |
56 | } WFC_WAITER_T; |
57 | |
58 | /** We have an array of waiters and allocate them to waiting |
59 | * threads. They can be released back to the pool in any order. |
60 | * If there are none free, the calling thread will block until |
61 | * one becomes available. |
62 | */ |
63 | typedef struct |
64 | { |
65 | WFC_WAITER_T waiters[WFC_CLIENT_IPC_MAX_WAITERS]; |
66 | VCOS_SEMAPHORE_T sem; |
67 | } WFC_WAITPOOL_T; |
68 | |
69 | struct WFC_CLIENT_IPC_T |
70 | { |
71 | int refcount; |
72 | int keep_alive_count; |
73 | VCOS_MUTEX_T lock; |
74 | VCHIQ_SERVICE_HANDLE_T service; |
75 | WFC_WAITPOOL_T waitpool; |
76 | }; |
77 | typedef struct WFC_CLIENT_IPC_T WFC_CLIENT_IPC_T; |
78 | |
79 | /* One client per process/VC connection. Multiple threads may |
80 | * be using a single client. |
81 | */ |
82 | static WFC_CLIENT_IPC_T wfc_client_ipc; |
83 | |
84 | static void init_once(void) |
85 | { |
86 | vcos_mutex_create(&wfc_client_ipc.lock, VCOS_FUNCTION); |
87 | } |
88 | |
89 | /** Create a pool of wait-structures. |
90 | */ |
91 | static VCOS_STATUS_T wfc_client_ipc_create_waitpool(WFC_WAITPOOL_T *waitpool) |
92 | { |
93 | VCOS_STATUS_T status; |
94 | int i; |
95 | |
96 | status = vcos_semaphore_create(&waitpool->sem, VCOS_FUNCTION, |
97 | WFC_CLIENT_IPC_MAX_WAITERS); |
98 | if (status != VCOS_SUCCESS) |
99 | return status; |
100 | |
101 | for (i = 0; i < WFC_CLIENT_IPC_MAX_WAITERS; i++) |
102 | { |
103 | waitpool->waiters[i].inuse = 0; |
104 | status = vcos_semaphore_create(&waitpool->waiters[i].sem, |
105 | "wfc ipc waiter" , 0); |
106 | if (status != VCOS_SUCCESS) |
107 | break; |
108 | } |
109 | |
110 | if (status != VCOS_SUCCESS) |
111 | { |
112 | /* clean up */ |
113 | i--; |
114 | while (i >= 0) |
115 | { |
116 | vcos_semaphore_delete(&waitpool->waiters[i].sem); |
117 | i--; |
118 | } |
119 | vcos_semaphore_delete(&waitpool->sem); |
120 | } |
121 | return status; |
122 | } |
123 | |
124 | static void wfc_client_ipc_destroy_waitpool(WFC_WAITPOOL_T *waitpool) |
125 | { |
126 | int i; |
127 | |
128 | for (i = 0; i < WFC_CLIENT_IPC_MAX_WAITERS; i++) |
129 | vcos_semaphore_delete(&waitpool->waiters[i].sem); |
130 | |
131 | vcos_semaphore_delete(&waitpool->sem); |
132 | } |
133 | |
134 | /** Grab a waiter from the pool. Return immediately if one already |
135 | * available, or wait for one to become available. |
136 | */ |
137 | static WFC_WAITER_T *wfc_client_ipc_get_waiter(WFC_CLIENT_IPC_T *client) |
138 | { |
139 | int i; |
140 | WFC_WAITER_T *waiter = NULL; |
141 | |
142 | vcos_semaphore_wait(&client->waitpool.sem); |
143 | vcos_mutex_lock(&client->lock); |
144 | |
145 | for (i = 0; i < WFC_CLIENT_IPC_MAX_WAITERS; i++) |
146 | { |
147 | if (client->waitpool.waiters[i].inuse == 0) |
148 | break; |
149 | } |
150 | |
151 | /* If this fails, the semaphore isn't working */ |
152 | if (vcos_verify(i != WFC_CLIENT_IPC_MAX_WAITERS)) |
153 | { |
154 | waiter = client->waitpool.waiters + i; |
155 | waiter->inuse = 1; |
156 | } |
157 | vcos_mutex_unlock(&client->lock); |
158 | |
159 | return waiter; |
160 | } |
161 | |
162 | /** Return a waiter to the pool. |
163 | */ |
164 | static void wfc_client_ipc_release_waiter(WFC_CLIENT_IPC_T *client, WFC_WAITER_T *waiter) |
165 | { |
166 | vcos_log_trace("%s: at %p" , VCOS_FUNCTION, waiter); |
167 | |
168 | vcos_assert(waiter); |
169 | vcos_assert(waiter->inuse); |
170 | |
171 | waiter->inuse = 0; |
172 | vcos_semaphore_post(&client->waitpool.sem); |
173 | } |
174 | |
175 | /** Callback invoked by VCHIQ |
176 | */ |
177 | static VCHIQ_STATUS_T wfc_client_ipc_vchiq_callback(VCHIQ_REASON_T reason, |
178 | VCHIQ_HEADER_T *, |
179 | VCHIQ_SERVICE_HANDLE_T service, |
180 | void *context) |
181 | { |
182 | vcos_log_trace("%s: reason %d" , VCOS_FUNCTION, reason); |
183 | |
184 | switch (reason) |
185 | { |
186 | case VCHIQ_MESSAGE_AVAILABLE: |
187 | { |
188 | WFC_IPC_MSG_HEADER_T *response = (WFC_IPC_MSG_HEADER_T *)vchiq_header->data; |
189 | |
190 | vcos_assert(vchiq_header->size >= sizeof(*response)); |
191 | vcos_assert(response->magic == WFC_IPC_MSG_MAGIC); |
192 | |
193 | if (response->type == WFC_IPC_MSG_CALLBACK) |
194 | { |
195 | WFC_IPC_MSG_CALLBACK_T *callback_msg = (WFC_IPC_MSG_CALLBACK_T *)response; |
196 | WFC_CALLBACK_T cb_func = callback_msg->callback_fn.ptr; |
197 | |
198 | vcos_assert(vchiq_header->size == sizeof(*callback_msg)); |
199 | if (vcos_verify(cb_func != NULL)) |
200 | { |
201 | /* Call the client function */ |
202 | (*cb_func)(callback_msg->callback_data.ptr); |
203 | } |
204 | vchiq_release_message(service, vchiq_header); |
205 | } |
206 | else |
207 | { |
208 | WFC_WAITER_T *waiter = response->waiter.ptr; |
209 | int len; |
210 | |
211 | vcos_assert(waiter != NULL); |
212 | |
213 | vcos_log_trace("%s: waking up waiter at %p" , VCOS_FUNCTION, waiter); |
214 | vcos_assert(waiter->inuse); |
215 | |
216 | /* Limit response data length */ |
217 | len = vcos_min(waiter->destlen, vchiq_header->size - sizeof(*response)); |
218 | waiter->destlen = len; |
219 | |
220 | vcos_log_trace("%s: copying %d bytes from %p to %p first word 0x%x" , |
221 | VCOS_FUNCTION, len, response + 1, waiter->dest, *(uint32_t *)(response + 1)); |
222 | memcpy(waiter->dest, response + 1, len); |
223 | |
224 | vchiq_release_message(service, vchiq_header); |
225 | vcos_semaphore_post(&waiter->sem); |
226 | } |
227 | } |
228 | break; |
229 | case VCHIQ_BULK_TRANSMIT_DONE: |
230 | case VCHIQ_BULK_RECEIVE_DONE: |
231 | case VCHIQ_BULK_RECEIVE_ABORTED: |
232 | case VCHIQ_BULK_TRANSMIT_ABORTED: |
233 | { |
234 | vcos_assert_msg(0, "bulk messages not used" ); |
235 | vchiq_release_message(service, vchiq_header); |
236 | } |
237 | break; |
238 | case VCHIQ_SERVICE_OPENED: |
239 | vcos_log_trace("%s: service %x opened" , VCOS_FUNCTION, service); |
240 | break; |
241 | case VCHIQ_SERVICE_CLOSED: |
242 | vcos_log_trace("%s: service %x closed" , VCOS_FUNCTION, service); |
243 | break; |
244 | default: |
245 | vcos_assert_msg(0, "unexpected message reason" ); |
246 | break; |
247 | } |
248 | return VCHIQ_SUCCESS; |
249 | } |
250 | |
251 | static VCOS_STATUS_T wfc_client_ipc_send_client_pid(void) |
252 | { |
253 | WFC_IPC_MSG_SET_CLIENT_PID_T msg; |
254 | uint64_t pid = vcos_process_id_current(); |
255 | uint32_t pid_lo = (uint32_t) pid; |
256 | uint32_t pid_hi = (uint32_t) (pid >> 32); |
257 | |
258 | msg.header.type = WFC_IPC_MSG_SET_CLIENT_PID; |
259 | msg.pid_lo = pid_lo; |
260 | msg.pid_hi = pid_hi; |
261 | |
262 | vcos_log_trace("%s: setting client pid to 0x%x%08x" , VCOS_FUNCTION, pid_hi, pid_lo); |
263 | |
264 | return wfc_client_ipc_send(&msg.header, sizeof(msg)); |
265 | } |
266 | |
267 | VCOS_STATUS_T wfc_client_ipc_sendwait(WFC_IPC_MSG_HEADER_T *msg, |
268 | size_t size, |
269 | void *dest, |
270 | size_t *destlen) |
271 | { |
272 | VCOS_STATUS_T ret = VCOS_SUCCESS; |
273 | WFC_WAITER_T *waiter; |
274 | VCHIQ_STATUS_T vst; |
275 | VCHIQ_ELEMENT_T elems[] = {{msg, size}}; |
276 | |
277 | vcos_assert(size >= sizeof(*msg)); |
278 | vcos_assert(dest); |
279 | |
280 | if (!vcos_verify(wfc_client_ipc.refcount)) |
281 | { |
282 | VCOS_ALERT("%s: client uninitialised" , VCOS_FUNCTION); |
283 | /* Client has not been initialised */ |
284 | return VCOS_EINVAL; |
285 | } |
286 | |
287 | msg->magic = WFC_IPC_MSG_MAGIC; |
288 | |
289 | waiter = wfc_client_ipc_get_waiter(&wfc_client_ipc); |
290 | waiter->dest = dest; |
291 | waiter->destlen = *destlen; |
292 | msg->waiter.ptr = waiter; |
293 | |
294 | wfc_client_ipc_use_keep_alive(); |
295 | |
296 | vcos_log_trace("%s: wait %p, reply to %p" , VCOS_FUNCTION, waiter, dest); |
297 | vst = vchiq_queue_message(wfc_client_ipc.service, elems, 1); |
298 | |
299 | if (vst != VCHIQ_SUCCESS) |
300 | { |
301 | vcos_log_error("%s: failed to queue, 0x%x" , VCOS_FUNCTION, vst); |
302 | ret = VCOS_ENXIO; |
303 | goto completed; |
304 | } |
305 | |
306 | /* now wait for the reply... |
307 | * |
308 | * FIXME: we could do with a timeout here. Need to be careful to cancel |
309 | * the semaphore on a timeout. |
310 | */ |
311 | vcos_semaphore_wait(&waiter->sem); |
312 | vcos_log_trace("%s: got reply (len %i/%i)" , VCOS_FUNCTION, (int)*destlen, (int)waiter->destlen); |
313 | *destlen = waiter->destlen; |
314 | |
315 | /* Drop through completion code */ |
316 | |
317 | completed: |
318 | wfc_client_ipc_release_waiter(&wfc_client_ipc, waiter); |
319 | wfc_client_ipc_release_keep_alive(); |
320 | |
321 | return ret; |
322 | } |
323 | |
324 | VCOS_STATUS_T wfc_client_ipc_send(WFC_IPC_MSG_HEADER_T *msg, |
325 | size_t size) |
326 | { |
327 | VCHIQ_STATUS_T vst; |
328 | VCHIQ_ELEMENT_T elems[] = {{msg, size}}; |
329 | |
330 | vcos_log_trace("%s: type %d, len %d" , VCOS_FUNCTION, msg->type, size); |
331 | |
332 | vcos_assert(size >= sizeof(*msg)); |
333 | |
334 | if (!vcos_verify(wfc_client_ipc.refcount)) |
335 | { |
336 | VCOS_ALERT("%s: client uninitialised" , VCOS_FUNCTION); |
337 | /* Client has not been initialised */ |
338 | return VCOS_EINVAL; |
339 | } |
340 | |
341 | msg->magic = WFC_IPC_MSG_MAGIC; |
342 | msg->waiter.ptr = NULL; |
343 | |
344 | wfc_client_ipc_use_keep_alive(); |
345 | |
346 | vst = vchiq_queue_message(wfc_client_ipc.service, elems, 1); |
347 | |
348 | wfc_client_ipc_release_keep_alive(); |
349 | |
350 | if (vst != VCHIQ_SUCCESS) |
351 | { |
352 | vcos_log_error("%s: failed to queue, 0x%x" , VCOS_FUNCTION, vst); |
353 | return VCOS_ENXIO; |
354 | } |
355 | |
356 | return VCOS_SUCCESS; |
357 | } |
358 | |
359 | VCOS_STATUS_T wfc_client_ipc_init(void) |
360 | { |
361 | VCHIQ_SERVICE_PARAMS_T vchiq_params; |
362 | bool vchiq_initialised = false, waitpool_initialised = false; |
363 | bool service_initialised = false; |
364 | VCOS_STATUS_T status = VCOS_ENXIO; |
365 | VCHIQ_STATUS_T vchiq_status; |
366 | |
367 | vcos_once(&wfc_client_ipc_once, init_once); |
368 | |
369 | vcos_mutex_lock(&wfc_client_ipc.lock); |
370 | |
371 | if (wfc_client_ipc.refcount++ > 0) |
372 | { |
373 | /* Already initialised so nothing to do */ |
374 | vcos_mutex_unlock(&wfc_client_ipc.lock); |
375 | return VCOS_SUCCESS; |
376 | } |
377 | |
378 | vcos_log_set_level(VCOS_LOG_CATEGORY, WFC_CLIENT_IPC_LOGLEVEL); |
379 | vcos_log_register("wfcipc" , VCOS_LOG_CATEGORY); |
380 | |
381 | vcos_log_trace("%s: starting initialisation" , VCOS_FUNCTION); |
382 | |
383 | /* Initialise a VCHIQ instance */ |
384 | vchiq_status = vchiq_initialise(&wfc_client_ipc_vchiq_instance); |
385 | if (vchiq_status != VCHIQ_SUCCESS) |
386 | { |
387 | vcos_log_error("%s: failed to initialise vchiq: %d" , VCOS_FUNCTION, vchiq_status); |
388 | goto error; |
389 | } |
390 | vchiq_initialised = true; |
391 | |
392 | vchiq_status = vchiq_connect(wfc_client_ipc_vchiq_instance); |
393 | if (vchiq_status != VCHIQ_SUCCESS) |
394 | { |
395 | vcos_log_error("%s: failed to connect to vchiq: %d" , VCOS_FUNCTION, vchiq_status); |
396 | goto error; |
397 | } |
398 | |
399 | memset(&vchiq_params, 0, sizeof(vchiq_params)); |
400 | vchiq_params.fourcc = WFC_IPC_CONTROL_FOURCC(); |
401 | vchiq_params.callback = wfc_client_ipc_vchiq_callback; |
402 | vchiq_params.userdata = &wfc_client_ipc; |
403 | vchiq_params.version = WFC_IPC_VER_CURRENT; |
404 | vchiq_params.version_min = WFC_IPC_VER_MINIMUM; |
405 | |
406 | vchiq_status = vchiq_open_service(wfc_client_ipc_vchiq_instance, &vchiq_params, &wfc_client_ipc.service); |
407 | if (vchiq_status != VCHIQ_SUCCESS) |
408 | { |
409 | vcos_log_error("%s: could not open vchiq service: %d" , VCOS_FUNCTION, vchiq_status); |
410 | goto error; |
411 | } |
412 | service_initialised = true; |
413 | |
414 | status = wfc_client_ipc_create_waitpool(&wfc_client_ipc.waitpool); |
415 | if (status != VCOS_SUCCESS) |
416 | { |
417 | vcos_log_error("%s: could not create wait pool: %d" , VCOS_FUNCTION, status); |
418 | goto error; |
419 | } |
420 | waitpool_initialised = true; |
421 | |
422 | /* Allow videocore to suspend, drops count to zero. */ |
423 | vchiq_release_service(wfc_client_ipc.service); |
424 | |
425 | vcos_mutex_unlock(&wfc_client_ipc.lock); |
426 | |
427 | status = wfc_client_ipc_send_client_pid(); |
428 | if (status != VCOS_SUCCESS) |
429 | { |
430 | vcos_log_error("%s: could not send client pid: %d" , VCOS_FUNCTION, status); |
431 | vcos_mutex_lock(&wfc_client_ipc.lock); |
432 | goto error; |
433 | } |
434 | |
435 | return VCOS_SUCCESS; |
436 | |
437 | error: |
438 | if (waitpool_initialised) |
439 | wfc_client_ipc_destroy_waitpool(&wfc_client_ipc.waitpool); |
440 | if (service_initialised) |
441 | vchiq_remove_service(wfc_client_ipc.service); |
442 | if (vchiq_initialised) |
443 | vchiq_shutdown(wfc_client_ipc_vchiq_instance); |
444 | vcos_log_unregister(VCOS_LOG_CATEGORY); |
445 | wfc_client_ipc.refcount--; |
446 | |
447 | vcos_mutex_unlock(&wfc_client_ipc.lock); |
448 | return status; |
449 | } |
450 | |
451 | bool wfc_client_ipc_deinit(void) |
452 | { |
453 | bool service_destroyed = false; |
454 | |
455 | vcos_once(&wfc_client_ipc_once, init_once); |
456 | |
457 | vcos_mutex_lock(&wfc_client_ipc.lock); |
458 | |
459 | if (!wfc_client_ipc.refcount) |
460 | { |
461 | /* Never initialised */ |
462 | goto completed; |
463 | } |
464 | |
465 | if (--wfc_client_ipc.refcount != 0) |
466 | { |
467 | /* Still in use so don't do anything */ |
468 | goto completed; |
469 | } |
470 | |
471 | vcos_log_trace("%s: starting deinitialisation" , VCOS_FUNCTION); |
472 | |
473 | /* Last reference dropped, tear down service */ |
474 | wfc_client_ipc_destroy_waitpool(&wfc_client_ipc.waitpool); |
475 | vchiq_remove_service(wfc_client_ipc.service); |
476 | vchiq_shutdown(wfc_client_ipc_vchiq_instance); |
477 | vcos_log_unregister(VCOS_LOG_CATEGORY); |
478 | |
479 | wfc_client_ipc.service = 0; |
480 | |
481 | service_destroyed = true; |
482 | |
483 | completed: |
484 | vcos_mutex_unlock(&wfc_client_ipc.lock); |
485 | |
486 | return service_destroyed; |
487 | } |
488 | |
489 | /* ------------------------------------------------------------------------- */ |
490 | |
491 | void wfc_client_ipc_use_keep_alive(void) |
492 | { |
493 | vcos_mutex_lock(&wfc_client_ipc.lock); |
494 | |
495 | if (!wfc_client_ipc.keep_alive_count++) |
496 | vchiq_use_service(wfc_client_ipc.service); |
497 | |
498 | vcos_mutex_unlock(&wfc_client_ipc.lock); |
499 | } |
500 | |
501 | /* ------------------------------------------------------------------------- */ |
502 | |
503 | void wfc_client_ipc_release_keep_alive(void) |
504 | { |
505 | vcos_mutex_lock(&wfc_client_ipc.lock); |
506 | |
507 | if (vcos_verify(wfc_client_ipc.keep_alive_count > 0)) |
508 | { |
509 | if (!--wfc_client_ipc.keep_alive_count) |
510 | vchiq_release_service(wfc_client_ipc.service); |
511 | } |
512 | |
513 | vcos_mutex_unlock(&wfc_client_ipc.lock); |
514 | } |
515 | |
516 | /* ------------------------------------------------------------------------- */ |
517 | |
518 | WFC_CLIENT_IPC_T *wfc_client_ipc_get_client(void) |
519 | { |
520 | return &wfc_client_ipc; |
521 | } |
522 | |