]> code.delx.au - pulseaudio/blobdiff - src/modules/module-hal-detect.c
echo-cancel: Increase threshold for resyncing, make it configurable
[pulseaudio] / src / modules / module-hal-detect.c
index ca22d70be0c87b898bae3b9234e93f342602e39e..5ca683a35990be71aea64f66c2922d26aa267be6 100644 (file)
@@ -1,5 +1,3 @@
-/* $Id$ */
-
 /***
     This file is part of PulseAudio.
 
 /***
     This file is part of PulseAudio.
 
@@ -8,7 +6,7 @@
 
     PulseAudio is free software; you can redistribute it and/or modify
     it under the terms of the GNU Lesser General Public License as published
 
     PulseAudio is free software; you can redistribute it and/or modify
     it under the terms of the GNU Lesser General Public License as published
-    by the Free Software Foundation; either version 2 of the License,
+    by the Free Software Foundation; either version 2.1 of the License,
     or (at your option) any later version.
 
     PulseAudio is distributed in the hope that it will be useful, but
     or (at your option) any later version.
 
     PulseAudio is distributed in the hope that it will be useful, but
 #include <errno.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <sys/types.h>
-#include <sys/stat.h>
 
 #include <pulse/xmalloc.h>
 
 #include <pulse/xmalloc.h>
-#include <pulse/timeval.h>
 
 
-#include <pulsecore/core-error.h>
 #include <pulsecore/module.h>
 #include <pulsecore/log.h>
 #include <pulsecore/hashmap.h>
 #include <pulsecore/idxset.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/namereg.h>
 #include <pulsecore/module.h>
 #include <pulsecore/log.h>
 #include <pulsecore/hashmap.h>
 #include <pulsecore/idxset.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/namereg.h>
-#include <pulsecore/core-scache.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/dbus-shared.h>
 
 #include <hal/libhal.h>
 
 
 #include <hal/libhal.h>
 
-#include "dbus-util.h"
 #include "module-hal-detect-symdef.h"
 
 #include "module-hal-detect-symdef.h"
 
-PA_MODULE_AUTHOR("Shahms King")
-PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers")
-PA_MODULE_VERSION(PACKAGE_VERSION)
+PA_MODULE_AUTHOR("Shahms King");
+PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+#if defined(HAVE_ALSA) && defined(HAVE_OSS_OUTPUT)
+PA_MODULE_USAGE("api=<alsa or oss> "
+                "tsched=<enable system timer based scheduling mode?> "
+                "subdevices=<init all subdevices>");
+#elif defined(HAVE_ALSA)
+PA_MODULE_USAGE("api=<alsa> "
+                "tsched=<enable system timer based scheduling mode?>");
+#elif defined(HAVE_OSS_OUTPUT)
+PA_MODULE_USAGE("api=<oss> "
+                "subdevices=<init all subdevices>");
+#endif
+PA_MODULE_DEPRECATED("Please use module-udev-detect instead of module-hal-detect!");
 
 struct device {
 
 struct device {
-    uint32_t index;
-    char *udi;
-    char *sink_name, *source_name;
+    char *udi, *originating_udi;
+    char *card_name, *sink_name, *source_name;
+    uint32_t module;
+    pa_bool_t acl_race_fix;
 };
 
 struct userdata {
     pa_core *core;
     LibHalContext *context;
     pa_dbus_connection *connection;
 };
 
 struct userdata {
     pa_core *core;
     LibHalContext *context;
     pa_dbus_connection *connection;
-    pa_hashmap *devices;
+    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 */
     const char *capability;
     const char *capability;
-};
-
-struct timerdata {
-    struct userdata *u;
-    char *udi;
+#ifdef HAVE_ALSA
+    pa_bool_t use_tsched;
+#endif
+#ifdef HAVE_OSS_OUTPUT
+    pa_bool_t init_subdevs;
+#endif
+    pa_bool_t filter_added:1;
 };
 
 #define CAPABILITY_ALSA "alsa"
 #define CAPABILITY_OSS "oss"
 
 };
 
 #define CAPABILITY_ALSA "alsa"
 #define CAPABILITY_OSS "oss"
 
-static void hal_device_free(struct device* d) {
+static const char* const valid_modargs[] = {
+    "api",
+#ifdef HAVE_ALSA
+    "tsched",
+#endif
+#ifdef HAVE_OSS_OUTPUT
+    "subdevices",
+#endif
+    NULL
+};
+
+static void device_free(struct device* d) {
     pa_assert(d);
     pa_assert(d);
-    
+
     pa_xfree(d->udi);
     pa_xfree(d->udi);
+    pa_xfree(d->originating_udi);
     pa_xfree(d->sink_name);
     pa_xfree(d->source_name);
     pa_xfree(d->sink_name);
     pa_xfree(d->source_name);
+    pa_xfree(d->card_name);
     pa_xfree(d);
 }
 
     pa_xfree(d);
 }
 
-static void hal_device_free_cb(void *d, PA_GCC_UNUSED void *data) {
-    hal_device_free(d);
-}
-
 static const char *strip_udi(const char *udi) {
     const char *slash;
 static const char *strip_udi(const char *udi) {
     const char *slash;
-    
+
+    pa_assert(udi);
+
     if ((slash = strrchr(udi, '/')))
         return slash+1;
 
     if ((slash = strrchr(udi, '/')))
         return slash+1;
 
@@ -102,393 +124,395 @@ static const char *strip_udi(const char *udi) {
 
 #ifdef HAVE_ALSA
 
 
 #ifdef HAVE_ALSA
 
-typedef enum {
-    ALSA_TYPE_SINK,
-    ALSA_TYPE_SOURCE,
-    ALSA_TYPE_OTHER,
-    ALSA_TYPE_MAX
-} alsa_type_t;
+enum alsa_type {
+    ALSA_TYPE_PLAYBACK,
+    ALSA_TYPE_CAPTURE,
+    ALSA_TYPE_CONTROL,
+    ALSA_TYPE_OTHER
+};
 
 
-static alsa_type_t hal_alsa_device_get_type(LibHalContext *context, const char *udi, DBusError *error) {
+static enum alsa_type hal_alsa_device_get_type(LibHalContext *context, const char *udi) {
     char *type;
     char *type;
-    alsa_type_t t;
+    enum alsa_type t = ALSA_TYPE_OTHER;
+    DBusError error;
+
+    dbus_error_init(&error);
 
 
-    if (!(type = libhal_device_get_property_string(context, udi, "alsa.type", error)))
-        return ALSA_TYPE_OTHER;
+    pa_assert(context);
+    pa_assert(udi);
 
 
-    if (!strcmp(type, "playback")) 
-        t = ALSA_TYPE_SINK;
-    else if (!strcmp(type, "capture"))
-        t = ALSA_TYPE_SOURCE;
-    else 
-        t = ALSA_TYPE_OTHER;
+    if (!(type = libhal_device_get_property_string(context, udi, "alsa.type", &error)))
+        goto finish;
+
+    if (pa_streq(type, "playback"))
+        t = ALSA_TYPE_PLAYBACK;
+    else if (pa_streq(type, "capture"))
+        t = ALSA_TYPE_CAPTURE;
+    else if (pa_streq(type, "control"))
+        t = ALSA_TYPE_CONTROL;
 
     libhal_free_string(type);
 
 
     libhal_free_string(type);
 
+finish:
+    if (dbus_error_is_set(&error)) {
+        pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error.name, error.message);
+        dbus_error_free(&error);
+    }
+
     return t;
 }
 
     return t;
 }
 
-static int hal_alsa_device_is_modem(LibHalContext *context, const char *udi, DBusError *error) {
+static pa_bool_t hal_alsa_device_is_modem(LibHalContext *context, const char *udi) {
     char *class;
     char *class;
-    int r;
-    
-    if (!(class = libhal_device_get_property_string(context, udi, "alsa.pcm_class", error)))
-        return 0;
+    pa_bool_t r = FALSE;
+    DBusError error;
+
+    dbus_error_init(&error);
+
+    pa_assert(context);
+    pa_assert(udi);
+
+    if (!(class = libhal_device_get_property_string(context, udi, "alsa.pcm_class", &error)))
+        goto finish;
+
+    r = pa_streq(class, "modem");
+    libhal_free_string(class);
+
+finish:
+    if (dbus_error_is_set(&error)) {
+        if (!dbus_error_has_name(&error, "org.freedesktop.Hal.NoSuchProperty"))
+            pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error.name, error.message);
+        dbus_error_free(&error);
+    }
 
 
-    r = strcmp(class, "modem") == 0;
-    pa_xfree(class);
-    
     return r;
 }
 
     return r;
 }
 
-static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi, char **sink_name, char **source_name) {
-    char args[128];
-    alsa_type_t type;
-    int device, card;
-    const char *module_name;
+static int hal_device_load_alsa(struct userdata *u, const char *udi, struct device *d) {
+    enum alsa_type type;
+    int card;
     DBusError error;
     pa_module *m;
     DBusError error;
     pa_module *m;
-    
+    char *args, *originating_udi = NULL, *card_name = NULL;
+
     dbus_error_init(&error);
 
     pa_assert(u);
     dbus_error_init(&error);
 
     pa_assert(u);
-    pa_assert(sink_name);
-    pa_assert(source_name);
+    pa_assert(udi);
+    pa_assert(d);
+
+    /* We only care for PCM devices */
+    type = hal_alsa_device_get_type(u->context, udi);
+
+    /* For each ALSA card that appears the control device will be the
+     * last one to be created, this is considered part of the ALSA
+     * userspace API. We rely on this and load our modules only when
+     * the control device is available assuming that *all* device
+     * nodes have been properly created and assigned the right ACLs at
+     * that time. Also see:
+     *
+     * http://mailman.alsa-project.org/pipermail/alsa-devel/2009-April/015958.html
+     *
+     * and the associated thread.*/
+
+    if (type != ALSA_TYPE_CONTROL)
+        goto fail;
 
 
-    *sink_name = *source_name = NULL;
-    
-    type = hal_alsa_device_get_type(u->context, udi, &error);
-    if (dbus_error_is_set(&error) || type == ALSA_TYPE_OTHER)
+    /* We don't care for modems -- this is most likely not set for
+     * control devices, so kind of pointless here. */
+    if (hal_alsa_device_is_modem(u->context, udi))
         goto fail;
 
         goto fail;
 
-    device = libhal_device_get_property_int(u->context, udi, "alsa.device", &error);
-    if (dbus_error_is_set(&error) || device != 0)
+    /* We store only one entry per card, hence we look for the originating device */
+    originating_udi = libhal_device_get_property_string(u->context, udi, "alsa.originating_device", &error);
+    if (dbus_error_is_set(&error) || !originating_udi)
         goto fail;
 
         goto fail;
 
+    /* Make sure we only load one module per card */
+    if (pa_hashmap_get(u->devices, originating_udi))
+        goto fail;
+
+    /* We need the identifier */
     card = libhal_device_get_property_int(u->context, udi, "alsa.card", &error);
     if (dbus_error_is_set(&error))
         goto fail;
 
     card = libhal_device_get_property_int(u->context, udi, "alsa.card", &error);
     if (dbus_error_is_set(&error))
         goto fail;
 
-    if (hal_alsa_device_is_modem(u->context, udi, &error))
-        goto fail;
+    card_name = pa_sprintf_malloc("alsa_card.%s", strip_udi(originating_udi));
+    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);
 
 
-    if (type == ALSA_TYPE_SINK) {
-        *sink_name = pa_sprintf_malloc("alsa_output.%s", strip_udi(udi));
-        
-        module_name = "module-alsa-sink";
-        pa_snprintf(args, sizeof(args), "device=hw:%u sink_name=%s", card, *sink_name);
-    } else {
-        *source_name = pa_sprintf_malloc("alsa_output.%s", strip_udi(udi));
-        
-        module_name = "module-alsa-source";
-        pa_snprintf(args, sizeof(args), "device=hw:%u source_name=%s", card, *source_name);
-    }
+    pa_log_debug("Loading module-alsa-card with arguments '%s'", args);
+    m = pa_module_load(u->core, "module-alsa-card", args);
+    pa_xfree(args);
 
 
-    pa_log_debug("Loading %s with arguments '%s'", module_name, args);
+    if (!m)
+        goto fail;
 
 
-    m = pa_module_load(u->core, module_name, args);
+    d->originating_udi = originating_udi;
+    d->module = m->index;
+    d->card_name = card_name;
 
 
-    if (!m) {
-        pa_xfree(*sink_name);
-        pa_xfree(*source_name);
-        *sink_name = *source_name = NULL;
-    }
-    
-    return m;
+    return 0;
 
 fail:
     if (dbus_error_is_set(&error)) {
 
 fail:
     if (dbus_error_is_set(&error)) {
-        pa_log_error("D-Bus error while parsing ALSA data: %s: %s", error.name, error.message);
+        pa_log_error("D-Bus error while parsing HAL ALSA data: %s: %s", error.name, error.message);
         dbus_error_free(&error);
     }
 
         dbus_error_free(&error);
     }
 
-    return NULL;
+    pa_xfree(originating_udi);
+    pa_xfree(card_name);
+
+    return -1;
 }
 
 #endif
 
 }
 
 #endif
 
-#ifdef HAVE_OSS
+#ifdef HAVE_OSS_OUTPUT
 
 
-static int hal_oss_device_is_pcm(LibHalContext *context, const char *udi, DBusError *error) {
+static pa_bool_t hal_oss_device_is_pcm(LibHalContext *context, const char *udi, pa_bool_t init_subdevices) {
     char *class = NULL, *dev = NULL, *e;
     int device;
     char *class = NULL, *dev = NULL, *e;
     int device;
-    int r = 0;
+    pa_bool_t r = FALSE;
+    DBusError error;
+
+    dbus_error_init(&error);
+
+    pa_assert(context);
+    pa_assert(udi);
 
 
-    class = libhal_device_get_property_string(context, udi, "oss.type", error);
-    if (dbus_error_is_set(error) || !class)
+    /* We only care for PCM devices */
+    class = libhal_device_get_property_string(context, udi, "oss.type", &error);
+    if (dbus_error_is_set(&error) || !class)
         goto finish;
 
         goto finish;
 
-    if (strcmp(class, "pcm"))
+    if (!pa_streq(class, "pcm"))
         goto finish;
 
         goto finish;
 
-    dev = libhal_device_get_property_string(context, udi, "oss.device_file", error);
-    if (dbus_error_is_set(error) || !dev)
+    /* We don't like /dev/audio */
+    dev = libhal_device_get_property_string(context, udi, "oss.device_file", &error);
+    if (dbus_error_is_set(&error) || !dev)
         goto finish;
 
     if ((e = strrchr(dev, '/')))
         if (pa_startswith(e + 1, "audio"))
             goto finish;
 
         goto finish;
 
     if ((e = strrchr(dev, '/')))
         if (pa_startswith(e + 1, "audio"))
             goto finish;
 
-    device = libhal_device_get_property_int(context, udi, "oss.device", error);
-    if (dbus_error_is_set(error) || device != 0)
+    /* We only care for the main device */
+    device = libhal_device_get_property_int(context, udi, "oss.device", &error);
+    if (dbus_error_is_set(&error) || (device != 0 && init_subdevices == FALSE))
         goto finish;
 
         goto finish;
 
-    r = 1;
+    r = TRUE;
 
 finish:
 
 
 finish:
 
+    if (dbus_error_is_set(&error)) {
+        pa_log_error("D-Bus error while parsing HAL OSS data: %s: %s", error.name, error.message);
+        dbus_error_free(&error);
+    }
+
     libhal_free_string(class);
     libhal_free_string(dev);
     libhal_free_string(class);
     libhal_free_string(dev);
-    
+
     return r;
 }
 
     return r;
 }
 
