After commit c018f5fd29 Fenrir is being switched over to go. If you do not want to be part of the testing process, please switch to master.

This commit is contained in:
Storm Dragon committed 2026-10-27 20:30:55 -04:00
1 parent c018f5fd29
commit 0e50de4c76
1047 files changed
+58364 -63747

No files matched your search

+203
View File
@@ -0,0 +1,203 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
#include "hardware_native.h"
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/file.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
struct SpeechSerial {
int fd;
struct termios original;
};
SpeechSerial *speech_serial_open(const char *path, int baud, int xon, int *status) {
SpeechSerial *port = calloc(1, sizeof(*port));
*status = -ENOMEM;
if (!port) return NULL;
port->fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK | O_CLOEXEC);
if (port->fd < 0) goto failed;
// A short-lived candidate must never configure another reader's device.
if (flock(port->fd, LOCK_EX | LOCK_NB) < 0) goto failed;
if (tcgetattr(port->fd, &port->original) < 0) goto failed;
struct termios options = port->original;
cfmakeraw(&options);
options.c_cflag |= CLOCAL | CREAD;
options.c_cflag &= ~(CRTSCTS | CSTOPB);
options.c_iflag &= ~(IXON | IXOFF | IXANY);
if (xon) options.c_iflag |= IXON;
options.c_cc[VSTART] = 0x11;
options.c_cc[VSTOP] = 0x13;
speed_t speed;
switch (baud) {
case 1200: speed = B1200; break;
case 2400: speed = B2400; break;
case 4800: speed = B4800; break;
case 9600: speed = B9600; break;
case 19200: speed = B19200; break;
case 38400: speed = B38400; break;
case 57600: speed = B57600; break;
case 115200: speed = B115200; break;
default: errno = EINVAL; goto failed;
}
cfsetispeed(&options, speed);
cfsetospeed(&options, speed);
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 0;
if (tcsetattr(port->fd, TCSANOW, &options) < 0) goto failed;
*status = 0;
return port;
failed:
*status = -errno;
if (port->fd >= 0) close(port->fd);
free(port);
return NULL;
}
int speech_serial_write(SpeechSerial *port, const void *data, size_t length) {
ssize_t result = write(port->fd, data, length);
return result < 0 ? -errno : (int)result;
}
int speech_serial_cancel(SpeechSerial *port, unsigned char command) {
if (tcflush(port->fd, TCOFLUSH) < 0) return -errno;
if (tcflow(port->fd, TCOON) < 0) return -errno;
// TCOON clears an explicit tcflow stop, but not a received XOFF in Linux's
// N_TTY discipline. Temporarily clearing IXON restarts that output path.
// Restore software flow control immediately after submitting the cancel.
struct termios options;
if (tcgetattr(port->fd, &options) < 0) return -errno;
int xon = (options.c_iflag & IXON) != 0;
if (xon) {
struct termios resumed = options;
resumed.c_iflag &= ~IXON;
if (tcsetattr(port->fd, TCSANOW, &resumed) < 0) return -errno;
}
int result = speech_serial_write(port, &command, 1);
if (xon && tcsetattr(port->fd, TCSANOW, &options) < 0) return -errno;
return result;
}
void speech_serial_close(SpeechSerial *port) {
tcsetattr(port->fd, TCSANOW, &port->original);
close(port->fd);
free(port);
}
// Optional libusb 1.0 ABI, resolved at runtime like Enchant. Neither headers
// nor a link-time libusb dependency are required for other speech drivers.
typedef struct libusb_context USBContext;
typedef struct libusb_device USBDevice;
typedef struct libusb_device_handle USBHandle;
struct USBDescriptor {
uint8_t length, type;
uint16_t usb;
uint8_t device_class, subclass, protocol, packet_size;
uint16_t vendor, product, device;
uint8_t manufacturer, product_name, serial, configurations;
};
struct SpeechUSB {
void *library;
USBContext *context;
USBHandle *handle;
int claimed;
int (*init)(USBContext **);
void (*exit)(USBContext *);
ssize_t (*get_device_list)(USBContext *, USBDevice ***);
void (*free_device_list)(USBDevice **, int);
int (*get_device_descriptor)(USBDevice *, struct USBDescriptor *);
int (*open)(USBDevice *, USBHandle **);
void (*close)(USBHandle *);
int (*get_configuration)(USBHandle *, int *);
int (*set_configuration)(USBHandle *, int);
int (*claim_interface)(USBHandle *, int);
int (*release_interface)(USBHandle *, int);
int (*bulk_transfer)(USBHandle *, unsigned char, unsigned char *, int, int *, unsigned int);
};
void speech_usb_close(SpeechUSB *port) {
if (!port) return;
if (port->claimed) port->release_interface(port->handle, 0);
if (port->handle) port->close(port->handle);
if (port->context) port->exit(port->context);
if (port->library) dlclose(port->library);
free(port);
}
static SpeechUSB *load_usb(void) {
SpeechUSB *port = calloc(1, sizeof(*port));
if (!port) return NULL;
port->library = dlopen("libusb-1.0.so.0", RTLD_NOW | RTLD_LOCAL);
if (!port->library) goto failed;
#define LOAD(field) port->field = dlsym(port->library, "libusb_" #field); if (!port->field) goto failed
LOAD(init); LOAD(exit); LOAD(get_device_list); LOAD(free_device_list);
LOAD(get_device_descriptor); LOAD(open); LOAD(close);
LOAD(get_configuration); LOAD(set_configuration);
LOAD(claim_interface); LOAD(release_interface); LOAD(bulk_transfer);
#undef LOAD
return port;
failed:
speech_usb_close(port);
return NULL;
}
int speech_usb_available(void) {
SpeechUSB *port = load_usb();
if (!port) return 0;
speech_usb_close(port);
return 1;
}
SpeechUSB *speech_usb_open(int *status) {
*status = -100;
SpeechUSB *port = load_usb();
if (!port) return NULL;
*status = port->init(&port->context);
if (*status < 0) goto failed;
USBDevice **devices = NULL;
ssize_t count = port->get_device_list(port->context, &devices);
if (count < 0) { *status = (int)count; goto failed; }
USBDevice *selected = NULL;
int matches = 0;
for (ssize_t index = 0; index < count; index++) {
struct USBDescriptor descriptor;
if (port->get_device_descriptor(devices[index], &descriptor) == 0 &&
descriptor.vendor == 0x0dd0 && descriptor.product == 0x2001) {
selected = devices[index];
matches++;
}
}
*status = matches == 0 ? -4 : -101;
if (matches == 1) *status = port->open(selected, &port->handle);
port->free_device_list(devices, 1);
if (*status < 0) goto failed;
int configuration = 0;
*status = port->get_configuration(port->handle, &configuration);
if (*status < 0) goto failed;
if (configuration == 0) {
*status = port->set_configuration(port->handle, 1);
if (*status < 0) goto failed;
}
*status = port->claim_interface(port->handle, 0);
if (*status < 0) goto failed;
port->claimed = 1;
return port;
failed:
speech_usb_close(port);
return NULL;
}
int speech_usb_write(SpeechUSB *port, unsigned char endpoint, void *data, int length) {
int transferred = 0;
int result = port->bulk_transfer(port->handle, endpoint, data, length, &transferred, 1000);
// Never replay a partial failed transfer: it may already be on the wire.
if (result < 0) return result;
return transferred == length ? 0 : -102;
}