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>
35 #include <pulse/xmalloc.h>
37 #include <pulsecore/module.h>
38 #include <pulsecore/log.h>
39 #include <pulsecore/hashmap.h>
40 #include <pulsecore/idxset.h>
41 #include <pulsecore/core-util.h>
42 #include <pulsecore/namereg.h>
43 #include <pulsecore/modargs.h>
44 #include <pulsecore/dbus-shared.h>
46 #include <hal/libhal.h>
48 #include "module-hal-detect-symdef.h"
50 PA_MODULE_AUTHOR("Shahms King");
51 PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers");
52 PA_MODULE_VERSION(PACKAGE_VERSION);
53 PA_MODULE_LOAD_ONCE(TRUE);
54 #if defined(HAVE_ALSA) && defined(HAVE_OSS_OUTPUT)
55 PA_MODULE_USAGE("api=<alsa or oss> "
56 "tsched=<enable system timer based scheduling mode?> "
57 "subdevices=<init all subdevices>");
58 #elif defined(HAVE_ALSA)
59 PA_MODULE_USAGE("api=<alsa> "
60 "tsched=<enable system timer based scheduling mode?>");
61 #elif defined(HAVE_OSS_OUTPUT)
62 PA_MODULE_USAGE("api=<oss> "
63 "subdevices=<init all subdevices>");
65 PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!");
68 char *udi, *originating_udi;
69 char *card_name, *sink_name, *source_name;
71 pa_bool_t acl_race_fix;
76 LibHalContext *context;
77 pa_dbus_connection *connection;
78 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 */
79 const char *capability;
83 #ifdef HAVE_OSS_OUTPUT
84 pa_bool_t init_subdevs;
86 pa_bool_t filter_added:1;
89 #define CAPABILITY_ALSA "alsa"
90 #define CAPABILITY_OSS "oss"
92 static const char* const valid_modargs[] = {
97 #ifdef HAVE_OSS_OUTPUT
103 static void device_free(struct device* d) {
107 pa_xfree(d->originating_udi);
108 pa_xfree(d->sink_name);
109 pa_xfree(d->source_name);
110 pa_xfree(d->card_name);
114 static const char *strip_udi(const char *udi) {
119 if ((slash = strrchr(udi, '/')))
134 static enum alsa_type hal_alsa_device_get_type(LibHalContext *context, const char *udi) {
136 enum alsa_type t = ALSA_TYPE_OTHER;
139 dbus_error_init(&error);
144 if (!(type = libhal_device_get_property_string(context, udi, "alsa.type", &error)))
147 if (pa_streq(type, "playback"))
148 t = ALSA_TYPE_PLAYBACK;
149 else if (pa_streq(type, "capture"))
150 t = ALSA_TYPE_CAPTURE;
151 else if (pa_streq(type, "control"))
152 t = ALSA_TYPE_CONTROL;
154 libhal_free_string(type);
157 if (dbus_error_is_set(&error)) {
158 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error.name, error.message);
159 dbus_error_free(&error);
165 static pa_bool_t hal_alsa_device_is_modem(LibHalContext *context, const char *udi) {
170 dbus_error_init(&error);
175 if (!(class = libhal_device_get_property_string(context, udi, "alsa.pcm_class", &error)))
178 r = pa_streq(class, "modem");
179 libhal_free_string(class);
182 if (dbus_error_is_set(&error)) {
183 if (!dbus_error_has_name(&error, "org.freedesktop.Hal.NoSuchProperty"))
184 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error.name, error.message);
185 dbus_error_free(&error);
191 static int hal_device_load_alsa(struct userdata *u, const char *udi, struct device *d) {
196 char *args, *originating_udi = NULL, *card_name = NULL;
198 dbus_error_init(&error);
204 /* We only care for PCM devices */
205 type = hal_alsa_device_get_type(u->context, udi);
207 /* For each ALSA card that appears the control device will be the
208 * last one to be created, this is considered part of the ALSA
209 * userspace API. We rely on this and load our modules only when
210 * the control device is available assuming that *all* device
211 * nodes have been properly created and assigned the right ACLs at
212 * that time. Also see:
214 * http://mailman.alsa-project.org/pipermail/alsa-devel/2009-April/015958.html
216 * and the associated thread.*/
218 if (type != ALSA_TYPE_CONTROL)
221 /* We don't care for modems -- this is most likely not set for
222 * control devices, so kind of pointless here. */
223 if (hal_alsa_device_is_modem(u->context, udi))
226 /* We store only one entry per card, hence we look for the originating device */
227 originating_udi = libhal_device_get_property_string(u->context, udi, "alsa.originating_device", &error);
228 if (dbus_error_is_set(&error) || !originating_udi)
231 /* Make sure we only load one module per card */
232 if (pa_hashmap_get(u->devices, originating_udi))
235 /* We need the identifier */
236 card = libhal_device_get_property_int(u->context, udi, "alsa.card", &error);
237 if (dbus_error_is_set(&error))
240 card_name = pa_sprintf_malloc("alsa_card.%s", strip_udi(originating_udi));
241 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);
243 pa_log_debug("Loading module-alsa-card with arguments '%s'", args);
244 m = pa_module_load(u->core, "module-alsa-card", args);
250 d->originating_udi = originating_udi;
251 d->module = m->index;
252 d->card_name = card_name;
257 if (dbus_error_is_set(&error)) {
258 pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error.name, error.message);
259 dbus_error_free(&error);
262 pa_xfree(originating_udi);
270 #ifdef HAVE_OSS_OUTPUT
272 static pa_bool_t hal_oss_device_is_pcm(LibHalContext *context, const char *udi, pa_bool_t init_subdevices) {
273 char *class = NULL, *dev = NULL, *e;
278 dbus_error_init(&error);
283 /* We only care for PCM devices */
284 class = libhal_device_get_property_string(context, udi, "oss.type", &error);
285 if (dbus_error_is_set(&error) || !class)
288 if (!pa_streq(class, "pcm"))
291 /* We don't like /dev/audio */
292 dev = libhal_device_get_property_string(context, udi, "oss.device_file", &error);
293 if (dbus_error_is_set(&error) || !dev)
296 if ((e = strrchr(dev, '/')))
297 if (pa_startswith(e + 1, "audio"))
300 /* We only care for the main device */
301 device = libhal_device_get_property_int(context, udi, "oss.device", &error);
302 if (dbus_error_is_set(&error) || (device != 0 && init_subdevices == FALSE))
309 if (dbus_error_is_set(&error)) {
310 pa_log_error("D-Bus error while parsing HAL OSS data: %s: %s", error.name, error.message);
311 dbus_error_free(&error);
314 libhal_free_string(class);
315 libhal_free_string(dev);
320 static int hal_device_load_oss(struct userdata *u, const char *udi, struct device *d) {
323 char *args, *originating_udi = NULL, *device, *sink_name = NULL, *source_name = NULL;
325 dbus_error_init(&error);
331 /* We only care for OSS PCM devices */
332 if (!hal_oss_device_is_pcm(u->context, udi, u->init_subdevs))
335 /* We store only one entry per card, hence we look for the originating device */
336 originating_udi = libhal_device_get_property_string(u->context, udi, "oss.originating_device", &error);
337 if (dbus_error_is_set(&error) || !originating_udi)
340 /* Make sure we only load one module per card */
341 if (pa_hashmap_get(u->devices, originating_udi))
344 /* We need the device file */
345 device = libhal_device_get_property_string(u->context, udi, "oss.device_file", &error);
346 if (!device || dbus_error_is_set(&error))
349 sink_name = pa_sprintf_malloc("oss_output.%s", strip_udi(udi));
350 source_name = pa_sprintf_malloc("oss_input.%s", strip_udi(udi));
351 args = pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device, sink_name, source_name);
353 libhal_free_string(device);
355 pa_log_debug("Loading module-oss with arguments '%s'", args);
356 m = pa_module_load(u->core, "module-oss", args);
362 d->originating_udi = originating_udi;
363 d->module = m->index;
364 d->sink_name = sink_name;
365 d->source_name = source_name;
370 if (dbus_error_is_set(&error)) {
371 pa_log_error("D-Bus error while parsing OSS HAL data: %s: %s", error.name, error.message);
372 dbus_error_free(&error);
375 pa_xfree(originating_udi);
376 pa_xfree(source_name);
383 static struct device* hal_device_add(struct userdata *u, const char *udi) {
388 pa_assert(u->capability);
390 d = pa_xnew(struct device, 1);
391 d->acl_race_fix = FALSE;
392 d->udi = pa_xstrdup(udi);
393 d->originating_udi = NULL;
394 d->module = PA_INVALID_INDEX;
395 d->sink_name = d->source_name = d->card_name = NULL;
399 if (pa_streq(u->capability, CAPABILITY_ALSA))
400 r = hal_device_load_alsa(u, udi, d);
402 #ifdef HAVE_OSS_OUTPUT
403 if (pa_streq(u->capability, CAPABILITY_OSS))
404 r = hal_device_load_oss(u, udi, d);
412 pa_hashmap_put(u->devices, d->udi, d);
413 pa_hashmap_put(u->devices, d->originating_udi, d);
418 static int hal_device_add_all(struct userdata *u) {
423 dbus_error_init(&error);
427 udis = libhal_find_device_by_capability(u->context, u->capability, &n, &error);
428 if (dbus_error_is_set(&error) || !udis)
434 for (i = 0; i < n; i++) {
435 if (hal_device_add(u, udis[i])) {
437 pa_log_debug("Loaded device %s", udis[i]);
439 pa_log_debug("Not loaded device %s", udis[i]);
443 libhal_free_string_array(udis);
448 if (dbus_error_is_set(&error)) {
449 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error.name, error.message);
450 dbus_error_free(&error);
456 static void device_added_cb(LibHalContext *context, const char *udi) {
459 pa_bool_t good = FALSE;
461 dbus_error_init(&error);
466 pa_assert_se(u = libhal_ctx_get_user_data(context));
468 good = libhal_device_query_capability(context, udi, u->capability, &error);
469 if (dbus_error_is_set(&error) || !good)
472 if (!hal_device_add(u, udi))
473 pa_log_debug("Not loaded device %s", udi);
475 pa_log_debug("Loaded device %s", udi);
478 if (dbus_error_is_set(&error)) {
479 if (!dbus_error_has_name(&error, "org.freedesktop.Hal.NoSuchProperty"))
480 pa_log_error("D-Bus error while parsing HAL data: %s: %s", error.name, error.message);
481 dbus_error_free(&error);
485 static void device_removed_cb(LibHalContext* context, const char *udi) {
492 pa_assert_se(u = libhal_ctx_get_user_data(context));
494 if (!(d = pa_hashmap_get(u->devices, udi)))
497 pa_hashmap_remove(u->devices, d->originating_udi);
498 pa_hashmap_remove(u->devices, d->udi);
500 pa_log_debug("Removing HAL device: %s", d->originating_udi);
502 pa_module_unload_request_by_index(u->core, d->module, TRUE);
506 static void new_capability_cb(LibHalContext *context, const char *udi, const char* capability) {
511 pa_assert(capability);
513 pa_assert_se(u = libhal_ctx_get_user_data(context));
515 if (pa_streq(u->capability, capability))
516 /* capability we care about, pretend it's a new device */
517 device_added_cb(context, udi);
520 static void lost_capability_cb(LibHalContext *context, const char *udi, const char* capability) {
525 pa_assert(capability);
527 pa_assert_se(u = libhal_ctx_get_user_data(context));
529 if (pa_streq(u->capability, capability))
530 /* capability we care about, pretend it was removed */
531 device_removed_cb(context, udi);
534 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) {
540 pa_assert_se(u = userdata);
542 dbus_error_init(&error);
544 pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
545 dbus_message_get_interface(message),
546 dbus_message_get_path(message),
547 dbus_message_get_member(message));
549 if (dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
550 dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
552 pa_bool_t suspend = strcmp(dbus_message_get_member(message), "ACLRemoved") == 0;
554 if (!dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
555 pa_log_error("Failed to parse ACL message: %s: %s", error.name, error.message);
559 /* Check if this is about us? */
560 if (uid == getuid() || uid == geteuid()) {
564 udi = dbus_message_get_path(message);
566 if ((d = pa_hashmap_get(u->devices, udi))) {
567 pa_bool_t send_acl_race_fix_message = FALSE;
568 d->acl_race_fix = FALSE;
573 if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK))) {
574 pa_bool_t success = pa_sink_suspend(sink, suspend, PA_SUSPEND_SESSION) >= 0;
576 if (!success && !suspend)
577 d->acl_race_fix = TRUE; /* resume failed, let's try again */
579 send_acl_race_fix_message = TRUE; /* suspend finished, let's tell everyone to try again */
583 if (d->source_name) {
586 if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE))) {
587 pa_bool_t success = pa_source_suspend(source, suspend, PA_SUSPEND_SESSION) >= 0;
589 if (!success && !suspend)
590 d->acl_race_fix = TRUE; /* resume failed, let's try again */
592 send_acl_race_fix_message = TRUE; /* suspend finished, let's tell everyone to try again */
599 if ((card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD))) {
600 pa_bool_t success = pa_card_suspend(card, suspend, PA_SUSPEND_SESSION) >= 0;
602 if (!success && !suspend)
603 d->acl_race_fix = TRUE; /* resume failed, let's try again */
605 send_acl_race_fix_message = TRUE; /* suspend finished, let's tell everyone to try again */
609 if (send_acl_race_fix_message) {
611 msg = dbus_message_new_signal(udi, "org.pulseaudio.Server", "DirtyGiveUpMessage");
612 dbus_connection_send(pa_dbus_connection_get(u->connection), msg, NULL);
613 dbus_message_unref(msg);
617 device_added_cb(u->context, udi);
621 } else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
622 /* We use this message to avoid a dirty race condition when we
623 get an ACLAdded message before the previously owning PA
624 sever has closed the device. We can remove this as
625 soon as HAL learns frevoke() */
630 udi = dbus_message_get_path(message);
632 if ((d = pa_hashmap_get(u->devices, udi))) {
634 if (d->acl_race_fix) {
635 d->acl_race_fix = FALSE;
636 pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi);
641 if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK)))
642 pa_sink_suspend(sink, FALSE, PA_SUSPEND_SESSION);
645 if (d->source_name) {
648 if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE)))
649 pa_source_suspend(source, FALSE, PA_SUSPEND_SESSION);
655 if ((card = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_CARD)))
656 pa_card_suspend(card, FALSE, PA_SUSPEND_SESSION);
661 /* Yes, we don't check the UDI for validity, but hopefully HAL will */
662 device_added_cb(u->context, udi);
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_xnew0(struct userdata, 1);
735 u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
738 u->use_tsched = TRUE;
740 if (pa_modargs_get_value_boolean(ma, "tsched", &u->use_tsched) < 0) {
741 pa_log("Failed to parse tsched argument.");
745 api = pa_modargs_get_value(ma, "api", "alsa");
747 if (pa_streq(api, "alsa"))
748 u->capability = CAPABILITY_ALSA;
750 api = pa_modargs_get_value(ma, "api", "oss");
753 #ifdef HAVE_OSS_OUTPUT
754 if (pa_streq(api, "oss"))
755 u->capability = CAPABILITY_OSS;
758 if (!u->capability) {
759 pa_log_error("Invalid API specification.");
763 #ifdef HAVE_OSS_OUTPUT
764 if (pa_modargs_get_value_boolean(ma, "subdevices", &u->init_subdevs) < 0) {
765 pa_log("Failed to parse subdevices= argument.");
770 if (!(u->connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) {
771 pa_log_error("Unable to contact DBUS system bus: %s: %s", error.name, error.message);
775 if (!(u->context = hal_context_new(pa_dbus_connection_get(u->connection)))) {
776 /* pa_hal_context_new() logs appropriate errors */
780 n = hal_device_add_all(u);
782 libhal_ctx_set_user_data(u->context, u);
783 libhal_ctx_set_device_added(u->context, device_added_cb);
784 libhal_ctx_set_device_removed(u->context, device_removed_cb);
785 libhal_ctx_set_device_new_capability(u->context, new_capability_cb);
786 libhal_ctx_set_device_lost_capability(u->context, lost_capability_cb);
788 if (!libhal_device_property_watch_all(u->context, &error)) {
789 pa_log_error("Error monitoring device list: %s: %s", error.name, error.message);
793 if (!dbus_connection_add_filter(pa_dbus_connection_get(u->connection), filter_cb, u, NULL)) {
794 pa_log_error("Failed to add filter function");
797 u->filter_added = TRUE;
799 if (pa_dbus_add_matches(
800 pa_dbus_connection_get(u->connection), &error,
801 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
802 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
803 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL) < 0) {
804 pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error.name, error.message);
808 pa_log_info("Loaded %i modules.", n);
818 dbus_error_free(&error);
824 void pa__done(pa_module *m) {
829 if (!(u = m->userdata))
833 hal_context_free(u->context);
838 while ((d = pa_hashmap_first(u->devices))) {
839 pa_hashmap_remove(u->devices, d->udi);
840 pa_hashmap_remove(u->devices, d->originating_udi);
844 pa_hashmap_free(u->devices, NULL, NULL);
848 pa_dbus_remove_matches(
849 pa_dbus_connection_get(u->connection),
850 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
851 "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
852 "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL);
855 dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), filter_cb, u);
856 pa_dbus_connection_unref(u->connection);