Add support for rebooting devices using the FCP socket interface
This commit is contained in:
19
src/fcp-shared.c
Normal file
19
src/fcp-shared.c
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||||
|
// 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"
|
||||||
|
};
|
||||||
80
src/fcp-shared.h
Normal file
80
src/fcp-shared.h
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
220
src/fcp-socket.c
Normal file
220
src/fcp-socket.c
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
27
src/fcp-socket.h
Normal file
27
src/fcp-socket.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Geoffrey D. Bennett <g@b4.vu>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#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);
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "device-reset-config.h"
|
#include "device-reset-config.h"
|
||||||
#include "device-update-firmware.h"
|
#include "device-update-firmware.h"
|
||||||
|
#include "fcp-socket.h"
|
||||||
#include "gtkhelper.h"
|
#include "gtkhelper.h"
|
||||||
#include "scarlett2.h"
|
#include "scarlett2.h"
|
||||||
#include "scarlett2-ioctls.h"
|
#include "scarlett2-ioctls.h"
|
||||||
@@ -236,21 +237,32 @@ static void add_reset_action(
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void reboot_device(GtkWidget *button, struct alsa_card *card) {
|
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);
|
// HWDEP (Scarlett2) driver type
|
||||||
if (err < 0) {
|
if (card->driver_type == DRIVER_TYPE_HWDEP) {
|
||||||
fprintf(stderr, "unable to open hwdep interface: %s\n", snd_strerror(err));
|
snd_hwdep_t *hwdep;
|
||||||
return;
|
|
||||||
|
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(
|
static void add_reset_actions(
|
||||||
@@ -259,7 +271,8 @@ static void add_reset_actions(
|
|||||||
int *grid_y,
|
int *grid_y,
|
||||||
int show_reboot_option
|
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;
|
return;
|
||||||
|
|
||||||
// Add reboot action if there is a control that requires a reboot
|
// Add reboot action if there is a control that requires a reboot
|
||||||
|
|||||||
Reference in New Issue
Block a user