audio: sndmonitor
Monitor sound card status and report online/offline
events back to the streams registered with it and to audio_dev.
Bug: 30075678
CRs-Fixed: 1053073
Change-Id: Iea74d5c83f7fe92ba4a24f4f4650ce255bdb48e8
diff --git a/hal/Android.mk b/hal/Android.mk
index 7f42432..705e5e8 100644
--- a/hal/Android.mk
+++ b/hal/Android.mk
@@ -315,6 +315,11 @@
LOCAL_COPY_HEADERS_TO := mm-audio
LOCAL_COPY_HEADERS := audio_extn/audio_defs.h
+ifeq ($(strip $(AUDIO_FEATURE_ENABLED_SND_MONITOR)), true)
+ LOCAL_CFLAGS += -DSND_MONITOR_ENABLED
+ LOCAL_SRC_FILES += audio_extn/sndmonitor.c
+endif
+
LOCAL_MODULE := audio.primary.$(TARGET_BOARD_PLATFORM)
LOCAL_MODULE_RELATIVE_PATH := hw
diff --git a/hal/audio_extn/audio_extn.h b/hal/audio_extn/audio_extn.h
index 12a8082..0fd7d3a 100644
--- a/hal/audio_extn/audio_extn.h
+++ b/hal/audio_extn/audio_extn.h
@@ -588,4 +588,17 @@
#endif
+typedef void (* snd_mon_cb)(void * stream, struct str_parms * parms);
+#ifndef SND_MONITOR_ENABLED
+#define audio_extn_snd_mon_init() (0)
+#define audio_extn_snd_mon_deinit() (0)
+#define audio_extn_snd_mon_register_listener(stream, cb) (0)
+#define audio_extn_snd_mon_unregister_listener(stream) (0)
+#else
+int audio_extn_snd_mon_init();
+int audio_extn_snd_mon_deinit();
+int audio_extn_snd_mon_register_listener(void *stream, snd_mon_cb cb);
+int audio_extn_snd_mon_unregister_listener(void *stream);
+#endif
+
#endif /* AUDIO_EXTN_H */
diff --git a/hal/audio_extn/sndmonitor.c b/hal/audio_extn/sndmonitor.c
new file mode 100644
index 0000000..eecc448
--- /dev/null
+++ b/hal/audio_extn/sndmonitor.c
@@ -0,0 +1,684 @@
+/*
+* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are
+* met:
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above
+* copyright notice, this list of conditions and the following
+* disclaimer in the documentation and/or other materials provided
+* with the distribution.
+* * Neither the name of The Linux Foundation nor the names of its
+* contributors may be used to endorse or promote products derived
+* from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#define LOG_TAG "audio_hw_sndmonitor"
+/*#define LOG_NDEBUG 0*/
+#define LOG_NDDEBUG 0
+
+/* monitor sound card, cpe state
+
+ audio_dev registers for a callback from this module in adev_open
+ Each stream in audio_hal registers for a callback in
+ adev_open_*_stream.
+
+ A thread is spawned to poll() on sound card state files in /proc.
+ On observing a sound card state change, this thread invokes the
+ callbacks registered.
+
+ Callbacks are deregistered in adev_close_*_stream and adev_close
+*/
+#include <stdlib.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/poll.h>
+#include <cutils/list.h>
+#include <cutils/hashmap.h>
+#include <cutils/log.h>
+#include <cutils/str_parms.h>
+#include <ctype.h>
+
+#include "audio_hw.h"
+#include "audio_extn.h"
+
+//#define MONITOR_DEVICE_EVENTS
+#define CPE_MAGIC_NUM 0x2000
+#define MAX_CPE_SLEEP_RETRY 2
+#define CPE_SLEEP_WAIT 100
+
+#define MAX_SLEEP_RETRY 100
+#define AUDIO_INIT_SLEEP_WAIT 100 /* 100 ms */
+
+#define AUDIO_PARAMETER_KEY_EXT_AUDIO_DEVICE "ext_audio_device"
+#define INIT_MAP_SIZE 5
+
+typedef enum {
+ audio_event_on,
+ audio_event_off
+} audio_event_status;
+
+typedef struct {
+ int card;
+ int fd;
+ struct listnode node; // membership in sndcards list
+ card_status_t status;
+} sndcard_t;
+
+typedef struct {
+ char *dev;
+ int fd;
+ int status;
+ struct listnode node; // membership in deviceevents list;
+} dev_event_t;
+
+typedef void (*notifyfn)(const void *target, const char *msg);
+
+typedef struct {
+ const void *target;
+ notifyfn notify;
+ struct listnode cards;
+ unsigned int num_cards;
+ struct listnode dev_events;
+ unsigned int num_dev_events;
+ pthread_t monitor_thread;
+ int intpipe[2];
+ Hashmap *listeners; // from stream * -> callback func
+ bool initcheck;
+} sndmonitor_state_t;
+
+static sndmonitor_state_t sndmonitor;
+
+static char *read_state(int fd)
+{
+ struct stat buf;
+ if (fstat(fd, &buf) < 0)
+ return NULL;
+
+ off_t pos = lseek(fd, 0, SEEK_CUR);
+ off_t avail = buf.st_size - pos;
+ if (avail <= 0) {
+ ALOGE("avail %ld", avail);
+ return NULL;
+ }
+
+ char *state = (char *)calloc(avail+1, sizeof(char));
+ if (!state)
+ return NULL;
+
+ ssize_t bytes = read(fd, state, avail);
+ if (bytes <= 0)
+ return NULL;
+
+ // trim trailing whitespace
+ while (bytes && isspace(*(state+bytes-1))) {
+ *(state + bytes - 1) = '\0';
+ --bytes;
+ }
+ lseek(fd, 0, SEEK_SET);
+ return state;
+}
+
+static int add_new_sndcard(int card, int fd)
+{
+ sndcard_t *s = (sndcard_t *)calloc(sizeof(sndcard_t), 1);
+
+ if (!s)
+ return -1;
+
+ s->card = card;
+ s->fd = fd; // dup?
+
+ char *state = read_state(fd);
+
+ if (!state)
+ return -1;
+
+ bool online = state && !strcmp(state, "ONLINE");
+
+ ALOGV("card %d initial state %s %d", card, state, online);
+
+ if (state)
+ free(state);
+
+ s->status = online ? CARD_STATUS_ONLINE : CARD_STATUS_OFFLINE;
+ list_add_tail(&sndmonitor.cards, &s->node);
+ return 0;
+}
+
+static int validate_snd_card(const char *id)
+{
+ return !strncasecmp(id, "msm", 3) ? 0 : -1;
+}
+
+static int enum_sndcards()
+{
+ const char *cards = "/proc/asound/cards";
+ int tries = 10;
+ char *line = NULL;
+ size_t len = 0;
+ ssize_t bytes_read = -1;
+ char path[128] = {0};
+ char *ptr = NULL, *saveptr = NULL, *card_id = NULL;
+ int line_no=0;
+ unsigned int num_cards=0, num_cpe=0;
+ FILE *fp = NULL;
+ int fd = -1, ret = -1;
+
+ while (--tries) {
+ if ((fp = fopen(cards, "r")) == NULL) {
+ ALOGE("Cannot open %s file to get list of sound cards", cards);
+ usleep(100000);
+ continue;
+ }
+ break;
+ }
+
+ if (!tries)
+ return -ENODEV;
+
+ while ((bytes_read = getline(&line, &len, fp) != -1)) {
+ // skip every other line to to match
+ // the output format of /proc/asound/cards
+ if (line_no++ % 2)
+ continue;
+
+ ptr = strtok_r(line, " [", &saveptr);
+ if (!ptr)
+ continue;
+
+ card_id = strtok_r(saveptr+1, "]", &saveptr);
+ if (!card_id)
+ continue;
+
+ // Only consider sound cards associated with ADSP
+ if (validate_snd_card((const char *)card_id) < 0) {
+ ALOGW("Skip over non-ADSP snd card %s", card_id);
+ continue;
+ }
+
+ snprintf(path, sizeof(path), "/proc/asound/card%s/state", ptr);
+ ALOGV("Opening sound card state : %s", path);
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ ALOGE("Open %s failed : %s", path, strerror(errno));
+ continue;
+ }
+
+ ret = add_new_sndcard(atoi(ptr), fd);
+ if (ret != 0)
+ continue;
+
+ num_cards++;
+
+ // query cpe state for this card as well
+ tries = MAX_CPE_SLEEP_RETRY;
+ snprintf(path, sizeof(path), "/proc/asound/card%s/cpe0_state", ptr);
+
+ if (access(path, R_OK) < 0) {
+ ALOGW("access %s failed w/ err %s", path, strerror(errno));
+ continue;
+ }
+
+ ALOGV("Open cpe state card state %s", path);
+ while (--tries) {
+ if ((fd = open(path, O_RDONLY)) < 0) {
+ ALOGW("Open cpe state card state failed, retry : %s", path);
+ usleep(CPE_SLEEP_WAIT*1000);
+ continue;
+ }
+ break;
+ }
+
+ if (!tries)
+ continue;
+
+ ret = add_new_sndcard(CPE_MAGIC_NUM+num_cpe, fd);
+ if (ret != 0)
+ continue;
+
+ num_cpe++;
+ num_cards++;
+ }
+ if (line)
+ free(line);
+ fclose(fp);
+ ALOGV("sndmonitor registerer num_cards %d", num_cards);
+ sndmonitor.num_cards = num_cards;
+ return num_cards ? 0 : -1;
+}
+
+static void free_sndcards()
+{
+ while (!list_empty(&sndmonitor.cards)) {
+ struct listnode *n = list_head(&sndmonitor.cards);
+ sndcard_t *s = node_to_item(n, sndcard_t, node);
+ list_remove(n);
+ close(s->fd);
+ free(s);
+ }
+}
+
+#ifdef MONITOR_DEVICE_EVENTS
+static int add_new_dev_event(char *d_name, int fd)
+{
+ dev_event_t *d = (dev_event_t *)calloc(sizeof(dev_event_t), 1);
+
+ if (!d)
+ return -1;
+
+ d->dev = strdup(d_name);
+ d->fd = fd;
+ list_add_tail(&sndmonitor.dev_events, &d->node);
+ return 0;
+}
+
+static int enum_dev_events()
+{
+ const char *events_dir = "/sys/class/switch/";
+ DIR *dp;
+ struct dirent *in_file;
+ int fd;
+ char path[128] = {0};
+ unsigned int num_dev_events = 0;
+
+ if ((dp = opendir(events_dir)) == NULL) {
+ ALOGE("Cannot open switch directory %s err %s",
+ events_dir, strerror(errno));
+ return -1;
+ }
+
+ while ((in_file = readdir(dp)) != NULL) {
+ if (!strstr(in_file->d_name, "qc_"))
+ continue;
+
+ snprintf(path, sizeof(path), "%s/%s/state",
+ events_dir, in_file->d_name);
+
+ ALOGV("Opening audio dev event state : %s ", path);
+ fd = open(path, O_RDONLY);
+ if (fd == -1) {
+ ALOGE("Open %s failed : %s", path, strerror(errno));
+ } else {
+ if (!add_new_dev_event(in_file->d_name, fd))
+ num_dev_events++;
+ }
+ }
+ closedir(dp);
+ sndmonitor.num_dev_events = num_dev_events;
+ return num_dev_events ? 0 : -1;
+}
+#endif
+
+static void free_dev_events()
+{
+ while (!list_empty(&sndmonitor.dev_events)) {
+ struct listnode *n = list_head(&sndmonitor.dev_events);
+ dev_event_t *d = node_to_item(n, dev_event_t, node);
+ list_remove(n);
+ close(d->fd);
+ free(d->dev);
+ free(d);
+ }
+}
+
+static int notify(const struct str_parms *params)
+{
+ if (!params)
+ return -1;
+
+ char *str = str_parms_to_str((struct str_parms *)params);
+
+ if (!str)
+ return -1;
+
+ if (sndmonitor.notify)
+ sndmonitor.notify(sndmonitor.target, str);
+
+ ALOGV("%s", str);
+ free(str);
+ return 0;
+}
+
+int on_dev_event(dev_event_t *dev_event)
+{
+ char state_buf[2];
+ if (read(dev_event->fd, state_buf, 1) <= 0)
+ return -1;
+
+ lseek(dev_event->fd, 0, SEEK_SET);
+ state_buf[1]='\0';
+ if (atoi(state_buf) == dev_event->status)
+ return 0;
+
+ dev_event->status = atoi(state_buf);
+
+ struct str_parms *params = str_parms_create();
+
+ if (!params)
+ return -1;
+
+ char val[32] = {0};
+ snprintf(val, sizeof(val), "%s,%s", dev_event->dev,
+ dev_event->status ? "ON" : "OFF");
+
+ if (str_parms_add_str(params, AUDIO_PARAMETER_KEY_EXT_AUDIO_DEVICE, val) < 0)
+ return -1;
+
+ int ret = notify(params);
+ str_parms_destroy(params);
+ return ret;
+}
+
+bool on_sndcard_state_update(sndcard_t *s)
+{
+ char rd_buf[9]={0};
+ card_status_t status;
+
+ if (read(s->fd, rd_buf, 8) <= 0)
+ return -1;
+
+ rd_buf[8] = '\0';
+ lseek(s->fd, 0, SEEK_SET);
+
+ ALOGV("card num %d, new state %s", s->card, rd_buf);
+
+ bool is_cpe = (s->card >= CPE_MAGIC_NUM);
+ if (strstr(rd_buf, "OFFLINE"))
+ status = CARD_STATUS_OFFLINE;
+ else if (strstr(rd_buf, "ONLINE"))
+ status = CARD_STATUS_ONLINE;
+ else {
+ ALOGE("unknown state");
+ return 0;
+ }
+
+ if (status == s->status) // no change
+ return 0;
+
+ s->status = status;
+
+ struct str_parms *params = str_parms_create();
+
+ if (!params)
+ return -1;
+
+ char val[32] = {0};
+ // cpe actual card num is (card - MAGIC_NUM). so subtract accordingly
+ snprintf(val, sizeof(val), "%d,%s", s->card - (is_cpe ? CPE_MAGIC_NUM : 0),
+ status == CARD_STATUS_ONLINE ? "ONLINE" : "OFFLINE");
+
+ if (str_parms_add_str(params, is_cpe ? "CPE_STATUS" : "SND_CARD_STATUS",
+ val) < 0)
+ return -1;
+
+ int ret = notify(params);
+ str_parms_destroy(params);
+ return ret;
+}
+
+void *monitor_thread_loop(void *args __unused)
+{
+ ALOGV("Start threadLoop()");
+ unsigned int num_poll_fds = sndmonitor.num_cards +
+ sndmonitor.num_dev_events + 1/*pipe*/;
+ struct pollfd *pfd = (struct pollfd *)calloc(sizeof(struct pollfd),
+ num_poll_fds);
+ if (!pfd)
+ return NULL;
+
+ pfd[0].fd = sndmonitor.intpipe[0];
+ pfd[0].events = POLLPRI|POLLIN;
+
+ int i = 1;
+ struct listnode *node;
+ list_for_each(node, &sndmonitor.cards) {
+ sndcard_t *s = node_to_item(node, sndcard_t, node);
+ pfd[i].fd = s->fd;
+ pfd[i].events = POLLPRI;
+ ++i;
+ }
+
+ list_for_each(node, &sndmonitor.dev_events) {
+ dev_event_t *d = node_to_item(node, dev_event_t, node);
+ pfd[i].fd = d->fd;
+ pfd[i].events = POLLPRI;
+ ++i;
+ }
+
+ while (1) {
+ if (poll(pfd, num_poll_fds, -1) < 0) {
+ int errno_ = errno;
+ ALOGE("poll() failed w/ err %s", strerror(errno_));
+ switch (errno_) {
+ case EINTR:
+ case ENOMEM:
+ sleep(2);
+ continue;
+ default:
+ /* above errors can be caused due to current system
+ state .. any other error is not expected */
+ LOG_ALWAYS_FATAL("unxpected poll() system call failure");
+ break;
+ }
+ }
+ ALOGV("out of poll()");
+
+#define READY_TO_READ(p) ((p)->revents & (POLLIN|POLLPRI))
+#define ERROR_IN_FD(p) ((p)->revents & (POLLERR|POLLHUP|POLLNVAL))
+
+ // check if requested to exit
+ if (READY_TO_READ(&pfd[0])) {
+ char buf[2]={0};
+ read(pfd[0].fd, buf, 1);
+ if (!strcmp(buf, "Q"))
+ break;
+ } else if (ERROR_IN_FD(&pfd[0])) {
+ // do not consider for poll again
+ // POLLERR - can this happen?
+ // POLLHUP - adev must not close pipe
+ // POLLNVAL - fd is valid
+ LOG_ALWAYS_FATAL("unxpected error in pipe poll fd 0x%x",
+ pfd[0].revents);
+ // FIXME: If not fatal, then need some logic to close
+ // these fds on error
+ pfd[0].fd *= -1;
+ }
+
+ i = 1;
+ list_for_each(node, &sndmonitor.cards) {
+ sndcard_t *s = node_to_item(node, sndcard_t, node);
+ if (READY_TO_READ(&pfd[i]))
+ on_sndcard_state_update(s);
+ else if (ERROR_IN_FD(&pfd[i])) {
+ // do not consider for poll again
+ // POLLERR - can this happen as we are reading from a fs?
+ // POLLHUP - not valid for cardN/state
+ // POLLNVAL - fd is valid
+ LOG_ALWAYS_FATAL("unxpected error in card poll fd 0x%x",
+ pfd[i].revents);
+ // FIXME: If not fatal, then need some logic to close
+ // these fds on error
+ pfd[i].fd *= -1;
+ }
+ ++i;
+ }
+
+ list_for_each(node, &sndmonitor.dev_events) {
+ dev_event_t *d = node_to_item(node, dev_event_t, node);
+ if (READY_TO_READ(&pfd[i]))
+ on_dev_event(d);
+ else if (ERROR_IN_FD(&pfd[i])) {
+ // do not consider for poll again
+ // POLLERR - can this happen as we are reading from a fs?
+ // POLLHUP - not valid for switch/state
+ // POLLNVAL - fd is valid
+ LOG_ALWAYS_FATAL("unxpected error in dev poll fd 0x%x",
+ pfd[i].revents);
+ // FIXME: If not fatal, then need some logic to close
+ // these fds on error
+ pfd[i].fd *= -1;
+ }
+ ++i;
+ }
+ }
+
+ return NULL;
+}
+
+// ---- listener static APIs ---- //
+static int hashfn(void *key)
+{
+ return (int)key;
+}
+
+static bool hasheq(void *key1, void *key2)
+{
+ return key1 == key2;
+}
+
+static bool snd_cb(void *key, void *value, void *context)
+{
+ snd_mon_cb cb = (snd_mon_cb)value;
+ cb(key, context);
+ return true;
+}
+
+static void snd_mon_update(const void *target __unused, const char *msg)
+{
+ // target can be used to check if this message is intended for the
+ // recipient or not. (using some statically saved state)
+
+ struct str_parms *parms = str_parms_create_str(msg);
+
+ if (!parms)
+ return;
+
+ hashmapLock(sndmonitor.listeners);
+ hashmapForEach(sndmonitor.listeners, snd_cb, parms);
+ hashmapUnlock(sndmonitor.listeners);
+
+ str_parms_destroy(parms);
+}
+
+static int listeners_init()
+{
+ sndmonitor.listeners = hashmapCreate(INIT_MAP_SIZE, hashfn, hasheq);
+ if (!sndmonitor.listeners)
+ return -1;
+ return 0;
+}
+
+static int listeners_deinit()
+{
+ // XXX TBD
+ return -1;
+}
+
+static int add_listener(void *stream, snd_mon_cb cb)
+{
+ Hashmap *map = sndmonitor.listeners;
+ hashmapLock(map);
+ hashmapPut(map, stream, cb);
+ hashmapUnlock(map);
+ return 0;
+}
+
+static int del_listener(void * stream)
+{
+ Hashmap *map = sndmonitor.listeners;
+ hashmapLock(map);
+ hashmapRemove(map, stream);
+ hashmapUnlock(map);
+ return 0;
+}
+
+// --- public APIs --- //
+
+int audio_extn_snd_mon_deinit()
+{
+ if (!sndmonitor.initcheck)
+ return -1;
+
+ write(sndmonitor.intpipe[1], "Q", 1);
+ pthread_join(sndmonitor.monitor_thread, (void **) NULL);
+ listeners_deinit();
+ free_sndcards();
+ free_dev_events();
+ sndmonitor.initcheck = 0;
+ return 0;
+}
+
+int audio_extn_snd_mon_init()
+{
+ sndmonitor.notify = snd_mon_update;
+ sndmonitor.target = NULL; // unused for now
+ list_init(&sndmonitor.cards);
+ list_init(&sndmonitor.dev_events);
+ sndmonitor.initcheck = false;
+
+ if (pipe(sndmonitor.intpipe) < 0)
+ return -ENODEV;
+
+ if (enum_sndcards() < 0)
+ return -ENODEV;
+
+ if (listeners_init() < 0)
+ return -ENODEV;
+
+#ifdef MONITOR_DEVICE_EVENTS
+ enum_dev_events(); // failure here isn't fatal
+#endif
+
+ int ret = pthread_create(&sndmonitor.monitor_thread,
+ (const pthread_attr_t *) NULL,
+ monitor_thread_loop, NULL);
+
+ if (ret) {
+ free_sndcards();
+ free_dev_events();
+ close(sndmonitor.intpipe[0]);
+ close(sndmonitor.intpipe[1]);
+ return -ENODEV;
+ }
+ sndmonitor.initcheck = true;
+ return 0;
+}
+
+int audio_extn_snd_mon_register_listener(void *stream, snd_mon_cb cb)
+{
+ if (!sndmonitor.initcheck) {
+ ALOGW("sndmonitor initcheck failed, cannot register");
+ return -1;
+ }
+
+ return add_listener(stream, cb);
+}
+
+int audio_extn_snd_mon_unregister_listener(void *stream)
+{
+ if (!sndmonitor.initcheck) {
+ ALOGW("sndmonitor initcheck failed, cannot deregister");
+ return -1;
+ }
+
+ ALOGV("deregister listener for stream %p ", stream);
+ return del_listener(stream);
+}
diff --git a/hal/audio_hw.h b/hal/audio_hw.h
index 197807c..664d1fc 100644
--- a/hal/audio_hw.h
+++ b/hal/audio_hw.h
@@ -79,6 +79,11 @@
#define MAX_PERF_LOCK_OPTS 20
+typedef enum card_status_t {
+ CARD_STATUS_OFFLINE,
+ CARD_STATUS_ONLINE
+} card_status_t;
+
/* These are the supported use cases by the hardware.
* Each usecase is mapped to a specific PCM device.
* Refer to pcm_device_table[].
@@ -162,6 +167,7 @@
OFFLOAD_CMD_DRAIN, /* send a full drain request to DSP */
OFFLOAD_CMD_PARTIAL_DRAIN, /* send a partial drain request to DSP */
OFFLOAD_CMD_WAIT_FOR_BUFFER, /* wait for buffer released by DSP */
+ OFFLOAD_CMD_ERROR, /* offload playback hit some error */
};
enum {
diff --git a/hal/platform_api.h b/hal/platform_api.h
index 625f4eb..ec64206 100644
--- a/hal/platform_api.h
+++ b/hal/platform_api.h
@@ -37,6 +37,8 @@
int na_mode;
} native_audio_prop;
+enum card_status_t;
+
void *platform_init(struct audio_device *adev);
void platform_deinit(void *platform);
const char *platform_get_snd_device_name(snd_device_t snd_device);