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>");
67 PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!");
70 char *udi
, *originating_udi
;
71 char *card_name
, *sink_name
, *source_name
;
73 pa_bool_t acl_race_fix
;
78 LibHalContext
*context
;
79 pa_dbus_connection
*connection
;
80 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 */
81 const char *capability
;
87 #define CAPABILITY_ALSA "alsa"
88 #define CAPABILITY_OSS "oss"
90 static const char* const valid_modargs
[] = {
98 static void device_free(struct device
* d
) {
102 pa_xfree(d
->originating_udi
);
103 pa_xfree(d
->sink_name
);
104 pa_xfree(d
->source_name
);
105 pa_xfree(d
->card_name
);
109 static const char *strip_udi(const char *udi
) {
114 if ((slash
= strrchr(udi
, '/')))
129 static enum alsa_type
hal_alsa_device_get_type(LibHalContext
*context
, const char *udi
) {
131 enum alsa_type t
= ALSA_TYPE_OTHER
;
134 dbus_error_init(&error
);
139 if (!(type
= libhal_device_get_property_string(context
, udi
, "alsa.type", &error
)))
142 if (pa_streq(type
, "playback"))
143 t
= ALSA_TYPE_PLAYBACK
;
144 else if (pa_streq(type
, "capture"))
145 t
= ALSA_TYPE_CAPTURE
;
146 else if (pa_streq(type
, "control"))
147 t
= ALSA_TYPE_CONTROL
;
149 libhal_free_string(type
);
152 if (dbus_error_is_set(&error
)) {
153 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error
.name
, error
.message
);
154 dbus_error_free(&error
);
160 static pa_bool_t
hal_alsa_device_is_modem(LibHalContext
*context
, const char *udi
) {
165 dbus_error_init(&error
);
170 if (!(class = libhal_device_get_property_string(context
, udi
, "alsa.pcm_class", &error
)))
173 r
= pa_streq(class, "modem");
174 libhal_free_string(class);
177 if (dbus_error_is_set(&error
)) {
178 if (!dbus_error_has_name(&error
, "org.freedesktop.Hal.NoSuchProperty"))
179 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error
.name
, error
.message
);
180 dbus_error_free(&error
);
186 static int hal_device_load_alsa(struct userdata
*u
, const char *udi
, struct device
*d
) {
191 char *args
, *originating_udi
= NULL
, *card_name
= NULL
;
193 dbus_error_init(&error
);
199 /* We only care for PCM devices */
200 type
= hal_alsa_device_get_type(u
->context
, udi
);
202 /* For each ALSA card that appears the control device will be the
203 * last one to be created, this is considered part of the ALSA
204 * usperspace API. We rely on this and load our modules only when
205 * the control device is available assuming that *all* device
206 * nodes have been properly created and assigned the right ACLs at
207 * that time. Also see:
209 * http://mailman.alsa-project.org/pipermail/alsa-devel/2009-April/015958.html
211 * and the associated thread.*/
213 if (type
!= ALSA_TYPE_CONTROL
)
216 /* We don't care for modems -- this is most likely not set for
217 * control devices, so kind of pointless here. */
218 if (hal_alsa_device_is_modem(u
->context
, udi
))
221 /* We store only one entry per card, hence we look for the originating device */
222 originating_udi
= libhal_device_get_property_string(u
->context
, udi
, "alsa.originating_device", &error
);
223 if (dbus_error_is_set(&error
) || !originating_udi
)
226 /* Make sure we only load one module per card */
227 if (pa_hashmap_get(u
->devices
, originating_udi
))
230 /* We need the identifier */
231 card
= libhal_device_get_property_int(u
->context
, udi
, "alsa.card", &error
);
232 if (dbus_error_is_set(&error
))
235 card_name
= pa_sprintf_malloc("alsa_card.%s", strip_udi(originating_udi
));
236 args
= pa_sprintf_malloc("device_id=%u name=\"%s\" card_name=\"%s\" tsched=%i card_properties=\"module-hal-detect.discovered=1\"", card
, strip_udi(originating_udi
), card_name
, (int) u
->use_tsched
);
238 pa_log_debug("Loading module-alsa-card with arguments '%s'", args
);
239 m
= pa_module_load(u
->core
, "module-alsa-card", args
);
245 d
->originating_udi
= originating_udi
;
246 d
->module
= m
->index
;
247 d
->card_name
= card_name
;
252 if (dbus_error_is_set(&error
)) {
253 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error
.name
, error
.message
);
254 dbus_error_free(&error
);
257 pa_xfree(originating_udi
);
267 static pa_bool_t
hal_oss_device_is_pcm(LibHalContext
*context
, const char *udi
) {
268 char *class = NULL
, *dev
= NULL
, *e
;
273 dbus_error_init(&error
);
278 /* We only care for PCM devices */
279 class = libhal_device_get_property_string(context
, udi
, "oss.type", &error
);
280 if (dbus_error_is_set(&error
) || !class)
283 if (!pa_streq(class, "pcm"))
286 /* We don't like /dev/audio */
287 dev
= libhal_device_get_property_string(context
, udi
, "oss.device_file", &error
);
288 if (dbus_error_is_set(&error
) || !dev
)
291 if ((e
= strrchr(dev
, '/')))
292 if (pa_startswith(e
+ 1, "audio"))
295 /* We only care for the main device */
296 device
= libhal_device_get_property_int(context
, udi
, "oss.device", &error
);
297 if (dbus_error_is_set(&error
) || device
!= 0)
304 if (dbus_error_is_set(&error
)) {
305 pa_log_error("D-Bus error while parsing HAL OSS data: %s: %s", error
.name
, error
.message
);
306 dbus_error_free(&error
);
309 libhal_free_string(class);
310 libhal_free_string(dev
);
315 static int hal_device_load_oss(struct userdata
*u
, const char *udi
, struct device
*d
) {
318 char *args
, *originating_udi
= NULL
, *device
, *sink_name
= NULL
, *source_name
= NULL
;
320 dbus_error_init(&error
);
326 /* We only care for OSS PCM devices */
327 if (!hal_oss_device_is_pcm(u
->context
, udi
))
330 /* We store only one entry per card, hence we look for the originating device */
331 originating_udi
= libhal_device_get_property_string(u
->context
, udi
, "oss.originating_device", &error
);
332 if (dbus_error_is_set(&error
) || !originating_udi
)
335 /* Make sure we only load one module per card */
336 if (pa_hashmap_get(u
->devices
, originating_udi
))
339 /* We need the device file */
340 device
= libhal_device_get_property_string(u
->context
, udi
, "oss.device_file", &error
);
341 if (!device
|| dbus_error_is_set(&error
))
344 sink_name
= pa_sprintf_malloc("oss_output.%s", strip_udi(udi
));
345 source_name
= pa_sprintf_malloc("oss_input.%s", strip_udi(udi
));
346 args
= pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device
, sink_name
, source_name
);
348 libhal_free_string(device
);
350 pa_log_debug("Loading module-oss with arguments '%s'", args
);
351 m
= pa_module_load(u
->core
, "module-oss", args
);
357 d
->originating_udi
= originating_udi
;
358 d
->module
= m
->index
;
359 d
->sink_name
= sink_name
;
360 d
->source_name
= source_name
;
365 if (dbus_error_is_set(&error
)) {
366 pa_log_error("D-Bus error while parsing OSS HAL data: %s: %s", error
.name
, error
.message
);
367 dbus_error_free(&error
);
370 pa_xfree(originating_udi
);
371 pa_xfree(source_name
);
378 static struct device
* hal_device_add(struct userdata
*u
, const char *udi
) {
383 pa_assert(u
->capability
);
385 d
= pa_xnew(struct device
, 1);
386 d
->acl_race_fix
= FALSE
;
387 d
->udi
= pa_xstrdup(udi
);
388 d
->originating_udi
= NULL
;
389 d
->module
= PA_INVALID_INDEX
;
390 d
->sink_name
= d
->source_name
= d
->card_name
= NULL
;
394 if (pa_streq(u
->capability
, CAPABILITY_ALSA
))
395 r
= hal_device_load_alsa(u
, udi
, d
);
398 if (pa_streq(u
->capability
, CAPABILITY_OSS
))
399 r
= hal_device_load_oss(u
, udi
, d
);
407 pa_hashmap_put(u
->devices
, d
->udi
, d
);
408 pa_hashmap_put(u
->devices
, d
->originating_udi
, d
);
413 static int hal_device_add_all(struct userdata
*u
) {
418 dbus_error_init(&error
);
422 udis
= libhal_find_device_by_capability(u
->context
, u
->capability
, &n
, &error
);
423 if (dbus_error_is_set(&error
) || !udis
)
429 for (i
= 0; i
< n
; i
++) {
432 if ((d
= hal_device_add(u
, udis
[i
]))) {
434 pa_log_debug("Loaded device %s", udis
[i
]);
436 pa_log_debug("Not loaded device %s", udis
[i
]);
440 libhal_free_string_array(udis
);
445 if (dbus_error_is_set(&error
)) {
446 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error
.name
, error
.message
);
447 dbus_error_free(&error
);
453 static void device_added_cb(LibHalContext
*context
, const char *udi
) {
456 pa_bool_t good
= FALSE
;
458 dbus_error_init(&error
);
463 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
465 good
= libhal_device_query_capability(context
, udi
, u
->capability
, &error
);
466 if (dbus_error_is_set(&error
) || !good
)
469 if (!hal_device_add(u
, udi
))
470 pa_log_debug("Not loaded device %s", udi
);
472 pa_log_debug("Loaded device %s", udi
);
475 if (dbus_error_is_set(&error
)) {
476 if (!dbus_error_has_name(&error
, "org.freedesktop.Hal.NoSuchProperty"))
477 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error
.name
, error
.message
);
478 dbus_error_free(&error
);
482 static void device_removed_cb(LibHalContext
* context
, const char *udi
) {
489 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
491 if (!(d
= pa_hashmap_get(u
->devices
, udi
)))
494 pa_hashmap_remove(u
->devices
, d
->originating_udi
);
495 pa_hashmap_remove(u
->devices
, d
->udi
);
497 pa_log_debug("Removing HAL device: %s", d
->originating_udi
);
499 pa_module_unload_request_by_index(u
->core
, d
->module
, TRUE
);
503 static void new_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
508 pa_assert(capability
);
510 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
512 if (pa_streq(u
->capability
, capability
))
513 /* capability we care about, pretend it's a new device */
514 device_added_cb(context
, udi
);
517 static void lost_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
522 pa_assert(capability
);
524 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
526 if (pa_streq(u
->capability
, capability
))
527 /* capability we care about, pretend it was removed */
528 device_removed_cb(context
, udi
);
531 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*message
, void *userdata
) {
537 pa_assert_se(u
= userdata
);
539 dbus_error_init(&error
);
541 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
542 dbus_message_get_interface(message
),
543 dbus_message_get_path(message
),
544 dbus_message_get_member(message
));
546 if (dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
547 dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
549 pa_bool_t suspend
= strcmp(dbus_message_get_member(message
), "ACLRemoved") == 0;
551 if (!dbus_message_get_args(message
, &error
, DBUS_TYPE_UINT32
, &uid
, DBUS_TYPE_INVALID
) || dbus_error_is_set(&error
)) {
552 pa_log_error("Failed to parse ACL message: %s: %s", error
.name
, error
.message
);
556 /* Check if this is about us? */
557 if (uid
== getuid() || uid
== geteuid()) {
561 udi
= dbus_message_get_path(message
);
563 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
564 pa_bool_t send_acl_race_fix_message
= FALSE
;
565 d
->acl_race_fix
= FALSE
;
570 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
))) {
571 pa_bool_t success
= pa_sink_suspend(sink
, suspend
, PA_SUSPEND_SESSION
) >= 0;
573 if (!success
&& !suspend
)
574 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
576 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
580 if (d
->source_name
) {
583 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
))) {
584 pa_bool_t success
= pa_source_suspend(source
, suspend
, PA_SUSPEND_SESSION
) >= 0;
586 if (!success
&& !suspend
)
587 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
589 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
596 if ((card
= pa_namereg_get(u
->core
, d
->card_name
, PA_NAMEREG_CARD
))) {
597 pa_bool_t success
= pa_card_suspend(card
, suspend
, PA_SUSPEND_SESSION
) >= 0;
599 if (!success
&& !suspend
)
600 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
602 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
606 if (send_acl_race_fix_message
) {
608 msg
= dbus_message_new_signal(udi
, "org.pulseaudio.Server", "DirtyGiveUpMessage");
609 dbus_connection_send(pa_dbus_connection_get(u
->connection
), msg
, NULL
);
610 dbus_message_unref(msg
);
614 device_added_cb(u
->context
, udi
);
618 return DBUS_HANDLER_RESULT_HANDLED
;
620 } else if (dbus_message_is_signal(message
, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
621 /* We use this message to avoid a dirty race condition when we
622 get an ACLAdded message before the previously owning PA
623 sever has closed the device. We can remove this as
624 soon as HAL learns frevoke() */
629 udi
= dbus_message_get_path(message
);
631 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
633 if (d
->acl_race_fix
) {
634 d
->acl_race_fix
= FALSE
;
635 pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi
);
640 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
)))
641 pa_sink_suspend(sink
, FALSE
, PA_SUSPEND_SESSION
);
644 if (d
->source_name
) {
647 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
)))
648 pa_source_suspend(source
, FALSE
, PA_SUSPEND_SESSION
);
654 if ((card
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_CARD
)))
655 pa_card_suspend(card
, FALSE
, PA_SUSPEND_SESSION
);
660 /* Yes, we don't check the UDI for validity, but hopefully HAL will */
661 device_added_cb(u
->context
, udi
);
663 return DBUS_HANDLER_RESULT_HANDLED
;
667 dbus_error_free(&error
);
669 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
672 static void hal_context_free(LibHalContext
* hal_context
) {
675 dbus_error_init(&error
);
677 libhal_ctx_shutdown(hal_context
, &error
);
678 libhal_ctx_free(hal_context
);
680 dbus_error_free(&error
);
683 static LibHalContext
* hal_context_new(DBusConnection
*connection
) {
685 LibHalContext
*hal_context
= NULL
;
687 dbus_error_init(&error
);
689 pa_assert(connection
);
691 if (!(hal_context
= libhal_ctx_new())) {
692 pa_log_error("libhal_ctx_new() failed");
696 if (!libhal_ctx_set_dbus_connection(hal_context
, connection
)) {
697 pa_log_error("Error establishing DBUS connection: %s: %s", error
.name
, error
.message
);
701 if (!libhal_ctx_init(hal_context
, &error
)) {
702 pa_log_error("Couldn't connect to hald: %s: %s", error
.name
, error
.message
);
710 hal_context_free(hal_context
);
712 dbus_error_free(&error
);
717 int pa__init(pa_module
*m
) {
719 struct userdata
*u
= NULL
;
726 dbus_error_init(&error
);
728 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
729 pa_log("Failed to parse module arguments");
733 m
->userdata
= u
= pa_xnew(struct userdata
, 1);
736 u
->connection
= NULL
;
737 u
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
738 u
->capability
= NULL
;
741 u
->use_tsched
= TRUE
;
743 if (pa_modargs_get_value_boolean(ma
, "tsched", &u
->use_tsched
) < 0) {
744 pa_log("Failed to parse tsched argument.");
748 api
= pa_modargs_get_value(ma
, "api", "alsa");
750 if (pa_streq(api
, "alsa"))
751 u
->capability
= CAPABILITY_ALSA
;
753 api
= pa_modargs_get_value(ma
, "api", "oss");
757 if (pa_streq(api
, "oss"))
758 u
->capability
= CAPABILITY_OSS
;
761 if (!u
->capability
) {
762 pa_log_error("Invalid API specification.");
766 if (!(u
->connection
= pa_dbus_bus_get(m
->core
, DBUS_BUS_SYSTEM
, &error
)) || dbus_error_is_set(&error
)) {
767 pa_log_error("Unable to contact DBUS system bus: %s: %s", error
.name
, error
.message
);
771 if (!(u
->context
= hal_context_new(pa_dbus_connection_get(u
->connection
)))) {
772 /* pa_hal_context_new() logs appropriate errors */
776 n
= hal_device_add_all(u
);
778 libhal_ctx_set_user_data(u
->context
, u
);
779 libhal_ctx_set_device_added(u
->context
, device_added_cb
);
780 libhal_ctx_set_device_removed(u
->context
, device_removed_cb
);
781 libhal_ctx_set_device_new_capability(u
->context
, new_capability_cb
);
782 libhal_ctx_set_device_lost_capability(u
->context
, lost_capability_cb
);
784 if (!libhal_device_property_watch_all(u
->context
, &error
)) {
785 pa_log_error("Error monitoring device list: %s: %s", error
.name
, error
.message
);
789 if (!dbus_connection_add_filter(pa_dbus_connection_get(u
->connection
), filter_cb
, u
, NULL
)) {
790 pa_log_error("Failed to add filter function");
794 if (pa_dbus_add_matches(
795 pa_dbus_connection_get(u
->connection
), &error
,
796 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
797 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
798 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL
) < 0) {
799 pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error
.name
, error
.message
);
803 pa_log_info("Loaded %i modules.", n
);
813 dbus_error_free(&error
);
819 void pa__done(pa_module
*m
) {
824 if (!(u
= m
->userdata
))
828 hal_context_free(u
->context
);
833 while ((d
= pa_hashmap_first(u
->devices
))) {
834 pa_hashmap_remove(u
->devices
, d
->udi
);
835 pa_hashmap_remove(u
->devices
, d
->originating_udi
);
839 pa_hashmap_free(u
->devices
, NULL
, NULL
);
843 pa_dbus_remove_matches(
844 pa_dbus_connection_get(u
->connection
),
845 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
846 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
847 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL
);
849 dbus_connection_remove_filter(pa_dbus_connection_get(u
->connection
), filter_cb
, u
);
850 pa_dbus_connection_unref(u
->connection
);