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
;
92 #define CAPABILITY_ALSA "alsa"
93 #define CAPABILITY_OSS "oss"
95 static const char* const valid_modargs
[] = {
100 #ifdef HAVE_OSS_OUTPUT
106 static void device_free(struct device
* d
) {
110 pa_xfree(d
->originating_udi
);
111 pa_xfree(d
->sink_name
);
112 pa_xfree(d
->source_name
);
113 pa_xfree(d
->card_name
);
117 static const char *strip_udi(const char *udi
) {
122 if ((slash
= strrchr(udi
, '/')))
137 static enum alsa_type
hal_alsa_device_get_type(LibHalContext
*context
, const char *udi
) {
139 enum alsa_type t
= ALSA_TYPE_OTHER
;
142 dbus_error_init(&error
);
147 if (!(type
= libhal_device_get_property_string(context
, udi
, "alsa.type", &error
)))
150 if (pa_streq(type
, "playback"))
151 t
= ALSA_TYPE_PLAYBACK
;
152 else if (pa_streq(type
, "capture"))
153 t
= ALSA_TYPE_CAPTURE
;
154 else if (pa_streq(type
, "control"))
155 t
= ALSA_TYPE_CONTROL
;
157 libhal_free_string(type
);
160 if (dbus_error_is_set(&error
)) {
161 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error
.name
, error
.message
);
162 dbus_error_free(&error
);
168 static pa_bool_t
hal_alsa_device_is_modem(LibHalContext
*context
, const char *udi
) {
173 dbus_error_init(&error
);
178 if (!(class = libhal_device_get_property_string(context
, udi
, "alsa.pcm_class", &error
)))
181 r
= pa_streq(class, "modem");
182 libhal_free_string(class);
185 if (dbus_error_is_set(&error
)) {
186 if (!dbus_error_has_name(&error
, "org.freedesktop.Hal.NoSuchProperty"))
187 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error
.name
, error
.message
);
188 dbus_error_free(&error
);
194 static int hal_device_load_alsa(struct userdata
*u
, const char *udi
, struct device
*d
) {
199 char *args
, *originating_udi
= NULL
, *card_name
= NULL
;
201 dbus_error_init(&error
);
207 /* We only care for PCM devices */
208 type
= hal_alsa_device_get_type(u
->context
, udi
);
210 /* For each ALSA card that appears the control device will be the
211 * last one to be created, this is considered part of the ALSA
212 * usperspace API. We rely on this and load our modules only when
213 * the control device is available assuming that *all* device
214 * nodes have been properly created and assigned the right ACLs at
215 * that time. Also see:
217 * http://mailman.alsa-project.org/pipermail/alsa-devel/2009-April/015958.html
219 * and the associated thread.*/
221 if (type
!= ALSA_TYPE_CONTROL
)
224 /* We don't care for modems -- this is most likely not set for
225 * control devices, so kind of pointless here. */
226 if (hal_alsa_device_is_modem(u
->context
, udi
))
229 /* We store only one entry per card, hence we look for the originating device */
230 originating_udi
= libhal_device_get_property_string(u
->context
, udi
, "alsa.originating_device", &error
);
231 if (dbus_error_is_set(&error
) || !originating_udi
)
234 /* Make sure we only load one module per card */
235 if (pa_hashmap_get(u
->devices
, originating_udi
))
238 /* We need the identifier */
239 card
= libhal_device_get_property_int(u
->context
, udi
, "alsa.card", &error
);
240 if (dbus_error_is_set(&error
))
243 card_name
= pa_sprintf_malloc("alsa_card.%s", strip_udi(originating_udi
));
244 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
);
246 pa_log_debug("Loading module-alsa-card with arguments '%s'", args
);
247 m
= pa_module_load(u
->core
, "module-alsa-card", args
);
253 d
->originating_udi
= originating_udi
;
254 d
->module
= m
->index
;
255 d
->card_name
= card_name
;
260 if (dbus_error_is_set(&error
)) {
261 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error
.name
, error
.message
);
262 dbus_error_free(&error
);
265 pa_xfree(originating_udi
);
273 #ifdef HAVE_OSS_OUTPUT
275 static pa_bool_t
hal_oss_device_is_pcm(LibHalContext
*context
, const char *udi
, pa_bool_t init_subdevices
) {
276 char *class = NULL
, *dev
= NULL
, *e
;
281 dbus_error_init(&error
);
286 /* We only care for PCM devices */
287 class = libhal_device_get_property_string(context
, udi
, "oss.type", &error
);
288 if (dbus_error_is_set(&error
) || !class)
291 if (!pa_streq(class, "pcm"))
294 /* We don't like /dev/audio */
295 dev
= libhal_device_get_property_string(context
, udi
, "oss.device_file", &error
);
296 if (dbus_error_is_set(&error
) || !dev
)
299 if ((e
= strrchr(dev
, '/')))
300 if (pa_startswith(e
+ 1, "audio"))
303 /* We only care for the main device */
304 device
= libhal_device_get_property_int(context
, udi
, "oss.device", &error
);
305 if (dbus_error_is_set(&error
) || (device
!= 0 && init_subdevices
== FALSE
))
312 if (dbus_error_is_set(&error
)) {
313 pa_log_error("D-Bus error while parsing HAL OSS data: %s: %s", error
.name
, error
.message
);
314 dbus_error_free(&error
);
317 libhal_free_string(class);
318 libhal_free_string(dev
);
323 static int hal_device_load_oss(struct userdata
*u
, const char *udi
, struct device
*d
) {
326 char *args
, *originating_udi
= NULL
, *device
, *sink_name
= NULL
, *source_name
= NULL
;
328 dbus_error_init(&error
);
334 /* We only care for OSS PCM devices */
335 if (!hal_oss_device_is_pcm(u
->context
, udi
, u
->init_subdevs
))
338 /* We store only one entry per card, hence we look for the originating device */
339 originating_udi
= libhal_device_get_property_string(u
->context
, udi
, "oss.originating_device", &error
);
340 if (dbus_error_is_set(&error
) || !originating_udi
)
343 /* Make sure we only load one module per card */
344 if (pa_hashmap_get(u
->devices
, originating_udi
))
347 /* We need the device file */
348 device
= libhal_device_get_property_string(u
->context
, udi
, "oss.device_file", &error
);
349 if (!device
|| dbus_error_is_set(&error
))
352 sink_name
= pa_sprintf_malloc("oss_output.%s", strip_udi(udi
));
353 source_name
= pa_sprintf_malloc("oss_input.%s", strip_udi(udi
));
354 args
= pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device
, sink_name
, source_name
);
356 libhal_free_string(device
);
358 pa_log_debug("Loading module-oss with arguments '%s'", args
);
359 m
= pa_module_load(u
->core
, "module-oss", args
);
365 d
->originating_udi
= originating_udi
;
366 d
->module
= m
->index
;
367 d
->sink_name
= sink_name
;
368 d
->source_name
= source_name
;
373 if (dbus_error_is_set(&error
)) {
374 pa_log_error("D-Bus error while parsing OSS HAL data: %s: %s", error
.name
, error
.message
);
375 dbus_error_free(&error
);
378 pa_xfree(originating_udi
);
379 pa_xfree(source_name
);
386 static struct device
* hal_device_add(struct userdata
*u
, const char *udi
) {
391 pa_assert(u
->capability
);
393 d
= pa_xnew(struct device
, 1);
394 d
->acl_race_fix
= FALSE
;
395 d
->udi
= pa_xstrdup(udi
);
396 d
->originating_udi
= NULL
;
397 d
->module
= PA_INVALID_INDEX
;
398 d
->sink_name
= d
->source_name
= d
->card_name
= NULL
;
402 if (pa_streq(u
->capability
, CAPABILITY_ALSA
))
403 r
= hal_device_load_alsa(u
, udi
, d
);
405 #ifdef HAVE_OSS_OUTPUT
406 if (pa_streq(u
->capability
, CAPABILITY_OSS
))
407 r
= hal_device_load_oss(u
, udi
, d
);
415 pa_hashmap_put(u
->devices
, d
->udi
, d
);
416 pa_hashmap_put(u
->devices
, d
->originating_udi
, d
);
421 static int hal_device_add_all(struct userdata
*u
) {
426 dbus_error_init(&error
);
430 udis
= libhal_find_device_by_capability(u
->context
, u
->capability
, &n
, &error
);
431 if (dbus_error_is_set(&error
) || !udis
)
437 for (i
= 0; i
< n
; i
++) {
438 if (hal_device_add(u
, udis
[i
])) {
440 pa_log_debug("Loaded device %s", udis
[i
]);
442 pa_log_debug("Not loaded device %s", udis
[i
]);
446 libhal_free_string_array(udis
);
451 if (dbus_error_is_set(&error
)) {
452 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error
.name
, error
.message
);
453 dbus_error_free(&error
);
459 static void device_added_cb(LibHalContext
*context
, const char *udi
) {
462 pa_bool_t good
= FALSE
;
464 dbus_error_init(&error
);
469 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
471 good
= libhal_device_query_capability(context
, udi
, u
->capability
, &error
);
472 if (dbus_error_is_set(&error
) || !good
)
475 if (!hal_device_add(u
, udi
))
476 pa_log_debug("Not loaded device %s", udi
);
478 pa_log_debug("Loaded device %s", udi
);
481 if (dbus_error_is_set(&error
)) {
482 if (!dbus_error_has_name(&error
, "org.freedesktop.Hal.NoSuchProperty"))
483 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error
.name
, error
.message
);
484 dbus_error_free(&error
);
488 static void device_removed_cb(LibHalContext
* context
, const char *udi
) {
495 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
497 if (!(d
= pa_hashmap_get(u
->devices
, udi
)))
500 pa_hashmap_remove(u
->devices
, d
->originating_udi
);
501 pa_hashmap_remove(u
->devices
, d
->udi
);
503 pa_log_debug("Removing HAL device: %s", d
->originating_udi
);
505 pa_module_unload_request_by_index(u
->core
, d
->module
, TRUE
);
509 static void new_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
514 pa_assert(capability
);
516 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
518 if (pa_streq(u
->capability
, capability
))
519 /* capability we care about, pretend it's a new device */
520 device_added_cb(context
, udi
);
523 static void lost_capability_cb(LibHalContext
*context
, const char *udi
, const char* capability
) {
528 pa_assert(capability
);
530 pa_assert_se(u
= libhal_ctx_get_user_data(context
));
532 if (pa_streq(u
->capability
, capability
))
533 /* capability we care about, pretend it was removed */
534 device_removed_cb(context
, udi
);
537 static DBusHandlerResult
filter_cb(DBusConnection
*bus
, DBusMessage
*message
, void *userdata
) {
543 pa_assert_se(u
= userdata
);
545 dbus_error_init(&error
);
547 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
548 dbus_message_get_interface(message
),
549 dbus_message_get_path(message
),
550 dbus_message_get_member(message
));
552 if (dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
553 dbus_message_is_signal(message
, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
555 pa_bool_t suspend
= strcmp(dbus_message_get_member(message
), "ACLRemoved") == 0;
557 if (!dbus_message_get_args(message
, &error
, DBUS_TYPE_UINT32
, &uid
, DBUS_TYPE_INVALID
) || dbus_error_is_set(&error
)) {
558 pa_log_error("Failed to parse ACL message: %s: %s", error
.name
, error
.message
);
562 /* Check if this is about us? */
563 if (uid
== getuid() || uid
== geteuid()) {
567 udi
= dbus_message_get_path(message
);
569 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
570 pa_bool_t send_acl_race_fix_message
= FALSE
;
571 d
->acl_race_fix
= FALSE
;
576 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
))) {
577 pa_bool_t success
= pa_sink_suspend(sink
, suspend
, PA_SUSPEND_SESSION
) >= 0;
579 if (!success
&& !suspend
)
580 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
582 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
586 if (d
->source_name
) {
589 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
))) {
590 pa_bool_t success
= pa_source_suspend(source
, suspend
, PA_SUSPEND_SESSION
) >= 0;
592 if (!success
&& !suspend
)
593 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
595 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
602 if ((card
= pa_namereg_get(u
->core
, d
->card_name
, PA_NAMEREG_CARD
))) {
603 pa_bool_t success
= pa_card_suspend(card
, suspend
, PA_SUSPEND_SESSION
) >= 0;
605 if (!success
&& !suspend
)
606 d
->acl_race_fix
= TRUE
; /* resume failed, let's try again */
608 send_acl_race_fix_message
= TRUE
; /* suspend finished, let's tell everyone to try again */
612 if (send_acl_race_fix_message
) {
614 msg
= dbus_message_new_signal(udi
, "org.pulseaudio.Server", "DirtyGiveUpMessage");
615 dbus_connection_send(pa_dbus_connection_get(u
->connection
), msg
, NULL
);
616 dbus_message_unref(msg
);
620 device_added_cb(u
->context
, udi
);
624 } else if (dbus_message_is_signal(message
, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
625 /* We use this message to avoid a dirty race condition when we
626 get an ACLAdded message before the previously owning PA
627 sever has closed the device. We can remove this as
628 soon as HAL learns frevoke() */
633 udi
= dbus_message_get_path(message
);
635 if ((d
= pa_hashmap_get(u
->devices
, udi
))) {
637 if (d
->acl_race_fix
) {
638 d
->acl_race_fix
= FALSE
;
639 pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi
);
644 if ((sink
= pa_namereg_get(u
->core
, d
->sink_name
, PA_NAMEREG_SINK
)))
645 pa_sink_suspend(sink
, FALSE
, PA_SUSPEND_SESSION
);
648 if (d
->source_name
) {
651 if ((source
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_SOURCE
)))
652 pa_source_suspend(source
, FALSE
, PA_SUSPEND_SESSION
);
658 if ((card
= pa_namereg_get(u
->core
, d
->source_name
, PA_NAMEREG_CARD
)))
659 pa_card_suspend(card
, FALSE
, PA_SUSPEND_SESSION
);
664 /* Yes, we don't check the UDI for validity, but hopefully HAL will */
665 device_added_cb(u
->context
, udi
);
670 dbus_error_free(&error
);
672 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED
;
675 static void hal_context_free(LibHalContext
* hal_context
) {
678 dbus_error_init(&error
);
680 libhal_ctx_shutdown(hal_context
, &error
);
681 libhal_ctx_free(hal_context
);
683 dbus_error_free(&error
);
686 static LibHalContext
* hal_context_new(DBusConnection
*connection
) {
688 LibHalContext
*hal_context
= NULL
;
690 dbus_error_init(&error
);
692 pa_assert(connection
);
694 if (!(hal_context
= libhal_ctx_new())) {
695 pa_log_error("libhal_ctx_new() failed");
699 if (!libhal_ctx_set_dbus_connection(hal_context
, connection
)) {
700 pa_log_error("Error establishing DBUS connection: %s: %s", error
.name
, error
.message
);
704 if (!libhal_ctx_init(hal_context
, &error
)) {
705 pa_log_error("Couldn't connect to hald: %s: %s", error
.name
, error
.message
);
713 hal_context_free(hal_context
);
715 dbus_error_free(&error
);
720 int pa__init(pa_module
*m
) {
722 struct userdata
*u
= NULL
;
729 dbus_error_init(&error
);
731 if (!(ma
= pa_modargs_new(m
->argument
, valid_modargs
))) {
732 pa_log("Failed to parse module arguments");
736 m
->userdata
= u
= pa_xnew(struct userdata
, 1);
739 u
->connection
= NULL
;
740 u
->devices
= pa_hashmap_new(pa_idxset_string_hash_func
, pa_idxset_string_compare_func
);
741 u
->capability
= NULL
;
744 u
->use_tsched
= TRUE
;
746 if (pa_modargs_get_value_boolean(ma
, "tsched", &u
->use_tsched
) < 0) {
747 pa_log("Failed to parse tsched argument.");
751 api
= pa_modargs_get_value(ma
, "api", "alsa");
753 if (pa_streq(api
, "alsa"))
754 u
->capability
= CAPABILITY_ALSA
;
756 api
= pa_modargs_get_value(ma
, "api", "oss");
759 #ifdef HAVE_OSS_OUTPUT
760 if (pa_streq(api
, "oss"))
761 u
->capability
= CAPABILITY_OSS
;
764 if (!u
->capability
) {
765 pa_log_error("Invalid API specification.");
769 #ifdef HAVE_OSS_OUTPUT
770 if (pa_modargs_get_value_boolean(ma
, "subdevices", &u
->init_subdevs
) < 0) {
771 pa_log("Failed to parse subdevices= argument.");
776 if (!(u
->connection
= pa_dbus_bus_get(m
->core
, DBUS_BUS_SYSTEM
, &error
)) || dbus_error_is_set(&error
)) {
777 pa_log_error("Unable to contact DBUS system bus: %s: %s", error
.name
, error
.message
);
781 if (!(u
->context
= hal_context_new(pa_dbus_connection_get(u
->connection
)))) {
782 /* pa_hal_context_new() logs appropriate errors */
786 n
= hal_device_add_all(u
);
788 libhal_ctx_set_user_data(u
->context
, u
);
789 libhal_ctx_set_device_added(u
->context
, device_added_cb
);
790 libhal_ctx_set_device_removed(u
->context
, device_removed_cb
);
791 libhal_ctx_set_device_new_capability(u
->context
, new_capability_cb
);
792 libhal_ctx_set_device_lost_capability(u
->context
, lost_capability_cb
);
794 if (!libhal_device_property_watch_all(u
->context
, &error
)) {
795 pa_log_error("Error monitoring device list: %s: %s", error
.name
, error
.message
);
799 if (!dbus_connection_add_filter(pa_dbus_connection_get(u
->connection
), filter_cb
, u
, NULL
)) {
800 pa_log_error("Failed to add filter function");
804 if (pa_dbus_add_matches(
805 pa_dbus_connection_get(u
->connection
), &error
,
806 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
807 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
808 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL
) < 0) {
809 pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error
.name
, error
.message
);
813 pa_log_info("Loaded %i modules.", n
);
823 dbus_error_free(&error
);
829 void pa__done(pa_module
*m
) {
834 if (!(u
= m
->userdata
))
838 hal_context_free(u
->context
);
843 while ((d
= pa_hashmap_first(u
->devices
))) {
844 pa_hashmap_remove(u
->devices
, d
->udi
);
845 pa_hashmap_remove(u
->devices
, d
->originating_udi
);
849 pa_hashmap_free(u
->devices
, NULL
, NULL
);
853 pa_dbus_remove_matches(
854 pa_dbus_connection_get(u
->connection
),
855 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
856 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
857 "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
);