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/inotify.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include <libudev.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
|
|
@ -47,9 +48,6 @@
|
|||
|
||||
#define MAX_DEVICES 64
|
||||
|
||||
#define RETRY_COUNT 1
|
||||
#define RETRY_MSEC 2000
|
||||
|
||||
#define ACTION_ADD 0
|
||||
#define ACTION_REMOVE 1
|
||||
#define ACTION_DISABLE 2
|
||||
|
|
@ -57,7 +55,7 @@
|
|||
struct device {
|
||||
uint32_t id;
|
||||
struct udev_device *dev;
|
||||
uint8_t retry;
|
||||
unsigned int unavailable:1;
|
||||
unsigned int accessible:1;
|
||||
unsigned int ignored:1;
|
||||
unsigned int emitted:1;
|
||||
|
|
@ -84,7 +82,6 @@ struct impl {
|
|||
|
||||
struct spa_source source;
|
||||
struct spa_source notify;
|
||||
struct spa_source retry_timer;
|
||||
unsigned int use_acp:1;
|
||||
};
|
||||
|
||||
|
|
@ -249,37 +246,94 @@ static void unescape(const char *src, char *dst)
|
|||
*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;
|
||||
|
||||
/* Check if some pcm devices of the card cannot be opened because they are busy */
|
||||
|
||||
for (dev = -1; snd_ctl_pcm_next_device(ctl_hndl, &dev) >= 0 && dev >= 0;) {
|
||||
char devpath[64];
|
||||
int i;
|
||||
|
||||
snprintf(devpath, sizeof(devpath), "hw:%u,%u", device->id, dev);
|
||||
|
||||
for (i = 0; i < 2; ++i) {
|
||||
snd_pcm_t *handle;
|
||||
char path[PATH_MAX];
|
||||
DIR *card = NULL, *pcm = NULL;
|
||||
FILE *f;
|
||||
char buf[16];
|
||||
size_t sz;
|
||||
struct dirent *entry, *entry_pcm;
|
||||
int res;
|
||||
|
||||
res = snd_pcm_open(&handle, devpath,
|
||||
(i == 0) ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE,
|
||||
SND_PCM_NONBLOCK | SND_PCM_NO_AUTO_RESAMPLE |
|
||||
SND_PCM_NO_AUTO_CHANNELS | SND_PCM_NO_AUTO_FORMAT);
|
||||
if (res == -EBUSY) {
|
||||
spa_log_debug(this->log, "pcm device %s busy", devpath);
|
||||
return -EBUSY;
|
||||
} else if (res >= 0) {
|
||||
snd_pcm_close(handle);
|
||||
}
|
||||
}
|
||||
spa_log_debug(this->log, "pcm device %s free", devpath);
|
||||
}
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
return 0;
|
||||
*num_pcm = 0;
|
||||
|
||||
spa_scnprintf(path, sizeof(path), "/proc/asound/card%u", (unsigned int)device->id);
|
||||
|
||||
if ((card = opendir(path)) == NULL)
|
||||
goto done;
|
||||
|
||||
while ((errno = 0, entry = readdir(card)) != NULL) {
|
||||
if (!(entry->d_type == DT_DIR &&
|
||||
spa_strstartswith(entry->d_name, "pcm")))
|
||||
continue;
|
||||
|
||||
/* Check device class */
|
||||
spa_scnprintf(path, sizeof(path), "/sys/class/sound/pcmC%uD%s/pcm_class",
|
||||
(unsigned int)device->id, entry->d_name+3);
|
||||
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);
|
||||
}
|
||||
if (errno != 0)
|
||||
goto done;
|
||||
|
||||
++*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)
|
||||
|
|
@ -287,42 +341,28 @@ static int emit_object_info(struct impl *this, struct device *device)
|
|||
struct spa_device_object_info info;
|
||||
uint32_t id = device->id;
|
||||
struct udev_device *dev = device->dev;
|
||||
snd_ctl_t *ctl_hndl;
|
||||
const char *str;
|
||||
char path[32], *cn = NULL, *cln = NULL;
|
||||
struct spa_dict_item items[25];
|
||||
uint32_t n_items = 0;
|
||||
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) {
|
||||
spa_log_error(this->log, "can't open control for card %s: %s",
|
||||
path, snd_strerror(res));
|
||||
if ((res = check_device_available(this, device, &pcm)) < 0)
|
||||
return res;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (pcm == 0) {
|
||||
spa_log_debug(this->log, "no pcm devices for %s", path);
|
||||
device->ignored = true;
|
||||
res = 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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
spa_log_debug(this->log, "close card %s", path);
|
||||
snd_ctl_close(ctl_hndl);
|
||||
|
||||
if (res < 0 || device->ignored)
|
||||
return res;
|
||||
snprintf(path, sizeof(path), "hw:%u", id);
|
||||
spa_log_debug(this->log, "emitting card %s", path);
|
||||
device->emitted = true;
|
||||
|
||||
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);
|
||||
|
||||
spa_device_emit_object_info(&this->hooks, id, &info);
|
||||
device->emitted = true;
|
||||
free(cn);
|
||||
free(cln);
|
||||
|
||||
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)
|
||||
{
|
||||
char path[128];
|
||||
bool accessible;
|
||||
|
||||
snprintf(path, sizeof(path), "/dev/snd/controlC%u", device->id);
|
||||
device->accessible = access(path, R_OK|W_OK) >= 0;
|
||||
spa_log_debug(this->log, "%s accessible:%u", path, device->accessible);
|
||||
accessible = access(path, R_OK|W_OK) >= 0;
|
||||
if (accessible != device->accessible)
|
||||
spa_log_debug(this->log, "%s accessible:%u", path, accessible);
|
||||
device->accessible = accessible;
|
||||
|
||||
return device->accessible;
|
||||
}
|
||||
|
|
@ -528,6 +494,7 @@ static void process_device(struct impl *this, uint32_t action, struct udev_devic
|
|||
uint32_t id;
|
||||
struct device *device;
|
||||
bool emitted;
|
||||
int res;
|
||||
|
||||
if ((id = get_card_id(this, dev)) == SPA_ID_INVALID)
|
||||
return;
|
||||
|
|
@ -544,13 +511,23 @@ static void process_device(struct impl *this, uint32_t action, struct udev_devic
|
|||
return;
|
||||
if (!check_access(this, device))
|
||||
return;
|
||||
device->retry = RETRY_COUNT;
|
||||
if (emit_object_info(this, device) == -EBUSY) {
|
||||
spa_log_debug(this->log, "device %u busy (remaining retries %u)",
|
||||
device->id, device->retry);
|
||||
start_retry(this);
|
||||
res = emit_object_info(this, device);
|
||||
if (res < 0) {
|
||||
if (device->ignored)
|
||||
spa_log_info(this->log, "ALSA card %u unavailable (%s): it is ignored",
|
||||
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 {
|
||||
device->retry = 0;
|
||||
if (device->unavailable)
|
||||
spa_log_info(this->log, "ALSA card %u now available",
|
||||
device->id);
|
||||
device->unavailable = false;
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -566,7 +543,6 @@ static void process_device(struct impl *this, uint32_t action, struct udev_devic
|
|||
case ACTION_DISABLE:
|
||||
if (device == NULL)
|
||||
return;
|
||||
device->retry = 0;
|
||||
if (device->emitted) {
|
||||
device->emitted = false;
|
||||
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;
|
||||
|
||||
if ((event->mask & IN_ATTRIB)) {
|
||||
/* Device becomes accessible or not busy */
|
||||
if ((event->mask & (IN_ATTRIB | IN_CLOSE_WRITE))) {
|
||||
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;
|
||||
if ((device = find_device(this, id)) == NULL)
|
||||
continue;
|
||||
|
||||
access = check_access(this, device);
|
||||
if (access && !device->emitted)
|
||||
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;
|
||||
stop_monitor(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;
|
||||
}
|
||||
|
||||
|
|
@ -887,7 +866,6 @@ impl_init(const struct spa_handle_factory *factory,
|
|||
|
||||
this = (struct impl *) handle;
|
||||
this->notify.fd = -1;
|
||||
this->retry_timer.fd = -1;
|
||||
|
||||
this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_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->retry_timer.fd = spa_system_timerfd_create(this->main_system,
|
||||
CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue