Add support for waiting for FCP driver initialisation

When a card using the FCP driver is added at runtime, we need to wait
for fcp-server to finish creating all the controls before we attempt
to enumerate them.
This commit is contained in:
Geoffrey D. Bennett
2025-03-08 04:38:58 +10:30
parent 6f0ab1890d
commit 97f993db7b
4 changed files with 237 additions and 22 deletions

View File

@@ -792,6 +792,77 @@ static void card_destroy_callback(void *data) {
}
}
// Complete card initialisation after the driver is ready
static void complete_card_init(struct alsa_card *card) {
// Get full element list and create main window
alsa_get_elem_list(card);
alsa_set_lr_nums(card);
alsa_get_routing_controls(card);
card->best_firmware_version = scarlett2_get_best_firmware_version(card->pid);
if (card->serial) {
// Call the reopen callbacks for this card
struct reopen_callback *rc = g_hash_table_lookup(
reopen_callbacks, card->serial
);
if (rc)
rc->callback(rc->data);
g_hash_table_remove(reopen_callbacks, card->serial);
}
create_card_window(card);
}
// Check if the Firmware Version control has a TLV and is locked,
// indicating the driver is ready
static int check_driver_ready(snd_ctl_elem_info_t *info) {
return snd_ctl_elem_info_is_tlv_readable(info) &&
snd_ctl_elem_info_is_locked(info);
}
// Check if the FCP driver is initialised
static void check_driver_init(
struct alsa_card *card, int numid, unsigned int mask
) {
// Ignore controls going away
if (mask == SND_CTL_EVENT_MASK_REMOVE)
return;
// Get the control's info
snd_ctl_elem_id_t *id;
snd_ctl_elem_info_t *info;
snd_ctl_elem_id_alloca(&id);
snd_ctl_elem_info_alloca(&info);
snd_ctl_elem_id_set_numid(id, numid);
snd_ctl_elem_info_set_id(info, id);
if (snd_ctl_elem_info(card->handle, info) < 0) {
fprintf(stderr, "error getting elem info %d\n", numid);
return;
}
const char *name = snd_ctl_elem_info_get_name(info);
// Check if it's the Firmware Version control being updated
if (strcmp(name, "Firmware Version"))
return;
// Check if the driver is ready
if (!check_driver_ready(info))
return;
// The driver is initialised; update the card's driver type
card->driver_type = DRIVER_TYPE_SOCKET;
// Complete the card initialisation
complete_card_init(card);
}
static gboolean alsa_card_callback(
GIOChannel *source,
GIOCondition condition,
@@ -823,6 +894,13 @@ static gboolean alsa_card_callback(
int numid = snd_ctl_event_elem_get_numid(event);
unsigned int mask = snd_ctl_event_elem_get_mask(event);
// Check if we're waiting for FCP driver to initialise and check if
// it's now ready
if (card->driver_type == DRIVER_TYPE_SOCKET_UNINIT) {
check_driver_init(card, numid, mask);
return 1;
}
if (mask == SND_CTL_EVENT_MASK_REMOVE) {
card_destroy_callback(card);
return 0;
@@ -1091,8 +1169,7 @@ static int check_firmware_version_locked(struct alsa_card *card) {
if (err < 0)
return 0;
return snd_ctl_elem_info_is_writable(info) &&
snd_ctl_elem_info_is_locked(info);
return check_driver_ready(info);
}
// return the driver type for this card
@@ -1145,31 +1222,20 @@ static int get_driver_type(struct alsa_card *card) {
}
static void card_init(struct alsa_card *card) {
card->driver_type = get_driver_type(card);
alsa_get_elem_list(card);
alsa_set_lr_nums(card);
alsa_get_routing_controls(card);
alsa_subscribe(card);
alsa_get_usbid(card);
alsa_get_serial_number(card);
card->best_firmware_version = scarlett2_get_best_firmware_version(card->pid);
alsa_subscribe(card);
alsa_add_card_callback(card);
if (card->serial) {
card->driver_type = get_driver_type(card);
// call the reopen callbacks for this card
struct reopen_callback *rc = g_hash_table_lookup(
reopen_callbacks, card->serial
);
if (rc)
rc->callback(rc->data);
g_hash_table_remove(reopen_callbacks, card->serial);
// Driver not ready? Create the iface-waiting window
if (card->driver_type == DRIVER_TYPE_SOCKET_UNINIT) {
create_card_window(card);
return;
}
create_card_window(card);
alsa_add_card_callback(card);
complete_card_init(card);
}
static void alsa_scan_cards(void) {

117
src/iface-waiting.c Normal file
View File

@@ -0,0 +1,117 @@
// SPDX-FileCopyrightText: 2025 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <gtk/gtk.h>
#include "alsa.h"
#include "iface-waiting.h"
#include "scarlett2-ioctls.h"
#include "window-iface.h"
// Structure to hold timeout-related widgets
struct timeout_data {
GtkWidget *box;
GtkWidget *spinner;
GtkWidget *message_label;
guint timeout_id;
};
// Timeout callback function
static gboolean on_timeout(gpointer user_data) {
struct timeout_data *data = (struct timeout_data *)user_data;
// Remove spinner
gtk_box_remove(GTK_BOX(data->box), data->spinner);
// Update message with clickable link
if (data->message_label && GTK_IS_WIDGET(data->message_label))
gtk_label_set_markup(
GTK_LABEL(data->message_label),
"Driver not detected. Please ensure "
"<span font='monospace'>fcp-server</span> from "
"<a href=\"https://github.com/geoffreybennett/fcp-support\">"
"https://github.com/geoffreybennett/fcp-support</a> "
"has been installed."
);
// Reset the timeout ID since it won't be called again
data->timeout_id = 0;
// Return FALSE to prevent the timeout from repeating
return FALSE;
}
// Weak reference callback for cleanup
static void on_widget_dispose(gpointer data, GObject *where_the_object_was) {
struct timeout_data *timeout_data = (struct timeout_data *)data;
// Cancel the timeout if it's still active
if (timeout_data->timeout_id > 0)
g_source_remove(timeout_data->timeout_id);
// Free the data structure
g_free(timeout_data);
}
GtkWidget *create_iface_waiting_main(struct alsa_card *card) {
struct timeout_data *data;
// Main vertical box
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 20);
gtk_widget_set_margin_start(box, 40);
gtk_widget_set_margin_end(box, 40);
gtk_widget_set_margin_top(box, 40);
gtk_widget_set_margin_bottom(box, 40);
// Heading
GtkWidget *label = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(label),
"<span weight='bold' size='large'>Waiting for FCP Server</span>");
gtk_box_append(GTK_BOX(box), label);
// Add picture (scaled down properly)
GtkWidget *picture_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_set_hexpand(picture_box, TRUE);
gtk_widget_set_halign(picture_box, GTK_ALIGN_CENTER);
GtkWidget *picture = gtk_picture_new_for_resource(
"/vu/b4/alsa-scarlett-gui/icons/vu.b4.alsa-scarlett-gui.png"
);
gtk_picture_set_can_shrink(GTK_PICTURE(picture), TRUE);
gtk_widget_set_size_request(picture, 128, 128);
gtk_box_append(GTK_BOX(picture_box), picture);
gtk_box_append(GTK_BOX(box), picture_box);
// Add spinner
GtkWidget *spinner = gtk_spinner_new();
gtk_spinner_start(GTK_SPINNER(spinner));
gtk_widget_set_size_request(spinner, 48, 48);
gtk_box_append(GTK_BOX(box), spinner);
// Description
label = gtk_label_new(
"Waiting for the user-space FCP driver to initialise..."
);
gtk_label_set_wrap(GTK_LABEL(label), TRUE);
gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
gtk_label_set_max_width_chars(GTK_LABEL(label), 1);
gtk_widget_set_hexpand(label, TRUE);
gtk_widget_set_halign(label, GTK_ALIGN_FILL);
gtk_box_append(GTK_BOX(box), label);
// Setup timeout
data = g_new(struct timeout_data, 1);
data->box = box;
data->spinner = spinner;
data->message_label = label;
// Set timeout
data->timeout_id = g_timeout_add_seconds(5, on_timeout, data);
// Ensure data is freed when the box is destroyed
g_object_weak_ref(G_OBJECT(box), on_widget_dispose, data);
return box;
}

8
src/iface-waiting.h Normal file
View File

@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2025 Geoffrey D. Bennett <g@b4.vu>
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "alsa.h"
GtkWidget *create_iface_waiting_main(struct alsa_card *card);

View File

@@ -8,6 +8,7 @@
#include "iface-none.h"
#include "iface-unknown.h"
#include "iface-update.h"
#include "iface-waiting.h"
#include "main.h"
#include "menu.h"
#include "window-iface.h"
@@ -21,11 +22,34 @@ void create_card_window(struct alsa_card *card) {
gtk_window_destroy(GTK_WINDOW(no_cards_window));
no_cards_window = NULL;
}
window_count++;
// Replacing an existing window
if (card->window_main)
gtk_window_destroy(GTK_WINDOW(card->window_main));
// New window
else
window_count++;
int has_startup = true;
int has_mixer = true;
// Check if the FCP driver is not initialised yet
if (card->driver_type == DRIVER_TYPE_SOCKET_UNINIT) {
card->window_main_contents = create_iface_waiting_main(card);
has_startup = false;
has_mixer = false;
// Create minimal window with only the waiting interface
card->window_main = gtk_application_window_new(app);
gtk_window_set_resizable(GTK_WINDOW(card->window_main), FALSE);
gtk_window_set_title(GTK_WINDOW(card->window_main), card->name);
gtk_window_set_child(GTK_WINDOW(card->window_main), card->window_main_contents);
gtk_widget_set_visible(card->window_main, TRUE);
return;
}
struct alsa_elem *msd_elem =
get_elem_by_name(card->elems, "MSD Mode Switch");
int in_msd_mode = msd_elem && alsa_get_elem_value(msd_elem);