mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-10-29 05:40:27 -04:00
spa/alsa-udev: retry busy devices on inotify close event, not timeout
Alsa device acp probe results to missing profiles if some PCM devices are busy. Currently, we retry based on a timeout and give up after some retries. However, exposing cards with missing profiles is never useful. Never expose cards if some PCM devices are busy. Instead, retry adding device on inotify fd close events, which arrive when some process has closed a PCM device. When probing for devices in alsa-udev, check via /proc to avoid inotify busy loop.
This commit is contained in:
parent
4341d27c2c
commit
b369401c8e
1 changed files with 122 additions and 147 deletions
|
|
@ -29,6 +29,7 @@
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/inotify.h>
|
#include <sys/inotify.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
#include <libudev.h>
|
#include <libudev.h>
|
||||||
#include <alsa/asoundlib.h>
|
#include <alsa/asoundlib.h>
|
||||||
|
|
@ -47,9 +48,6 @@
|
||||||
|
|
||||||
#define MAX_DEVICES 64
|
#define MAX_DEVICES 64
|
||||||
|
|
||||||
#define RETRY_COUNT 1
|
|
||||||
#define RETRY_MSEC 2000
|
|
||||||
|
|
||||||
#define ACTION_ADD 0
|
#define ACTION_ADD 0
|
||||||
#define ACTION_REMOVE 1
|
#define ACTION_REMOVE 1
|
||||||
#define ACTION_DISABLE 2
|
#define ACTION_DISABLE 2
|
||||||
|
|
@ -57,7 +55,7 @@
|
||||||
struct device {
|
struct device {
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
struct udev_device *dev;
|
struct udev_device *dev;
|
||||||
uint8_t retry;
|
unsigned int unavailable:1;
|
||||||
unsigned int accessible:1;
|
unsigned int accessible:1;
|
||||||
unsigned int ignored:1;
|
unsigned int ignored:1;
|
||||||
unsigned int emitted:1;
|
unsigned int emitted:1;
|
||||||
|
|
@ -84,7 +82,6 @@ struct impl {
|
||||||
|
|
||||||
struct spa_source source;
|
struct spa_source source;
|
||||||
struct spa_source notify;
|
struct spa_source notify;
|
||||||
struct spa_source retry_timer;
|
|
||||||
unsigned int use_acp:1;
|
unsigned int use_acp:1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -249,37 +246,94 @@ static void unescape(const char *src, char *dst)
|
||||||
*d = 0;
|
*d = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int check_device_busy(struct impl *this, struct device *device, snd_ctl_t *ctl_hndl)
|
static int check_device_available(struct impl *this, struct device *device, int *num_pcm)
|
||||||
{
|
{
|
||||||
int dev;
|
char path[PATH_MAX];
|
||||||
|
DIR *card = NULL, *pcm = NULL;
|
||||||
|
FILE *f;
|
||||||
|
char buf[16];
|
||||||
|
size_t sz;
|
||||||
|
struct dirent *entry, *entry_pcm;
|
||||||
|
int res;
|
||||||
|
|
||||||
/* Check if some pcm devices of the card cannot be opened because they are busy */
|
/*
|
||||||
|
* Check if some pcm devices of the card are busy. Check it via /proc, as we
|
||||||
|
* don't want to actually open any devices using alsa-lib (generates uncontrolled
|
||||||
|
* number of inotify events), or replicate its subdevice logic.
|
||||||
|
*/
|
||||||
|
|
||||||
for (dev = -1; snd_ctl_pcm_next_device(ctl_hndl, &dev) >= 0 && dev >= 0;) {
|
*num_pcm = 0;
|
||||||
char devpath[64];
|
|
||||||
int i;
|
|
||||||
|
|
||||||
snprintf(devpath, sizeof(devpath), "hw:%u,%u", device->id, dev);
|
spa_scnprintf(path, sizeof(path), "/proc/asound/card%u", (unsigned int)device->id);
|
||||||
|
|
||||||
for (i = 0; i < 2; ++i) {
|
if ((card = opendir(path)) == NULL)
|
||||||
snd_pcm_t *handle;
|
goto done;
|
||||||
int res;
|
|
||||||
|
|
||||||
res = snd_pcm_open(&handle, devpath,
|
while ((errno = 0, entry = readdir(card)) != NULL) {
|
||||||
(i == 0) ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE,
|
if (!(entry->d_type == DT_DIR &&
|
||||||
SND_PCM_NONBLOCK | SND_PCM_NO_AUTO_RESAMPLE |
|
spa_strstartswith(entry->d_name, "pcm")))
|
||||||
SND_PCM_NO_AUTO_CHANNELS | SND_PCM_NO_AUTO_FORMAT);
|
continue;
|
||||||
if (res == -EBUSY) {
|
|
||||||
spa_log_debug(this->log, "pcm device %s busy", devpath);
|
/* Check device class */
|
||||||
return -EBUSY;
|
spa_scnprintf(path, sizeof(path), "/sys/class/sound/pcmC%uD%s/pcm_class",
|
||||||
} else if (res >= 0) {
|
(unsigned int)device->id, entry->d_name+3);
|
||||||
snd_pcm_close(handle);
|
f = fopen(path, "r");
|
||||||
|
if (f == NULL)
|
||||||
|
goto done;
|
||||||
|
sz = fread(buf, 1, sizeof(buf) - 1, f);
|
||||||
|
buf[sz] = '\0';
|
||||||
|
fclose(f);
|
||||||
|
if (spa_strstartswith(buf, "modem"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Check busy status */
|
||||||
|
spa_scnprintf(path, sizeof(path), "/proc/asound/card%u/%s",
|
||||||
|
(unsigned int)device->id, entry->d_name);
|
||||||
|
if ((pcm = opendir(path)) == NULL)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
while ((errno = 0, entry_pcm = readdir(pcm)) != NULL) {
|
||||||
|
if (!(entry_pcm->d_type == DT_DIR &&
|
||||||
|
spa_strstartswith(entry_pcm->d_name, "sub")))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
spa_scnprintf(path, sizeof(path), "/proc/asound/card%u/%s/%s/status",
|
||||||
|
(unsigned int)device->id, entry->d_name, entry_pcm->d_name);
|
||||||
|
|
||||||
|
f = fopen(path, "r");
|
||||||
|
if (f == NULL)
|
||||||
|
goto done;
|
||||||
|
sz = fread(buf, 1, 6, f);
|
||||||
|
buf[sz] = '\0';
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
if (!spa_strstartswith(buf, "closed")) {
|
||||||
|
spa_log_debug(this->log, "card %u pcm device %s busy",
|
||||||
|
(unsigned int)device->id, entry->d_name);
|
||||||
|
errno = EBUSY;
|
||||||
|
goto done;
|
||||||
}
|
}
|
||||||
|
spa_log_debug(this->log, "card %u pcm device %s free",
|
||||||
|
(unsigned int)device->id, entry->d_name);
|
||||||
}
|
}
|
||||||
spa_log_debug(this->log, "pcm device %s free", devpath);
|
if (errno != 0)
|
||||||
}
|
goto done;
|
||||||
|
|
||||||
return 0;
|
++*num_pcm;
|
||||||
|
|
||||||
|
closedir(pcm);
|
||||||
|
pcm = NULL;
|
||||||
|
}
|
||||||
|
if (errno != 0)
|
||||||
|
goto done;
|
||||||
|
|
||||||
|
done:
|
||||||
|
res = -errno;
|
||||||
|
if (card)
|
||||||
|
closedir(card);
|
||||||
|
if (pcm)
|
||||||
|
closedir(pcm);
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int emit_object_info(struct impl *this, struct device *device)
|
static int emit_object_info(struct impl *this, struct device *device)
|
||||||
|
|
@ -287,42 +341,28 @@ static int emit_object_info(struct impl *this, struct device *device)
|
||||||
struct spa_device_object_info info;
|
struct spa_device_object_info info;
|
||||||
uint32_t id = device->id;
|
uint32_t id = device->id;
|
||||||
struct udev_device *dev = device->dev;
|
struct udev_device *dev = device->dev;
|
||||||
snd_ctl_t *ctl_hndl;
|
|
||||||
const char *str;
|
const char *str;
|
||||||
char path[32], *cn = NULL, *cln = NULL;
|
char path[32], *cn = NULL, *cln = NULL;
|
||||||
struct spa_dict_item items[25];
|
struct spa_dict_item items[25];
|
||||||
uint32_t n_items = 0;
|
uint32_t n_items = 0;
|
||||||
int res, pcm;
|
int res, pcm;
|
||||||
|
|
||||||
snprintf(path, sizeof(path), "hw:%u", id);
|
/*
|
||||||
spa_log_debug(this->log, "open card %s", path);
|
* inotify close events under /dev/snd must not be emitted, except after setting
|
||||||
|
* device->emitted to true. alsalib functions can be used after that.
|
||||||
|
*/
|
||||||
|
|
||||||
if ((res = snd_ctl_open(&ctl_hndl, path, 0)) < 0) {
|
if ((res = check_device_available(this, device, &pcm)) < 0)
|
||||||
spa_log_error(this->log, "can't open control for card %s: %s",
|
|
||||||
path, snd_strerror(res));
|
|
||||||
return res;
|
return res;
|
||||||
}
|
if (pcm == 0) {
|
||||||
|
|
||||||
pcm = -1;
|
|
||||||
res = snd_ctl_pcm_next_device(ctl_hndl, &pcm);
|
|
||||||
|
|
||||||
if (res < 0) {
|
|
||||||
spa_log_error(this->log, "error iterating devices: %s", snd_strerror(res));
|
|
||||||
device->ignored = true;
|
|
||||||
} else if (pcm < 0) {
|
|
||||||
spa_log_debug(this->log, "no pcm devices for %s", path);
|
spa_log_debug(this->log, "no pcm devices for %s", path);
|
||||||
device->ignored = true;
|
device->ignored = true;
|
||||||
res = 0;
|
return 0;
|
||||||
} else if (device->retry > 0) {
|
|
||||||
/* Check if we can open all PCM devices (retry later if not) */
|
|
||||||
res = check_device_busy(this, device, ctl_hndl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
spa_log_debug(this->log, "close card %s", path);
|
snprintf(path, sizeof(path), "hw:%u", id);
|
||||||
snd_ctl_close(ctl_hndl);
|
spa_log_debug(this->log, "emitting card %s", path);
|
||||||
|
device->emitted = true;
|
||||||
if (res < 0 || device->ignored)
|
|
||||||
return res;
|
|
||||||
|
|
||||||
info = SPA_DEVICE_OBJECT_INFO_INIT();
|
info = SPA_DEVICE_OBJECT_INFO_INIT();
|
||||||
|
|
||||||
|
|
@ -429,96 +469,22 @@ static int emit_object_info(struct impl *this, struct device *device)
|
||||||
info.props = &SPA_DICT_INIT(items, n_items);
|
info.props = &SPA_DICT_INIT(items, n_items);
|
||||||
|
|
||||||
spa_device_emit_object_info(&this->hooks, id, &info);
|
spa_device_emit_object_info(&this->hooks, id, &info);
|
||||||
device->emitted = true;
|
|
||||||
free(cn);
|
free(cn);
|
||||||
free(cln);
|
free(cln);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void start_retry(struct impl *this);
|
|
||||||
|
|
||||||
static void stop_retry(struct impl *this);
|
|
||||||
|
|
||||||
static void retry_timer_event(struct spa_source *source)
|
|
||||||
{
|
|
||||||
struct impl *this = source->data;
|
|
||||||
bool have_retry = false;
|
|
||||||
size_t i;
|
|
||||||
|
|
||||||
stop_retry(this);
|
|
||||||
|
|
||||||
for (i = 0; i < this->n_devices; ++i) {
|
|
||||||
struct device *device = &this->devices[i];
|
|
||||||
if (device->ignored)
|
|
||||||
device->retry = 0;
|
|
||||||
if (device->retry > 0) {
|
|
||||||
--device->retry;
|
|
||||||
|
|
||||||
spa_log_debug(this->log, "retrying device %u", device->id);
|
|
||||||
|
|
||||||
if (emit_object_info(this, device) == -EBUSY) {
|
|
||||||
spa_log_debug(this->log, "device %u busy (remaining retries %u)",
|
|
||||||
device->id, device->retry);
|
|
||||||
} else {
|
|
||||||
device->retry = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (device->retry > 0)
|
|
||||||
have_retry = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (have_retry)
|
|
||||||
start_retry(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void start_retry(struct impl *this)
|
|
||||||
{
|
|
||||||
struct itimerspec ts;
|
|
||||||
|
|
||||||
spa_log_debug(this->log, "start retry");
|
|
||||||
|
|
||||||
if (this->retry_timer.data == NULL) {
|
|
||||||
this->retry_timer.data = this;
|
|
||||||
this->retry_timer.func = retry_timer_event;
|
|
||||||
this->retry_timer.mask = SPA_IO_IN;
|
|
||||||
this->retry_timer.rmask = 0;
|
|
||||||
spa_loop_add_source(this->main_loop, &this->retry_timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
ts.it_value.tv_sec = ((uint64_t)RETRY_MSEC * SPA_NSEC_PER_MSEC) / SPA_NSEC_PER_SEC;
|
|
||||||
ts.it_value.tv_nsec = ((uint64_t)RETRY_MSEC * SPA_NSEC_PER_MSEC) % SPA_NSEC_PER_SEC;
|
|
||||||
ts.it_interval.tv_sec = 0;
|
|
||||||
ts.it_interval.tv_nsec = 0;
|
|
||||||
spa_system_timerfd_settime(this->main_system, this->retry_timer.fd, 0, &ts, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void stop_retry(struct impl *this)
|
|
||||||
{
|
|
||||||
struct itimerspec ts;
|
|
||||||
|
|
||||||
if (this->retry_timer.data == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
spa_log_debug(this->log, "stop retry");
|
|
||||||
|
|
||||||
spa_loop_remove_source(this->main_loop, &this->retry_timer);
|
|
||||||
this->retry_timer.data = NULL;
|
|
||||||
|
|
||||||
ts.it_value.tv_sec = 0;
|
|
||||||
ts.it_value.tv_nsec = 0;
|
|
||||||
ts.it_interval.tv_sec = 0;
|
|
||||||
ts.it_interval.tv_nsec = 0;
|
|
||||||
spa_system_timerfd_settime(this->main_system, this->retry_timer.fd, 0, &ts, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool check_access(struct impl *this, struct device *device)
|
static bool check_access(struct impl *this, struct device *device)
|
||||||
{
|
{
|
||||||
char path[128];
|
char path[128];
|
||||||
|
bool accessible;
|
||||||
|
|
||||||
snprintf(path, sizeof(path), "/dev/snd/controlC%u", device->id);
|
snprintf(path, sizeof(path), "/dev/snd/controlC%u", device->id);
|
||||||
device->accessible = access(path, R_OK|W_OK) >= 0;
|
accessible = access(path, R_OK|W_OK) >= 0;
|
||||||
spa_log_debug(this->log, "%s accessible:%u", path, device->accessible);
|
if (accessible != device->accessible)
|
||||||
|
spa_log_debug(this->log, "%s accessible:%u", path, accessible);
|
||||||
|
device->accessible = accessible;
|
||||||
|
|
||||||
return device->accessible;
|
return device->accessible;
|
||||||
}
|
}
|
||||||
|
|
@ -528,6 +494,7 @@ static void process_device(struct impl *this, uint32_t action, struct udev_devic
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
struct device *device;
|
struct device *device;
|
||||||
bool emitted;
|
bool emitted;
|
||||||
|
int res;
|
||||||
|
|
||||||
if ((id = get_card_id(this, dev)) == SPA_ID_INVALID)
|
if ((id = get_card_id(this, dev)) == SPA_ID_INVALID)
|
||||||
return;
|
return;
|
||||||
|
|
@ -544,13 +511,23 @@ static void process_device(struct impl *this, uint32_t action, struct udev_devic
|
||||||
return;
|
return;
|
||||||
if (!check_access(this, device))
|
if (!check_access(this, device))
|
||||||
return;
|
return;
|
||||||
device->retry = RETRY_COUNT;
|
res = emit_object_info(this, device);
|
||||||
if (emit_object_info(this, device) == -EBUSY) {
|
if (res < 0) {
|
||||||
spa_log_debug(this->log, "device %u busy (remaining retries %u)",
|
if (device->ignored)
|
||||||
device->id, device->retry);
|
spa_log_info(this->log, "ALSA card %u unavailable (%s): it is ignored",
|
||||||
start_retry(this);
|
device->id, spa_strerror(res));
|
||||||
|
else if (!device->unavailable)
|
||||||
|
spa_log_info(this->log, "ALSA card %u unavailable (%s): wait for it",
|
||||||
|
device->id, spa_strerror(res));
|
||||||
|
else
|
||||||
|
spa_log_debug(this->log, "ALSA card %u still unavailable (%s)",
|
||||||
|
device->id, spa_strerror(res));
|
||||||
|
device->unavailable = true;
|
||||||
} else {
|
} else {
|
||||||
device->retry = 0;
|
if (device->unavailable)
|
||||||
|
spa_log_info(this->log, "ALSA card %u now available",
|
||||||
|
device->id);
|
||||||
|
device->unavailable = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -566,7 +543,6 @@ static void process_device(struct impl *this, uint32_t action, struct udev_devic
|
||||||
case ACTION_DISABLE:
|
case ACTION_DISABLE:
|
||||||
if (device == NULL)
|
if (device == NULL)
|
||||||
return;
|
return;
|
||||||
device->retry = 0;
|
|
||||||
if (device->emitted) {
|
if (device->emitted) {
|
||||||
device->emitted = false;
|
device->emitted = false;
|
||||||
spa_device_emit_object_info(&this->hooks, id, NULL);
|
spa_device_emit_object_info(&this->hooks, id, NULL);
|
||||||
|
|
@ -615,12 +591,19 @@ static void impl_on_notify_events(struct spa_source *source)
|
||||||
|
|
||||||
event = (const struct inotify_event *) p;
|
event = (const struct inotify_event *) p;
|
||||||
|
|
||||||
if ((event->mask & IN_ATTRIB)) {
|
/* Device becomes accessible or not busy */
|
||||||
|
if ((event->mask & (IN_ATTRIB | IN_CLOSE_WRITE))) {
|
||||||
bool access;
|
bool access;
|
||||||
if (sscanf(event->name, "controlC%u", &id) != 1)
|
|
||||||
|
if ((event->mask & IN_ATTRIB) &&
|
||||||
|
spa_strstartswith(event->name, "pcm"))
|
||||||
|
continue;
|
||||||
|
if (sscanf(event->name, "controlC%u", &id) != 1 &&
|
||||||
|
sscanf(event->name, "pcmC%uD", &id) != 1)
|
||||||
continue;
|
continue;
|
||||||
if ((device = find_device(this, id)) == NULL)
|
if ((device = find_device(this, id)) == NULL)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
access = check_access(this, device);
|
access = check_access(this, device);
|
||||||
if (access && !device->emitted)
|
if (access && !device->emitted)
|
||||||
process_device(this, ACTION_ADD, device->dev);
|
process_device(this, ACTION_ADD, device->dev);
|
||||||
|
|
@ -855,10 +838,6 @@ static int impl_clear(struct spa_handle *handle)
|
||||||
struct impl *this = (struct impl *) handle;
|
struct impl *this = (struct impl *) handle;
|
||||||
stop_monitor(this);
|
stop_monitor(this);
|
||||||
impl_udev_close(this);
|
impl_udev_close(this);
|
||||||
stop_retry(this);
|
|
||||||
if (this->retry_timer.fd >= 0)
|
|
||||||
spa_system_close(this->main_system, this->retry_timer.fd);
|
|
||||||
this->retry_timer.fd = -1;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -887,7 +866,6 @@ impl_init(const struct spa_handle_factory *factory,
|
||||||
|
|
||||||
this = (struct impl *) handle;
|
this = (struct impl *) handle;
|
||||||
this->notify.fd = -1;
|
this->notify.fd = -1;
|
||||||
this->retry_timer.fd = -1;
|
|
||||||
|
|
||||||
this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
|
this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log);
|
||||||
alsa_log_topic_init(this->log);
|
alsa_log_topic_init(this->log);
|
||||||
|
|
@ -919,9 +897,6 @@ impl_init(const struct spa_handle_factory *factory,
|
||||||
this->use_acp = spa_atob(str);
|
this->use_acp = spa_atob(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->retry_timer.fd = spa_system_timerfd_create(this->main_system,
|
|
||||||
CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue