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, '/')))
128 static enum alsa_type hal_alsa_device_get_type(LibHalContext *context, const char *udi) {
130 enum alsa_type t = ALSA_TYPE_OTHER;
133 dbus_error_init(&error);
138 if (!(type = libhal_device_get_property_string(context, udi, "alsa.type", &error)))
141 if (pa_streq(type, "playback"))
142 t = ALSA_TYPE_PLAYBACK;
143 else if (pa_streq(type, "capture"))
144 t = ALSA_TYPE_CAPTURE;
145 else if (pa_streq(type, "control"))
146 t = ALSA_TYPE_CONTROL;
148 libhal_free_string(type);
151 if (dbus_error_is_set(&error)) {
152 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error.name, error.message);
153 dbus_error_free(&error);
159 static pa_bool_t hal_alsa_device_is_modem(LibHalContext *context, const char *udi) {
164 dbus_error_init(&error);
169 if (!(class = libhal_device_get_property_string(context, udi, "alsa.pcm_class", &error)))
172 r = pa_streq(class, "modem");
173 libhal_free_string(class);
176 if (dbus_error_is_set(&error)) {
177 if (!dbus_error_has_name(&error, "org.freedesktop.Hal.NoSuchProperty"))
178 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error.name, error.message);
179 dbus_error_free(&error);
185 static int hal_device_load_alsa(struct userdata *u, const char *udi, struct device *d) {
190 char *args, *originating_udi = NULL, *card_name = NULL;
192 dbus_error_init(&error);
198 /* We only care for PCM devices */
199 type = hal_alsa_device_get_type(u->context, udi);
201 /* For each ALSA card that appears the control device will be the
202 * last one to be created, this is considered part of the ALSA
203 * usperspace API. We rely on this and load our modules only when
204 * the control device is available assuming that *all* device
205 * nodes have been properly created and assigned the right ACLs at
206 * that time. Also see:
208 * http://mailman.alsa-project.org/pipermail/alsa-devel/2009-April/015958.html
210 * and the associated thread.*/
212 if (type != ALSA_TYPE_CONTROL)
215 /* We don't care for modems -- this is most likely not set for
216 * control devices, so kind of pointless here. */
217 if (hal_alsa_device_is_modem(u->context, udi))
220 /* We store only one entry per card, hence we look for the originating device */
221 originating_udi = libhal_device_get_property_string(u->context, udi, "alsa.originating_device", &error);
222 if (dbus_error_is_set(&error) || !originating_udi)
225 /* Make sure we only load one module per card */
226 if (pa_hashmap_get(u->devices, originating_udi))
229 /* We need the identifier */
230 card = libhal_device_get_property_int(u->context, udi, "alsa.card", &error);
231 if (dbus_error_is_set(&error))
234 card_name = pa_sprintf_malloc("alsa_card.%s", strip_udi(originating_udi));
235 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);
237 pa_log_debug("Loading module-alsa-card with arguments '%s'", args);
238 m = pa_module_load(u->core, "module-alsa-card", args);
244 d->originating_udi = originating_udi;
245 d->module = m->index;
246 d->card_name = card_name;
251 if (dbus_error_is_set(&error)) {
252 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error.name, error.message);
253 dbus_error_free(&error);
256 pa_xfree(originating_udi);
266 static pa_bool_t hal_oss_device_is_pcm(LibHalContext *context, const char *udi) {
267 char *class = NULL, *dev = NULL, *e;
272 dbus_error_init(&error);
277 /* We only care for PCM devices */
278 class = libhal_device_get_property_string(context, udi, "oss.type", &error);
279 if (dbus_error_is_set(&error) || !class)
282 if (!pa_streq(class, "pcm"))
285 /* We don't like /dev/audio */
286 dev = libhal_device_get_property_string(context, udi, "oss.device_file", &error);
287 if (dbus_error_is_set(&error) || !dev)
290 if ((e = strrchr(dev, '/')))
291 if (pa_startswith(e + 1, "audio"))
294 /* We only care for the main device */
295 device = libhal_device_get_property_int(context, udi, "oss.device", &error);
296 if (dbus_error_is_set(&error) || device != 0)
303 if (dbus_error_is_set(&error)) {
304 pa_log_error("D-Bus error while parsing HAL OSS data: %s: %s", error.name, error.message);
305 dbus_error_free(&error);
308 libhal_free_string(class);
309 libhal_free_string(dev);
314 static int hal_device_load_oss(struct userdata *u, const char *udi, struct device *d) {
317 char *args, *originating_udi = NULL, *device, *sink_name = NULL, *source_name = NULL;
319 dbus_error_init(&error);
325 /* We only care for OSS PCM devices */
326 if (!hal_oss_device_is_pcm(u->context, udi))
329 /* We store only one entry per card, hence we look for the originating device */
330 originating_udi = libhal_device_get_property_string(u->context, udi, "oss.originating_device", &error);
331 if (dbus_error_is_set(&error) || !originating_udi)
334 /* Make sure we only load one module per card */
335 if (pa_hashmap_get(u->devices, originating_udi))
338 /* We need the device file */
339 device = libhal_device_get_property_string(u->context, udi, "oss.device_file", &error);
340 if (!device || dbus_error_is_set(&error))
343 sink_name = pa_sprintf_malloc("oss_output.%s", strip_udi(udi));
344 source_name = pa_sprintf_malloc("oss_input.%s", strip_udi(udi));
345 args = pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device, sink_name, source_name);
347 libhal_free_string(device);
349 pa_log_debug("Loading module-oss with arguments '%s'", args);
350 m = pa_module_load(u->core, "module-oss", args);
356 d->originating_udi = originating_udi;
357 d->module = m->index;
358 d->sink_name = sink_name;
359 d->source_name = source_name;
364 if (dbus_error_is_set(&error)) {
365 pa_log_error("D-Bus error while parsing OSS HAL data: %s: %s", error.name, error.message);
366 dbus_error_free(&error);
369 pa_xfree(originating_udi);
370 pa_xfree(source_name);
377 static struct device* hal_device_add(struct userdata *u, const char *udi) {
382 pa_assert(u->capability);
384 d = pa_xnew(struct device, 1);
385 d->acl_race_fix = FALSE;
386 d->udi = pa_xstrdup(udi);
387 d->originating_udi = NULL;
388 d->module = PA_INVALID_INDEX;
389 d->sink_name = d->source_name = d->card_name = NULL;
393 if (pa_streq(u->capability, CAPABILITY_ALSA))
394 r = hal_device_load_alsa(u, udi, d);
397 if (pa_streq(u->capability, CAPABILITY_OSS))
398 r = hal_device_load_oss(u, udi, d);
406 pa_hashmap_put(u->devices, d->udi, d);
407 pa_hashmap_put(u->devices, d->originating_udi, d);
412 static int hal_device_add_all(struct userdata *u) {
417 dbus_error_init(&error);
421 udis = libhal_find_device_by_capability(u->context, u->capability, &n, &error);
422 if (dbus_error_is_set(&error) || !udis)
428 for (i = 0; i < n; i++) {
431 if ((d = hal_device_add(u, udis[i]))) {
433 pa_log_debug("Loaded device %s", udis[i]);
435 pa_log_debug("Not loaded device %s", udis[i]);
439 libhal_free_string_array(udis);
444 if (dbus_error_is_set(&error)) {
445 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error.name, error.message);
446 dbus_error_free(&error);
452 static void device_added_cb(LibHalContext *context, const char *udi) {
455 pa_bool_t good = FALSE;
457 dbus_error_init(&error);
462 pa_assert_se(u = libhal_ctx_get_user_data(context));
464 good = libhal_device_query_capability(context, udi, u->capability, &error);
465 if (dbus_error_is_set(&error) || !good)
468 if (!hal_device_add(u, udi))
469 pa_log_debug("Not loaded device %s", udi);
471 pa_log_debug("Loaded device %s", udi);
474 if (dbus_error_is_set(&error)) {
475 if (!dbus_error_has_name(&error, "org.freedesktop.Hal.NoSuchProperty"))
476 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error.name, error.message);
477 dbus_error_free(&error);
481 static void device_removed_cb(LibHalContext* context, const char *udi) {
488 pa_assert_se(u = libhal_ctx_get_user_data(context));
490 if (!(d = pa_hashmap_get(u->devices, udi)))
493 pa_hashmap_remove(u->devices, d->originating_udi);
494 pa_hashmap_remove(u->devices, d->udi);
496 pa_log_debug("Removing HAL device: %s", d->originating_udi);
498 pa_module_unload_request_by_index(u->core, d->module, TRUE);
502 static void new_capability_cb(LibHalContext *context, const char *udi, const char* capability) {
507 pa_assert(capability);
509 pa_assert_se(u = libhal_ctx_get_user_data(context));
511 if (pa_streq(u->capability, capability))
512 /* capability we care about, pretend it's a new device */
513 device_added_cb(context, udi);
516 static void lost_capability_cb(LibHalContext *context, const char *udi, const char* capability) {
521 pa_assert(capability);
523 pa_assert_se(u = libhal_ctx_get_user_data(context));
525 if (pa_streq(u->capability, capability))
526 /* capability we care about, pretend it was removed */
527 device_removed_cb(context, udi);
530 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) {
536 pa_assert_se(u = userdata);
538 dbus_error_init(&error);
540 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
541 dbus_message_get_interface(message),
542 dbus_message_get_path(message),
543 dbus_message_get_member(message));
545 if (dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
546 dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
548 pa_bool_t suspend = strcmp(dbus_message_get_member(message), "ACLRemoved") == 0;
550 if (!dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
551 pa_log_error("Failed to parse ACL message: %s: %s", error.name, error.message);
555 /* Check if this is about us? */
556 if (uid == getuid() || uid == geteuid()) {
560 udi = dbus_message_get_path(message);
562 if ((d = pa_hashmap_get(u->devices, udi))) {
563 pa_bool_t send_acl_race_fix_message = FALSE;
564 d->acl_race_fix = FALSE;
569 if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK))) {
570 pa_bool_t success = pa_sink_suspend(sink, suspend) >= 0;
572 if (!success && !suspend)
573 d->acl_race_fix = TRUE; /* resume failed, let's try again */
575 send_acl_race_fix_message = TRUE; /* suspend finished, let's tell everyone to try again */
579 if (d->source_name) {
582 if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE))) {
583 pa_bool_t success = pa_source_suspend(source, suspend) >= 0;
585 if (!success && !suspend)
586 d->acl_race_fix = TRUE; /* resume failed, let's try again */
588 send_acl_race_fix_message = TRUE; /* suspend finished, let's tell everyone to try again */
595 if ((card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD))) {
596 pa_bool_t success = pa_card_suspend(card, suspend) >= 0;
598 if (!success && !suspend)
599 d->acl_race_fix = TRUE; /* resume failed, let's try again */
601 send_acl_race_fix_message = TRUE; /* suspend finished, let's tell everyone to try again */
605 if (send_acl_race_fix_message) {
607 msg = dbus_message_new_signal(udi, "org.pulseaudio.Server", "DirtyGiveUpMessage");
608 dbus_connection_send(pa_dbus_connection_get(u->connection), msg, NULL);
609 dbus_message_unref(msg);
613 device_added_cb(u->context, udi);
617 return DBUS_HANDLER_RESULT_HANDLED;
619 } else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
620 /* We use this message to avoid a dirty race condition when we
621 get an ACLAdded message before the previously owning PA
622 sever has closed the device. We can remove this as
623 soon as HAL learns frevoke() */
628 udi = dbus_message_get_path(message);
630 if ((d = pa_hashmap_get(u->devices, udi))) {
632 if (d->acl_race_fix) {
633 d->acl_race_fix = FALSE;
634 pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi);
639 if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK)))
640 pa_sink_suspend(sink, FALSE);
643 if (d->source_name) {
646 if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE)))
647 pa_source_suspend(source, FALSE);
653 if ((card = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_CARD)))
654 pa_card_suspend(card, FALSE);
659 /* Yes, we don't check the UDI for validity, but hopefully HAL will */
660 device_added_cb(u->context, udi);
662 return DBUS_HANDLER_RESULT_HANDLED;
666 dbus_error_free(&error);
668 return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
671 static void hal_context_free(LibHalContext* hal_context) {
674 dbus_error_init(&error);
676 libhal_ctx_shutdown(hal_context, &error);
677 libhal_ctx_free(hal_context);
679 dbus_error_free(&error);
682 static LibHalContext* hal_context_new(DBusConnection *connection) {
684 LibHalContext *hal_context = NULL;
686 dbus_error_init(&error);
688 pa_assert(connection);
690 if (!(hal_context = libhal_ctx_new())) {
691 pa_log_error("libhal_ctx_new() failed");
695 if (!libhal_ctx_set_dbus_connection(hal_context, connection)) {
696 pa_log_error("Error establishing DBUS connection: %s: %s", error.name, error.message);
700 if (!libhal_ctx_init(hal_context, &error)) {
701 pa_log_error("Couldn't connect to hald: %s: %s", error.name, error.message);
709 hal_context_free(hal_context);
711 dbus_error_free(&error);
716 int pa__init(pa_module*m) {
718 struct userdata *u = NULL;
725 dbus_error_init(&error);
727 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
728 pa_log("Failed to parse module arguments");
732 m->userdata = u = pa_xnew(struct userdata, 1);
735 u->connection = NULL;
736 u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
737 u->capability = NULL;
740 u->use_tsched = TRUE;
742 if (pa_modargs_get_value_boolean(ma, "tsched", &u->use_tsched) < 0) {
743 pa_log("Failed to parse tsched argument.");
747 api = pa_modargs_get_value(ma, "api", "alsa");
749 if (pa_streq(api, "alsa"))
750 u->capability = CAPABILITY_ALSA;
752 api = pa_modargs_get_value(ma, "api", "oss");
756 if (pa_streq(api, "oss"))
757 u->capability = CAPABILITY_OSS;
760 if (!u->capability) {
761 pa_log_error("Invalid API specification.");
765 if (!(u->connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) {
766 pa_log_error("Unable to contact DBUS system bus: %s: %s", error.name, error.message);
770 if (!(u->context = hal_context_new(pa_dbus_connection_get(u->connection)))) {
771 /* pa_hal_context_new() logs appropriate errors */
775 n = hal_device_add_all(u);
777 libhal_ctx_set_user_data(u->context, u);
778 libhal_ctx_set_device_added(u->context, device_added_cb);
779 libhal_ctx_set_device_removed(u->context, device_removed_cb);
780 libhal_ctx_set_device_new_capability(u->context, new_capability_cb);
781 libhal_ctx_set_device_lost_capability(u->context, lost_capability_cb);
783 if (!libhal_device_property_watch_all(u->context, &error)) {
784 pa_log_error("Error monitoring device list: %s: %s", error.name, error.message);
788 if (!dbus_connection_add_filter(pa_dbus_connection_get(u->connection), filter_cb, u, NULL)) {
789 pa_log_error("Failed to add filter function");
793 if (pa_dbus_add_matches(
794 pa_dbus_connection_get(u->connection), &error,
795 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
796 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
797 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL) < 0) {
798 pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error.name, error.message);
802 pa_log_info("Loaded %i modules.", n);
812 dbus_error_free(&error);
818 void pa__done(pa_module *m) {
823 if (!(u = m->userdata))
827 hal_context_free(u->context);
832 while ((d = pa_hashmap_first(u->devices))) {
833 pa_hashmap_remove(u->devices, d->udi);
834 pa_hashmap_remove(u->devices, d->originating_udi);
838 pa_hashmap_free(u->devices, NULL, NULL);
842 pa_dbus_remove_matches(
843 pa_dbus_connection_get(u->connection),
844 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
845 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
846 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL);
848 dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), filter_cb, u);
849 pa_dbus_connection_unref(u->connection);