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

Last change on this file since 08e103d4 was 08e103d4, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 7 years ago

Use clearer naming for string length functions

This and the following commit change the names of functions, as well as
their documentation, to use unambiguous terms "bytes" and "code points"
instead of ambiguous terms "size", "length", and "characters".

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