[PATCH 6/6] alsa: load jack detection module

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch adds support for udev detection of sound card jack event
devices.

The udev methods used to detect ALSA sound cards and sound card input
devices (for jack insertion/removal) are similar and we duplicate the
method in order to detect whether a sound card also has an associated
input event device to report jack status.

If we find a sound card input device, we then load the jack detection
module and pass in details of the newly discovered input device.

Signed-off-by: Margarita Olaya Cabrera <magi at slimlogic.co.uk>
---
 src/modules/module-udev-detect.c |  291 ++++++++++++++++++++++++++++++++++++--
 1 files changed, 279 insertions(+), 12 deletions(-)

diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c
index 63ad195..be7a2dc 100644
--- a/src/modules/module-udev-detect.c
+++ b/src/modules/module-udev-detect.c
@@ -60,6 +60,7 @@ struct device {
 struct userdata {
     pa_core *core;
     pa_hashmap *devices;
+    pa_hashmap *input_devices;

     pa_bool_t use_tsched:1;
     pa_bool_t ignore_dB:1;
@@ -71,6 +72,9 @@ struct userdata {

     int inotify_fd;
     pa_io_event *inotify_io;
+
+    int inotify_input_fd;
+    pa_io_event *inotify_input_io;
 };

 static const char* const valid_modargs[] = {
@@ -81,6 +85,7 @@ static const char* const valid_modargs[] = {
 };

 static int setup_inotify(struct userdata *u);
+static int setup_input_notify(struct userdata *u);

 static void device_free(struct device *d) {
     pa_assert(d);
@@ -163,6 +168,21 @@ static pa_bool_t pcm_is_modem(const char
*card_idx, const char *pcm) {
     return is_modem;
 }

+static const char *path_get_input_id(const char *path) {
+    const char *e;
+
+    if (!path)
+        return NULL;
+
+    if (!(e = strrchr(path, '/')))
+        return NULL;
+
+    if (!pa_startswith(e, "/event"))
+        return NULL;
+
+    return e + 6;
+}
+
 static pa_bool_t is_card_busy(const char *id) {
     char *card_path = NULL, *pcm_path = NULL, *sub_status = NULL;
     DIR *card_dir = NULL, *pcm_dir = NULL;
@@ -352,6 +372,40 @@ static void verify_access(struct userdata *u,
struct device *d) {
     }
 }

+static void verify_input_access(struct userdata *u, struct device *d) {
+    char *cd;
+    char *args;
+    pa_card *card;
+    pa_bool_t accessible;
+
+    pa_assert(u);
+    pa_assert(d);
+
+    cd = pa_sprintf_malloc("%s/input/event%s",
udev_get_dev_path(u->udev), path_get_input_id(d->path));
+    accessible = access(cd, R_OK) >= 0;
+    pa_log_debug("%s is accessible: %s", cd, pa_yes_no(accessible));
+
+    if (d->module == PA_INVALID_INDEX) {
+
+        /* If we are not loaded, try to load */
+        if (accessible) {
+            pa_module *m;
+
+            args = pa_sprintf_malloc("device_id=\"%s\""
+                            "card_name=\"%s\" ", cd, d->card_name);
+
+            pa_log_debug("Loading module-alsa-jack detect with
arguments '%s'", args);
+            m = pa_module_load(u->core, "module-alsa-jack-detect", args);
+            if (m) {
+                  d->module = m->index;
+                  pa_log_info("Card %s (%s) jack module loaded.",
d->path, d->card_name);
+            } else
+                  pa_log_info("Card %s (%s) failed to load jack
module.", d->path, d->card_name);
+        }
+    }
+    pa_xfree(cd);
+}
+
 static void card_changed(struct userdata *u, struct udev_device *dev) {
     struct device *d;
     const char *path;
@@ -364,6 +418,8 @@ static void card_changed(struct userdata *u,
struct udev_device *dev) {
     /* Maybe /dev/snd is now available? */
     setup_inotify(u);

+    setup_input_notify(u);
+
     path = udev_device_get_devpath(dev);

     if ((d = pa_hashmap_get(u->devices, path))) {
@@ -421,6 +477,53 @@ static void remove_card(struct userdata *u,
struct udev_device *dev) {
     device_free(d);
 }

+static void input_changed(struct userdata *u, struct udev_device *dev) {
+    struct device *d;
+    const char *path;
+    const char *t;
+    char *n;
+
+    pa_assert(u);
+    pa_assert(dev);
+
+    /* Maybe /dev/snd is now available? */
+    setup_inotify(u);
+
+    setup_input_notify(u);
+
+    path = udev_device_get_devpath(dev);
+
+    if ((d = pa_hashmap_get(u->input_devices, path))) {
+        verify_input_access(u, d);
+        return;
+    }
+
+    d = pa_xnew0(struct device, 1);
+    d->path = pa_xstrdup(path);
+    d->module = PA_INVALID_INDEX;
+    PA_INIT_RATELIMIT(d->ratelimit, 10*PA_USEC_PER_SEC, 5);
+    pa_hashmap_put(u->input_devices, d->path, d);
+
+    verify_input_access(u, d);
+}
+
+static void remove_input(struct userdata *u, struct udev_device *dev) {
+    struct device *d;
+
+    pa_assert(u);
+    pa_assert(dev);
+
+    if (!(d = pa_hashmap_remove(u->input_devices,
udev_device_get_devpath(dev))))
+        return;
+
+    pa_log_info("Input %s removed.", d->path);
+
+    if (d->module != PA_INVALID_INDEX)
+        pa_module_unload_request_by_index(u->core, d->module, TRUE);
+
+    device_free(d);
+}
+
 static void process_device(struct userdata *u, struct udev_device *dev) {
     const char *action, *ff;

@@ -440,19 +543,31 @@ static void process_device(struct userdata *u,
struct udev_device *dev) {

     action = udev_device_get_action(dev);

-    if (action && pa_streq(action, "remove"))
-        remove_card(u, dev);
-    else if ((!action || pa_streq(action, "change")) &&
udev_device_get_property_value(dev, "SOUND_INITIALIZED"))
-        card_changed(u, dev);
-
-    /* For an explanation why we don't look for 'add' events here
-     * have a look into /lib/udev/rules.d/78-sound-card.rules! */
+    if (path_get_card_id(udev_device_get_devpath(dev))) {
+
+        if (action && pa_streq(action, "remove"))
+            remove_card(u, dev);
+        else if ((!action || pa_streq(action, "change")) &&
+                 udev_device_get_property_value(dev, "SOUND_INITIALIZED"))
+            card_changed(u, dev);
+
+        /* For an explanation why we don't look for 'add' events here
+         * have a look into /lib/udev/rules.d/78-sound-card.rules! */
+
+       } else if (path_get_input_id(udev_device_get_devpath(dev)) &&
+           strstr(udev_device_get_property_value(dev, "DEVPATH"), "sound")) {
+            /* input devices for Jack insertion are handled slightly
differently */
+            if (action && pa_streq(action, "remove"))
+                remove_input(u, dev);
+            else if (!action || pa_streq(action, "change") ||
pa_streq(action, "add"))
+                input_changed(u, dev);
+       }
 }

 static void process_path(struct userdata *u, const char *path) {
     struct udev_device *dev;

-    if (!path_get_card_id(path))
+    if (!path_get_card_id(path) && !path_get_input_id(path))
         return;

     if (!(dev = udev_device_new_from_syspath(u->udev, path))) {
@@ -481,13 +596,12 @@ static void monitor_cb(
         goto fail;
     }

-    if (!path_get_card_id(udev_device_get_devpath(dev))) {
+    if (path_get_card_id(udev_device_get_devpath(dev)) ||
+       path_get_input_id(udev_device_get_devpath(dev))) {
+        process_device(u, dev);
         udev_device_unref(dev);
-        return;
     }

-    process_device(u, dev);
-    udev_device_unref(dev);
     return;

 fail:
@@ -523,6 +637,21 @@ static pa_bool_t control_node_belongs_to_device(
     return b;
 }

+static pa_bool_t input_node_belongs_to_device(
+        struct device *d,
+        const char *node) {
+
+    char *cd;
+    pa_bool_t b;
+
+    cd = pa_sprintf_malloc("event%s", path_get_input_id(d->path));
+
+    b = pa_streq(node, cd);
+    pa_xfree(cd);
+
+    return b;
+}
+
 static void inotify_cb(
         pa_mainloop_api*a,
         pa_io_event* e,
@@ -615,6 +744,82 @@ fail:
     }
 }

+static void input_notify_cb( pa_mainloop_api*a, pa_io_event* e, int fd,
+    pa_io_event_flags_t events, void *userdata) {
+
+    struct {
+        struct inotify_event e;
+        char name[NAME_MAX];
+    } buf;
+    struct userdata *u = userdata;
+    static int type = 0;
+    pa_bool_t deleted = FALSE;
+    struct device *d;
+    void *state;
+
+    for (;;) {
+        ssize_t r;
+        struct inotify_event *event;
+
+        pa_zero(buf);
+        if ((r = pa_read(fd, &buf, sizeof(buf), &type)) <= 0) {
+            if (r < 0 && errno == EAGAIN)
+                break;
+            pa_log("read() from inotify failed: %s", r < 0 ?
pa_cstrerror(errno) : "EOF");
+            goto fail;
+        }
+
+        event = &buf.e;
+        while (r > 0) {
+            size_t len;
+            if ((size_t) r < sizeof(struct inotify_event)) {
+                pa_log("read() too short.");
+                goto fail;
+            }
+
+            len = sizeof(struct inotify_event) + event->len;
+            if ((size_t) r < len) {
+                pa_log("Payload missing.");
+                goto fail;
+            }
+
+            /* From udev we get the guarantee that the control
+             * device's ACL is changed last. To avoid races when ACLs
+             * are changed we hence watch only the control device */
+            if (((event->mask & IN_ATTRIB) &&
pa_startswith(event->name, "event")))
+                PA_HASHMAP_FOREACH(d, u->input_devices, state)
+                    if (input_node_belongs_to_device(d, event->name))
+                        d->need_verify = TRUE;
+
+            /* /dev/input/ might have been removed */
+            if ((event->mask & (IN_DELETE_SELF|IN_MOVE_SELF)))
+                deleted = TRUE;
+            event = (struct inotify_event*) ((uint8_t*) event + len);
+            r -= len;
+        }
+    }
+
+    PA_HASHMAP_FOREACH(d, u->input_devices, state)
+        if (d->need_verify) {
+            d->need_verify = FALSE;
+            verify_input_access(u, d);
+        }
+
+    if (!deleted)
+        return;
+
+fail:
+    if (u->inotify_input_io) {
+        a->io_free(u->inotify_input_io);
+        u->inotify_input_io = NULL;
+    }
+
+    if (u->inotify_input_fd >= 0) {
+        pa_close(u->inotify_input_fd);
+        u->inotify_input_fd = -1;
+    }
+}
+
 static int setup_inotify(struct userdata *u) {
     char *dev_snd;
     int r;
@@ -659,6 +864,43 @@ static int setup_inotify(struct userdata *u) {
     return 0;
 }

+static int setup_input_notify(struct userdata *u) {
+    char *dev_input;
+    int r;
+    if (u->inotify_input_fd >= 0)
+        return 0;
+
+    if ((u->inotify_input_fd = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) {
+        pa_log("inotify_init1() failed: %s", pa_cstrerror(errno));
+        return -1;
+    }
+
+    dev_input = pa_sprintf_malloc("%s/input", udev_get_dev_path(u->udev));
+    r = inotify_add_watch(u->inotify_input_fd, dev_input,
IN_ATTRIB|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF);
+    pa_xfree(dev_input);
+    if (r < 0) {
+        int saved_errno = errno;
+        pa_close(u->inotify_input_fd);
+        u->inotify_input_fd = -1;
+
+        if (saved_errno == ENOENT) {
+            pa_log_debug("/dev/input/ is apparently not existing yet,
retrying to create inotify watch later.");
+            return 0;
+        }
+
+        if (saved_errno == ENOSPC) {
+            pa_log("You apparently ran out of inotify watches,
probably because Tracker/Beagle took them all away.");
+        return 0;
+        }
+
+        pa_log("inotify_add_watch() failed: %s", pa_cstrerror(saved_errno));
+        return -1;
+    }
+    pa_assert_se(u->inotify_input_io =
u->core->mainloop->io_new(u->core->mainloop, u->inotify_input_fd,
PA_IO_EVENT_INPUT, input_notify_cb, u));
+
+    return 0;
+}
+
 int pa__init(pa_module *m) {
     struct userdata *u = NULL;
     pa_modargs *ma;
@@ -678,7 +920,9 @@ int pa__init(pa_module *m) {
     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->input_devices = pa_hashmap_new(pa_idxset_string_hash_func,
pa_idxset_string_compare_func);
     u->inotify_fd = -1;
+    u->inotify_input_fd = -1;

     if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) {
         pa_log("Failed to parse tsched= argument.");
@@ -706,6 +950,9 @@ int pa__init(pa_module *m) {
     if (setup_inotify(u) < 0)
         goto fail;

+    if (setup_input_notify(u) < 0)
+        goto fail;
+
     if (!(u->monitor = udev_monitor_new_from_netlink(u->udev, "udev"))) {
         pa_log("Failed to initialize monitor.");
         goto fail;
@@ -743,6 +990,11 @@ int pa__init(pa_module *m) {
         goto fail;
     }

+     if (udev_enumerate_add_match_subsystem(enumerate, "input") < 0) {
+        pa_log("Failed to match to subsystem.");
+        goto fail;
+    }
+
     if (udev_enumerate_scan_devices(enumerate) < 0) {
         pa_log("Failed to scan for devices.");
         goto fail;
@@ -796,6 +1048,12 @@ void pa__done(pa_module *m) {
     if (u->inotify_fd >= 0)
         pa_close(u->inotify_fd);

+    if (u->inotify_input_io)
+        m->core->mainloop->io_free(u->inotify_input_io);
+
+    if (u->inotify_input_fd >= 0)
+        pa_close(u->inotify_input_fd);
+
     if (u->devices) {
         struct device *d;

@@ -805,5 +1063,14 @@ void pa__done(pa_module *m) {
         pa_hashmap_free(u->devices, NULL, NULL);
     }

+   if (u->input_devices) {
+        struct device *d;
+
+        while ((d = pa_hashmap_steal_first(u->input_devices)))
+            device_free(d);
+
+        pa_hashmap_free(u->input_devices, NULL, NULL);
+    }
+
     pa_xfree(u);
 }
-- 
1.7.1



[Index of Archives]     [Linux Audio Users]     [AMD Graphics]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux