From b8420ba31cc836210074d3c732b3959d57008367 Mon Sep 17 00:00:00 2001 From: "Geoffrey D. Bennett" Date: Sat, 8 Mar 2025 06:12:20 +1030 Subject: [PATCH] Add support for rebooting devices using the FCP socket interface --- src/fcp-shared.c | 19 ++++ src/fcp-shared.h | 80 ++++++++++++++++ src/fcp-socket.c | 220 +++++++++++++++++++++++++++++++++++++++++++ src/fcp-socket.h | 27 ++++++ src/window-startup.c | 41 +++++--- 5 files changed, 373 insertions(+), 14 deletions(-) create mode 100644 src/fcp-shared.c create mode 100644 src/fcp-shared.h create mode 100644 src/fcp-socket.c create mode 100644 src/fcp-socket.h diff --git a/src/fcp-shared.c b/src/fcp-shared.c new file mode 100644 index 0000000..09c44f6 --- /dev/null +++ b/src/fcp-shared.c @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +// Error messages +const char *fcp_socket_error_messages[] = { + "Success", + "Invalid magic", + "Invalid command", + "Invalid length", + "Invalid hash", + "Firmware PID does not match USB PID", + "Configuration error (check fcp-server log)", + "FCP communication error", + "Timeout", + "Read error", + "Write error", + "Not running leapfrog firmware", + "Invalid state" +}; diff --git a/src/fcp-shared.h b/src/fcp-shared.h new file mode 100644 index 0000000..1849bd0 --- /dev/null +++ b/src/fcp-shared.h @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +// Error codes +#define FCP_SOCKET_ERR_INVALID_MAGIC 1 +#define FCP_SOCKET_ERR_INVALID_COMMAND 2 +#define FCP_SOCKET_ERR_INVALID_LENGTH 3 +#define FCP_SOCKET_ERR_INVALID_HASH 4 +#define FCP_SOCKET_ERR_INVALID_USB_ID 5 +#define FCP_SOCKET_ERR_CONFIG 6 +#define FCP_SOCKET_ERR_FCP 7 +#define FCP_SOCKET_ERR_TIMEOUT 8 +#define FCP_SOCKET_ERR_READ 9 +#define FCP_SOCKET_ERR_WRITE 10 +#define FCP_SOCKET_ERR_NOT_LEAPFROG 11 +#define FCP_SOCKET_ERR_INVALID_STATE 12 +#define FCP_SOCKET_ERR_MAX 12 + +// Protocol constants +#define FCP_SOCKET_PROTOCOL_VERSION 1 +#define FCP_SOCKET_MAGIC_REQUEST 0x53 +#define FCP_SOCKET_MAGIC_RESPONSE 0x73 + +// Maximum payload length (2MB) +#define MAX_PAYLOAD_LENGTH 2 * 1024 * 1024 + +// Request types +#define FCP_SOCKET_REQUEST_REBOOT 0x0001 +#define FCP_SOCKET_REQUEST_CONFIG_ERASE 0x0002 +#define FCP_SOCKET_REQUEST_APP_FIRMWARE_ERASE 0x0003 +#define FCP_SOCKET_REQUEST_APP_FIRMWARE_UPDATE 0x0004 +#define FCP_SOCKET_REQUEST_ESP_FIRMWARE_UPDATE 0x0005 + +// Response types +#define FCP_SOCKET_RESPONSE_VERSION 0x00 +#define FCP_SOCKET_RESPONSE_SUCCESS 0x01 +#define FCP_SOCKET_RESPONSE_ERROR 0x02 +#define FCP_SOCKET_RESPONSE_PROGRESS 0x03 + +extern const char *fcp_socket_error_messages[]; + +// Message structures +#pragma pack(push, 1) + +struct fcp_socket_msg_header { + uint8_t magic; + uint8_t msg_type; + uint32_t payload_length; +}; + +struct firmware_payload { + uint32_t size; + uint16_t usb_vid; + uint16_t usb_pid; + uint8_t sha256[32]; + uint8_t md5[16]; + uint8_t data[]; +}; + +struct version_msg { + struct fcp_socket_msg_header header; + uint8_t version; +}; + +struct progress_msg { + struct fcp_socket_msg_header header; + uint8_t percent; +}; + +struct error_msg { + struct fcp_socket_msg_header header; + int16_t error_code; +}; + +#pragma pack(pop) + diff --git a/src/fcp-socket.c b/src/fcp-socket.c new file mode 100644 index 0000000..2de1201 --- /dev/null +++ b/src/fcp-socket.c @@ -0,0 +1,220 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fcp-shared.h" +#include "fcp-socket.h" +#include "error.h" + +// Connect to the FCP socket server for the given card +int fcp_socket_connect(struct alsa_card *card) { + if (!card || !card->fcp_socket) { + fprintf(stderr, "FCP socket path is not available"); + return -1; + } + + int sock_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock_fd < 0) { + fprintf(stderr, "Cannot create socket: %s", strerror(errno)); + return -1; + } + + struct sockaddr_un addr = { + .sun_family = AF_UNIX + }; + strncpy(addr.sun_path, card->fcp_socket, sizeof(addr.sun_path) - 1); + + if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { + fprintf(stderr, "Cannot connect to server at %s: %s", + addr.sun_path, strerror(errno)); + close(sock_fd); + return -1; + } + + return sock_fd; +} + +// Send a simple command with no payload to the server +int fcp_socket_send_command(int sock_fd, uint8_t command) { + struct fcp_socket_msg_header header = { + .magic = FCP_SOCKET_MAGIC_REQUEST, + .msg_type = command, + .payload_length = 0 + }; + + if (write(sock_fd, &header, sizeof(header)) != sizeof(header)) { + fprintf(stderr, "Error sending command: %s", strerror(errno)); + return -1; + } + + return 0; +} + +// Handle server responses from a command +int fcp_socket_handle_response(int sock_fd, bool show_progress) { + struct fcp_socket_msg_header header; + ssize_t bytes_read; + + // Read response header + bytes_read = read(sock_fd, &header, sizeof(header)); + if (bytes_read != sizeof(header)) { + if (bytes_read == 0) { + // Server closed the connection + return 0; + } + fprintf(stderr, "Error reading response header: %s", strerror(errno)); + return -1; + } + + // Verify the magic value + if (header.magic != FCP_SOCKET_MAGIC_RESPONSE) { + fprintf(stderr, "Invalid response magic: 0x%02x", header.magic); + return -1; + } + + // Handle different response types + switch (header.msg_type) { + case FCP_SOCKET_RESPONSE_VERSION: { + // Protocol version response + uint8_t version; + bytes_read = read(sock_fd, &version, sizeof(version)); + if (bytes_read != sizeof(version)) { + fprintf(stderr, "Error reading version: %s", strerror(errno)); + return -1; + } + // Protocol version mismatch? + if (version != FCP_SOCKET_PROTOCOL_VERSION) { + fprintf(stderr, "Protocol version mismatch: expected %d, got %d", + FCP_SOCKET_PROTOCOL_VERSION, version); + return -1; + } + break; + } + + case FCP_SOCKET_RESPONSE_SUCCESS: + // Command completed successfully + return 0; + + case FCP_SOCKET_RESPONSE_ERROR: { + // Error response + int16_t error_code; + bytes_read = read(sock_fd, &error_code, sizeof(error_code)); + if (bytes_read != sizeof(error_code)) { + fprintf(stderr, "Error reading error code: %s", strerror(errno)); + return -1; + } + + if (error_code > 0 && error_code <= FCP_SOCKET_ERR_MAX) { + fprintf(stderr, "Server error: %s", fcp_socket_error_messages[error_code]); + } else { + fprintf(stderr, "Unknown server error code: %d", error_code); + } + return -1; + } + + case FCP_SOCKET_RESPONSE_PROGRESS: { + // Progress update + if (show_progress) { + uint8_t percent; + bytes_read = read(sock_fd, &percent, sizeof(percent)); + if (bytes_read != sizeof(percent)) { + fprintf(stderr, "Error reading progress: %s", strerror(errno)); + return -1; + } + fprintf(stderr, "\rProgress: %d%%", percent); + if (percent == 100) + fprintf(stderr, "\n"); + } else { + // Skip the progress byte + uint8_t dummy; + if (read(sock_fd, &dummy, sizeof(dummy)) < 0) { + fprintf(stderr, "Error reading progress: %s", strerror(errno)); + return -1; + } + } + + // Continue reading responses + return fcp_socket_handle_response(sock_fd, show_progress); + } + + default: + fprintf(stderr, "Unknown response type: 0x%02x", header.msg_type); + return -1; + } + + return 0; +} + +// Wait for server to disconnect (used after reboot command) +int fcp_socket_wait_for_disconnect(int sock_fd) { + fd_set rfds; + struct timeval tv, start_time, now; + char buf[1]; + const int TIMEOUT_SECS = 2; + + gettimeofday(&start_time, NULL); + + while (1) { + FD_ZERO(&rfds); + FD_SET(sock_fd, &rfds); + + gettimeofday(&now, NULL); + int elapsed = now.tv_sec - start_time.tv_sec; + if (elapsed >= TIMEOUT_SECS) { + fprintf(stderr, "Timeout waiting for server disconnect\n"); + return -1; + } + + tv.tv_sec = TIMEOUT_SECS - elapsed; + tv.tv_usec = 0; + + int ret = select(sock_fd + 1, &rfds, NULL, NULL, &tv); + if (ret < 0) { + if (errno == EINTR) + continue; + fprintf(stderr, "Select error: %s\n", strerror(errno)); + return -1; + } + + if (ret > 0) { + // Try to read one byte + ssize_t n = read(sock_fd, buf, 1); + if (n < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + fprintf(stderr, "Read error: %s\n", strerror(errno)); + return -1; + } + if (n == 0) { + // EOF received - server has disconnected + return 0; + } + // Ignore any data received, just keep waiting for EOF + } + } +} + +// Reboot a device using the FCP socket interface +int fcp_socket_reboot_device(struct alsa_card *card) { + int sock_fd, ret = -1; + + sock_fd = fcp_socket_connect(card); + if (sock_fd < 0) + return -1; + + // Send reboot command and wait for server to disconnect + if (fcp_socket_send_command(sock_fd, FCP_SOCKET_REQUEST_REBOOT) == 0) + ret = fcp_socket_wait_for_disconnect(sock_fd); + + close(sock_fd); + return ret; +} diff --git a/src/fcp-socket.h b/src/fcp-socket.h new file mode 100644 index 0000000..2e63dc3 --- /dev/null +++ b/src/fcp-socket.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include "alsa.h" + +// Connect to the FCP socket server for the given card +// Returns socket file descriptor on success, -1 on error +int fcp_socket_connect(struct alsa_card *card); + +// Send a simple command with no payload to the server +// Returns 0 on success, -1 on error +int fcp_socket_send_command(int sock_fd, uint8_t command); + +// Handle server responses from a command +// Returns 0 on success, -1 on error +int fcp_socket_handle_response(int sock_fd, bool show_progress); + +// Wait for server to disconnect (used after reboot command) +// Returns 0 if disconnected, -1 on timeout or error +int fcp_socket_wait_for_disconnect(int sock_fd); + +// Reboot a device using the FCP socket interface +// Returns 0 on success, -1 on error +int fcp_socket_reboot_device(struct alsa_card *card); \ No newline at end of file diff --git a/src/window-startup.c b/src/window-startup.c index acf063e..968b3f5 100644 --- a/src/window-startup.c +++ b/src/window-startup.c @@ -3,6 +3,7 @@ #include "device-reset-config.h" #include "device-update-firmware.h" +#include "fcp-socket.h" #include "gtkhelper.h" #include "scarlett2.h" #include "scarlett2-ioctls.h" @@ -236,21 +237,32 @@ static void add_reset_action( } static void reboot_device(GtkWidget *button, struct alsa_card *card) { - snd_hwdep_t *hwdep; + int err = 0; - int err = scarlett2_open_card(card->device, &hwdep); - if (err < 0) { - fprintf(stderr, "unable to open hwdep interface: %s\n", snd_strerror(err)); - return; + // HWDEP (Scarlett2) driver type + if (card->driver_type == DRIVER_TYPE_HWDEP) { + snd_hwdep_t *hwdep; + + err = scarlett2_open_card(card->device, &hwdep); + if (err < 0) { + fprintf(stderr, "unable to open hwdep interface: %s\n", snd_strerror(err)); + return; + } + + err = scarlett2_reboot(hwdep); + if (err < 0) { + fprintf(stderr, "unable to reboot device: %s\n", snd_strerror(err)); + return; + } + + scarlett2_close(hwdep); + + // Socket (FCP) driver type + } else if (card->driver_type == DRIVER_TYPE_SOCKET) { + err = fcp_socket_reboot_device(card); + if (err < 0) + fprintf(stderr, "unable to reboot device via socket\n"); } - - err = scarlett2_reboot(hwdep); - if (err < 0) { - fprintf(stderr, "unable to reboot device: %s\n", snd_strerror(err)); - return; - } - - scarlett2_close(hwdep); } static void add_reset_actions( @@ -259,7 +271,8 @@ static void add_reset_actions( int *grid_y, int show_reboot_option ) { - if (card->driver_type != DRIVER_TYPE_HWDEP) + if (card->driver_type != DRIVER_TYPE_HWDEP && + card->driver_type != DRIVER_TYPE_SOCKET) return; // Add reboot action if there is a control that requires a reboot