From fae4d2595504fd7287b5613747bdb67d69f6f8c9 Mon Sep 17 00:00:00 2001 From: Jonathan Aillet Date: Mon, 19 Aug 2019 11:41:24 +0200 Subject: Add events when ALSA card are added/removed Add 'soundcard-added'/'soundcard-removed' application framework events to be notified when ALSA card are added/removed. Use subscribe verb and json '{ "devid" : "added" }' to subscribe to added card events. Use subscribe verb and json '{ "devid" : "removed" }' to subscribe to removed card events. Bug-AGL: SPEC-2749 Change-Id: Ia021d7f6984b2263a5011121a69f17bb4ee2bf9e Signed-off-by: Jonathan Aillet --- alsa-binding/Alsa-RegEvt.c | 273 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 271 insertions(+), 2 deletions(-) diff --git a/alsa-binding/Alsa-RegEvt.c b/alsa-binding/Alsa-RegEvt.c index c9ac5f1..28b66b1 100644 --- a/alsa-binding/Alsa-RegEvt.c +++ b/alsa-binding/Alsa-RegEvt.c @@ -18,14 +18,35 @@ #define _GNU_SOURCE // needed for vasprintf +#include + +#include + #include "Alsa-ApiHat.h" +// Defines for inotify events +#define INOTIFY_EVENT_SIZE (sizeof(struct inotify_event)) +#define INOTIFY_EVENT_BUF_LEN (1024 * (INOTIFY_EVENT_SIZE + 16)) + +#define DIR_TO_WATCH "/dev/snd/by-path" + #ifndef MAX_SND_HAL #define MAX_SND_HAL 10 #endif -// generic sndctrl event handle hook to event callback when pooling +typedef enum { + SUBSCRIPTION_CARD_ADDED_EVENTS = 0, + SUBSCRIPTION_CARD_REMOVED_EVENTS = 1 +} SubscriptionCardEventTypeT; + +typedef struct { + int fd; + int wd; + sd_event_source *src; + afb_event_t afbevt; +} sndCardEvtHandleT; +// generic sndctrl event handle hook to event callback when pooling typedef struct { struct pollfd pfds; sd_event_source *src; @@ -35,13 +56,22 @@ typedef struct { } ctrlEvtHandleT; typedef struct { + int available; ctrlEvtHandleT *evtHandle; } sndHandleT; typedef struct { sndHandleT sndHandles[MAX_SND_CARD]; + sndCardEvtHandleT *cardAdded; + sndCardEvtHandleT *cardRemoved; + afb_api_t apiHandle; } sndCardsT; +typedef struct { + char *createdFileName; + sndCardsT *sndCards; +} sndCardCardFileEventHandleT; + typedef struct { int cardid; char *devid; @@ -88,6 +118,144 @@ OnErrorExit: return NULL; } +STATIC void updateSelectedAlsaCardsAvailabilityAndFireEvents(sndCardsT *sndCards, int first, int last) { + int idx, available; + char *cardId; + + if (first < 0 || first >= MAX_SND_CARD) + return; + + if (last < 0 || last >= MAX_SND_CARD) + return; + + if (last < first) + return; + + for (idx = first; idx < (last + 1); idx++) { + available = snd_card_load(idx); + if (available && ! sndCards->sndHandles[idx].available) { + sndCards->sndHandles[idx].available = 1; + + if (sndCards->cardAdded) + afb_event_push(sndCards->cardAdded->afbevt, getCardInfo(idx)); + } + else if (! available && sndCards->sndHandles[idx].available) { + sndCards->sndHandles[idx].available = 0; + + if (sndCards->cardRemoved && (asprintf(&cardId, "hw:%i", idx) > 0)) + afb_event_push(sndCards->cardRemoved->afbevt, json_object_new_string(cardId)); + + if (sndCards->sndHandles[idx].evtHandle) { + sd_event_source_unref(sndCards->sndHandles[idx].evtHandle->src); + afb_event_unref(sndCards->sndHandles[idx].evtHandle->afbevt); + snd_ctl_close(sndCards->sndHandles[idx].evtHandle->ctlDev); + free(sndCards->sndHandles[idx].evtHandle); + sndCards->sndHandles[idx].evtHandle = NULL; + } + } + } +} + +STATIC void updateAllAlsaCardsAvailabilityAndFireEvents(sndCardsT *sndCards) { + updateSelectedAlsaCardsAvailabilityAndFireEvents(sndCards, 0, (MAX_SND_CARD - 1)); +} + +// This routine is called every few microseconds to verify when the newly created sound card file will be openable (using timer event firing) +STATIC int sndCardTimerForCreatedFileCB(TimerHandleT *context) { + int createdFileCardNb; + + sndCardCardFileEventHandleT *createdFileHandle = (sndCardCardFileEventHandleT *) context->context; + + createdFileCardNb = getCardNbFromCardPath(createdFileHandle->createdFileName); + if(createdFileCardNb < 0) { + AFB_ERROR("Error %i happened when tried to get created card name file '%s' card nb", + createdFileCardNb, + createdFileHandle->createdFileName); + return -1; + } + + updateSelectedAlsaCardsAvailabilityAndFireEvents(createdFileHandle->sndCards, createdFileCardNb, createdFileCardNb); + + free(createdFileHandle->createdFileName); + free(createdFileHandle); + + context->count = 1; + + return 1; +} + +// This routine is called when sound cards are added/removed (when inotify '/dev/snd/by-path' events are fired) +STATIC int sndCardEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData) { + ssize_t length, i = 0; + char buffer[INOTIFY_EVENT_BUF_LEN]; + char *createdFile; + + struct inotify_event *event; + + sndCardCardFileEventHandleT *createdFileHandle; + TimerHandleT *createdFileTimerHandle; + + sndCardsT *sndCards = (sndCardsT *) userData; + + if (revents & EPOLLERR) { + AFB_ERROR("An error has been send by event loop polling, prevent new errors to be fired by deleting event src"); + + sd_event_source_unref(src); + + afb_event_unref(sndCards->cardAdded->afbevt); + afb_event_unref(sndCards->cardRemoved->afbevt); + + inotify_rm_watch(sndCards->cardAdded->fd, sndCards->cardAdded->wd); + close(sndCards->cardAdded->fd); + + free(sndCards->cardAdded); + free(sndCards->cardRemoved); + + sndCards->cardAdded = NULL; + sndCards->cardRemoved = NULL; + + goto OnErrorExit; + } + + if (revents & EPOLLIN) { + length = read(fd, buffer, INOTIFY_EVENT_BUF_LEN); + if (length < 0) { + AFB_ERROR("Nothing read using inotify"); + goto OnErrorExit; + } + + while (i < length) { + event = (struct inotify_event *) &buffer[i]; + + if(event->len && + (event->mask & IN_CREATE) && + ! (event->mask & IN_ISDIR) && + (asprintf(&createdFile, "%s/%s", DIR_TO_WATCH, event->name) > 0)) { + createdFileHandle = calloc(1, sizeof(sndCardCardFileEventHandleT)); + createdFileHandle->createdFileName = strdup(createdFile); + createdFileHandle->sndCards = sndCards; + + createdFileTimerHandle = calloc(1, sizeof(TimerHandleT)); + + createdFileTimerHandle->uid = "sound card file creation delay"; + createdFileTimerHandle->count = 10; + createdFileTimerHandle->delay = 2; + + TimerEvtStart(sndCards->apiHandle, createdFileTimerHandle, sndCardTimerForCreatedFileCB, (void *) createdFileHandle); + } + + if(event->len && (event->mask & IN_DELETE) && ! (event->mask & IN_ISDIR)) + updateAllAlsaCardsAvailabilityAndFireEvents(sndCards); + + i += INOTIFY_EVENT_SIZE + event->len; + } + } + + return 0; + +OnErrorExit: + return -1; +} // This routine is called when ALSA control events are fired STATIC int sndCtlEventCB(sd_event_source* src, int fd, uint32_t revents, void* userData) { @@ -173,6 +341,101 @@ OnErrorExit: return -1; } +// Subscribe to each time a sound card is added/removed (using inotify '/dev/snd/by-path' events) +STATIC afb_event_t alsaEvtSubscribeSoundCardEvent(afb_req_t request, sndCardsT *sndCards, SubscriptionCardEventTypeT subscriptionType) { + int fd = -1, wd = -1, err; + + // Update card list + updateAllAlsaCardsAvailabilityAndFireEvents(sndCards); + + if (! sndCards->cardAdded && ! sndCards->cardRemoved) { + fd = inotify_init(); + if (fd < 0) { + afb_req_fail_f(request, "inotify-init", "Error %s happened while getting file descriptor for inotify", strerror(errno)); + goto OnErrorExit; + } + + wd = inotify_add_watch(fd, DIR_TO_WATCH, IN_CREATE | IN_DELETE); + if (wd < 0) { + afb_req_fail_f(request, + "inotify-watch", + "Error %s happened while setting watchdog on '%s' directory using inotify", + strerror(errno), + DIR_TO_WATCH); + goto OnErrorExit; + } + + sndCards->cardAdded = malloc(sizeof (sndCardEvtHandleT)); + if (! sndCards->cardAdded) { + afb_req_fail(request, "card-added-event", "Error while allocating card added event handle"); + goto OnErrorExit; + } + + sndCards->cardRemoved = malloc(sizeof (sndCardEvtHandleT)); + if (! sndCards->cardRemoved) { + afb_req_fail(request, "card-removed-event", "Error while allocating card removed event handle"); + goto OnErrorExit; + } + + sndCards->cardAdded->fd = fd; + sndCards->cardRemoved->fd = fd; + + sndCards->cardAdded->wd = wd; + sndCards->cardRemoved->wd = wd; + + sndCards->cardAdded->afbevt = afb_daemon_make_event("soundcard-added"); + if (!afb_event_is_valid(sndCards->cardAdded->afbevt)) { + afb_req_fail_f(request, "register-event", "Cannot register new binder event name=%s", "soundcard-added"); + goto OnErrorExit; + } + + sndCards->cardRemoved->afbevt = afb_daemon_make_event("soundcard-removed"); + if (!afb_event_is_valid(sndCards->cardRemoved->afbevt)) { + afb_req_fail_f(request, "register-event", "Cannot register new binder event name=%s", "soundcard-added"); + goto OnErrorExit; + } + + // register sound event to binder main loop + err = sd_event_add_io(afb_daemon_get_event_loop(), + &sndCards->cardAdded->src, + fd, + EPOLLIN, + sndCardEventCB, + sndCards); + if (err < 0) { + afb_req_fail_f(request, "register-mainloop", "Cannot hook sound card events to mainloop err=%d", err); + goto OnErrorExit; + } + + sndCards->cardRemoved->src = sndCards->cardAdded->src; + } + + if(subscriptionType == SUBSCRIPTION_CARD_ADDED_EVENTS) + return sndCards->cardAdded->afbevt; + + if(subscriptionType == SUBSCRIPTION_CARD_REMOVED_EVENTS) + return sndCards->cardRemoved->afbevt; + + return NULL; + +OnErrorExit: + if(afb_event_is_valid(sndCards->cardRemoved->afbevt)) + afb_event_unref(sndCards->cardRemoved->afbevt); + + if(afb_event_is_valid(sndCards->cardAdded->afbevt)) + afb_event_unref(sndCards->cardAdded->afbevt); + + free(sndCards->cardRemoved); + free(sndCards->cardAdded); + + if (wd != -1) + inotify_rm_watch(fd, wd); + + if (fd != -1) + close(fd); + + return NULL; +} // Subscribe to every Alsa CtlEvent send by a given board STATIC afb_event_t alsaEvtSubscribeAlsaControlEvent(afb_req_t request, sndCardsT *sndCards, queryValuesT *queryValues) { @@ -263,12 +526,18 @@ PUBLIC void alsaEvtSubcribe(afb_req_t request) { if(! sndCards) { sndCards = calloc(1, sizeof(sndCardsT)); + sndCards->apiHandle = afb_req_get_api(request); } queryJ = alsaCheckQuery(request, &queryValues); if (!queryJ) return; - eventToSubscribe = alsaEvtSubscribeAlsaControlEvent(request, sndCards, &queryValues); + if(! strcasecmp(queryValues.devid, "added")) + eventToSubscribe = alsaEvtSubscribeSoundCardEvent(request, sndCards, SUBSCRIPTION_CARD_ADDED_EVENTS); + else if(! strcasecmp(queryValues.devid, "removed")) + eventToSubscribe = alsaEvtSubscribeSoundCardEvent(request, sndCards, SUBSCRIPTION_CARD_REMOVED_EVENTS); + else + eventToSubscribe = alsaEvtSubscribeAlsaControlEvent(request, sndCards, &queryValues); if(! eventToSubscribe) return; -- cgit 1.2.3-korg