mirror of
https://git.planet-casio.com/Lephenixnoir/fxsdk.git
synced 2024-12-28 04:23:37 +01:00
libfxlink: fix race condition leading to lost messages
Basically if the calculator sends two messages in a row, it is possible for a single libusb_handle_events() to get both. And the comm structure wasn't designed for that, because it could buffer only one message at a time, which the user needed to read after event handling. The comm structure now has a 16-message buffer, which should be more than enough for any single event handling loop. On the user level this has implications in that fxlink_device_finish_bulk_IN() must be called *in a loop* after each event handling cycle. Reported in https://git.planet-casio.com/Lephenixnoir/gint/pulls/27
This commit is contained in:
parent
2d293eeb35
commit
85d7fcf9e9
5 changed files with 81 additions and 19 deletions
|
@ -549,8 +549,8 @@ int main_tui_interactive(libusb_context *ctx)
|
||||||
/* Check for devices with finished transfers */
|
/* Check for devices with finished transfers */
|
||||||
for(int i = 0; i < TUI.devices.count; i++) {
|
for(int i = 0; i < TUI.devices.count; i++) {
|
||||||
struct fxlink_device *fdev = &TUI.devices.devices[i];
|
struct fxlink_device *fdev = &TUI.devices.devices[i];
|
||||||
struct fxlink_message *msg = fxlink_device_finish_bulk_IN(fdev);
|
struct fxlink_message *msg;
|
||||||
if(msg) {
|
while((msg = fxlink_device_finish_bulk_IN(fdev))) {
|
||||||
fxlink_interactive_handle_message(msg);
|
fxlink_interactive_handle_message(msg);
|
||||||
fxlink_message_free(msg, true);
|
fxlink_message_free(msg, true);
|
||||||
fxlink_device_start_bulk_IN(fdev);
|
fxlink_device_start_bulk_IN(fdev);
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
char const *fmt_to_ANSI(int format)
|
char const *fmt_to_ANSI(int format)
|
||||||
{
|
{
|
||||||
|
@ -146,3 +147,26 @@ bool delay_cycle(delay_t *delay)
|
||||||
if(*delay > 0) (*delay)--;
|
if(*delay > 0) (*delay)--;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fxlink_hexdump(char const *data, int size, FILE *fp)
|
||||||
|
{
|
||||||
|
for(int read = 0; read < size; read += 16) {
|
||||||
|
for(int i = 0; i < 16; i++) {
|
||||||
|
if(read + i < size) {
|
||||||
|
int byte = data[read + i] & 0xff;
|
||||||
|
fprintf(fp, " %02x", byte);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
fprintf(fp, " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(fp, " | ");
|
||||||
|
for(int i = 0; i < 16 && read + i < size; i++) {
|
||||||
|
int byte = data[read + i] & 0xff;
|
||||||
|
fprintf(fp, "%c", isprint(byte) ? byte : '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(fp, "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
static void fxlink_device_queue_finished_IN(struct fxlink_device *fdev);
|
||||||
|
|
||||||
char const *fxlink_device_id(struct fxlink_device const *fdev)
|
char const *fxlink_device_id(struct fxlink_device const *fdev)
|
||||||
{
|
{
|
||||||
static char str[32];
|
static char str[32];
|
||||||
|
@ -401,17 +403,18 @@ static void bulk_IN_callback(struct libusb_transfer *transfer)
|
||||||
|
|
||||||
switch(transfer->status) {
|
switch(transfer->status) {
|
||||||
case LIBUSB_TRANSFER_COMPLETED:
|
case LIBUSB_TRANSFER_COMPLETED:
|
||||||
/* Start or continue an fxlink transfer. When finished, don't resubmit,
|
// log_("bulk_IN_callback: got %d bytes (comm->ftransfer_IN=%p)\n",
|
||||||
instead have the user pick up the message before continuing. */
|
// data_size, comm->ftransfer_IN);
|
||||||
|
// fxlink_hexdump(data, data_size, stderr);
|
||||||
|
/* Start or continue an fxlink transfer. */
|
||||||
resubmit = true;
|
resubmit = true;
|
||||||
if(comm->ftransfer_IN) {
|
if(comm->ftransfer_IN)
|
||||||
fxlink_transfer_receive(comm->ftransfer_IN, data, data_size);
|
fxlink_transfer_receive(comm->ftransfer_IN, data, data_size);
|
||||||
if(fxlink_transfer_complete(comm->ftransfer_IN))
|
else
|
||||||
resubmit = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
comm->ftransfer_IN = fxlink_transfer_make_IN(data, data_size);
|
comm->ftransfer_IN = fxlink_transfer_make_IN(data, data_size);
|
||||||
}
|
|
||||||
|
if(fxlink_transfer_complete(comm->ftransfer_IN))
|
||||||
|
fxlink_device_queue_finished_IN(fdev);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Error: drop data and don't resubmit */
|
/* Error: drop data and don't resubmit */
|
||||||
|
@ -483,18 +486,16 @@ bool fxlink_device_start_bulk_IN(struct fxlink_device *fdev)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct fxlink_message *fxlink_device_finish_bulk_IN(struct fxlink_device *fdev)
|
static void fxlink_device_queue_finished_IN(struct fxlink_device *fdev)
|
||||||
{
|
{
|
||||||
struct fxlink_comm *comm = fdev->comm;
|
struct fxlink_comm *comm = fdev->comm;
|
||||||
|
|
||||||
if(!comm || !comm->ftransfer_IN)
|
if(!comm || !comm->ftransfer_IN)
|
||||||
return NULL;
|
return;
|
||||||
if(!fxlink_transfer_complete(comm->ftransfer_IN))
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
struct fxlink_message *msg = fxlink_transfer_finish_IN(comm->ftransfer_IN);
|
struct fxlink_message *msg = fxlink_transfer_finish_IN(comm->ftransfer_IN);
|
||||||
if(!msg)
|
if(!msg)
|
||||||
return NULL;
|
return;
|
||||||
|
|
||||||
int version_major = (msg->version >> 8) & 0xff;
|
int version_major = (msg->version >> 8) & 0xff;
|
||||||
int version_minor = msg->version & 0xff;
|
int version_minor = msg->version & 0xff;
|
||||||
|
@ -505,6 +506,28 @@ struct fxlink_message *fxlink_device_finish_bulk_IN(struct fxlink_device *fdev)
|
||||||
msg->application, msg->type, fxlink_size_string(msg->size));
|
msg->application, msg->type, fxlink_size_string(msg->size));
|
||||||
|
|
||||||
comm->ftransfer_IN = NULL;
|
comm->ftransfer_IN = NULL;
|
||||||
|
|
||||||
|
if(comm->queue_IN.size >= FXLINK_DEVICE_IN_QUEUE_SIZE) {
|
||||||
|
elog("device queue full (how?!), dropping above message");
|
||||||
|
fxlink_message_free(msg, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
comm->queue_IN.messages[comm->queue_IN.size++] = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fxlink_message *fxlink_device_finish_bulk_IN(struct fxlink_device *fdev)
|
||||||
|
{
|
||||||
|
struct fxlink_comm *comm = fdev->comm;
|
||||||
|
if(!comm || comm->queue_IN.size <= 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
struct fxlink_message *msg = comm->queue_IN.messages[0];
|
||||||
|
for(int i = 1; i < comm->queue_IN.size; i++)
|
||||||
|
comm->queue_IN.messages[i-1] = comm->queue_IN.messages[i];
|
||||||
|
comm->queue_IN.size--;
|
||||||
|
comm->queue_IN.messages[comm->queue_IN.size] = 0;
|
||||||
|
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <poll.h>
|
#include <poll.h>
|
||||||
|
|
||||||
static inline int min(int x, int y)
|
static inline int min(int x, int y)
|
||||||
|
@ -90,6 +91,9 @@ int fxlink_multipoll(int timeout, struct pollfd *fds1, int count1, ...);
|
||||||
pointer to a statically-allocated string. */
|
pointer to a statically-allocated string. */
|
||||||
char const *fxlink_size_string(int bytes);
|
char const *fxlink_size_string(int bytes);
|
||||||
|
|
||||||
|
/* Hexdump a data buffer to FILE. */
|
||||||
|
void fxlink_hexdump(char const *data, int size, FILE *fp);
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Delay
|
// Delay
|
||||||
//---
|
//---
|
||||||
|
|
|
@ -167,6 +167,10 @@ enum {
|
||||||
/* Return a string representation of the calc determined system. */
|
/* Return a string representation of the calc determined system. */
|
||||||
char const *fxlink_device_system_string(struct fxlink_device const *fdev);
|
char const *fxlink_device_system_string(struct fxlink_device const *fdev);
|
||||||
|
|
||||||
|
/* Size of the queue where received messages are stored between USB handling
|
||||||
|
and user code reading them. It almost never has more then one element. */
|
||||||
|
#define FXLINK_DEVICE_IN_QUEUE_SIZE 16
|
||||||
|
|
||||||
/* Device state tracked for communication targets. */
|
/* Device state tracked for communication targets. */
|
||||||
struct fxlink_comm {
|
struct fxlink_comm {
|
||||||
/* Whether the fxlink interface could be claimed */
|
/* Whether the fxlink interface could be claimed */
|
||||||
|
@ -184,6 +188,11 @@ struct fxlink_comm {
|
||||||
struct fxlink_transfer *ftransfer_IN;
|
struct fxlink_transfer *ftransfer_IN;
|
||||||
/* Cancellation flag */
|
/* Cancellation flag */
|
||||||
bool cancelled_IN;
|
bool cancelled_IN;
|
||||||
|
/* Completed input message queue */
|
||||||
|
struct {
|
||||||
|
struct fxlink_message *messages[FXLINK_DEVICE_IN_QUEUE_SIZE];
|
||||||
|
int size;
|
||||||
|
} queue_IN;
|
||||||
|
|
||||||
/* Current OUT transfer */
|
/* Current OUT transfer */
|
||||||
struct libusb_transfer *tr_bulk_OUT;
|
struct libusb_transfer *tr_bulk_OUT;
|
||||||
|
@ -211,10 +220,12 @@ bool fxlink_device_claim_fxlink(struct fxlink_device *fdev);
|
||||||
device structure is always ready to receive data from the calculator. */
|
device structure is always ready to receive data from the calculator. */
|
||||||
bool fxlink_device_start_bulk_IN(struct fxlink_device *fdev);
|
bool fxlink_device_start_bulk_IN(struct fxlink_device *fdev);
|
||||||
|
|
||||||
/* Finish an IN transfer and obtain the completed message. This function should
|
/* Get a pointer to a completed IN message. This function should be checked *in
|
||||||
be checked every frame as it will return a non-NULL pointer as soon as the
|
a loop* at every frame as completed messages queue up in a small buffer and
|
||||||
message is completed and the device will only start a new bulk IN transfer
|
the device device will only start a new bulk IN transfer until the message
|
||||||
until the message is moved out by this function. */
|
is moved out by this function. In practice, the buffer will only ever hold
|
||||||
|
more than one message if two messages complete within a single libusb event
|
||||||
|
handling call. But that happens. */
|
||||||
struct fxlink_message *fxlink_device_finish_bulk_IN(
|
struct fxlink_message *fxlink_device_finish_bulk_IN(
|
||||||
struct fxlink_device *fdev);
|
struct fxlink_device *fdev);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue