mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2026-01-26 14:13:19 +00:00
Merge branch 'wireless_status_sync' into 'master'
Draft: alsa-udev: Add wireless device status monitoring Closes #3102 See merge request pipewire/pipewire!2673
This commit is contained in:
commit
58ab473c16
@ -39,15 +39,28 @@ enum action {
|
|||||||
/* Used for unavailable devices in the card structure. */
|
/* Used for unavailable devices in the card structure. */
|
||||||
#define ID_DEVICE_NOT_SUPPORTED 0
|
#define ID_DEVICE_NOT_SUPPORTED 0
|
||||||
|
|
||||||
|
#define MAX_PARENT_TRAVERSAL_DEPTH 5
|
||||||
|
|
||||||
|
enum wireless_status {
|
||||||
|
WIRELESS_STATUS_UNKNOWN = 0,
|
||||||
|
WIRELESS_STATUS_CONNECTED,
|
||||||
|
WIRELESS_STATUS_DISCONNECTED,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Forward declaration for struct impl */
|
||||||
|
struct impl;
|
||||||
|
|
||||||
/* This represents an ALSA card.
|
/* This represents an ALSA card.
|
||||||
* One card can have up to 1 PCM and 1 Compress-Offload device. */
|
* One card can have up to 1 PCM and 1 Compress-Offload device. */
|
||||||
struct card {
|
struct card {
|
||||||
|
struct impl *impl;
|
||||||
unsigned int card_nr;
|
unsigned int card_nr;
|
||||||
struct udev_device *udev_device;
|
struct udev_device *udev_device;
|
||||||
unsigned int unavailable:1;
|
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;
|
||||||
|
unsigned int wireless_disconnected:1;
|
||||||
|
|
||||||
/* Local SPA object IDs. (Global IDs are produced by PipeWire
|
/* Local SPA object IDs. (Global IDs are produced by PipeWire
|
||||||
* out of this using its registry.) Compress-Offload or PCM
|
* out of this using its registry.) Compress-Offload or PCM
|
||||||
@ -59,6 +72,10 @@ struct card {
|
|||||||
* is used because 0 is a valid ALSA card number. */
|
* is used because 0 is a valid ALSA card number. */
|
||||||
uint32_t pcm_device_id;
|
uint32_t pcm_device_id;
|
||||||
uint32_t compress_offload_device_id;
|
uint32_t compress_offload_device_id;
|
||||||
|
|
||||||
|
char *wireless_status_path;
|
||||||
|
int wireless_status_fd;
|
||||||
|
struct spa_source wireless_status_source;
|
||||||
};
|
};
|
||||||
|
|
||||||
static uint32_t calc_pcm_device_id(struct card *card)
|
static uint32_t calc_pcm_device_id(struct card *card)
|
||||||
@ -97,6 +114,11 @@ struct impl {
|
|||||||
int use_ucm;
|
int use_ucm;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Forward declarations */
|
||||||
|
static void process_card(struct impl *this, enum action action, struct card *card);
|
||||||
|
static void stop_wireless_status_monitor(struct impl *this, struct card *card);
|
||||||
|
static void on_wireless_status_change(struct spa_source *source);
|
||||||
|
|
||||||
static int impl_udev_open(struct impl *this)
|
static int impl_udev_open(struct impl *this)
|
||||||
{
|
{
|
||||||
if (this->udev == NULL) {
|
if (this->udev == NULL) {
|
||||||
@ -124,7 +146,9 @@ static struct card *add_card(struct impl *this, unsigned int card_nr, struct ude
|
|||||||
|
|
||||||
card = &this->cards[this->n_cards++];
|
card = &this->cards[this->n_cards++];
|
||||||
spa_zero(*card);
|
spa_zero(*card);
|
||||||
|
card->impl = this;
|
||||||
card->card_nr = card_nr;
|
card->card_nr = card_nr;
|
||||||
|
card->wireless_status_fd = -1;
|
||||||
udev_device_ref(udev_device);
|
udev_device_ref(udev_device);
|
||||||
card->udev_device = udev_device;
|
card->udev_device = udev_device;
|
||||||
|
|
||||||
@ -143,15 +167,20 @@ static struct card *find_card(struct impl *this, unsigned int card_nr)
|
|||||||
|
|
||||||
static void remove_card(struct impl *this, struct card *card)
|
static void remove_card(struct impl *this, struct card *card)
|
||||||
{
|
{
|
||||||
|
stop_wireless_status_monitor(this, card);
|
||||||
udev_device_unref(card->udev_device);
|
udev_device_unref(card->udev_device);
|
||||||
*card = this->cards[--this->n_cards];
|
this->n_cards--;
|
||||||
|
if (card != &this->cards[this->n_cards])
|
||||||
|
*card = this->cards[this->n_cards];
|
||||||
}
|
}
|
||||||
|
|
||||||
static void clear_cards(struct impl *this)
|
static void clear_cards(struct impl *this)
|
||||||
{
|
{
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
for (i = 0; i < this->n_cards; i++)
|
for (i = 0; i < this->n_cards; i++) {
|
||||||
|
stop_wireless_status_monitor(this, &this->cards[i]);
|
||||||
udev_device_unref(this->cards[i].udev_device);
|
udev_device_unref(this->cards[i].udev_device);
|
||||||
|
}
|
||||||
this->n_cards = 0;
|
this->n_cards = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,6 +289,240 @@ static void unescape(const char *src, char *dst)
|
|||||||
*d = 0;
|
*d = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static enum wireless_status parse_wireless_status(const char *buf)
|
||||||
|
{
|
||||||
|
if (spa_streq(buf, "connected"))
|
||||||
|
return WIRELESS_STATUS_CONNECTED;
|
||||||
|
if (spa_streq(buf, "disconnected"))
|
||||||
|
return WIRELESS_STATUS_DISCONNECTED;
|
||||||
|
return WIRELESS_STATUS_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum wireless_status read_wireless_status(const char *sysfs_path)
|
||||||
|
{
|
||||||
|
char buf[16];
|
||||||
|
ssize_t sz;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = open(sysfs_path, O_RDONLY | O_CLOEXEC);
|
||||||
|
if (fd < 0)
|
||||||
|
return WIRELESS_STATUS_UNKNOWN;
|
||||||
|
|
||||||
|
sz = read(fd, buf, sizeof(buf) - 1);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
if (sz <= 0)
|
||||||
|
return WIRELESS_STATUS_UNKNOWN;
|
||||||
|
|
||||||
|
buf[sz] = '\0';
|
||||||
|
if (buf[sz - 1] == '\n')
|
||||||
|
buf[sz - 1] = '\0';
|
||||||
|
|
||||||
|
return parse_wireless_status(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_usb_interface_dir(const char *name)
|
||||||
|
{
|
||||||
|
const char *colon = strchr(name, ':');
|
||||||
|
if (!colon || !strchr(name, '-'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (const char *p = colon + 1; *p; p++)
|
||||||
|
if (*p != '.' && !(*p >= '0' && *p <= '9'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *check_wireless_status_at_path(struct impl *this, const char *path)
|
||||||
|
{
|
||||||
|
char wireless_path[PATH_MAX];
|
||||||
|
int res;
|
||||||
|
|
||||||
|
res = spa_scnprintf(wireless_path, sizeof(wireless_path),
|
||||||
|
"%s/wireless_status", path);
|
||||||
|
if (res < 0 || (size_t)res >= sizeof(wireless_path))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (access(wireless_path, R_OK) < 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return strdup(wireless_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *search_siblings_for_wireless_status(struct impl *this, const char *parent_path)
|
||||||
|
{
|
||||||
|
struct dirent *entry;
|
||||||
|
char *result = NULL;
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
spa_autoptr(DIR) parent_dir = opendir(parent_path);
|
||||||
|
if (!parent_dir)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
while ((entry = readdir(parent_dir)) != NULL) {
|
||||||
|
char sibling_path[PATH_MAX];
|
||||||
|
char *path;
|
||||||
|
int res;
|
||||||
|
|
||||||
|
if (entry->d_name[0] == '.')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!is_usb_interface_dir(entry->d_name))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (entry->d_type != DT_UNKNOWN && entry->d_type != DT_DIR)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
res = spa_scnprintf(sibling_path, sizeof(sibling_path),
|
||||||
|
"%s/%s", parent_path, entry->d_name);
|
||||||
|
if (res < 0 || (size_t)res >= sizeof(sibling_path))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
path = check_wireless_status_at_path(this, sibling_path);
|
||||||
|
if (path) {
|
||||||
|
count++;
|
||||||
|
if (!result)
|
||||||
|
result = path;
|
||||||
|
else
|
||||||
|
free(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 1)
|
||||||
|
spa_log_info(this->log, "found %d wireless_status files, using first one", count);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *find_wireless_status_path(struct impl *this, const char *card_syspath)
|
||||||
|
{
|
||||||
|
char usb_device_path[PATH_MAX];
|
||||||
|
char *last_slash, *result;
|
||||||
|
int i, res;
|
||||||
|
|
||||||
|
res = spa_scnprintf(usb_device_path, sizeof(usb_device_path), "%s", card_syspath);
|
||||||
|
if (res < 0 || (size_t)res >= sizeof(usb_device_path))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
result = check_wireless_status_at_path(this, usb_device_path);
|
||||||
|
if (result)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
for (i = 0; i < MAX_PARENT_TRAVERSAL_DEPTH; i++) {
|
||||||
|
last_slash = strrchr(usb_device_path, '/');
|
||||||
|
if (!last_slash || last_slash == usb_device_path)
|
||||||
|
break;
|
||||||
|
|
||||||
|
*last_slash = '\0';
|
||||||
|
|
||||||
|
result = search_siblings_for_wireless_status(this, usb_device_path);
|
||||||
|
if (result)
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stop_wireless_status_monitor(struct impl *this, struct card *card)
|
||||||
|
{
|
||||||
|
if (card->wireless_status_fd >= 0) {
|
||||||
|
spa_loop_remove_source(this->main_loop, &card->wireless_status_source);
|
||||||
|
close(card->wireless_status_fd);
|
||||||
|
card->wireless_status_fd = -1;
|
||||||
|
}
|
||||||
|
if (card->wireless_status_path) {
|
||||||
|
free(card->wireless_status_path);
|
||||||
|
card->wireless_status_path = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int start_wireless_status_monitor(struct impl *this, struct card *card, const char *path)
|
||||||
|
{
|
||||||
|
char buf[16];
|
||||||
|
ssize_t sz;
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
fd = open(path, O_RDONLY | O_CLOEXEC);
|
||||||
|
if (fd < 0) {
|
||||||
|
spa_log_debug(this->log, "card %u: failed to open wireless_status: %s",
|
||||||
|
card->card_nr, spa_strerror(-errno));
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
sz = read(fd, buf, sizeof(buf) - 1);
|
||||||
|
if (sz <= 0) {
|
||||||
|
spa_log_debug(this->log, "card %u: failed to read wireless_status: %s",
|
||||||
|
card->card_nr, spa_strerror(sz < 0 ? -errno : -EIO));
|
||||||
|
close(fd);
|
||||||
|
return sz < 0 ? -errno : -EIO;
|
||||||
|
}
|
||||||
|
|
||||||
|
lseek(fd, 0, SEEK_SET);
|
||||||
|
|
||||||
|
card->wireless_status_path = strdup(path);
|
||||||
|
if (!card->wireless_status_path) {
|
||||||
|
close(fd);
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
card->wireless_status_fd = fd;
|
||||||
|
card->wireless_status_source.func = on_wireless_status_change;
|
||||||
|
card->wireless_status_source.data = card;
|
||||||
|
card->wireless_status_source.fd = fd;
|
||||||
|
card->wireless_status_source.mask = SPA_IO_ERR;
|
||||||
|
|
||||||
|
spa_loop_add_source(this->main_loop, &card->wireless_status_source);
|
||||||
|
|
||||||
|
spa_log_info(this->log, "card %u: monitoring wireless_status at %s",
|
||||||
|
card->card_nr, path);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_wireless_status_change(struct spa_source *source)
|
||||||
|
{
|
||||||
|
struct card *card = source->data;
|
||||||
|
struct impl *this = card->impl;
|
||||||
|
enum wireless_status status;
|
||||||
|
bool disconnected;
|
||||||
|
char buf[16];
|
||||||
|
ssize_t sz;
|
||||||
|
|
||||||
|
lseek(card->wireless_status_fd, 0, SEEK_SET);
|
||||||
|
sz = read(card->wireless_status_fd, buf, sizeof(buf) - 1);
|
||||||
|
if (sz <= 0) {
|
||||||
|
spa_log_info(this->log, "card %u wireless_status unreadable, removing monitor",
|
||||||
|
card->card_nr);
|
||||||
|
stop_wireless_status_monitor(this, card);
|
||||||
|
card->wireless_disconnected = false;
|
||||||
|
process_card(this, ACTION_CHANGE, card);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[sz] = '\0';
|
||||||
|
if (buf[sz - 1] == '\n')
|
||||||
|
buf[sz - 1] = '\0';
|
||||||
|
|
||||||
|
status = parse_wireless_status(buf);
|
||||||
|
if (status == WIRELESS_STATUS_UNKNOWN) {
|
||||||
|
spa_log_info(this->log, "card %u wireless_status unknown, removing monitor",
|
||||||
|
card->card_nr);
|
||||||
|
stop_wireless_status_monitor(this, card);
|
||||||
|
card->wireless_disconnected = false;
|
||||||
|
process_card(this, ACTION_CHANGE, card);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnected = (status == WIRELESS_STATUS_DISCONNECTED);
|
||||||
|
if (disconnected != card->wireless_disconnected) {
|
||||||
|
spa_log_info(this->log, "card %u wireless device %s",
|
||||||
|
card->card_nr, disconnected ? "disconnected" : "connected");
|
||||||
|
card->wireless_disconnected = disconnected;
|
||||||
|
process_card(this, ACTION_CHANGE, card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int check_device_pcm_class(const char *devname)
|
static int check_device_pcm_class(const char *devname)
|
||||||
{
|
{
|
||||||
char path[PATH_MAX];
|
char path[PATH_MAX];
|
||||||
@ -485,6 +748,30 @@ static int emit_added_object_info(struct impl *this, struct card *card)
|
|||||||
|
|
||||||
snprintf(path, sizeof(path), "hw:%u", card->card_nr);
|
snprintf(path, sizeof(path), "hw:%u", card->card_nr);
|
||||||
|
|
||||||
|
if (card->wireless_status_fd < 0) {
|
||||||
|
const char *syspath = udev_device_get_syspath(udev_device);
|
||||||
|
char *wireless_path;
|
||||||
|
|
||||||
|
if (syspath && (wireless_path = find_wireless_status_path(this, syspath))) {
|
||||||
|
enum wireless_status status = read_wireless_status(wireless_path);
|
||||||
|
|
||||||
|
if (status == WIRELESS_STATUS_DISCONNECTED) {
|
||||||
|
spa_log_info(this->log, "card %u: wireless device disconnected, not emitting",
|
||||||
|
card->card_nr);
|
||||||
|
free(wireless_path);
|
||||||
|
card->wireless_disconnected = true;
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == WIRELESS_STATUS_CONNECTED) {
|
||||||
|
if (start_wireless_status_monitor(this, card, wireless_path) < 0)
|
||||||
|
free(wireless_path);
|
||||||
|
} else {
|
||||||
|
free(wireless_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((res = check_pcm_device_availability(this, card, &num_pcm_devices)) < 0)
|
if ((res = check_pcm_device_availability(this, card, &num_pcm_devices)) < 0)
|
||||||
return res;
|
return res;
|
||||||
if ((res = check_compress_offload_device_availability(this, card, &num_compress_offload_devices)) < 0)
|
if ((res = check_compress_offload_device_availability(this, card, &num_compress_offload_devices)) < 0)
|
||||||
@ -731,7 +1018,7 @@ static void process_card(struct impl *this, enum action action, struct card *car
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case ACTION_CHANGE: {
|
case ACTION_CHANGE: {
|
||||||
check_access(this, card);
|
check_access(this, card);
|
||||||
if (card->accessible && !card->emitted) {
|
if (card->accessible && !card->wireless_disconnected && !card->emitted) {
|
||||||
int res = emit_added_object_info(this, card);
|
int res = emit_added_object_info(this, card);
|
||||||
if (res < 0) {
|
if (res < 0) {
|
||||||
if (card->ignored)
|
if (card->ignored)
|
||||||
@ -750,7 +1037,7 @@ static void process_card(struct impl *this, enum action action, struct card *car
|
|||||||
card->card_nr);
|
card->card_nr);
|
||||||
card->unavailable = false;
|
card->unavailable = false;
|
||||||
}
|
}
|
||||||
} else if (!card->accessible && card->emitted) {
|
} else if ((!card->accessible || card->wireless_disconnected) && card->emitted) {
|
||||||
card->emitted = false;
|
card->emitted = false;
|
||||||
|
|
||||||
if (card->pcm_device_id != ID_DEVICE_NOT_SUPPORTED)
|
if (card->pcm_device_id != ID_DEVICE_NOT_SUPPORTED)
|
||||||
@ -900,18 +1187,17 @@ static void impl_on_fd_events(struct spa_source *source)
|
|||||||
if (udev_device == NULL)
|
if (udev_device == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if ((action = udev_device_get_action(udev_device)) == NULL)
|
action = udev_device_get_action(udev_device);
|
||||||
|
if (action == NULL)
|
||||||
action = "change";
|
action = "change";
|
||||||
|
|
||||||
spa_log_debug(this->log, "action %s", action);
|
|
||||||
|
|
||||||
start_inotify(this);
|
start_inotify(this);
|
||||||
|
|
||||||
if (spa_streq(action, "add") || spa_streq(action, "change")) {
|
if (spa_streq(action, "add") || spa_streq(action, "change"))
|
||||||
process_udev_device(this, ACTION_CHANGE, udev_device);
|
process_udev_device(this, ACTION_CHANGE, udev_device);
|
||||||
} else if (spa_streq(action, "remove")) {
|
else if (spa_streq(action, "remove"))
|
||||||
process_udev_device(this, ACTION_REMOVE, udev_device);
|
process_udev_device(this, ACTION_REMOVE, udev_device);
|
||||||
}
|
|
||||||
udev_device_unref(udev_device);
|
udev_device_unref(udev_device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user