You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
670 lines
17 KiB
670 lines
17 KiB
/* |
|
* Copyright (C) 2016 The Android Open Source Project |
|
* |
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
* you may not use this file except in compliance with the License. |
|
* You may obtain a copy of the License at |
|
* |
|
* http://www.apache.org/licenses/LICENSE-2.0 |
|
* |
|
* Unless required by applicable law or agreed to in writing, software |
|
* distributed under the License is distributed on an "AS IS" BASIS, |
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
* See the License for the specific language governing permissions and |
|
* limitations under the License. |
|
*/ |
|
|
|
#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 <pthread.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" |
|
|
|
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) { |
|
ALOGD("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); |
|
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 enum_sndcards() |
|
{ |
|
const char* cards = "/proc/asound/cards"; |
|
int tries = 10; |
|
char *line = NULL; |
|
size_t len = 0; |
|
ssize_t bytes_read; |
|
char path[128] = {0}; |
|
char *ptr, *saveptr, *card_id; |
|
int line_no=0; |
|
unsigned int num_cards=0, num_cpe=0; |
|
FILE *fp; |
|
int fd, ret; |
|
|
|
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; |
|
|
|
// Limit to 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, "apq", 3) != 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) { |
|
close(fd); // card state fd ownership is taken by sndcard on success |
|
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); // card state fd ownership is taken by sndcard on success |
|
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); |
|
} |
|
} |
|
|
|
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; |
|
} |
|
|
|
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); |
|
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); |
|
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); |
|
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(5, 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); |
|
free_dev_events(); |
|
listeners_deinit(); |
|
free_sndcards(); |
|
close(sndmonitor.intpipe[0]); |
|
close(sndmonitor.intpipe[1]); |
|
|
|
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) |
|
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 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); |
|
}
|
|
|