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_OUTPUT)
59 PA_MODULE_USAGE("api=<alsa or oss> "
60 "tsched=<enable system timer based scheduling mode?>"
61 "subdevices=<init all subdevices>");
62 #elif defined(HAVE_ALSA)
63 PA_MODULE_USAGE("api=<alsa> "
64 "tsched=<enable system timer based scheduling mode?>");
65 #elif defined(HAVE_OSS_OUTPUT)
66 PA_MODULE_USAGE("api=<oss>"
67 "subdevices=<init all subdevices>");
69 PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!");
72 char *udi
, *originating_udi
;
73 char *card_name
, *sink_name
, *source_name
;
75 pa_bool_t acl_race_fix
;
80 LibHalContext
*context
;
81 pa_dbus_connection
*connection
;
82 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 */
83 const char *capability
;
87 #ifdef HAVE_OSS_OUTPUT
88 pa_bool_t init_subdevs
;
90 pa_bool_t filter_added
:1;
93 #define CAPABILITY_ALSA "alsa"
94 #define CAPABILITY_OSS "oss"
96 static const char* const valid_modargs
[] = {
101 #ifdef HAVE_OSS_OUTPUT
107 static void device_free(struct device
* d
) {
111 pa_xfree(d
->originating_udi
);
112 pa_xfree(d
->sink_name
);
113 pa_xfree(d
->source_name
);
114 pa_xfree(d
->card_name
);
118 static const char *strip_udi(const char *udi
) {
123 if ((slash
= strrchr(udi
, '/')))
138 static enum alsa_type
hal_alsa_device_get_type(LibHalContext
*context
, const char *udi
) {
140 enum alsa_type t
= ALSA_TYPE_OTHER
;
143 dbus_error_init(&error
);
148 if (!(type
= libhal_device_get_property_string(context
, udi
, "alsa.type", &error
)))
151 if (pa_streq(type
, "playback"))
152 t
= ALSA_TYPE_PLAYBACK
;
153 else if (pa_streq(type
, "capture"))
154 t
= ALSA_TYPE_CAPTURE
;
155 else if (pa_streq(type
, "control"))
156 t
= ALSA_TYPE_CONTROL
;
158 libhal_free_string(type
);
161 if (dbus_error_is_set(&error
)) {
162 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error
.name
, error
.message
);
163 dbus_error_free(&error
);
169 static pa_bool_t
hal_alsa_device_is_modem(LibHalContext
*context
, const char *udi
) {
174 dbus_error_init(&error
);
179 if (!(class = libhal_device_get_property_string(context
, udi
, "alsa.pcm_class", &error
)))
182 r
= pa_streq(class, "modem");
183 libhal_free_string(class);
186 if (dbus_error_is_set(&error
)) {
187 if (!dbus_error_has_name(&error
, "org.freedesktop.Hal.NoSuchProperty"))
188 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error
.name
, error
.message
);
189 dbus_error_free(&error
);
195 static int hal_device_load_alsa(struct userdata
*u
, const char *udi
, struct device
*d
) {
200 char *args
, *originating_udi
= NULL
, *card_name
= NULL
;
202 dbus_error_init(&error
);
208 /* We only care for PCM devices */
209 type
= hal_alsa_device_get_type(u
->context
, udi
);
211 /* For each ALSA card that appears the control device will be the
212 * last one to be created, this is considered part of the ALSA
213 * usperspace API. We rely on this and load our modules only when
214 * the control device is available assuming that *all* device
215 * nodes have been properly created and assigned the right ACLs at
216 * that time. Also see:
218 * http://mailman.alsa-project.org/pipermail/alsa-devel/2009-April/015958.html
220 * and the associated thread.*/
222 if (type
!= ALSA_TYPE_CONTROL
)
225 /* We don't care for modems -- this is most likely not set for
226 * control devices, so kind of pointless here. */
227 if (hal_alsa_device_is_modem(u
->context
, udi
))
230 /* We store only one entry per card, hence we look for the originating device */
231 originating_udi
= libhal_device_get_property_string(u
->context
, udi
, "alsa.originating_device", &error
);
232 if (dbus_error_is_set(&error
) || !originating_udi
)
235 /* Make sure we only load one module per card */
236 if (pa_hashmap_get(u
->devices
, originating_udi
))
239 /* We need the identifier */
240 card
= libhal_device_get_property_int(u
->context
, udi
, "alsa.card", &error
);
241 if (dbus_error_is_set(&error
))
244 card_name
= pa_sprintf_malloc("alsa_card.%s", strip_udi(originating_udi
));
245 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
);
247 pa_log_debug("Loading module-alsa-card with arguments '%s'", args
);
248 m
= pa_module_load(u
->core
, "module-alsa-card", args
);
254 d
->originating_udi
= originating_udi
;
255 d
->module
= m
->index
;
256 d
->card_name
= card_name
;
261 if (dbus_error_is_set(&error
)) {
262 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error
.name
, error
.message
);
263 dbus_error_free(&error
);
266 pa_xfree(originating_udi
);
274 #ifdef HAVE_OSS_OUTPUT
276 static pa_bool_t
hal_oss_device_is_pcm(LibHalContext
*context
, const char *udi
, pa_bool_t init_subdevices
) {
277 char *class = NULL
, *dev
= NULL
, *e
;
282 dbus_error_init(&error
);
287 /* We only care for PCM devices */
288 class = libhal_device_get_property_string(context
, udi
, "oss.type", &error
);
289 if (dbus_error_is_set(&error
) || !class)
292 if (!pa_streq(class, "pcm"))
295 /* We don't like /dev/audio */
296 dev
= libhal_device_get_property_string(context
, udi
, "oss.device_file", &error
);
297 if (dbus_error_is_set(&error
) || !dev
)
300 if ((e
= strrchr(dev
, '/')))
301 if (pa_startswith(e
+ 1, "audio"))
304 /* We only care for the main device */
305 device
= libhal_device_get_property_int(context
, udi
, "oss.device", &error
);
306 if (dbus_error_is_set(&error
) || (device
!= 0 && init_subdevices
== FALSE
))
313 if (dbus_error_is_set(&error
)) {
314 pa_log_error("D-Bus error while parsing HAL OSS data: %s: %s", error
.name
, error
.message
);
315 dbus_error_free(&error
);
318 libhal_free_string(class);
319 libhal_free_string(dev
);
324 static int hal_device_load_oss(struct userdata
*u
, const char *udi
, struct device
*d
) {
327 char *args
, *originating_udi
= NULL
, *device
, *sink_name
= NULL
, *source_name
= NULL
;
329 dbus_error_init(&error
);
335 /* We only care for OSS PCM devices */
336 if (!hal_oss_device_is_pcm(u
->context
, udi
, u
->init_subdevs
))
339 /* We store only one entry per card, hence we look for the originating device */
340 originating_udi
= libhal_device_get_property_string(u
->context
, udi
, "oss.originating_device", &error
);
341 if (dbus_error_is_set(&error
) || !originating_udi
)
344 /* Make sure we only load one module per card */
345 if (pa_hashmap_get(u
->devices
, originating_udi
))
348 /* We need the device file */
349 device
= libhal_device_get_property_string(u
->context
, udi
, "oss.device_file", &error
);
350 if (!device
|| dbus_error_is_set(&error
))
353 sink_name
= pa_sprintf_malloc("oss_output.%s", strip_udi(udi
));
354 source_name
= pa_sprintf_malloc("oss_input.%s", strip_udi(udi
));
355 args
= pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device
, sink_name
, source_name
);
357 libhal_free_string(device
);
359 pa_log_debug("Loading module-oss with arguments '%s'", args
);
360 m
= pa_module_load(u
->core
, "module-oss", args
);
366 d
->originating_udi
= originating_udi
;
367 d
->module
= m
->index
;
368 d
->sink_name
= sink_name
;
369 d
->source_name
= source_name
;
374 if (dbus_error_is_set(&error
)) {
375 pa_log_error("D-Bus error while parsing OSS HAL data: %s: %s", error
.name
, error
.message
);
376 dbus_error_free(&error
);
379 pa_xfree(originating_udi
);
380 pa_xfree(source_name
);
387 static struct device
* hal_device_add(struct userdata
*u
, const char *udi
) {
392 pa_assert(u
->capability
);
394 d
= pa_xnew(struct device
, 1);
395 d
->acl_race_fix
= FALSE
;
396 d
->udi
= pa_xstrdup(udi
);
397 d
->originating_udi
= NULL
;
398 d
->module
= PA_INVALID_INDEX
;
399 d
->sink_name
= d
->source_name
= d
->card_name
= NULL
;
403 if (pa_streq(u
->capability
, CAPABILITY_ALSA
))
404 r
= hal_device_load_alsa(u
, udi
, d
);
406 #ifdef HAVE_OSS_OUTPUT
407 if (pa_streq(u
->capability
, CAPABILITY_OSS
))
408 r
= hal_device_load_oss(u
, udi
, d
);
416 pa_hashmap_put(u
->devices
, d
->udi
, d
);
417 pa_hashmap_put(u
->devices
, d
->originating_udi
, d
);
422 static int hal_device_add_all(struct userdata
*u
) {
427 dbus_error_init(&error
);
431 udis
= libhal_find_device_by_capability(u
->context
, u
->capability
, &n
, &error
);
432 if (dbus_error_is_set(&error
) || !udis
)
438 for (i
= 0; i
< n
; i
++) {
439 if (hal_device_add(u
, udis
[i
])) {
441 pa_log_debug("Loaded device %s", udis
[i
]);
443 pa_log_debug("Not loaded device %s", udis
[i
]);
447 libhal_free_string_array(udis
);
452 if (dbus_error_is_set(&error
)) {
453 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error
.name
, error
.message
);
454 dbus_error_free(&error
);
460 static void device_added_cb(LibHalContext
*context
, const char *udi
) {
463 pa_bool_t good
= FALSE
;
465 dbus_error_init(&error
);
470 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
472 good
= libhal_device_query_capability(context
, udi
, u
->capability
, &error
);
473 if (dbus_error_is_set(&error
) || !good
)
476 if (!hal_device_add(u
, udi
))
477 pa_log_debug("Not loaded device %s", udi
);
479 pa_log_debug("Loaded device %s", udi
);
482 if (dbus_error_is_set(&error
)) {
483 if (!dbus_error_has_name(&error
, "org.freedesktop.Hal.NoSuchProperty"))
484 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error
.name
, error
.message
);
485 dbus_error_free(&error
);
489 static void device_removed_cb(LibHalContext
* context
, const char *udi
) {
496 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
498 if (!(d
= pa_hashmap_get(u
->devices
, udi
)))
501 pa_hashmap_remove(u
->devices
, d
->originating_udi
);
502 pa_hashmap_remove(u
->devices
, d
->udi
);
504 pa_log_debug("Removing HAL device: %s", d
->originating_udi
);
506 pa_module_unload_request_by_index(u
->core
, d
->module
, TRUE
);
510 static void new_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
515 pa_assert(capability
);
517 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
519 if (pa_streq(u
->capability
, capability
))
520 /* capability we care about, pretend it's a new device */
521 device_added_cb(context
, udi
);
524 static void lost_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
529 pa_assert(capability
);
531 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
533 if (pa_streq(u
->capability
, capability
))
534 /* capability we care about, pretend it was removed */
535 device_removed_cb(context
, udi
);
538 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*message
, void *userdata
) {
544 pa_assert_se(u
= userdata
);
546 dbus_error_init(&error
);
548 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
549 dbus_message_get_interface(message
),
550 dbus_message_get_path(message
),
551 dbus_message_get_member(message
));
553 if (dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
554 dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
556 pa_bool_t suspend
= strcmp(dbus_message_get_member(message
), "ACLRemoved") == 0;
558 if (!dbus_message_get_args(message
, &error
, DBUS_TYPE_UINT32
, &uid
, DBUS_TYPE_INVALID
) || dbus_error_is_set(&error
)) {
559 pa_log_error("Failed to parse ACL message: %s: %s", error
.name
, error
.message
);
563 /* Check if this is about us? */
564 if (uid
== getuid() || uid
== geteuid()) {
568 udi
= dbus_message_get_path(message
);
570 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
571 pa_bool_t send_acl_race_fix_message
= FALSE
;
572 d
->acl_race_fix
= FALSE
;
577 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
))) {
578 pa_bool_t success
= pa_sink_suspend(sink
, suspend
, PA_SUSPEND_SESSION
) >= 0;
580 if (!success
&& !suspend
)
581 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
583 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
587 if (d
->source_name
) {
590 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
))) {
591 pa_bool_t success
= pa_source_suspend(source
, suspend
, PA_SUSPEND_SESSION
) >= 0;
593 if (!success
&& !suspend
)
594 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
596 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
603 if ((card
= pa_namereg_get(u
->core
, d
->card_name
, PA_NAMEREG_CARD
))) {
604 pa_bool_t success
= pa_card_suspend(card
, suspend
, PA_SUSPEND_SESSION
) >= 0;
606 if (!success
&& !suspend
)
607 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
609 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
613 if (send_acl_race_fix_message
) {
615 msg
= dbus_message_new_signal(udi
, "org.pulseaudio.Server", "DirtyGiveUpMessage");
616 dbus_connection_send(pa_dbus_connection_get(u
->connection
), msg
, NULL
);
617 dbus_message_unref(msg
);
621 device_added_cb(u
->context
, udi
);
625 } else if (dbus_message_is_signal(message
, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
626 /* We use this message to avoid a dirty race condition when we
627 get an ACLAdded message before the previously owning PA
628 sever has closed the device. We can remove this as
629 soon as HAL learns frevoke() */
634 udi
= dbus_message_get_path(message
);
636 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
638 if (d
->acl_race_fix
) {
639 d
->acl_race_fix
= FALSE
;
640 pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi
);
645 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
)))
646 pa_sink_suspend(sink
, FALSE
, PA_SUSPEND_SESSION
);
649 if (d
->source_name
) {
652 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
)))
653 pa_source_suspend(source
, FALSE
, PA_SUSPEND_SESSION
);
659 if ((card
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_CARD
)))
660 pa_card_suspend(card
, FALSE
, PA_SUSPEND_SESSION
);
665 /* Yes, we don't check the UDI for validity, but hopefully HAL will */
666 device_added_cb(u
->context
, udi
);
671 dbus_error_free(&error
);
673 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
676 static void hal_context_free(LibHalContext
* hal_context
) {
679 dbus_error_init(&error
);
681 libhal_ctx_shutdown(hal_context
, &error
);
682 libhal_ctx_free(hal_context
);
684 dbus_error_free(&error
);
687 static LibHalContext
* hal_context_new(DBusConnection
*connection
) {
689 LibHalContext
*hal_context
= NULL
;
691 dbus_error_init(&error
);
693 pa_assert(connection
);
695 if (!(hal_context
= libhal_ctx_new())) {
696 pa_log_error("libhal_ctx_new() failed");
700 if (!libhal_ctx_set_dbus_connection(hal_context
, connection
)) {
701 pa_log_error("Error establishing DBUS connection: %s: %s", error
.name
, error
.message
);
705 if (!libhal_ctx_init(hal_context
, &error
)) {
706 pa_log_error("Couldn't connect to hald: %s: %s", error
.name
, error
.message
);
714 hal_context_free(hal_context
);
716 dbus_error_free(&error
);
721 int pa__init(pa_module
*m
) {
723 struct userdata
*u
= NULL
;
730 dbus_error_init(&error
);
732 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
733 pa_log("Failed to parse module arguments");
737 m
->userdata
= u
= pa_xnew0(struct userdata
, 1);
739 u
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
742 u
->use_tsched
= TRUE
;
744 if (pa_modargs_get_value_boolean(ma
, "tsched", &u
->use_tsched
) < 0) {
745 pa_log("Failed to parse tsched argument.");
749 api
= pa_modargs_get_value(ma
, "api", "alsa");
751 if (pa_streq(api
, "alsa"))
752 u
->capability
= CAPABILITY_ALSA
;
754 api
= pa_modargs_get_value(ma
, "api", "oss");
757 #ifdef HAVE_OSS_OUTPUT
758 if (pa_streq(api
, "oss"))
759 u
->capability
= CAPABILITY_OSS
;
762 if (!u
->capability
) {
763 pa_log_error("Invalid API specification.");
767 #ifdef HAVE_OSS_OUTPUT
768 if (pa_modargs_get_value_boolean(ma
, "subdevices", &u
->init_subdevs
) < 0) {
769 pa_log("Failed to parse subdevices= argument.");
774 if (!(u
->connection
= pa_dbus_bus_get(m
->core
, DBUS_BUS_SYSTEM
, &error
)) || dbus_error_is_set(&error
)) {
775 pa_log_error("Unable to contact DBUS system bus: %s: %s", error
.name
, error
.message
);
779 if (!(u
->context
= hal_context_new(pa_dbus_connection_get(u
->connection
)))) {
780 /* pa_hal_context_new() logs appropriate errors */
784 n
= hal_device_add_all(u
);
786 libhal_ctx_set_user_data(u
->context
, u
);
787 libhal_ctx_set_device_added(u
->context
, device_added_cb
);
788 libhal_ctx_set_device_removed(u
->context
, device_removed_cb
);
789 libhal_ctx_set_device_new_capability(u
->context
, new_capability_cb
);
790 libhal_ctx_set_device_lost_capability(u
->context
, lost_capability_cb
);
792 if (!libhal_device_property_watch_all(u
->context
, &error
)) {
793 pa_log_error("Error monitoring device list: %s: %s", error
.name
, error
.message
);
797 if (!dbus_connection_add_filter(pa_dbus_connection_get(u
->connection
), filter_cb
, u
, NULL
)) {
798 pa_log_error("Failed to add filter function");
801 u
->filter_added
= TRUE
;
803 if (pa_dbus_add_matches(
804 pa_dbus_connection_get(u
->connection
), &error
,
805 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
806 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
807 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL
) < 0) {
808 pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error
.name
, error
.message
);
812 pa_log_info("Loaded %i modules.", n
);
822 dbus_error_free(&error
);
828 void pa__done(pa_module
*m
) {
833 if (!(u
= m
->userdata
))
837 hal_context_free(u
->context
);
842 while ((d
= pa_hashmap_first(u
->devices
))) {
843 pa_hashmap_remove(u
->devices
, d
->udi
);
844 pa_hashmap_remove(u
->devices
, d
->originating_udi
);
848 pa_hashmap_free(u
->devices
, NULL
, NULL
);
852 pa_dbus_remove_matches(
853 pa_dbus_connection_get(u
->connection
),
854 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
855 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
856 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL
);
859 dbus_connection_remove_filter(pa_dbus_connection_get(u
->connection
), filter_cb
, u
);
860 pa_dbus_connection_unref(u
->connection
);