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.1 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 store only one entry per card, hence we look for the originating device */
204 originating_udi
= libhal_device_get_property_string(u
->context
, udi
, "alsa.originating_device", &error
);
205 if (dbus_error_is_set(&error
) || !originating_udi
)
208 /* Make sure we only load one module per card */
209 if (pa_hashmap_get(u
->devices
, originating_udi
))
212 /* We need the identifier */
213 card
= libhal_device_get_property_int(u
->context
, udi
, "alsa.card", &error
);
214 if (dbus_error_is_set(&error
))
217 card_name
= pa_sprintf_malloc("alsa_card.%s", strip_udi(originating_udi
));
218 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
);
220 pa_log_debug("Loading module-alsa-card with arguments '%s'", args
);
221 m
= pa_module_load(u
->core
, "module-alsa-card", args
);
227 d
->originating_udi
= originating_udi
;
228 d
->module
= m
->index
;
229 d
->card_name
= card_name
;
234 if (dbus_error_is_set(&error
)) {
235 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error
.name
, error
.message
);
236 dbus_error_free(&error
);
239 pa_xfree(originating_udi
);
249 static pa_bool_t
hal_oss_device_is_pcm(LibHalContext
*context
, const char *udi
) {
250 char *class = NULL
, *dev
= NULL
, *e
;
255 dbus_error_init(&error
);
260 /* We only care for PCM devices */
261 class = libhal_device_get_property_string(context
, udi
, "oss.type", &error
);
262 if (dbus_error_is_set(&error
) || !class)
265 if (!pa_streq(class, "pcm"))
268 /* We don't like /dev/audio */
269 dev
= libhal_device_get_property_string(context
, udi
, "oss.device_file", &error
);
270 if (dbus_error_is_set(&error
) || !dev
)
273 if ((e
= strrchr(dev
, '/')))
274 if (pa_startswith(e
+ 1, "audio"))
277 /* We only care for the main device */
278 device
= libhal_device_get_property_int(context
, udi
, "oss.device", &error
);
279 if (dbus_error_is_set(&error
) || device
!= 0)
286 if (dbus_error_is_set(&error
)) {
287 pa_log_error("D-Bus error while parsing HAL OSS data: %s: %s", error
.name
, error
.message
);
288 dbus_error_free(&error
);
291 libhal_free_string(class);
292 libhal_free_string(dev
);
297 static int hal_device_load_oss(struct userdata
*u
, const char *udi
, struct device
*d
) {
300 char *args
, *originating_udi
= NULL
, *device
, *sink_name
= NULL
, *source_name
= NULL
;
302 dbus_error_init(&error
);
308 /* We only care for OSS PCM devices */
309 if (!hal_oss_device_is_pcm(u
->context
, udi
))
312 /* We store only one entry per card, hence we look for the originating device */
313 originating_udi
= libhal_device_get_property_string(u
->context
, udi
, "oss.originating_device", &error
);
314 if (dbus_error_is_set(&error
) || !originating_udi
)
317 /* Make sure we only load one module per card */
318 if (pa_hashmap_get(u
->devices
, originating_udi
))
321 /* We need the device file */
322 device
= libhal_device_get_property_string(u
->context
, udi
, "oss.device_file", &error
);
323 if (!device
|| dbus_error_is_set(&error
))
326 sink_name
= pa_sprintf_malloc("oss_output.%s", strip_udi(udi
));
327 source_name
= pa_sprintf_malloc("oss_input.%s", strip_udi(udi
));
328 args
= pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device
, sink_name
, source_name
);
330 libhal_free_string(device
);
332 pa_log_debug("Loading module-oss with arguments '%s'", args
);
333 m
= pa_module_load(u
->core
, "module-oss", args
);
339 d
->originating_udi
= originating_udi
;
340 d
->module
= m
->index
;
341 d
->sink_name
= sink_name
;
342 d
->source_name
= source_name
;
347 if (dbus_error_is_set(&error
)) {
348 pa_log_error("D-Bus error while parsing OSS HAL data: %s: %s", error
.name
, error
.message
);
349 dbus_error_free(&error
);
352 pa_xfree(originating_udi
);
353 pa_xfree(source_name
);
360 static struct device
* hal_device_add(struct userdata
*u
, const char *udi
) {
365 pa_assert(u
->capability
);
367 d
= pa_xnew(struct device
, 1);
368 d
->acl_race_fix
= FALSE
;
369 d
->udi
= pa_xstrdup(udi
);
370 d
->originating_udi
= NULL
;
371 d
->module
= PA_INVALID_INDEX
;
372 d
->sink_name
= d
->source_name
= d
->card_name
= NULL
;
376 if (pa_streq(u
->capability
, CAPABILITY_ALSA
))
377 r
= hal_device_load_alsa(u
, udi
, d
);
380 if (pa_streq(u
->capability
, CAPABILITY_OSS
))
381 r
= hal_device_load_oss(u
, udi
, d
);
389 pa_hashmap_put(u
->devices
, d
->udi
, d
);
390 pa_hashmap_put(u
->devices
, d
->originating_udi
, d
);
395 static int hal_device_add_all(struct userdata
*u
) {
400 dbus_error_init(&error
);
404 udis
= libhal_find_device_by_capability(u
->context
, u
->capability
, &n
, &error
);
405 if (dbus_error_is_set(&error
) || !udis
)
411 for (i
= 0; i
< n
; i
++) {
414 if ((d
= hal_device_add(u
, udis
[i
])))
417 pa_log_debug("Not loaded device %s", udis
[i
]);
421 libhal_free_string_array(udis
);
426 if (dbus_error_is_set(&error
)) {
427 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error
.name
, error
.message
);
428 dbus_error_free(&error
);
434 static void device_added_cb(LibHalContext
*context
, const char *udi
) {
437 pa_bool_t good
= FALSE
;
439 dbus_error_init(&error
);
444 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
446 good
= libhal_device_query_capability(context
, udi
, u
->capability
, &error
);
447 if (dbus_error_is_set(&error
) || !good
)
450 if (!hal_device_add(u
, udi
))
451 pa_log_debug("Not loaded device %s", udi
);
454 if (dbus_error_is_set(&error
)) {
455 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error
.name
, error
.message
);
456 dbus_error_free(&error
);
460 static void device_removed_cb(LibHalContext
* context
, const char *udi
) {
467 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
469 if (!(d
= pa_hashmap_get(u
->devices
, udi
)))
472 pa_hashmap_remove(u
->devices
, d
->originating_udi
);
473 pa_hashmap_remove(u
->devices
, d
->udi
);
475 pa_log_debug("Removing HAL device: %s", d
->originating_udi
);
477 pa_module_unload_request_by_index(u
->core
, d
->module
, TRUE
);
481 static void new_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
486 pa_assert(capability
);
488 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
490 if (pa_streq(u
->capability
, capability
))
491 /* capability we care about, pretend it's a new device */
492 device_added_cb(context
, udi
);
495 static void lost_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
500 pa_assert(capability
);
502 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
504 if (pa_streq(u
->capability
, capability
))
505 /* capability we care about, pretend it was removed */
506 device_removed_cb(context
, udi
);
509 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*message
, void *userdata
) {
515 pa_assert_se(u
= userdata
);
517 dbus_error_init(&error
);
519 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
520 dbus_message_get_interface(message
),
521 dbus_message_get_path(message
),
522 dbus_message_get_member(message
));
524 if (dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
525 dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
527 pa_bool_t suspend
= strcmp(dbus_message_get_member(message
), "ACLRemoved") == 0;
529 if (!dbus_message_get_args(message
, &error
, DBUS_TYPE_UINT32
, &uid
, DBUS_TYPE_INVALID
) || dbus_error_is_set(&error
)) {
530 pa_log_error("Failed to parse ACL message: %s: %s", error
.name
, error
.message
);
534 /* Check if this is about us? */
535 if (uid
== getuid() || uid
== geteuid()) {
539 udi
= dbus_message_get_path(message
);
541 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
542 pa_bool_t send_acl_race_fix_message
= FALSE
;
543 d
->acl_race_fix
= FALSE
;
548 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
))) {
549 pa_bool_t success
= pa_sink_suspend(sink
, suspend
) >= 0;
551 if (!success
&& !suspend
)
552 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
554 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
558 if (d
->source_name
) {
561 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
))) {
562 pa_bool_t success
= pa_source_suspend(source
, suspend
) >= 0;
564 if (!success
&& !suspend
)
565 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
567 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
574 if ((card
= pa_namereg_get(u
->core
, d
->card_name
, PA_NAMEREG_CARD
))) {
575 pa_bool_t success
= pa_card_suspend(card
, suspend
) >= 0;
577 if (!success
&& !suspend
)
578 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
580 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
584 if (send_acl_race_fix_message
) {
586 msg
= dbus_message_new_signal(udi
, "org.pulseaudio.Server", "DirtyGiveUpMessage");
587 dbus_connection_send(pa_dbus_connection_get(u
->connection
), msg
, NULL
);
588 dbus_message_unref(msg
);
592 device_added_cb(u
->context
, udi
);
596 return DBUS_HANDLER_RESULT_HANDLED
;
598 } else if (dbus_message_is_signal(message
, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
599 /* We use this message to avoid a dirty race condition when we
600 get an ACLAdded message before the previously owning PA
601 sever has closed the device. We can remove this as
602 soon as HAL learns frevoke() */
607 udi
= dbus_message_get_path(message
);
609 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
611 if (d
->acl_race_fix
) {
612 d
->acl_race_fix
= FALSE
;
613 pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi
);
618 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
)))
619 pa_sink_suspend(sink
, FALSE
);
622 if (d
->source_name
) {
625 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
)))
626 pa_source_suspend(source
, FALSE
);
632 if ((card
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_CARD
)))
633 pa_card_suspend(card
, FALSE
);
638 /* Yes, we don't check the UDI for validity, but hopefully HAL will */
639 device_added_cb(u
->context
, udi
);
641 return DBUS_HANDLER_RESULT_HANDLED
;
645 dbus_error_free(&error
);
647 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
650 static void hal_context_free(LibHalContext
* hal_context
) {
653 dbus_error_init(&error
);
655 libhal_ctx_shutdown(hal_context
, &error
);
656 libhal_ctx_free(hal_context
);
658 dbus_error_free(&error
);
661 static LibHalContext
* hal_context_new(DBusConnection
*connection
) {
663 LibHalContext
*hal_context
= NULL
;
665 dbus_error_init(&error
);
667 pa_assert(connection
);
669 if (!(hal_context
= libhal_ctx_new())) {
670 pa_log_error("libhal_ctx_new() failed");
674 if (!libhal_ctx_set_dbus_connection(hal_context
, connection
)) {
675 pa_log_error("Error establishing DBUS connection: %s: %s", error
.name
, error
.message
);
679 if (!libhal_ctx_init(hal_context
, &error
)) {
680 pa_log_error("Couldn't connect to hald: %s: %s", error
.name
, error
.message
);
688 hal_context_free(hal_context
);
690 dbus_error_free(&error
);
695 int pa__init(pa_module
*m
) {
697 struct userdata
*u
= NULL
;
704 dbus_error_init(&error
);
706 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
707 pa_log("Failed to parse module arguments");
711 m
->userdata
= u
= pa_xnew(struct userdata
, 1);
714 u
->connection
= NULL
;
715 u
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
716 u
->capability
= NULL
;
719 u
->use_tsched
= TRUE
;
721 if (pa_modargs_get_value_boolean(ma
, "tsched", &u
->use_tsched
) < 0) {
722 pa_log("Failed to parse tsched argument.");
726 api
= pa_modargs_get_value(ma
, "api", "alsa");
728 if (pa_streq(api
, "alsa"))
729 u
->capability
= CAPABILITY_ALSA
;
731 api
= pa_modargs_get_value(ma
, "api", "oss");
735 if (pa_streq(api
, "oss"))
736 u
->capability
= CAPABILITY_OSS
;
739 if (!u
->capability
) {
740 pa_log_error("Invalid API specification.");
744 if (!(u
->connection
= pa_dbus_bus_get(m
->core
, DBUS_BUS_SYSTEM
, &error
)) || dbus_error_is_set(&error
)) {
745 pa_log_error("Unable to contact DBUS system bus: %s: %s", error
.name
, error
.message
);
749 if (!(u
->context
= hal_context_new(pa_dbus_connection_get(u
->connection
)))) {
750 /* pa_hal_context_new() logs appropriate errors */
754 n
= hal_device_add_all(u
);
756 libhal_ctx_set_user_data(u
->context
, u
);
757 libhal_ctx_set_device_added(u
->context
, device_added_cb
);
758 libhal_ctx_set_device_removed(u
->context
, device_removed_cb
);
759 libhal_ctx_set_device_new_capability(u
->context
, new_capability_cb
);
760 libhal_ctx_set_device_lost_capability(u
->context
, lost_capability_cb
);
762 if (!libhal_device_property_watch_all(u
->context
, &error
)) {
763 pa_log_error("Error monitoring device list: %s: %s", error
.name
, error
.message
);
767 if (!dbus_connection_add_filter(pa_dbus_connection_get(u
->connection
), filter_cb
, u
, NULL
)) {
768 pa_log_error("Failed to add filter function");
772 if (pa_dbus_add_matches(
773 pa_dbus_connection_get(u
->connection
), &error
,
774 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
775 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
776 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL
) < 0) {
777 pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error
.name
, error
.message
);
781 pa_log_info("Loaded %i modules.", n
);
791 dbus_error_free(&error
);
797 void pa__done(pa_module
*m
) {
802 if (!(u
= m
->userdata
))
806 hal_context_free(u
->context
);
811 while ((d
= pa_hashmap_first(u
->devices
))) {
812 pa_hashmap_remove(u
->devices
, d
->udi
);
813 pa_hashmap_remove(u
->devices
, d
->originating_udi
);
817 pa_hashmap_free(u
->devices
, NULL
, NULL
);
821 pa_dbus_remove_matches(
822 pa_dbus_connection_get(u
->connection
),
823 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
824 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
825 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL
);
827 dbus_connection_remove_filter(pa_dbus_connection_get(u
->connection
), filter_cb
, u
);
828 pa_dbus_connection_unref(u
->connection
);