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>
48 #include <pulsecore/dbus-shared.h>
50 #include <hal/libhal.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 if (!dbus_error_has_name(&error
, "org.freedesktop.Hal.NoSuchProperty"))
456 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error
.name
, error
.message
);
457 dbus_error_free(&error
);
461 static void device_removed_cb(LibHalContext
* context
, const char *udi
) {
468 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
470 if (!(d
= pa_hashmap_get(u
->devices
, udi
)))
473 pa_hashmap_remove(u
->devices
, d
->originating_udi
);
474 pa_hashmap_remove(u
->devices
, d
->udi
);
476 pa_log_debug("Removing HAL device: %s", d
->originating_udi
);
478 pa_module_unload_request_by_index(u
->core
, d
->module
, TRUE
);
482 static void new_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
487 pa_assert(capability
);
489 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
491 if (pa_streq(u
->capability
, capability
))
492 /* capability we care about, pretend it's a new device */
493 device_added_cb(context
, udi
);
496 static void lost_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
501 pa_assert(capability
);
503 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
505 if (pa_streq(u
->capability
, capability
))
506 /* capability we care about, pretend it was removed */
507 device_removed_cb(context
, udi
);
510 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*message
, void *userdata
) {
516 pa_assert_se(u
= userdata
);
518 dbus_error_init(&error
);
520 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
521 dbus_message_get_interface(message
),
522 dbus_message_get_path(message
),
523 dbus_message_get_member(message
));
525 if (dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
526 dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
528 pa_bool_t suspend
= strcmp(dbus_message_get_member(message
), "ACLRemoved") == 0;
530 if (!dbus_message_get_args(message
, &error
, DBUS_TYPE_UINT32
, &uid
, DBUS_TYPE_INVALID
) || dbus_error_is_set(&error
)) {
531 pa_log_error("Failed to parse ACL message: %s: %s", error
.name
, error
.message
);
535 /* Check if this is about us? */
536 if (uid
== getuid() || uid
== geteuid()) {
540 udi
= dbus_message_get_path(message
);
542 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
543 pa_bool_t send_acl_race_fix_message
= FALSE
;
544 d
->acl_race_fix
= FALSE
;
549 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
))) {
550 pa_bool_t success
= pa_sink_suspend(sink
, suspend
) >= 0;
552 if (!success
&& !suspend
)
553 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
555 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
559 if (d
->source_name
) {
562 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
))) {
563 pa_bool_t success
= pa_source_suspend(source
, suspend
) >= 0;
565 if (!success
&& !suspend
)
566 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
568 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
575 if ((card
= pa_namereg_get(u
->core
, d
->card_name
, PA_NAMEREG_CARD
))) {
576 pa_bool_t success
= pa_card_suspend(card
, suspend
) >= 0;
578 if (!success
&& !suspend
)
579 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
581 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
585 if (send_acl_race_fix_message
) {
587 msg
= dbus_message_new_signal(udi
, "org.pulseaudio.Server", "DirtyGiveUpMessage");
588 dbus_connection_send(pa_dbus_connection_get(u
->connection
), msg
, NULL
);
589 dbus_message_unref(msg
);
593 device_added_cb(u
->context
, udi
);
597 return DBUS_HANDLER_RESULT_HANDLED
;
599 } else if (dbus_message_is_signal(message
, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
600 /* We use this message to avoid a dirty race condition when we
601 get an ACLAdded message before the previously owning PA
602 sever has closed the device. We can remove this as
603 soon as HAL learns frevoke() */
608 udi
= dbus_message_get_path(message
);
610 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
612 if (d
->acl_race_fix
) {
613 d
->acl_race_fix
= FALSE
;
614 pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi
);
619 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
)))
620 pa_sink_suspend(sink
, FALSE
);
623 if (d
->source_name
) {
626 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
)))
627 pa_source_suspend(source
, FALSE
);
633 if ((card
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_CARD
)))
634 pa_card_suspend(card
, FALSE
);
639 /* Yes, we don't check the UDI for validity, but hopefully HAL will */
640 device_added_cb(u
->context
, udi
);
642 return DBUS_HANDLER_RESULT_HANDLED
;
646 dbus_error_free(&error
);
648 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
651 static void hal_context_free(LibHalContext
* hal_context
) {
654 dbus_error_init(&error
);
656 libhal_ctx_shutdown(hal_context
, &error
);
657 libhal_ctx_free(hal_context
);
659 dbus_error_free(&error
);
662 static LibHalContext
* hal_context_new(DBusConnection
*connection
) {
664 LibHalContext
*hal_context
= NULL
;
666 dbus_error_init(&error
);
668 pa_assert(connection
);
670 if (!(hal_context
= libhal_ctx_new())) {
671 pa_log_error("libhal_ctx_new() failed");
675 if (!libhal_ctx_set_dbus_connection(hal_context
, connection
)) {
676 pa_log_error("Error establishing DBUS connection: %s: %s", error
.name
, error
.message
);
680 if (!libhal_ctx_init(hal_context
, &error
)) {
681 pa_log_error("Couldn't connect to hald: %s: %s", error
.name
, error
.message
);
689 hal_context_free(hal_context
);
691 dbus_error_free(&error
);
696 int pa__init(pa_module
*m
) {
698 struct userdata
*u
= NULL
;
705 dbus_error_init(&error
);
707 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
708 pa_log("Failed to parse module arguments");
712 m
->userdata
= u
= pa_xnew(struct userdata
, 1);
715 u
->connection
= NULL
;
716 u
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
717 u
->capability
= NULL
;
720 u
->use_tsched
= TRUE
;
722 if (pa_modargs_get_value_boolean(ma
, "tsched", &u
->use_tsched
) < 0) {
723 pa_log("Failed to parse tsched argument.");
727 api
= pa_modargs_get_value(ma
, "api", "alsa");
729 if (pa_streq(api
, "alsa"))
730 u
->capability
= CAPABILITY_ALSA
;
732 api
= pa_modargs_get_value(ma
, "api", "oss");
736 if (pa_streq(api
, "oss"))
737 u
->capability
= CAPABILITY_OSS
;
740 if (!u
->capability
) {
741 pa_log_error("Invalid API specification.");
745 if (!(u
->connection
= pa_dbus_bus_get(m
->core
, DBUS_BUS_SYSTEM
, &error
)) || dbus_error_is_set(&error
)) {
746 pa_log_error("Unable to contact DBUS system bus: %s: %s", error
.name
, error
.message
);
750 if (!(u
->context
= hal_context_new(pa_dbus_connection_get(u
->connection
)))) {
751 /* pa_hal_context_new() logs appropriate errors */
755 n
= hal_device_add_all(u
);
757 libhal_ctx_set_user_data(u
->context
, u
);
758 libhal_ctx_set_device_added(u
->context
, device_added_cb
);
759 libhal_ctx_set_device_removed(u
->context
, device_removed_cb
);
760 libhal_ctx_set_device_new_capability(u
->context
, new_capability_cb
);
761 libhal_ctx_set_device_lost_capability(u
->context
, lost_capability_cb
);
763 if (!libhal_device_property_watch_all(u
->context
, &error
)) {
764 pa_log_error("Error monitoring device list: %s: %s", error
.name
, error
.message
);
768 if (!dbus_connection_add_filter(pa_dbus_connection_get(u
->connection
), filter_cb
, u
, NULL
)) {
769 pa_log_error("Failed to add filter function");
773 if (pa_dbus_add_matches(
774 pa_dbus_connection_get(u
->connection
), &error
,
775 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
776 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
777 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL
) < 0) {
778 pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error
.name
, error
.message
);
782 pa_log_info("Loaded %i modules.", n
);
792 dbus_error_free(&error
);
798 void pa__done(pa_module
*m
) {
803 if (!(u
= m
->userdata
))
807 hal_context_free(u
->context
);
812 while ((d
= pa_hashmap_first(u
->devices
))) {
813 pa_hashmap_remove(u
->devices
, d
->udi
);
814 pa_hashmap_remove(u
->devices
, d
->originating_udi
);
818 pa_hashmap_free(u
->devices
, NULL
, NULL
);
822 pa_dbus_remove_matches(
823 pa_dbus_connection_get(u
->connection
),
824 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
825 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
826 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL
);
828 dbus_connection_remove_filter(pa_dbus_connection_get(u
->connection
), filter_cb
, u
);
829 pa_dbus_connection_unref(u
->connection
);