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

Last change on this file since cb20b05 was ca48672, checked in by Jiri Svoboda <jiri@…>, 8 days ago

loc_service_register() needs to take a port ID argument.

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