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);