-static pa_module* hal_device_load_oss(struct userdata *u, const char *udi, char **sink_name, char **source_name) {
-    char args[256];
-    char* device;
+static int hal_device_load_oss(struct userdata *u, const char *udi, struct device *d) {
     DBusError error;
     pa_module *m;
     DBusError error;
     pa_module *m;
-    
+    char *args, *originating_udi = NULL, *device, *sink_name = NULL, *source_name = NULL;
+
     dbus_error_init(&error);
 
     pa_assert(u);
     dbus_error_init(&error);
 
     pa_assert(u);
-    pa_assert(sink_name);
-    pa_assert(source_name);
+    pa_assert(udi);
+    pa_assert(d);
 
 
-    *sink_name = *source_name = NULL;
+    /* We only care for OSS PCM devices */
+    if (!hal_oss_device_is_pcm(u->context, udi, u->init_subdevs))
+        goto fail;
 
 
-    if (!hal_oss_device_is_pcm(u->context, udi, &error) || dbus_error_is_set(&error))
+    /* We store only one entry per card, hence we look for the originating device */
+    originating_udi = libhal_device_get_property_string(u->context, udi, "oss.originating_device", &error);
+    if (dbus_error_is_set(&error) || !originating_udi)
         goto fail;
 
         goto fail;
 
+    /* Make sure we only load one module per card */
+    if (pa_hashmap_get(u->devices, originating_udi))
+        goto fail;
+
+    /* We need the device file */
     device = libhal_device_get_property_string(u->context, udi, "oss.device_file", &error);
     if (!device || dbus_error_is_set(&error))
         goto fail;
 
     device = libhal_device_get_property_string(u->context, udi, "oss.device_file", &error);
     if (!device || dbus_error_is_set(&error))
         goto fail;
 
-    *sink_name = pa_sprintf_malloc("alsa_output.%s", strip_udi(udi));
-    *source_name = pa_sprintf_malloc("alsa_output.%s", strip_udi(udi));
-    
-    pa_snprintf(args, sizeof(args), "device=%s sink_name=%s source_name=%s", device, sink_name, source_name);
+    sink_name = pa_sprintf_malloc("oss_output.%s", strip_udi(udi));
+    source_name = pa_sprintf_malloc("oss_input.%s", strip_udi(udi));
+    args = pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device, sink_name, source_name);
+
     libhal_free_string(device);
 
     pa_log_debug("Loading module-oss with arguments '%s'", args);
     libhal_free_string(device);
 
     pa_log_debug("Loading module-oss with arguments '%s'", args);
-
     m = pa_module_load(u->core, "module-oss", args);
     m = pa_module_load(u->core, "module-oss", args);
+    pa_xfree(args);
 
 
-    if (!m) {
-        pa_xfree(*sink_name);
-        pa_xfree(*source_name);
-        *sink_name = *source_name = NULL;
-    }
+    if (!m)
+        goto fail;
+
+    d->originating_udi = originating_udi;
+    d->module = m->index;
+    d->sink_name = sink_name;
+    d->source_name = source_name;
 
 
-    return m;
+    return 0;
 
 fail:
     if (dbus_error_is_set(&error)) {
 
 fail:
     if (dbus_error_is_set(&error)) {
-        pa_log_error("D-Bus error while parsing OSS data: %s: %s", error.name, error.message);
+        pa_log_error("D-Bus error while parsing OSS HAL data: %s: %s", error.name, error.message);
         dbus_error_free(&error);
     }
 
         dbus_error_free(&error);
     }
 
-    return NULL;
+    pa_xfree(originating_udi);
+    pa_xfree(source_name);
+    pa_xfree(sink_name);
+
+    return -1;
 }
 #endif
 
 static struct device* hal_device_add(struct userdata *u, const char *udi) {
 }
 #endif
 
 static struct device* hal_device_add(struct userdata *u, const char *udi) {
-    pa_module* m = NULL;
     struct device *d;
     struct device *d;
-    char *sink_name = NULL, *source_name = NULL;
+    int r;
 
     pa_assert(u);
     pa_assert(u->capability);
 
 
     pa_assert(u);
     pa_assert(u->capability);
 
+    d = pa_xnew(struct device, 1);
+    d->acl_race_fix = FALSE;
+    d->udi = pa_xstrdup(udi);
+    d->originating_udi = NULL;
+    d->module = PA_INVALID_INDEX;
+    d->sink_name = d->source_name = d->card_name = NULL;
+    r = -1;
+
 #ifdef HAVE_ALSA
 #ifdef HAVE_ALSA
-    if (strcmp(u->capability, CAPABILITY_ALSA) == 0)
-        m = hal_device_load_alsa(u, udi, &sink_name, &source_name);
+    if (pa_streq(u->capability, CAPABILITY_ALSA))
+        r = hal_device_load_alsa(u, udi,  d);
 #endif
 #endif
-#ifdef HAVE_OSS
-    if (strcmp(u->capability, CAPABILITY_OSS) == 0)
-        m = hal_device_load_oss(u, udi, &sink_name, &source_name);
+#ifdef HAVE_OSS_OUTPUT
+    if (pa_streq(u->capability, CAPABILITY_OSS))
+        r = hal_device_load_oss(u, udi, d);
 #endif
 
 #endif
 
-    if (!m)
+    if (r < 0) {
+        device_free(d);
         return NULL;
         return NULL;
+    }
 
 
-    d = pa_xnew(struct device, 1);
-    d->udi = pa_xstrdup(udi);
-    d->index = m->index;
-    d->sink_name = sink_name;
-    d->source_name = source_name;
     pa_hashmap_put(u->devices, d->udi, d);
     pa_hashmap_put(u->devices, d->udi, d);
+    pa_hashmap_put(u->devices, d->originating_udi, d);
 
     return d;
 }
 
 
     return d;
 }
 
