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

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

Prefer handle over ID in naming handle variables

  • Property mode set to 100644
File size: 19.8 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/**
381 * Server side implementation of the hound protocol. IPC connection handler.
382 * @param iid initial call id
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, void *arg)
387{
388 hound_context_id_t id;
389 errno_t ret;
390 int flags;
391 void *source;
392 void *sink;
393
394 /* Accept connection if there is a valid iface*/
395 if (server_iface) {
396 async_answer_0(icall_handle, EOK);
397 } else {
398 async_answer_0(icall_handle, ENOTSUP);
399 return;
400 }
401
402 while (1) {
403 ipc_call_t call;
404 cap_call_handle_t chandle = async_get_call(&call);
405 switch (IPC_GET_IMETHOD(call)) {
406 case IPC_M_HOUND_CONTEXT_REGISTER:
407 /* check interface functions */
408 if (!server_iface || !server_iface->add_context) {
409 async_answer_0(chandle, ENOTSUP);
410 break;
411 }
412 bool record = IPC_GET_ARG1(call);
413 void *name;
414
415 /* Get context name */
416 ret = async_data_write_accept(&name, true, 0, 0, 0, 0);
417 if (ret != EOK) {
418 async_answer_0(chandle, ret);
419 break;
420 }
421
422 id = 0;
423 ret = server_iface->add_context(server_iface->server,
424 &id, name, record);
425 /** new context should create a copy */
426 free(name);
427 if (ret != EOK) {
428 async_answer_0(chandle, ret);
429 } else {
430 async_answer_1(chandle, EOK, CAP_HANDLE_RAW(id));
431 }
432 break;
433 case IPC_M_HOUND_CONTEXT_UNREGISTER:
434 /* check interface functions */
435 if (!server_iface || !server_iface->rem_context) {
436 async_answer_0(chandle, ENOTSUP);
437 break;
438 }
439
440 /* get id, 1st param */
441 id = (cap_handle_t) IPC_GET_ARG1(call);
442 ret = server_iface->rem_context(server_iface->server,
443 id);
444 async_answer_0(chandle, ret);
445 break;
446 case IPC_M_HOUND_GET_LIST:
447 /* check interface functions */
448 if (!server_iface || !server_iface->get_list) {
449 async_answer_0(chandle, ENOTSUP);
450 break;
451 }
452
453 char **list = NULL;
454 flags = IPC_GET_ARG1(call);
455 size_t count = IPC_GET_ARG2(call);
456 const bool conn = IPC_GET_ARG3(call);
457 char *conn_name = NULL;
458 ret = EOK;
459
460 /* get connected actor name if provided */
461 if (conn)
462 ret = async_data_write_accept(
463 (void**)&conn_name, true, 0, 0, 0, 0);
464
465 if (ret == EOK)
466 ret = server_iface->get_list(
467 server_iface->server, &list, &count,
468 conn_name, flags);
469 free(conn_name);
470
471 /* Alloc string sizes array */
472 size_t *sizes = NULL;
473 if (count)
474 sizes = calloc(count, sizeof(size_t));
475 if (count && !sizes)
476 ret = ENOMEM;
477 async_answer_1(chandle, ret, count);
478
479 /* We are done */
480 if (count == 0 || ret != EOK)
481 break;
482
483 /* Prepare sizes table */
484 for (unsigned i = 0; i < count; ++i)
485 sizes[i] = str_size(list[i]);
486
487 /* Send sizes table */
488 cap_call_handle_t id;
489 if (async_data_read_receive(&id, NULL)) {
490 ret = async_data_read_finalize(id, sizes,
491 count * sizeof(size_t));
492 }
493 free(sizes);
494
495 /* Proceed to send names */
496 for (unsigned i = 0; i < count; ++i) {
497 size_t size = str_size(list[i]);
498 cap_call_handle_t id;
499 if (ret == EOK &&
500 async_data_read_receive(&id, NULL)) {
501 ret = async_data_read_finalize(id,
502 list[i], size);
503 }
504 free(list[i]);
505 }
506 free(list);
507 break;
508 case IPC_M_HOUND_CONNECT:
509 /* check interface functions */
510 if (!server_iface || !server_iface->connect) {
511 async_answer_0(chandle, ENOTSUP);
512 break;
513 }
514
515 source = NULL;
516 sink = NULL;
517
518 /* read source name */
519 ret = async_data_write_accept(&source, true, 0, 0, 0,
520 0);
521 /* read sink name */
522 if (ret == EOK)
523 ret = async_data_write_accept(&sink,
524 true, 0, 0, 0, 0);
525
526 if (ret == EOK)
527 ret = server_iface->connect(
528 server_iface->server, source, sink);
529 free(source);
530 free(sink);
531 async_answer_0(chandle, ret);
532 break;
533 case IPC_M_HOUND_DISCONNECT:
534 /* check interface functions */
535 if (!server_iface || !server_iface->disconnect) {
536 async_answer_0(chandle, ENOTSUP);
537 break;
538 }
539
540 source = NULL;
541 sink = NULL;
542
543 /* read source name */
544 ret = async_data_write_accept(&source, true, 0, 0, 0,
545 0);
546 /*read sink name */
547 if (ret == EOK)
548 ret = async_data_write_accept(&sink,
549 true, 0, 0, 0, 0);
550 if (ret == EOK)
551 ret = server_iface->connect(
552 server_iface->server, source, sink);
553 free(source);
554 free(sink);
555 async_answer_0(chandle, ret);
556 break;
557 case IPC_M_HOUND_STREAM_ENTER:
558 /* check interface functions */
559 if (!server_iface || !server_iface->is_record_context
560 || !server_iface->add_stream
561 || !server_iface->rem_stream) {
562 async_answer_0(chandle, ENOTSUP);
563 break;
564 }
565
566 /* get parameters */
567 id = (cap_handle_t) IPC_GET_ARG1(call);
568 flags = IPC_GET_ARG2(call);
569 const format_convert_t c = {.arg = IPC_GET_ARG3(call)};
570 const pcm_format_t f = {
571 .sampling_rate = c.f.rate * 100,
572 .channels = c.f.channels,
573 .sample_format = c.f.format,
574 };
575 size_t bsize = IPC_GET_ARG4(call);
576
577 void *stream;
578 ret = server_iface->add_stream(server_iface->server,
579 id, flags, f, bsize, &stream);
580 if (ret != EOK) {
581 async_answer_0(chandle, ret);
582 break;
583 }
584 const bool rec = server_iface->is_record_context(
585 server_iface->server, id);
586 if (rec) {
587 if(server_iface->stream_data_read) {
588 async_answer_0(chandle, EOK);
589 /* start answering read calls */
590 hound_server_write_data(stream);
591 server_iface->rem_stream(
592 server_iface->server, stream);
593 } else {
594 async_answer_0(chandle, ENOTSUP);
595 }
596 } else {
597 if (server_iface->stream_data_write) {
598 async_answer_0(chandle, EOK);
599 /* accept write calls */
600 hound_server_read_data(stream);
601 server_iface->rem_stream(
602 server_iface->server, stream);
603 } else {
604 async_answer_0(chandle, ENOTSUP);
605 }
606 }
607 break;
608 case IPC_M_HOUND_STREAM_EXIT:
609 case IPC_M_HOUND_STREAM_DRAIN:
610 /* Stream exit/drain is only allowed in stream context*/
611 async_answer_0(chandle, EINVAL);
612 break;
613 default:
614 async_answer_0(chandle, ENOTSUP);
615 return;
616 }
617 }
618}
619
620/**
621 * Read data and push it to the stream.
622 * @param stream target stream, will push data there.
623 */
624static void hound_server_read_data(void *stream)
625{
626 cap_call_handle_t chandle;
627 ipc_call_t call;
628 size_t size = 0;
629 errno_t ret_answer = EOK;
630 /* accept data write or drain */
631 while (async_data_write_receive_call(&chandle, &call, &size)
632 || (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) {
633 /* check drain first */
634 if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) {
635 errno_t ret = ENOTSUP;
636 if (server_iface->drain_stream)
637 ret = server_iface->drain_stream(stream);
638 async_answer_0(chandle, ret);
639 continue;
640 }
641
642 /* there was an error last time */
643 if (ret_answer != EOK) {
644 async_answer_0(chandle, ret_answer);
645 continue;
646 }
647
648 char *buffer = malloc(size);
649 if (!buffer) {
650 async_answer_0(chandle, ENOMEM);
651 continue;
652 }
653 const errno_t ret = async_data_write_finalize(chandle, buffer, size);
654 if (ret == EOK) {
655 /* push data to stream */
656 ret_answer = server_iface->stream_data_write(
657 stream, buffer, size);
658 }
659 }
660 const errno_t ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT
661 ? EOK : EINVAL;
662
663 async_answer_0(chandle, ret);
664}
665
666/**
667 * Accept reads and pull data from the stream.
668 * @param stream target stream, will pull data from there.
669 */
670static void hound_server_write_data(void *stream)
671{
672
673 cap_call_handle_t chandle;
674 ipc_call_t call;
675 size_t size = 0;
676 errno_t ret_answer = EOK;
677 /* accept data read and drain */
678 while (async_data_read_receive_call(&chandle, &call, &size)
679 || (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) {
680 /* drain does not make much sense but it is allowed */
681 if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) {
682 errno_t ret = ENOTSUP;
683 if (server_iface->drain_stream)
684 ret = server_iface->drain_stream(stream);
685 async_answer_0(chandle, ret);
686 continue;
687 }
688 /* there was an error last time */
689 if (ret_answer != EOK) {
690 async_answer_0(chandle, ret_answer);
691 continue;
692 }
693 char *buffer = malloc(size);
694 if (!buffer) {
695 async_answer_0(chandle, ENOMEM);
696 continue;
697 }
698 errno_t ret = server_iface->stream_data_read(stream, buffer, size);
699 if (ret == EOK) {
700 ret_answer =
701 async_data_read_finalize(chandle, buffer, size);
702 }
703 }
704 const errno_t ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT
705 ? EOK : EINVAL;
706
707 async_answer_0(chandle, ret);
708}
709
710
711/***
712 * SERVER SIDE
713 ***/
714
715/**
716 * Register new hound service to the location service.
717 * @param[in] name server name
718 * @param[out] id assigned service id.
719 * @return Error code.
720 */
721errno_t hound_server_register(const char *name, service_id_t *id)
722{
723 if (!name || !id)
724 return EINVAL;
725
726 errno_t ret = loc_server_register(name);
727 if (ret != EOK)
728 return ret;
729
730 return loc_service_register(HOUND_SERVICE, id);
731}
732
733/**
734 * Unregister server from the location service.
735 * @param id previously assigned service id.
736 */
737void hound_server_unregister(service_id_t id)
738{
739 loc_service_unregister(id);
740}
741
742/**
743 * Set callback on device category change event.
744 * @param cb Callback function.
745 * @return Error code.
746 */
747errno_t hound_server_set_device_change_callback(dev_change_callback_t cb)
748{
749 return loc_register_cat_change_cb(cb);
750}
751
752/**
753 * Walk through all device in the audio-pcm category.
754 * @param callback Function to call on every device.
755 * @return Error code.
756 */
757errno_t hound_server_devices_iterate(device_callback_t callback)
758{
759 if (!callback)
760 return EINVAL;
761 static bool resolved = false;
762 static category_id_t cat_id = 0;
763
764 if (!resolved) {
765 const errno_t ret = loc_category_get_id("audio-pcm", &cat_id,
766 IPC_FLAG_BLOCKING);
767 if (ret != EOK)
768 return ret;
769 resolved = true;
770 }
771
772 service_id_t *svcs = NULL;
773 size_t count = 0;
774 const errno_t ret = loc_category_get_svcs(cat_id, &svcs, &count);
775 if (ret != EOK)
776 return ret;
777
778 for (unsigned i = 0; i < count; ++i) {
779 char *name = NULL;
780 loc_service_get_name(svcs[i], &name);
781 callback(svcs[i], name);
782 free(name);
783 }
784 free(svcs);
785 return EOK;
786}
787/**
788 * @}
789 */
Note: See TracBrowser for help on using the repository browser.