source: mainline/uspace/lib/hound/src/protocol.c@ 5e5498e

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 5e5498e was 984a9ba, checked in by Martin Decky <martin@…>, 7 years ago

do not expose the call capability handler from the async framework

Keep the call capability handler encapsulated within the async framework
and do not expose it explicitly via its API. Use the pointer to
ipc_call_t as the sole object identifying an IPC call in the code that
uses the async framework.

This plugs a major leak in the abstraction and also simplifies both the
async framework (slightly) and all IPC servers.

  • Property mode set to 100644
File size: 19.7 KB
Line 
1/*
2 * Copyright (c) 2012 Jan Vesely
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
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libhound
30 * @addtogroup audio
31 * @{
32 */
33/** @file
34 * Common USB functions.
35 */
36#include <adt/list.h>
37#include <errno.h>
38#include <loc.h>
39#include <macros.h>
40#include <str.h>
41#include <stdbool.h>
42#include <stdlib.h>
43#include <stdio.h>
44#include <types/common.h>
45
46#include "protocol.h"
47#include "client.h"
48#include "server.h"
49
50enum ipc_methods {
51 /** Create new context representation on the server side */
52 IPC_M_HOUND_CONTEXT_REGISTER = IPC_FIRST_USER_METHOD,
53 /** Release existing context representation on the server side */
54 IPC_M_HOUND_CONTEXT_UNREGISTER,
55 /** Request list of objects */
56 IPC_M_HOUND_GET_LIST,
57 /** Create new connection */
58 IPC_M_HOUND_CONNECT,
59 /** Destroy connection */
60 IPC_M_HOUND_DISCONNECT,
61 /** Switch IPC pipe to stream mode */
62 IPC_M_HOUND_STREAM_ENTER,
63 /** Switch IPC pipe back to general mode */
64 IPC_M_HOUND_STREAM_EXIT,
65 /** Wait until there is no data in the stream */
66 IPC_M_HOUND_STREAM_DRAIN,
67};
68
69
70/** PCM format conversion helper structure */
71typedef union {
72 struct {
73 uint16_t rate;
74 uint8_t channels;
75 uint8_t format;
76 } __attribute__((packed)) f;
77 sysarg_t arg;
78} format_convert_t;
79
80
81/*
82 * CLIENT
83 */
84
85/** Well defined service name */
86const char *HOUND_SERVICE = "audio/hound";
87
88/**
89 * Start a new audio session.
90 * @param service Named service typically 'HOUND_SERVICE' constant.
91 * @return Valid session on success, NULL on failure.
92 */
93hound_sess_t *hound_service_connect(const char *service)
94{
95 service_id_t id = 0;
96 const errno_t ret =
97 loc_service_get_id(service, &id, IPC_FLAG_BLOCKING);
98 if (ret != EOK)
99 return NULL;
100 return loc_service_connect(id, INTERFACE_HOUND, IPC_FLAG_BLOCKING);
101}
102
103/**
104 * End an existing audio session.
105 * @param sess The session.
106 */
107void hound_service_disconnect(hound_sess_t *sess)
108{
109 if (sess)
110 async_hangup(sess);
111}
112
113/**
114 * Register a named application context to the audio server.
115 * @param sess Valid audio session.
116 * @param name Valid string identifier
117 * @param record True if the application context wishes to receive data.
118 *
119 * @param[out] id Return context ID.
120 *
121 * @return EOK on success, Error code on failure.
122 */
123errno_t hound_service_register_context(hound_sess_t *sess,
124 const char *name, bool record, hound_context_id_t *id)
125{
126 assert(sess);
127 assert(name);
128 ipc_call_t call;
129 async_exch_t *exch = async_exchange_begin(sess);
130 aid_t mid =
131 async_send_1(exch, IPC_M_HOUND_CONTEXT_REGISTER, record, &call);
132 errno_t ret = mid ? EOK : EPARTY;
133
134 if (ret == EOK)
135 ret = async_data_write_start(exch, name, str_size(name));
136 else
137 async_forget(mid);
138
139 if (ret == EOK)
140 async_wait_for(mid, &ret);
141
142 async_exchange_end(exch);
143 if (ret == EOK) {
144 *id = (hound_context_id_t) IPC_GET_ARG1(call);
145 }
146
147 return ret;
148}
149
150/**
151 * Remove application context from the server's list.
152 * @param sess Valid audio session.
153 * @param id Valid context id.
154 * @return Error code.
155 */
156errno_t hound_service_unregister_context(hound_sess_t *sess,
157 hound_context_id_t id)
158{
159 assert(sess);
160 async_exch_t *exch = async_exchange_begin(sess);
161 const errno_t ret = async_req_1_0(exch, IPC_M_HOUND_CONTEXT_UNREGISTER,
162 CAP_HANDLE_RAW(id));
163 async_exchange_end(exch);
164 return ret;
165}
166
167/**
168 * Retrieve a list of server side actors.
169 * @param[in] sess Valid audio session.
170 * @param[out] ids list of string identifiers.
171 * @param[out] count Number of elements int the @p ids list.
172 * @param[in] flags list requirements.
173 * @param[in] connection name of target actor. Used only if the list should
174 * contain connected actors.
175 * @retval Error code.
176 */
177errno_t hound_service_get_list(hound_sess_t *sess, char ***ids, size_t *count,
178 int flags, const char *connection)
179{
180 assert(sess);
181 assert(ids);
182 assert(count);
183
184 if (connection && !(flags & HOUND_CONNECTED))
185 return EINVAL;
186
187 async_exch_t *exch = async_exchange_begin(sess);
188 if (!exch)
189 return ENOMEM;
190
191 ipc_call_t res_call;
192 aid_t mid = async_send_3(exch, IPC_M_HOUND_GET_LIST, flags, *count,
193 connection != NULL, &res_call);
194
195 errno_t ret = EOK;
196 if (mid && connection)
197 ret = async_data_write_start(exch, connection,
198 str_size(connection));
199
200 if (ret == EOK)
201 async_wait_for(mid, &ret);
202
203 if (ret != EOK) {
204 async_exchange_end(exch);
205 return ret;
206 }
207 unsigned name_count = IPC_GET_ARG1(res_call);
208
209 /* Start receiving names */
210 char **names = NULL;
211 if (name_count) {
212 size_t *sizes = calloc(name_count, sizeof(size_t));
213 names = calloc(name_count, sizeof(char *));
214 if (!names || !sizes)
215 ret = ENOMEM;
216
217 if (ret == EOK)
218 ret = async_data_read_start(exch, sizes,
219 name_count * sizeof(size_t));
220 for (unsigned i = 0; i < name_count && ret == EOK; ++i) {
221 char *name = malloc(sizes[i] + 1);
222 if (name) {
223 memset(name, 0, sizes[i] + 1);
224 ret = async_data_read_start(exch, name, sizes[i]);
225 names[i] = name;
226 } else {
227 ret = ENOMEM;
228 }
229 }
230 free(sizes);
231 }
232 async_exchange_end(exch);
233 if (ret != EOK) {
234 for (unsigned i = 0; i < name_count; ++i)
235 free(names[i]);
236 free(names);
237 } else {
238 *ids = names;
239 *count = name_count;
240 }
241 return ret;
242}
243
244/**
245 * Create a new connection between a source and a sink.
246 * @param sess Valid audio session.
247 * @param source Source name, valid string.
248 * @param sink Sink name, valid string.
249 * @return Error code.
250 */
251errno_t hound_service_connect_source_sink(hound_sess_t *sess, const char *source,
252 const char *sink)
253{
254 assert(sess);
255 assert(source);
256 assert(sink);
257
258 async_exch_t *exch = async_exchange_begin(sess);
259 if (!exch)
260 return ENOMEM;
261 ipc_call_t call;
262 aid_t id = async_send_0(exch, IPC_M_HOUND_CONNECT, &call);
263 errno_t ret = id ? EOK : EPARTY;
264 if (ret == EOK)
265 ret = async_data_write_start(exch, source, str_size(source));
266 if (ret == EOK)
267 ret = async_data_write_start(exch, sink, str_size(sink));
268 async_wait_for(id, &ret);
269 async_exchange_end(exch);
270 return ret;
271}
272
273/**
274 * Destroy an existing connection between a source and a sink.
275 * @param sess Valid audio session.
276 * @param source Source name, valid string.
277 * @param sink Sink name, valid string.
278 * @return Error code.
279 */
280errno_t hound_service_disconnect_source_sink(hound_sess_t *sess, const char *source,
281 const char *sink)
282{
283 assert(sess);
284 async_exch_t *exch = async_exchange_begin(sess);
285 if (!exch)
286 return ENOMEM;
287 ipc_call_t call;
288 aid_t id = async_send_0(exch, IPC_M_HOUND_DISCONNECT, &call);
289 errno_t ret = id ? EOK : EPARTY;
290 if (ret == EOK)
291 ret = async_data_write_start(exch, source, str_size(source));
292 if (ret == EOK)
293 ret = async_data_write_start(exch, sink, str_size(sink));
294 async_wait_for(id, &ret);
295 async_exchange_end(exch);
296 return ENOTSUP;
297}
298
299/**
300 * Switch IPC exchange to a STREAM mode.
301 * @param exch IPC exchange.
302 * @param id context id this stream should be associated with
303 * @param flags set stream properties
304 * @param format format of the new stream.
305 * @param bsize size of the server side buffer.
306 * @return Error code.
307 */
308errno_t hound_service_stream_enter(async_exch_t *exch, hound_context_id_t id,
309 int flags, pcm_format_t format, size_t bsize)
310{
311 const format_convert_t c = {
312 .f = {
313 .channels = format.channels,
314 .rate = format.sampling_rate / 100,
315 .format = format.sample_format,
316 }
317 };
318
319 return async_req_4_0(exch, IPC_M_HOUND_STREAM_ENTER, CAP_HANDLE_RAW(id),
320 flags, c.arg, bsize);
321}
322
323/**
324 * Destroy existing stream and return IPC exchange to general mode.
325 * @param exch IPC exchange.
326 * @return Error code.
327 */
328errno_t hound_service_stream_exit(async_exch_t *exch)
329{
330 return async_req_0_0(exch, IPC_M_HOUND_STREAM_EXIT);
331}
332
333/**
334 * Wait until the server side buffer is empty.
335 * @param exch IPC exchange.
336 * @return Error code.
337 */
338errno_t hound_service_stream_drain(async_exch_t *exch)
339{
340 return async_req_0_0(exch, IPC_M_HOUND_STREAM_DRAIN);
341}
342
343/**
344 * Write audio data to a stream.
345 * @param exch IPC exchange in STREAM MODE.
346 * @param data Audio data buffer.
347 * @size size of the buffer
348 * @return Error code.
349 */
350errno_t hound_service_stream_write(async_exch_t *exch, const void *data, size_t size)
351{
352 return async_data_write_start(exch, data, size);
353}
354
355/**
356 * Read data from a stream.
357 * @param exch IPC exchange in STREAM MODE.
358 * @param data Audio data buffer.
359 * @size size of the buffer
360 * @return Error code.
361 */
362errno_t hound_service_stream_read(async_exch_t *exch, void *data, size_t size)
363{
364 return async_data_read_start(exch, data, size);
365}
366
367/*
368 * SERVER
369 */
370
371static void hound_server_read_data(void *stream);
372static void hound_server_write_data(void *stream);
373static const hound_server_iface_t *server_iface;
374
375/**
376 * Set hound server interface implementation.
377 * @param iface Initialized Hound server interface.
378 */
379void hound_service_set_server_iface(const hound_server_iface_t *iface)
380{
381 server_iface = iface;
382}
383
384/** Server side implementation of the hound protocol. IPC connection handler.
385 *
386 * @param icall Pointer to initial call structure.
387 * @param arg (unused)
388 *
389 */
390void hound_connection_handler(ipc_call_t *icall, void *arg)
391{
392 hound_context_id_t context;
393 errno_t ret;
394 int flags;
395 void *source;
396 void *sink;
397
398 /* Accept connection if there is a valid iface*/
399 if (server_iface) {
400 async_answer_0(icall, EOK);
401 } else {
402 async_answer_0(icall, ENOTSUP);
403 return;
404 }
405
406 while (true) {
407 ipc_call_t call;
408 async_get_call(&call);
409
410 switch (IPC_GET_IMETHOD(call)) {
411 case IPC_M_HOUND_CONTEXT_REGISTER:
412 /* check interface functions */
413 if (!server_iface || !server_iface->add_context) {
414 async_answer_0(&call, ENOTSUP);
415 break;
416 }
417 bool record = IPC_GET_ARG1(call);
418 void *name;
419
420 /* Get context name */
421 ret = async_data_write_accept(&name, true, 0, 0, 0, 0);
422 if (ret != EOK) {
423 async_answer_0(&call, ret);
424 break;
425 }
426
427 context = 0;
428 ret = server_iface->add_context(server_iface->server,
429 &context, name, record);
430 /** new context should create a copy */
431 free(name);
432 if (ret != EOK) {
433 async_answer_0(&call, ret);
434 } else {
435 async_answer_1(&call, EOK, CAP_HANDLE_RAW(context));
436 }
437 break;
438 case IPC_M_HOUND_CONTEXT_UNREGISTER:
439 /* check interface functions */
440 if (!server_iface || !server_iface->rem_context) {
441 async_answer_0(&call, ENOTSUP);
442 break;
443 }
444
445 /* get id, 1st param */
446 context = (hound_context_id_t) IPC_GET_ARG1(call);
447 ret = server_iface->rem_context(server_iface->server,
448 context);
449 async_answer_0(&call, ret);
450 break;
451 case IPC_M_HOUND_GET_LIST:
452 /* check interface functions */
453 if (!server_iface || !server_iface->get_list) {
454 async_answer_0(&call, ENOTSUP);
455 break;
456 }
457
458 char **list = NULL;
459 flags = IPC_GET_ARG1(call);
460 size_t count = IPC_GET_ARG2(call);
461 const bool conn = IPC_GET_ARG3(call);
462 char *conn_name = NULL;
463 ret = EOK;
464
465 /* get connected actor name if provided */
466 if (conn)
467 ret = async_data_write_accept(
468 (void **)&conn_name, true, 0, 0, 0, 0);
469
470 if (ret == EOK)
471 ret = server_iface->get_list(
472 server_iface->server, &list, &count,
473 conn_name, flags);
474 free(conn_name);
475
476 /* Alloc string sizes array */
477 size_t *sizes = NULL;
478 if (count)
479 sizes = calloc(count, sizeof(size_t));
480 if (count && !sizes)
481 ret = ENOMEM;
482 async_answer_1(&call, ret, count);
483
484 /* We are done */
485 if (count == 0 || ret != EOK)
486 break;
487
488 /* Prepare sizes table */
489 for (unsigned i = 0; i < count; ++i)
490 sizes[i] = str_size(list[i]);
491
492 /* Send sizes table */
493 ipc_call_t id;
494 if (async_data_read_receive(&id, NULL)) {
495 ret = async_data_read_finalize(&id, sizes,
496 count * sizeof(size_t));
497 }
498 free(sizes);
499
500 /* Proceed to send names */
501 for (unsigned i = 0; i < count; ++i) {
502 size_t size = str_size(list[i]);
503 ipc_call_t id;
504 if (ret == EOK &&
505 async_data_read_receive(&id, NULL)) {
506 ret = async_data_read_finalize(&id,
507 list[i], size);
508 }
509 free(list[i]);
510 }
511 free(list);
512 break;
513 case IPC_M_HOUND_CONNECT:
514 /* check interface functions */
515 if (!server_iface || !server_iface->connect) {
516 async_answer_0(&call, ENOTSUP);
517 break;
518 }
519
520 source = NULL;
521 sink = NULL;
522
523 /* read source name */
524 ret = async_data_write_accept(&source, true, 0, 0, 0,
525 0);
526 /* read sink name */
527 if (ret == EOK)
528 ret = async_data_write_accept(&sink,
529 true, 0, 0, 0, 0);
530
531 if (ret == EOK)
532 ret = server_iface->connect(
533 server_iface->server, source, sink);
534 free(source);
535 free(sink);
536 async_answer_0(&call, ret);
537 break;
538 case IPC_M_HOUND_DISCONNECT:
539 /* check interface functions */
540 if (!server_iface || !server_iface->disconnect) {
541 async_answer_0(&call, ENOTSUP);
542 break;
543 }
544
545 source = NULL;
546 sink = NULL;
547
548 /* read source name */
549 ret = async_data_write_accept(&source, true, 0, 0, 0,
550 0);
551 /*read sink name */
552 if (ret == EOK)
553 ret = async_data_write_accept(&sink,
554 true, 0, 0, 0, 0);
555 if (ret == EOK)
556 ret = server_iface->connect(
557 server_iface->server, source, sink);
558 free(source);
559 free(sink);
560 async_answer_0(&call, ret);
561 break;
562 case IPC_M_HOUND_STREAM_ENTER:
563 /* check interface functions */
564 if (!server_iface || !server_iface->is_record_context ||
565 !server_iface->add_stream ||
566 !server_iface->rem_stream) {
567 async_answer_0(&call, ENOTSUP);
568 break;
569 }
570
571 /* get parameters */
572 context = (hound_context_id_t) IPC_GET_ARG1(call);
573 flags = IPC_GET_ARG2(call);
574 const format_convert_t c = { .arg = IPC_GET_ARG3(call) };
575 const pcm_format_t f = {
576 .sampling_rate = c.f.rate * 100,
577 .channels = c.f.channels,
578 .sample_format = c.f.format,
579 };
580 size_t bsize = IPC_GET_ARG4(call);
581
582 void *stream;
583 ret = server_iface->add_stream(server_iface->server,
584 context, flags, f, bsize, &stream);
585 if (ret != EOK) {
586 async_answer_0(&call, ret);
587 break;
588 }
589 const bool rec = server_iface->is_record_context(
590 server_iface->server, context);
591 if (rec) {
592 if (server_iface->stream_data_read) {
593 async_answer_0(&call, EOK);
594 /* start answering read calls */
595 hound_server_write_data(stream);
596 server_iface->rem_stream(
597 server_iface->server, stream);
598 } else {
599 async_answer_0(&call, ENOTSUP);
600 }
601 } else {
602 if (server_iface->stream_data_write) {
603 async_answer_0(&call, EOK);
604 /* accept write calls */
605 hound_server_read_data(stream);
606 server_iface->rem_stream(
607 server_iface->server, stream);
608 } else {
609 async_answer_0(&call, ENOTSUP);
610 }
611 }
612 break;
613 case IPC_M_HOUND_STREAM_EXIT:
614 case IPC_M_HOUND_STREAM_DRAIN:
615 /* Stream exit/drain is only allowed in stream context*/
616 async_answer_0(&call, EINVAL);
617 break;
618 default:
619 async_answer_0(&call, ENOTSUP);
620 return;
621 }
622 }
623}
624
625/**
626 * Read data and push it to the stream.
627 * @param stream target stream, will push data there.
628 */
629static void hound_server_read_data(void *stream)
630{
631 ipc_call_t call;
632 size_t size = 0;
633 errno_t ret_answer = EOK;
634
635 /* accept data write or drain */
636 while (async_data_write_receive(&call, &size) ||
637 (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) {
638 /* check drain first */
639 if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) {
640 errno_t ret = ENOTSUP;
641 if (server_iface->drain_stream)
642 ret = server_iface->drain_stream(stream);
643 async_answer_0(&call, ret);
644 continue;
645 }
646
647 /* there was an error last time */
648 if (ret_answer != EOK) {
649 async_answer_0(&call, ret_answer);
650 continue;
651 }
652
653 char *buffer = malloc(size);
654 if (!buffer) {
655 async_answer_0(&call, ENOMEM);
656 continue;
657 }
658 const errno_t ret = async_data_write_finalize(&call, buffer, size);
659 if (ret == EOK) {
660 /* push data to stream */
661 ret_answer = server_iface->stream_data_write(
662 stream, buffer, size);
663 }
664 }
665 const errno_t ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT ?
666 EOK : EINVAL;
667
668 async_answer_0(&call, ret);
669}
670
671/**
672 * Accept reads and pull data from the stream.
673 * @param stream target stream, will pull data from there.
674 */
675static void hound_server_write_data(void *stream)
676{
677
678 ipc_call_t call;
679 size_t size = 0;
680 errno_t ret_answer = EOK;
681
682 /* accept data read and drain */
683 while (async_data_read_receive(&call, &size) ||
684 (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) {
685 /* drain does not make much sense but it is allowed */
686 if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) {
687 errno_t ret = ENOTSUP;
688 if (server_iface->drain_stream)
689 ret = server_iface->drain_stream(stream);
690 async_answer_0(&call, ret);
691 continue;
692 }
693 /* there was an error last time */
694 if (ret_answer != EOK) {
695 async_answer_0(&call, ret_answer);
696 continue;
697 }
698 char *buffer = malloc(size);
699 if (!buffer) {
700 async_answer_0(&call, ENOMEM);
701 continue;
702 }
703 errno_t ret = server_iface->stream_data_read(stream, buffer, size);
704 if (ret == EOK) {
705 ret_answer =
706 async_data_read_finalize(&call, buffer, size);
707 }
708 }
709 const errno_t ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT ?
710 EOK : EINVAL;
711
712 async_answer_0(&call, ret);
713}
714
715
716/*
717 * SERVER SIDE
718 */
719
720/**
721 * Register new hound service to the location service.
722 * @param[in] name server name
723 * @param[out] id assigned service id.
724 * @return Error code.
725 */
726errno_t hound_server_register(const char *name, service_id_t *id)
727{
728 if (!name || !id)
729 return EINVAL;
730
731 errno_t ret = loc_server_register(name);
732 if (ret != EOK)
733 return ret;
734
735 return loc_service_register(HOUND_SERVICE, id);
736}
737
738/**
739 * Unregister server from the location service.
740 * @param id previously assigned service id.
741 */
742void hound_server_unregister(service_id_t id)
743{
744 loc_service_unregister(id);
745}
746
747/**
748 * Set callback on device category change event.
749 * @param cb Callback function.
750 * @return Error code.
751 */
752errno_t hound_server_set_device_change_callback(dev_change_callback_t cb)
753{
754 return loc_register_cat_change_cb(cb);
755}
756
757/**
758 * Walk through all device in the audio-pcm category.
759 * @param callback Function to call on every device.
760 * @return Error code.
761 */
762errno_t hound_server_devices_iterate(device_callback_t callback)
763{
764 if (!callback)
765 return EINVAL;
766 static bool resolved = false;
767 static category_id_t cat_id = 0;
768
769 if (!resolved) {
770 const errno_t ret = loc_category_get_id("audio-pcm", &cat_id,
771 IPC_FLAG_BLOCKING);
772 if (ret != EOK)
773 return ret;
774 resolved = true;
775 }
776
777 service_id_t *svcs = NULL;
778 size_t count = 0;
779 const errno_t ret = loc_category_get_svcs(cat_id, &svcs, &count);
780 if (ret != EOK)
781 return ret;
782
783 for (unsigned i = 0; i < count; ++i) {
784 char *name = NULL;
785 loc_service_get_name(svcs[i], &name);
786 callback(svcs[i], name);
787 free(name);
788 }
789 free(svcs);
790 return EOK;
791}
792/**
793 * @}
794 */
Note: See TracBrowser for help on using the repository browser.