-static int hal_device_add_all(struct userdata *u, const char *capability) {
-    DBusError error;
-    int i, n, count = 0;
+static int hal_device_add_all(struct userdata *u) {
+    int n, count = 0;
     char** udis;
     char** udis;
+    DBusError error;
 
 
-    pa_assert(u);
-    pa_assert(!u->capability);
-    
     dbus_error_init(&error);
 
     dbus_error_init(&error);
 
-    pa_log_info("Trying capability %s", capability);
+    pa_assert(u);
 
 
-    udis = libhal_find_device_by_capability(u->context, capability, &n, &error);
-    if (dbus_error_is_set(&error)) {
-        pa_log_error("Error finding devices: %s: %s", error.name, error.message);
-        dbus_error_free(&error);
-        return -1;
-    }
+    udis = libhal_find_device_by_capability(u->context, u->capability, &n, &error);
+    if (dbus_error_is_set(&error) || !udis)
+        goto fail;
 
     if (n > 0) {
 
     if (n > 0) {
-        u->capability = capability;
-        
+        int i;
+
         for (i = 0; i < n; i++) {
         for (i = 0; i < n; i++) {
-            struct device *d;
-            
-            if (!(d = hal_device_add(u, udis[i])))
-                pa_log_debug("Not loaded device %s", udis[i]);
-            else {
-                if (d->sink_name)
-                    pa_scache_play_item_by_name(u->core, "pulse-coldplug", d->sink_name, PA_VOLUME_NORM, 0);
+            if (hal_device_add(u, udis[i])) {
                 count++;
                 count++;
-            }
+                pa_log_debug("Loaded device %s", udis[i]);
+            } else
+                pa_log_debug("Not loaded device %s", udis[i]);
         }
     }
 
     libhal_free_string_array(udis);
         }
     }
 
     libhal_free_string_array(udis);
-    return count;
-}
 
 
-static dbus_bool_t device_has_capability(LibHalContext *context, const char *udi, const char* cap, DBusError *error){
-    dbus_bool_t has_prop;
-    
-    has_prop = libhal_device_property_exists(context, udi, "info.capabilities", error);
-    if (!has_prop || dbus_error_is_set(error))
-        return FALSE;
-
-    return libhal_device_query_capability(context, udi, cap, error);
-}
-
-static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev, const struct timeval *tv, void *userdata) {
-    DBusError error;
-    struct timerdata *td = userdata;
-    int b;
-    struct device *d;
+    return count;
 
 
-    dbus_error_init(&error);
-    
-    b = libhal_device_exists(td->u->context, td->udi, &error);
-    
+fail:
     if (dbus_error_is_set(&error)) {
     if (dbus_error_is_set(&error)) {
-        pa_log_error("Error adding device: %s: %s", error.name, error.message);
+        pa_log_error("D-Bus error while parsing HAL data: %s: %s", error.name, error.message);
         dbus_error_free(&error);
         dbus_error_free(&error);
-    } else if (b) {
-        if (!(d = hal_device_add(td->u, td->udi))) 
-            pa_log_debug("Not loaded device %s", td->udi);
-        else {
-            if (d->sink_name)
-                pa_scache_play_item_by_name(td->u->core, "pulse-hotplug", d->sink_name, PA_VOLUME_NORM, 0);
-        }
     }
 
     }
 
-    pa_xfree(td->udi);
-    pa_xfree(td);
-    ea->time_free(ev);
+    return -1;
 }
 
 static void device_added_cb(LibHalContext *context, const char *udi) {
     DBusError error;
 }
 
 static void device_added_cb(LibHalContext *context, const char *udi) {
     DBusError error;
-    struct timeval tv;
-    struct timerdata *t;
     struct userdata *u;
     struct userdata *u;
-    int good = 0;
-
-    pa_assert_se(u = libhal_ctx_get_user_data(context));
-    
-    pa_log_debug("HAL Device added: %s", udi);
+    pa_bool_t good = FALSE;
 
     dbus_error_init(&error);
 
 
     dbus_error_init(&error);
 
-    if (u->capability) {
-        
-        good = device_has_capability(context, udi, u->capability, &error);
-
-        if (dbus_error_is_set(&error)) {
-            pa_log_error("Error getting capability: %s: %s", error.name, error.message);
-            dbus_error_free(&error);
-            return;
-        }
-        
-    } else {
+    pa_assert(context);
+    pa_assert(udi);
 
 
-#ifdef HAVE_ALSA
-        good = device_has_capability(context, udi, CAPABILITY_ALSA, &error);
-
-        if (dbus_error_is_set(&error)) {
-            pa_log_error("Error getting capability: %s: %s", error.name, error.message);
-            dbus_error_free(&error);
-            return;
-        }
+    pa_assert_se(u = libhal_ctx_get_user_data(context));
 
 
-        if (good)
-            u->capability = CAPABILITY_ALSA;
-#endif
-#if defined(HAVE_OSS) && defined(HAVE_ALSA)
-        if (!good) {
-#endif        
-#ifdef HAS_OSS
-            good = device_has_capability(context, udi, CAPABILITY_OSS, &error);
-            
-            if (dbus_error_is_set(&error)) {
-                pa_log_error("Error getting capability: %s: %s", error.name, error.message);
-                dbus_error_free(&error);
-                return;
-            }
+    good = libhal_device_query_capability(context, udi, u->capability, &error);
+    if (dbus_error_is_set(&error) || !good)
+        goto finish;
 
 
-            if (good)
-                u->capability = CAPABILITY_OSS;
+    if (!hal_device_add(u, udi))
+        pa_log_debug("Not loaded device %s", udi);
+    else
+        pa_log_debug("Loaded device %s", udi);
 
 
-#endif
-#if defined(HAVE_OSS) && defined(HAVE_ALSA)
-        }
-#endif
+finish:
+    if (dbus_error_is_set(&error)) {
+        if (!dbus_error_has_name(&error, "org.freedesktop.Hal.NoSuchProperty"))
+            pa_log_error("D-Bus error while parsing HAL data: %s: %s", error.name, error.message);
+        dbus_error_free(&error);
     }
     }
-        
-    if (!good)
-        return;
-
-    /* actually add the device 1/2 second later */
-    t = pa_xnew(struct timerdata, 1);
-    t->u = u;
-    t->udi = pa_xstrdup(udi);
-
-    pa_gettimeofday(&tv);
-    pa_timeval_add(&tv, 500000);
-    u->core->mainloop->time_new(u->core->mainloop, &tv, device_added_time_cb, t);
 }
 
 static void device_removed_cb(LibHalContext* context, const char *udi) {
     struct device *d;
     struct userdata *u;
 
 }
 
 static void device_removed_cb(LibHalContext* context, const char *udi) {
     struct device *d;
     struct userdata *u;
 
+    pa_assert(context);
+    pa_assert(udi);
+
     pa_assert_se(u = libhal_ctx_get_user_data(context));
 
     pa_assert_se(u = libhal_ctx_get_user_data(context));
 
-    pa_log_debug("Device removed: %s", udi);
-    
-    if ((d = pa_hashmap_remove(u->devices, udi))) {
-        pa_module_unload_by_index(u->core, d->index);
-        hal_device_free(d);
-    }
+    if (!(d = pa_hashmap_get(u->devices, udi)))
+        return;
+
+    pa_hashmap_remove(u->devices, d->originating_udi);
+    pa_hashmap_remove(u->devices, d->udi);
+
+    pa_log_debug("Removing HAL device: %s", d->originating_udi);
+
+    pa_module_unload_request_by_index(u->core, d->module, TRUE);
+    device_free(d);
 }
 
 static void new_capability_cb(LibHalContext *context, const char *udi, const char* capability) {
     struct userdata *u;
 
 }
 
 static void new_capability_cb(LibHalContext *context, const char *udi, const char* capability) {
     struct userdata *u;
 
+    pa_assert(context);
+    pa_assert(udi);
+    pa_assert(capability);
+
     pa_assert_se(u = libhal_ctx_get_user_data(context));
 
     pa_assert_se(u = libhal_ctx_get_user_data(context));
 
-    if (!u->capability || strcmp(u->capability, capability) == 0)
+    if (pa_streq(u->capability, capability))
         /* capability we care about, pretend it's a new device */
         device_added_cb(context, udi);
 }
         /* capability we care about, pretend it's a new device */
         device_added_cb(context, udi);
 }
@@ -496,22 +520,25 @@ static void new_capability_cb(LibHalContext *context, const char *udi, const cha
 static void lost_capability_cb(LibHalContext *context, const char *udi, const char* capability) {
     struct userdata *u;
 
 static void lost_capability_cb(LibHalContext *context, const char *udi, const char* capability) {
     struct userdata *u;
 
+    pa_assert(context);
+    pa_assert(udi);
+    pa_assert(capability);
+
     pa_assert_se(u = libhal_ctx_get_user_data(context));
 
     pa_assert_se(u = libhal_ctx_get_user_data(context));
 
-    if (u->capability && strcmp(u->capability, capability) == 0)
+    if (pa_streq(u->capability, capability))
         /* capability we care about, pretend it was removed */
         device_removed_cb(context, udi);
 }
 
         /* capability we care about, pretend it was removed */
         device_removed_cb(context, udi);
 }
 
-
 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) {
 static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) {
-    struct userdata*u = userdata;
+    struct userdata*u;
     DBusError error;
 
     pa_assert(bus);
     pa_assert(message);
     DBusError error;
 
     pa_assert(bus);
     pa_assert(message);
-    pa_assert(u);
-    
+    pa_assert_se(u = userdata);
+
     dbus_error_init(&error);
 
     pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
     dbus_error_init(&error);
 
     pa_log_debug("dbus: interface=%s, path=%s, member=%s\n",
@@ -522,70 +549,151 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo
     if (dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
         dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
         uint32_t uid;
     if (dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") ||
         dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) {
         uint32_t uid;
-        int suspend = strcmp(dbus_message_get_member(message), "ACLRemoved") == 0;
-        
+        pa_bool_t suspend = strcmp(dbus_message_get_member(message), "ACLRemoved") == 0;
+
         if (!dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
             pa_log_error("Failed to parse ACL message: %s: %s", error.name, error.message);
             goto finish;
         }
 
         if (!dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) {
             pa_log_error("Failed to parse ACL message: %s: %s", error.name, error.message);
             goto finish;
         }
 
+        /* Check if this is about us? */
         if (uid == getuid() || uid == geteuid()) {
             struct device *d;
             const char *udi;
 
             udi = dbus_message_get_path(message);
         if (uid == getuid() || uid == geteuid()) {
             struct device *d;
             const char *udi;
 
             udi = dbus_message_get_path(message);
-            
+
             if ((d = pa_hashmap_get(u->devices, udi))) {
             if ((d = pa_hashmap_get(u->devices, udi))) {
-                
+                pa_bool_t send_acl_race_fix_message = FALSE;
+                d->acl_race_fix = FALSE;
+
                 if (d->sink_name) {
                     pa_sink *sink;
                 if (d->sink_name) {
                     pa_sink *sink;
-                    
-                    if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK, 0)))
-                        if (pa_sink_suspend(sink, suspend) >= 0)
-                            pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, PA_VOLUME_NORM, 0);
+
+                    if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK))) {
+                        pa_bool_t success = pa_sink_suspend(sink, suspend, PA_SUSPEND_SESSION) >= 0;
+
+                        if (!success && !suspend)
+                            d->acl_race_fix = TRUE; /* resume failed, let's try again */
+                        else if (suspend)
+                            send_acl_race_fix_message = TRUE; /* suspend finished, let's tell everyone to try again */
+                    }
                 }
 
                 if (d->source_name) {
                     pa_source *source;
 
                 }
 
                 if (d->source_name) {
                     pa_source *source;
 
-                    if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE, 0)))
-                        pa_source_suspend(source, suspend);
+                    if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE))) {
+                        pa_bool_t success = pa_source_suspend(source, suspend, PA_SUSPEND_SESSION) >= 0;
+
+                        if (!success && !suspend)
+                            d->acl_race_fix = TRUE; /* resume failed, let's try again */
+                        else if (suspend)
+                            send_acl_race_fix_message = TRUE; /* suspend finished, let's tell everyone to try again */
+                    }
                 }
                 }
-            }
+
+                if (d->card_name) {
+                    pa_card *card;
+
+                    if ((card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD))) {
+                        pa_bool_t success = pa_card_suspend(card, suspend, PA_SUSPEND_SESSION) >= 0;
+
+                        if (!success && !suspend)
+                            d->acl_race_fix = TRUE; /* resume failed, let's try again */
+                        else if (suspend)
+                            send_acl_race_fix_message = TRUE; /* suspend finished, let's tell everyone to try again */
+                    }
+                }
+
+                if (send_acl_race_fix_message) {
+                    DBusMessage *msg;
+                    msg = dbus_message_new_signal(udi, "org.pulseaudio.Server", "DirtyGiveUpMessage");
+                    dbus_connection_send(pa_dbus_connection_get(u->connection), msg, NULL);
+                    dbus_message_unref(msg);
+                }
+
+            } else if (!suspend)
+                device_added_cb(u->context, udi);
 
         }
 
         }
-        
+
+    } else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) {
+        /* We use this message to avoid a dirty race condition when we
+           get an ACLAdded message before the previously owning PA
+           sever has closed the device. We can remove this as
+           soon as HAL learns frevoke() */
+
+        struct device *d;
+        const char *udi;
+
+        udi = dbus_message_get_path(message);
+
+        if ((d = pa_hashmap_get(u->devices, udi))) {
+
+            if (d->acl_race_fix) {
+                d->acl_race_fix = FALSE;
+                pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi);
+
+                if (d->sink_name) {
+                    pa_sink *sink;
+
+                    if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK)))
+                        pa_sink_suspend(sink, FALSE, PA_SUSPEND_SESSION);
+                }
+
+                if (d->source_name) {
+                    pa_source *source;
+
+                    if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE)))
+                        pa_source_suspend(source, FALSE, PA_SUSPEND_SESSION);
+                }
+
+                if (d->card_name) {
+                    pa_card *card;
+
+                    if ((card = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_CARD)))
+                        pa_card_suspend(card, FALSE, PA_SUSPEND_SESSION);
+                }
+            }
+
+        } else
+            /* Yes, we don't check the UDI for validity, but hopefully HAL will */
+            device_added_cb(u->context, udi);
+
     }
 
 finish:
     dbus_error_free(&error);
     }
 
 finish:
     dbus_error_free(&error);
-        
-    return DBUS_HANDLER_RESULT_HANDLED;
+
+    return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
 }
 
 static void hal_context_free(LibHalContext* hal_context) {
     DBusError error;
 
     dbus_error_init(&error);
 }
 
 static void hal_context_free(LibHalContext* hal_context) {
     DBusError error;
 
     dbus_error_init(&error);
-    
+
     libhal_ctx_shutdown(hal_context, &error);
     libhal_ctx_free(hal_context);
 
     dbus_error_free(&error);
 }
 
     libhal_ctx_shutdown(hal_context, &error);
     libhal_ctx_free(hal_context);
 
     dbus_error_free(&error);
 }
 
-static LibHalContext* hal_context_new(pa_core* c, DBusConnection *conn) {
+static LibHalContext* hal_context_new(DBusConnection *connection) {
     DBusError error;
     LibHalContext *hal_context = NULL;
 
     dbus_error_init(&error);
     DBusError error;
     LibHalContext *hal_context = NULL;
 
     dbus_error_init(&error);
-    
+
+    pa_assert(connection);
+
     if (!(hal_context = libhal_ctx_new())) {
         pa_log_error("libhal_ctx_new() failed");
         goto fail;
     }
 
     if (!(hal_context = libhal_ctx_new())) {
         pa_log_error("libhal_ctx_new() failed");
         goto fail;
     }
 
-    if (!libhal_ctx_set_dbus_connection(hal_context, conn)) {
+    if (!libhal_ctx_set_dbus_connection(hal_context, connection)) {
         pa_log_error("Error establishing DBUS connection: %s: %s", error.name, error.message);
         goto fail;
     }
         pa_log_error("Error establishing DBUS connection: %s: %s", error.name, error.message);
         goto fail;
     }
@@ -606,86 +714,116 @@ fail:
     return NULL;
 }
 
     return NULL;
 }
 
