summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonathan Aillet <jonathan.aillet@iot.bzh>2019-08-19 11:41:24 +0200
committerJonathan Aillet <jonathan.aillet@iot.bzh>2019-08-19 11:49:04 +0200
commitfae4d2595504fd7287b5613747bdb67d69f6f8c9 (patch)
tree16b3ca17d2835fa9043b92d6c3dd24891310a522
parentb30eb2b2d8f83f29874e2d63dd752e9cf296da37 (diff)
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 <jonathan.aillet@iot.bzh>
-rw-r--r--alsa-binding/Alsa-RegEvt.c273
1 files 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 <sys/inotify.h>
+
+#include <afb-timer.h>
+
#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,14 +56,23 @@ 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;
char *apiprefix;
@@ -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;