2 This file is part of PulseAudio.
4 Copyright 2006 Lennart Poettering
5 Copyright 2006 Shams E. King
7 PulseAudio is free software; you can redistribute it and/or modify
8 it under the terms of the GNU Lesser General Public License as published
9 by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
12 PulseAudio is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with PulseAudio; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
33 #include <sys/types.h>
36 #include <pulse/xmalloc.h>
37 #include <pulse/timeval.h>
39 #include <pulsecore/core-error.h>
40 #include <pulsecore/module.h>
41 #include <pulsecore/log.h>
42 #include <pulsecore/hashmap.h>
43 #include <pulsecore/idxset.h>
44 #include <pulsecore/core-util.h>
45 #include <pulsecore/namereg.h>
46 #include <pulsecore/core-scache.h>
47 #include <pulsecore/modargs.h>
49 #include <hal/libhal.h>
51 #include "dbus-util.h"
52 #include "module-hal-detect-symdef.h"
54 PA_MODULE_AUTHOR("Shahms King");
55 PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers");
56 PA_MODULE_VERSION(PACKAGE_VERSION
);
57 PA_MODULE_LOAD_ONCE(TRUE
);
58 #if defined(HAVE_ALSA) && defined(HAVE_OSS)
59 PA_MODULE_USAGE("api=<alsa or oss> "
60 "tsched=<enable system timer based scheduling mode?>");
61 #elif defined(HAVE_ALSA)
62 PA_MODULE_USAGE("api=<alsa> "
63 "tsched=<enable system timer based scheduling mode?>");
64 #elif defined(HAVE_OSS)
65 PA_MODULE_USAGE("api=<oss>");
69 char *udi
, *originating_udi
;
70 char *card_name
, *sink_name
, *source_name
;
72 pa_bool_t acl_race_fix
;
77 LibHalContext
*context
;
78 pa_dbus_connection
*connection
;
79 pa_hashmap
*devices
; /* Every entry is indexed twice in this table: by the udi we found the device with and by the originating device's udi */
80 const char *capability
;
86 #define CAPABILITY_ALSA "alsa"
87 #define CAPABILITY_OSS "oss"
89 static const char* const valid_modargs
[] = {
97 static void device_free(struct device
* d
) {
101 pa_xfree(d
->originating_udi
);
102 pa_xfree(d
->sink_name
);
103 pa_xfree(d
->source_name
);
104 pa_xfree(d
->card_name
);
108 static const char *strip_udi(const char *udi
) {
113 if ((slash
= strrchr(udi
, '/')))
127 static enum alsa_type
hal_alsa_device_get_type(LibHalContext
*context
, const char *udi
) {
129 enum alsa_type t
= ALSA_TYPE_OTHER
;
132 dbus_error_init(&error
);
137 if (!(type
= libhal_device_get_property_string(context
, udi
, "alsa.type", &error
)))
140 if (pa_streq(type
, "playback"))
141 t
= ALSA_TYPE_PLAYBACK
;
142 else if (pa_streq(type
, "capture"))
143 t
= ALSA_TYPE_CAPTURE
;
145 libhal_free_string(type
);
148 if (dbus_error_is_set(&error
)) {
149 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error
.name
, error
.message
);
150 dbus_error_free(&error
);
156 static pa_bool_t
hal_alsa_device_is_modem(LibHalContext
*context
, const char *udi
) {
161 dbus_error_init(&error
);
166 if (!(class = libhal_device_get_property_string(context
, udi
, "alsa.pcm_class", &error
)))
169 r
= pa_streq(class, "modem");
170 libhal_free_string(class);
173 if (dbus_error_is_set(&error
)) {
174 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error
.name
, error
.message
);
175 dbus_error_free(&error
);
181 static int hal_device_load_alsa(struct userdata
*u
, const char *udi
, struct device
*d
) {
186 char *args
, *originating_udi
= NULL
, *card_name
= NULL
;
188 dbus_error_init(&error
);
194 /* We only care for PCM devices */
195 type
= hal_alsa_device_get_type(u
->context
, udi
);
196 if (type
== ALSA_TYPE_OTHER
)
199 /* We don't care for modems */
200 if (hal_alsa_device_is_modem(u
->context
, udi
))
203 /* We only care for the main device */
204 device
= libhal_device_get_property_int(u
->context
, udi
, "alsa.device", &error
);
205 if (dbus_error_is_set(&error
) || device
!= 0)
208 /* We store only one entry per card, hence we look for the originating device */
209 originating_udi
= libhal_device_get_property_string(u
->context
, udi
, "alsa.originating_device", &error
);
210 if (dbus_error_is_set(&error
) || !originating_udi
)
213 /* Make sure we only load one module per card */
214 if (pa_hashmap_get(u
->devices
, originating_udi
))
217 /* We need the identifier */
218 card
= libhal_device_get_property_int(u
->context
, udi
, "alsa.card", &error
);
219 if (dbus_error_is_set(&error
))
222 card_name
= pa_sprintf_malloc("alsa_card.%s", strip_udi(originating_udi
));
223 args
= pa_sprintf_malloc("device_id=%u name=%s card_name=%s tsched=%i", card
, strip_udi(originating_udi
), card_name
, (int) u
->use_tsched
);
225 pa_log_debug("Loading module-alsa-card with arguments '%s'", args
);
226 m
= pa_module_load(u
->core
, "module-alsa-card", args
);
232 d
->originating_udi
= originating_udi
;
233 d
->module
= m
->index
;
234 d
->card_name
= card_name
;
239 if (dbus_error_is_set(&error
)) {
240 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error
.name
, error
.message
);
241 dbus_error_free(&error
);
244 pa_xfree(originating_udi
);
254 static pa_bool_t
hal_oss_device_is_pcm(LibHalContext
*context
, const char *udi
) {
255 char *class = NULL
, *dev
= NULL
, *e
;
260 dbus_error_init(&error
);
265 /* We only care for PCM devices */
266 class = libhal_device_get_property_string(context
, udi
, "oss.type", &error
);
267 if (dbus_error_is_set(&error
) || !class)
270 if (!pa_streq(class, "pcm"))
273 /* We don't like /dev/audio */
274 dev
= libhal_device_get_property_string(context
, udi
, "oss.device_file", &error
);
275 if (dbus_error_is_set(&error
) || !dev
)
278 if ((e
= strrchr(dev
, '/')))
279 if (pa_startswith(e
+ 1, "audio"))
282 /* We only care for the main device */
283 device
= libhal_device_get_property_int(context
, udi
, "oss.device", &error
);
284 if (dbus_error_is_set(&error
) || device
!= 0)
291 if (dbus_error_is_set(&error
)) {
292 pa_log_error("D-Bus error while parsing HAL OSS data: %s: %s", error
.name
, error
.message
);
293 dbus_error_free(&error
);
296 libhal_free_string(class);
297 libhal_free_string(dev
);
302 static int hal_device_load_oss(struct userdata
*u
, const char *udi
, struct device
*d
) {
305 char *args
, *originating_udi
= NULL
, *device
, *sink_name
= NULL
, *source_name
= NULL
;
307 dbus_error_init(&error
);
313 /* We only care for OSS PCM devices */
314 if (!hal_oss_device_is_pcm(u
->context
, udi
))
317 /* We store only one entry per card, hence we look for the originating device */
318 originating_udi
= libhal_device_get_property_string(u
->context
, udi
, "oss.originating_device", &error
);
319 if (dbus_error_is_set(&error
) || !originating_udi
)
322 /* Make sure we only load one module per card */
323 if (pa_hashmap_get(u
->devices
, originating_udi
))
326 /* We need the device file */
327 device
= libhal_device_get_property_string(u
->context
, udi
, "oss.device_file", &error
);
328 if (!device
|| dbus_error_is_set(&error
))
331 sink_name
= pa_sprintf_malloc("oss_output.%s", strip_udi(udi
));
332 source_name
= pa_sprintf_malloc("oss_input.%s", strip_udi(udi
));
333 args
= pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device
, sink_name
, source_name
);
335 libhal_free_string(device
);
337 pa_log_debug("Loading module-oss with arguments '%s'", args
);
338 m
= pa_module_load(u
->core
, "module-oss", args
);
344 d
->originating_udi
= originating_udi
;
345 d
->module
= m
->index
;
346 d
->sink_name
= sink_name
;
347 d
->source_name
= source_name
;
352 if (dbus_error_is_set(&error
)) {
353 pa_log_error("D-Bus error while parsing OSS HAL data: %s: %s", error
.name
, error
.message
);
354 dbus_error_free(&error
);
357 pa_xfree(originating_udi
);
358 pa_xfree(source_name
);
365 static struct device
* hal_device_add(struct userdata
*u
, const char *udi
) {
370 pa_assert(u
->capability
);
372 d
= pa_xnew(struct device
, 1);
373 d
->acl_race_fix
= FALSE
;
374 d
->udi
= pa_xstrdup(udi
);
375 d
->originating_udi
= NULL
;
376 d
->module
= PA_INVALID_INDEX
;
377 d
->sink_name
= d
->source_name
= d
->card_name
= NULL
;
380 if (pa_streq(u
->capability
, CAPABILITY_ALSA
))
381 r
= hal_device_load_alsa(u
, udi
, d
);
384 if (pa_streq(u
->capability
, CAPABILITY_OSS
))
385 r
= hal_device_load_oss(u
, udi
, d
);
393 pa_hashmap_put(u
->devices
, d
->udi
, d
);
394 pa_hashmap_put(u
->devices
, d
->originating_udi
, d
);
399 static int hal_device_add_all(struct userdata
*u
) {
404 dbus_error_init(&error
);
408 udis
= libhal_find_device_by_capability(u
->context
, u
->capability
, &n
, &error
);
409 if (dbus_error_is_set(&error
) || !udis
)
415 for (i
= 0; i
< n
; i
++) {
418 if ((d
= hal_device_add(u
, udis
[i
])))
421 pa_log_debug("Not loaded device %s", udis
[i
]);
425 libhal_free_string_array(udis
);
430 if (dbus_error_is_set(&error
)) {
431 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error
.name
, error
.message
);
432 dbus_error_free(&error
);
438 static void device_added_cb(LibHalContext
*context
, const char *udi
) {
441 pa_bool_t good
= FALSE
;
443 dbus_error_init(&error
);
448 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
450 good
= libhal_device_query_capability(context
, udi
, u
->capability
, &error
);
451 if (dbus_error_is_set(&error
) || !good
)
454 if (!hal_device_add(u
, udi
))
455 pa_log_debug("Not loaded device %s", udi
);
458 if (dbus_error_is_set(&error
)) {
459 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error
.name
, error
.message
);
460 dbus_error_free(&error
);
464 static void device_removed_cb(LibHalContext
* context
, const char *udi
) {
471 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
473 if (!(d
= pa_hashmap_get(u
->devices
, udi
)))
476 pa_hashmap_remove(u
->devices
, d
->originating_udi
);
477 pa_hashmap_remove(u
->devices
, d
->udi
);
479 pa_log_debug("Removing HAL device: %s", d
->originating_udi
);
481 pa_module_unload_request_by_index(u
->core
, d
->module
, TRUE
);
485 static void new_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
490 pa_assert(capability
);
492 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
494 if (pa_streq(u
->capability
, capability
))
495 /* capability we care about, pretend it's a new device */
496 device_added_cb(context
, udi
);
499 static void lost_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
504 pa_assert(capability
);
506 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
508 if (pa_streq(u
->capability
, capability
))
509 /* capability we care about, pretend it was removed */
510 device_removed_cb(context
, udi
);
513 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*message
, void *userdata
) {
519 pa_assert_se(u
= userdata
);
521 dbus_error_init(&error
);
523 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
524 dbus_message_get_interface(message
),
525 dbus_message_get_path(message
),
526 dbus_message_get_member(message
));
528 if (dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
529 dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
531 pa_bool_t suspend
= strcmp(dbus_message_get_member(message
), "ACLRemoved") == 0;
533 if (!dbus_message_get_args(message
, &error
, DBUS_TYPE_UINT32
, &uid
, DBUS_TYPE_INVALID
) || dbus_error_is_set(&error
)) {
534 pa_log_error("Failed to parse ACL message: %s: %s", error
.name
, error
.message
);
538 /* Check if this is about us? */
539 if (uid
== getuid() || uid
== geteuid()) {
543 udi
= dbus_message_get_path(message
);
545 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
546 pa_bool_t send_acl_race_fix_message
= FALSE
;
547 d
->acl_race_fix
= FALSE
;
552 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
))) {
553 pa_bool_t success
= pa_sink_suspend(sink
, suspend
) >= 0;
555 if (!success
&& !suspend
)
556 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
558 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
562 if (d
->source_name
) {
565 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
))) {
566 pa_bool_t success
= pa_source_suspend(source
, suspend
) >= 0;
568 if (!success
&& !suspend
)
569 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
571 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
578 if ((card
= pa_namereg_get(u
->core
, d
->card_name
, PA_NAMEREG_CARD
))) {
579 pa_bool_t success
= pa_card_suspend(card
, suspend
) >= 0;
581 if (!success
&& !suspend
)
582 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
584 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
588 if (send_acl_race_fix_message
) {
590 msg
= dbus_message_new_signal(udi
, "org.pulseaudio.Server", "DirtyGiveUpMessage");
591 dbus_connection_send(pa_dbus_connection_get(u
->connection
), msg
, NULL
);
592 dbus_message_unref(msg
);
596 device_added_cb(u
->context
, udi
);
600 return DBUS_HANDLER_RESULT_HANDLED
;
602 } else if (dbus_message_is_signal(message
, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
603 /* We use this message to avoid a dirty race condition when we
604 get an ACLAdded message before the previously owning PA
605 sever has closed the device. We can remove this as
606 soon as HAL learns frevoke() */
611 udi
= dbus_message_get_path(message
);
613 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
615 if (d
->acl_race_fix
) {
616 d
->acl_race_fix
= FALSE
;
617 pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi
);
622 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
)))
623 pa_sink_suspend(sink
, FALSE
);
626 if (d
->source_name
) {
629 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
)))
630 pa_source_suspend(source
, FALSE
);
636 if ((card
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_CARD
)))
637 pa_card_suspend(card
, FALSE
);
642 /* Yes, we don't check the UDI for validity, but hopefully HAL will */
643 device_added_cb(u
->context
, udi
);
645 return DBUS_HANDLER_RESULT_HANDLED
;
649 dbus_error_free(&error
);
651 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
654 static void hal_context_free(LibHalContext
* hal_context
) {
657 dbus_error_init(&error
);
659 libhal_ctx_shutdown(hal_context
, &error
);
660 libhal_ctx_free(hal_context
);
662 dbus_error_free(&error
);
665 static LibHalContext
* hal_context_new(DBusConnection
*connection
) {
667 LibHalContext
*hal_context
= NULL
;
669 dbus_error_init(&error
);
671 pa_assert(connection
);
673 if (!(hal_context
= libhal_ctx_new())) {
674 pa_log_error("libhal_ctx_new() failed");
678 if (!libhal_ctx_set_dbus_connection(hal_context
, connection
)) {
679 pa_log_error("Error establishing DBUS connection: %s: %s", error
.name
, error
.message
);
683 if (!libhal_ctx_init(hal_context
, &error
)) {
684 pa_log_error("Couldn't connect to hald: %s: %s", error
.name
, error
.message
);
692 hal_context_free(hal_context
);
694 dbus_error_free(&error
);
699 int pa__init(pa_module
*m
) {
701 struct userdata
*u
= NULL
;
708 dbus_error_init(&error
);
710 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
711 pa_log("Failed to parse module arguments");
715 m
->userdata
= u
= pa_xnew(struct userdata
, 1);
718 u
->connection
= NULL
;
719 u
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
720 u
->capability
= NULL
;
723 u
->use_tsched
= TRUE
;
725 if (pa_modargs_get_value_boolean(ma
, "tsched", &u
->use_tsched
) < 0) {
726 pa_log("Failed to parse tsched argument.");
730 api
= pa_modargs_get_value(ma
, "api", "alsa");
732 if (pa_streq(api
, "alsa"))
733 u
->capability
= CAPABILITY_ALSA
;
735 api
= pa_modargs_get_value(ma
, "api", "oss");
739 if (pa_streq(api
, "oss"))
740 u
->capability
= CAPABILITY_OSS
;
743 if (!u
->capability
) {
744 pa_log_error("Invalid API specification.");
748 if (!(u
->connection
= pa_dbus_bus_get(m
->core
, DBUS_BUS_SYSTEM
, &error
)) || dbus_error_is_set(&error
)) {
749 pa_log_error("Unable to contact DBUS system bus: %s: %s", error
.name
, error
.message
);
753 if (!(u
->context
= hal_context_new(pa_dbus_connection_get(u
->connection
)))) {
754 /* pa_hal_context_new() logs appropriate errors */
758 n
= hal_device_add_all(u
);
760 libhal_ctx_set_user_data(u
->context
, u
);
761 libhal_ctx_set_device_added(u
->context
, device_added_cb
);
762 libhal_ctx_set_device_removed(u
->context
, device_removed_cb
);
763 libhal_ctx_set_device_new_capability(u
->context
, new_capability_cb
);
764 libhal_ctx_set_device_lost_capability(u
->context
, lost_capability_cb
);
766 if (!libhal_device_property_watch_all(u
->context
, &error
)) {
767 pa_log_error("Error monitoring device list: %s: %s", error
.name
, error
.message
);
771 if (!dbus_connection_add_filter(pa_dbus_connection_get(u
->connection
), filter_cb
, u
, NULL
)) {
772 pa_log_error("Failed to add filter function");
776 if (pa_dbus_add_matches(
777 pa_dbus_connection_get(u
->connection
), &error
,
778 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
779 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
780 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL
) < 0) {
781 pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error
.name
, error
.message
);
785 pa_log_info("Loaded %i modules.", n
);
795 dbus_error_free(&error
);
801 void pa__done(pa_module
*m
) {
806 if (!(u
= m
->userdata
))
810 hal_context_free(u
->context
);
815 while ((d
= pa_hashmap_first(u
->devices
))) {
816 pa_hashmap_remove(u
->devices
, d
->udi
);
817 pa_hashmap_remove(u
->devices
, d
->originating_udi
);
821 pa_hashmap_free(u
->devices
, NULL
, NULL
);
825 pa_dbus_remove_matches(
826 pa_dbus_connection_get(u
->connection
),
827 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
828 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
829 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL
);
831 dbus_connection_remove_filter(pa_dbus_connection_get(u
->connection
), filter_cb
, u
);
832 pa_dbus_connection_unref(u
->connection
);