-int pa__init(pa_core *c, pa_module*m) {
+int pa__init(pa_module*m) {
     DBusError error;
     DBusError error;
-    pa_dbus_connection *conn;
     struct userdata *u = NULL;
     struct userdata *u = NULL;
-    LibHalContext *hal_context = NULL;
     int n = 0;
     int n = 0;
-    
-    pa_assert(c);
+    pa_modargs *ma;
+    const char *api;
+
     pa_assert(m);
 
     dbus_error_init(&error);
     pa_assert(m);
 
     dbus_error_init(&error);
-    
-    if (!(conn = pa_dbus_bus_get(c, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) {
-        if (conn)
-            pa_dbus_connection_unref(conn);
-        pa_log_error("Unable to contact DBUS system bus: %s: %s", error.name, error.message);
-        goto fail;
-    }
 
 
-    if (!(hal_context = hal_context_new(c, pa_dbus_connection_get(conn)))) {
-        /* pa_hal_context_new() logs appropriate errors */
-        pa_dbus_connection_unref(conn);
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log("Failed to parse module arguments");
         goto fail;
     }
 
         goto fail;
     }
 
-    u = pa_xnew(struct userdata, 1);
-    u->core = c;
-    u->context = hal_context;
-    u->connection = conn;
+    m->userdata = u = pa_xnew0(struct userdata, 1);
+    u->core = m->core;
     u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
     u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
-    u->capability = NULL;
-    m->userdata = u;
 
 #ifdef HAVE_ALSA
 
 #ifdef HAVE_ALSA
-    n = hal_device_add_all(u, CAPABILITY_ALSA);
+    u->use_tsched = TRUE;
+
+    if (pa_modargs_get_value_boolean(ma, "tsched", &u->use_tsched) < 0) {
+        pa_log("Failed to parse tsched argument.");
+        goto fail;
+    }
+
+    api = pa_modargs_get_value(ma, "api", "alsa");
+
+    if (pa_streq(api, "alsa"))
+        u->capability = CAPABILITY_ALSA;
+#else
+    api = pa_modargs_get_value(ma, "api", "oss");
 #endif
 #endif
-#if defined(HAVE_ALSA) && defined(HAVE_OSS)
-    if (n <= 0)
-#endif        
-#ifdef HAVE_OSS
-        n += hal_device_add_all(u, CAPABILITY_OSS);
+
+#ifdef HAVE_OSS_OUTPUT
+    if (pa_streq(api, "oss"))
+        u->capability = CAPABILITY_OSS;
 #endif
 
 #endif
 
-    libhal_ctx_set_user_data(hal_context, u);
-    libhal_ctx_set_device_added(hal_context, device_added_cb);
-    libhal_ctx_set_device_removed(hal_context, device_removed_cb);
-    libhal_ctx_set_device_new_capability(hal_context, new_capability_cb);
-    libhal_ctx_set_device_lost_capability(hal_context, lost_capability_cb);
+    if (!u->capability) {
+        pa_log_error("Invalid API specification.");
+        goto fail;
+    }
 
 
-    if (!libhal_device_property_watch_all(hal_context, &error)) {
+#ifdef HAVE_OSS_OUTPUT
+    if (pa_modargs_get_value_boolean(ma, "subdevices", &u->init_subdevs) < 0) {
+        pa_log("Failed to parse subdevices= argument.");
+        goto fail;
+    }
+#endif
+
+    if (!(u->connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) {
+        pa_log_error("Unable to contact DBUS system bus: %s: %s", error.name, error.message);
+        goto fail;
+    }
+
+    if (!(u->context = hal_context_new(pa_dbus_connection_get(u->connection)))) {
+        /* pa_hal_context_new() logs appropriate errors */
+        goto fail;
+    }
+
+    n = hal_device_add_all(u);
+
+    libhal_ctx_set_user_data(u->context, u);
+    libhal_ctx_set_device_added(u->context, device_added_cb);
+    libhal_ctx_set_device_removed(u->context, device_removed_cb);
+    libhal_ctx_set_device_new_capability(u->context, new_capability_cb);
+    libhal_ctx_set_device_lost_capability(u->context, lost_capability_cb);
+
+    if (!libhal_device_property_watch_all(u->context, &error)) {
         pa_log_error("Error monitoring device list: %s: %s", error.name, error.message);
         goto fail;
     }
 
         pa_log_error("Error monitoring device list: %s: %s", error.name, error.message);
         goto fail;
     }
 
-    if (!dbus_connection_add_filter(pa_dbus_connection_get(conn), filter_cb, u, NULL)) {
+    if (!dbus_connection_add_filter(pa_dbus_connection_get(u->connection), filter_cb, u, NULL)) {
         pa_log_error("Failed to add filter function");
         goto fail;
     }
         pa_log_error("Failed to add filter function");
         goto fail;
     }
+    u->filter_added = TRUE;
 
 
-    dbus_bus_add_match(pa_dbus_connection_get(conn), "type='signal',sender='org.freedesktop.Hal', interface='org.freedesktop.Hal.Device.AccessControl'", &error);
-    if (dbus_error_is_set(&error)) {
+    if (pa_dbus_add_matches(
+                pa_dbus_connection_get(u->connection), &error,
+                "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
+                "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
+                "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL) < 0) {
         pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error.name, error.message);
         goto fail;
     }
         pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error.name, error.message);
         goto fail;
     }
-    
+
     pa_log_info("Loaded %i modules.", n);
 
     pa_log_info("Loaded %i modules.", n);
 
+    pa_modargs_free(ma);
+
     return 0;
 
 fail:
     return 0;
 
 fail:
+    if (ma)
+        pa_modargs_free(ma);
+
     dbus_error_free(&error);
     dbus_error_free(&error);
-    pa__done(c, m);
+    pa__done(m);
+
     return -1;
 }
 
     return -1;
 }
 
-
-void pa__done(PA_GCC_UNUSED pa_core *c, pa_module *m) {
+void pa__done(pa_module *m) {
     struct userdata *u;
     struct userdata *u;
-    
-    pa_assert(c);
+
     pa_assert(m);
 
     if (!(u = m->userdata))
     pa_assert(m);
 
     if (!(u = m->userdata))
@@ -694,11 +832,29 @@ void pa__done(PA_GCC_UNUSED pa_core *c, pa_module *m) {
     if (u->context)
         hal_context_free(u->context);
 
     if (u->context)
         hal_context_free(u->context);
 
-    if (u->devices)
-        pa_hashmap_free(u->devices, hal_device_free_cb, NULL);
+    if (u->devices) {
+        struct device *d;
 
 
-    if (u->connection)
+        while ((d = pa_hashmap_first(u->devices))) {
+            pa_hashmap_remove(u->devices, d->udi);
+            pa_hashmap_remove(u->devices, d->originating_udi);
+            device_free(d);
+        }
+
+        pa_hashmap_free(u->devices, NULL, NULL);
+    }
+
+    if (u->connection) {
+        pa_dbus_remove_matches(
+                pa_dbus_connection_get(u->connection),
+                "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLAdded'",
+                "type='signal',sender='org.freedesktop.Hal',interface='org.freedesktop.Hal.Device.AccessControl',member='ACLRemoved'",
+                "type='signal',interface='org.pulseaudio.Server',member='DirtyGiveUpMessage'", NULL);
+
+        if (u->filter_added)
+            dbus_connection_remove_filter(pa_dbus_connection_get(u->connection), filter_cb, u);
         pa_dbus_connection_unref(u->connection);
         pa_dbus_connection_unref(u->connection);
-    
+    }
+
     pa_xfree(u);
 }
     pa_xfree(u);
 }