1/*
2Copyright (c) 2012, Broadcom Europe Ltd
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, 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
16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
20DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23ON 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
25SOFTWARE, 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
43static VCOS_ONCE_T wfc_client_ipc_once = VCOS_ONCE_INIT;
44static VCHIQ_INSTANCE_T wfc_client_ipc_vchiq_instance;
45static 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 */
50typedef 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 */
63typedef struct
64{
65 WFC_WAITER_T waiters[WFC_CLIENT_IPC_MAX_WAITERS];
66 VCOS_SEMAPHORE_T sem;
67} WFC_WAITPOOL_T;
68
69struct 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};
77typedef 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 */
82static WFC_CLIENT_IPC_T wfc_client_ipc;
83
84static 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 */
91static 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
124static 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 */
137static 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 */
164static 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 */
177static VCHIQ_STATUS_T wfc_client_ipc_vchiq_callback(VCHIQ_REASON_T reason,
178 VCHIQ_HEADER_T *vchiq_header,
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
251static 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
267VCOS_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
317completed:
318 wfc_client_ipc_release_waiter(&wfc_client_ipc, waiter);
319 wfc_client_ipc_release_keep_alive();
320
321 return ret;
322}
323
324VCOS_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
359VCOS_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
437error:
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
451bool 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
483completed:
484 vcos_mutex_unlock(&wfc_client_ipc.lock);
485
486 return service_destroyed;
487}
488
489/* ------------------------------------------------------------------------- */
490
491void 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
503void 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
518WFC_CLIENT_IPC_T *wfc_client_ipc_get_client(void)
519{
520 return &wfc_client_ipc;
521}
522