blob: a0bd6d3628700537d72a9e322f0a5dd009888cfe [file] [log] [blame]
/*
* Copyright (c) 2016-2021, 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 <pthread.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include <cutils/list.h>
#include <cutils/hashmap.h>
#include <log/log.h>
#include <cutils/str_parms.h>
#include <ctype.h>
#include <linux/version.h>
#include "audio_hw.h"
#include "audio_extn.h"
#ifdef DYNAMIC_LOG_ENABLED
#include <log_xml_parser.h>
#define LOG_MASK HAL_MOD_FILE_SND_MONITOR
#include <log_utils.h>
#endif
//#define MONITOR_DEVICE_EVENTS
#define CPE_MAGIC_NUM 0x2000
#define MAX_CPE_SLEEP_RETRY 2
#define CPE_SLEEP_WAIT 100
#define SPLI_STATE_PATH "/proc/wcd-spi-ac/svc-state"
#define SLPI_MAGIC_NUM 0x3000
#define MAX_SLPI_SLEEP_RETRY 2
#define SLPI_SLEEP_WAIT_MS 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 (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0))
if (!state) {
free(s);
return -1;
}
bool online = state && !strcmp(state, "ONLINE");
if (state)
free(state);
#else
bool online = atoi(state);
#endif
ALOGV("card %d initial state %s %d", card, state, online);
s->status = online ? CARD_STATUS_ONLINE : CARD_STATUS_OFFLINE;
list_add_tail(&sndmonitor.cards, &s->node);
return 0;
}
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 ((strncasecmp(card_id, "msm", 3) != 0) &&
(strncasecmp(card_id, "sdm", 3) != 0) &&
(strncasecmp(card_id, "sdc", 3) != 0) &&
(strncasecmp(card_id, "sm", 2) != 0) &&
(strncasecmp(card_id, "trinket", 7) != 0) &&
(strncasecmp(card_id, "apq", 3) != 0) &&
(strncasecmp(card_id, "sa", 2) != 0) &&
(strncasecmp(card_id, "kona", 4) != 0) &&
(strncasecmp(card_id, "holi", 4) != 0) &&
(strncasecmp(card_id, "shima", 5) != 0) &&
(strncasecmp(card_id, "yupik", 5) != 0) &&
(strncasecmp(card_id, "lahaina", 7) != 0) &&
(strncasecmp(card_id, "atoll", 5) != 0) &&
(strncasecmp(card_id, "bengal", 6) != 0) &&
(strncasecmp(card_id, "lito", 4) != 0)) {
ALOGW("Skip over non-ADSP snd card %s", card_id);
continue;
}
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0))
snprintf(path, sizeof(path), "/proc/asound/card%s/state", ptr);
#else
snprintf(path, sizeof(path), "/sys/kernel/snd_card/card_state");
#endif
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) {
close(fd);
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) {
close(fd);
continue;
}
num_cpe++;
num_cards++;
}
if (line)
free(line);
fclose(fp);
/* Add fd to query for SLPI status */
if (access(SPLI_STATE_PATH, R_OK) < 0) {
ALOGV("access to %s failed: %s", SPLI_STATE_PATH, strerror(errno));
} else {
tries = MAX_SLPI_SLEEP_RETRY;
ALOGV("Open %s", SPLI_STATE_PATH);
while (tries--) {
if ((fd = open(SPLI_STATE_PATH, O_RDONLY)) < 0) {
ALOGW("Open %s failed %s, retry", SPLI_STATE_PATH,
strerror(errno));
usleep(SLPI_SLEEP_WAIT_MS * 1000);
continue;
}
break;
}
if (fd >= 0) {
ret = add_new_sndcard(SLPI_MAGIC_NUM, fd);
if (ret != 0)
close(fd);
else
num_cards++;
}
}
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");
int ret = 0;
if (str_parms_add_str(params, AUDIO_PARAMETER_KEY_EXT_AUDIO_DEVICE, val) < 0) {
ret = -1;
} else {
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) {
ALOGE("read card state error");
return -1;
}
rd_buf[8] = '\0';
lseek(s->fd, 0, SEEK_SET);
ALOGV("card num %d, new state %s", s->card, rd_buf);
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 15, 0))
if (strstr(rd_buf, "OFFLINE"))
status = CARD_STATUS_OFFLINE;
else if (strstr(rd_buf, "ONLINE"))
status = CARD_STATUS_ONLINE;
#else
if (!atoi(rd_buf))
status = CARD_STATUS_OFFLINE;
else if (atoi(rd_buf))
status = CARD_STATUS_ONLINE;
#endif
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};
bool is_cpe = ((s->card >= CPE_MAGIC_NUM) && (s->card < SLPI_MAGIC_NUM));
bool is_slpi = (s->card == SLPI_MAGIC_NUM);
char *key = NULL;
/*
* cpe actual card num is (card - CPE_MAGIC_NUM), so subtract accordingly.
* SLPI actual fd num is (card - SLPI_MAGIC_NUM), so subtract accordingly.
*/
snprintf(val, sizeof(val), "%d,%s",
s->card - (is_cpe ? CPE_MAGIC_NUM : (is_slpi ? SLPI_MAGIC_NUM : 0)),
status == CARD_STATUS_ONLINE ? "ONLINE" : "OFFLINE");
key = (is_cpe ? "CPE_STATUS" :
(is_slpi ? "SLPI_STATUS" :
"SND_CARD_STATUS"));
int ret = 0;
if (str_parms_add_str(params, key, val) < 0) {
ret = -1;
} else {
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;
}
}
if (pfd)
free(pfd);
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 snd_mon_deinit()
{
if (!sndmonitor.initcheck)
return -1;
write(sndmonitor.intpipe[1], "Q", 1);
pthread_join(sndmonitor.monitor_thread, (void **) NULL);
free_dev_events();
listeners_deinit();
free_sndcards();
close(sndmonitor.intpipe[0]);
close(sndmonitor.intpipe[1]);
sndmonitor.initcheck = 0;
return 0;
}
int 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)
goto pipe_error;
if (enum_sndcards() < 0)
goto enum_sncards_error;
if (listeners_init() < 0)
goto listeners_error;
#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) {
goto monitor_thread_create_error;
}
sndmonitor.initcheck = true;
return 0;
monitor_thread_create_error:
listeners_deinit();
listeners_error:
free_sndcards();
enum_sncards_error:
close(sndmonitor.intpipe[0]);
close(sndmonitor.intpipe[1]);
pipe_error:
return -ENODEV;
}
int 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 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);
}