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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since eed4139 was eed4139, checked in by Jakub Jermar <jakub@…>, 7 years ago

Fix some comments mentioning 'call IDs'

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