Rewrite command fifo reader

This commit is contained in:
Tasos Sahanidis
2022-09-12 12:21:59 +03:00
parent 417d03b93b
commit f54f4ab350
5 changed files with 81 additions and 154 deletions
+7 -19
View File
@@ -62,33 +62,23 @@ static const char* const cmd_strings[CMD_COUNT - 1] = {
#define TRY_WITH_RESET(action) \
while(action){ \
if(usb_tryreset(kb)){ \
free(word); \
return 1; \
} \
}
int readcmd(usbdevice* kb, const char* line){
char* word = malloc(strlen(line) + 1);
int wordlen;
const char* newline = 0;
int readcmd(usbdevice* kb, char* line){
const devcmd* vt = &kb->vtable;
usbprofile* profile = kb->profile;
usbmode* mode = 0;
usbmode* mode = profile->currentmode;
int notifynumber = 0;
// Read words from the input
cmd command = NONE;
while(sscanf(line, "%s%n", word, &wordlen) == 1){
line += wordlen;
// If we passed a newline, reset the context
if(line > newline){
mode = profile->currentmode;
command = NONE;
notifynumber = 0;
newline = strchr(line, '\n');
if(!newline)
newline = line + strlen(line);
}
char* ptr = NULL;
char* intok = line;
char* word = NULL;
while((word = strtok_r(intok, " ", &ptr))){
intok = NULL;
// Check for a command word
for(int i = 0; i < CMD_COUNT - 1; i++){
if(!strcmp(word, cmd_strings[i])){
@@ -252,7 +242,6 @@ int readcmd(usbdevice* kb, const char* line){
case FWUPDATE:
// FW update parses a whole word. Unlike hwload/hwsave, there's no try again on failure.
if(vt->fwupdate(kb, mode, notifynumber, 0, word)){
free(word);
return 1;
}
continue;
@@ -363,7 +352,6 @@ int readcmd(usbdevice* kb, const char* line){
TRY_WITH_RESET(vt->updatergb(kb, 0));
TRY_WITH_RESET(vt->updatedpi(kb, 0));
}
free(word);
#ifndef NDEBUG
if(command == RGB)
+1 -1
View File
@@ -155,7 +155,7 @@ typedef union devcmd {
// Parse input from FIFO. Lock dmutex first (see device.h)
// This function is also responsible for calling all of the cmd_ functions. They should not be invoked elsewhere.
int readcmd(usbdevice* kb, const char* line);
int readcmd(usbdevice* kb, char* line);
#endif // COMMAND_H
+41 -67
View File
@@ -476,75 +476,49 @@ int mkfwnode(usbdevice* kb){
return 0;
}
#define MAX_BUFFER (1024 * 1024 - 1)
struct _readlines_ctx {
char* buffer;
int buffersize;
int leftover, leftoverlen;
};
// -1 means EOF, < -1 means retry. Positive indicates remaining lines in buffer.
// (How many more times this function can be called before the read will block again).
int readline_fifo(int fd, readlines_ctx* ctx){
void readlines_ctx_init(readlines_ctx* ctx){
// Allocate buffers to store data
*ctx = calloc(1, sizeof(struct _readlines_ctx));
int buffersize = (*ctx)->buffersize = 4095;
(*ctx)->buffer = malloc(buffersize + 1);
}
void readlines_ctx_free(readlines_ctx ctx){
free(ctx->buffer);
free(ctx);
}
unsigned readlines(int fd, readlines_ctx ctx, const char** input){
// Move any data left over from a previous read to the start of the buffer
char* buffer = ctx->buffer;
int buffersize = ctx->buffersize;
int leftover = ctx->leftover, leftoverlen = ctx->leftoverlen;
memcpy(buffer, buffer + leftover, leftoverlen);
// Read data from the file
ssize_t length = read(fd, buffer + leftoverlen, buffersize - leftoverlen);
length = (length < 0 ? 0 : length) + leftoverlen;
leftover = ctx->leftover = leftoverlen = ctx->leftoverlen = 0;
if(length <= 0){
*input = 0;
return 0;
if(ctx->count || ctx->leftover_bytes) {
// Move everything from the start of the next string back to the beginning
memmove(ctx->buf, ctx->buf + ctx->next_start, ctx->buffer_bytes - ctx->next_start);
}
// Continue buffering until all available input is read or there's no room left
while(length == buffersize){
if(buffersize == MAX_BUFFER)
break;
int oldsize = buffersize;
buffersize += 4096;
ctx->buffersize = buffersize;
buffer = ctx->buffer = realloc(buffer, buffersize + 1);
ssize_t length2 = read(fd, buffer + oldsize, buffersize - oldsize);
if(length2 <= 0)
break;
length += length2;
}
buffer[length] = 0;
// Input should be issued one line at a time and should end with a newline.
char* lastline = memrchr(buffer, '\n', length);
if(lastline == buffer + length - 1){
// If the buffer ends in a newline, process the whole string
*input = buffer;
return length;
} else if(lastline){
// Otherwise, chop off the last line but process everything else
*lastline = 0;
leftover = ctx->leftover = lastline + 1 - buffer;
leftoverlen = ctx->leftoverlen = length - leftover;
*input = buffer;
return leftover - 1;
} else {
// If a newline wasn't found at all, process the whole buffer next time
*input = 0;
if(length == MAX_BUFFER){
// Unless the buffer is completely full, in which case discard it
ckb_warn("Too much input (1MB). Dropping.");
return 0;
if(!ctx->count) {
const size_t max_read = MAX_BUFFER - 1 - ctx->leftover_bytes;
// Read fresh data
ssize_t ret = read(fd, ctx->buf + ctx->leftover_bytes, max_read);
if(ret <= 0)
return ret - 1;
ctx->buf[ctx->leftover_bytes + ret] = '\0';
// Replace all newlines with null terminators
char* pos = ctx->buf;
char* last = ctx->buf;
while((pos = strchr(pos, '\n'))){
ctx->count++;
*pos++ = '\0';
last = pos;
}
if(!ctx->count && (size_t)ret == max_read){
ckb_warn("String too long (%dKB). Dropping...", MAX_BUFFER/1024);
ctx->leftover_bytes = 0;
return -2;
}
ctx->buffer_bytes = (size_t)ret + ctx->leftover_bytes;
ctx->leftover_bytes = ctx->buf + ctx->buffer_bytes - last;
// No newline found yet
if(!ctx->count){
ctx->next_start = 0;
return -2;
}
leftoverlen = ctx->leftoverlen = length;
return 0;
}
// Find where the next string starts from.
// We precalculate this here because after we return the string, we expect it to be modified by strtok.
ctx->next_start = strlen(ctx->buf) + 1;
return --ctx->count;
}
+7 -4
View File
@@ -37,9 +37,12 @@ int rmnotifynode(usbdevice* kb, int notify);
int mkfwnode(usbdevice* kb);
/// Custom readline is needed for FIFOs. fopen()/getline() will die if the data is sent in too fast.
typedef struct _readlines_ctx* readlines_ctx;
void readlines_ctx_init(readlines_ctx* ctx);
void readlines_ctx_free(readlines_ctx ctx);
unsigned readlines(int fd, readlines_ctx ctx, const char** input);
#define MAX_BUFFER (1024 * 128)
typedef struct {
char buf[MAX_BUFFER];
size_t leftover_bytes, buffer_bytes, next_start;
int count;
} readlines_ctx;
int readline_fifo(int fd, readlines_ctx* ctx);
#endif // DEVNODE_H
+25 -63
View File
@@ -293,81 +293,43 @@ static const devcmd* get_vtable(usbdevice* kb){
}
// USB device main loop
/// brief .
///
/// \brief devmain is called by _setupusb
/// \param kb the pointer to the device. Even if it has the name kb, it is valid also for a mouse (the whole driver seems to be implemented first for a keyboard).
/// \return always a nullptr
///
/// # Synchronization
/// The syncing via mutexes is interesting:
/// 1. \a imutex (the Input mutex)\n
/// This one is locked in \c setupusb().
/// That function does only two things: Locking the mutex and trying to start a thread at \c _setupusb().
/// _setupusb() unlocks \a imutex after getting some buffers and initalizing internal structures from the indicators
/// (this function often gets problems with error messages like "unable to read indicators" or "Timeout bla blubb").
/// \warning have a look at \a updateindicators() later.
/// \warning if creating the thread is not successful, the \a imutex remains blocked. Have a look at setupusb() later.
///
/// 2. \a dmutex (the Device mutex)\n
/// This one is very interesting, because it is handled in devmain().
/// It seems that it is locked only in \a _ledthread(), which is a thread created in \a os_setupindicators().
/// os_setupindicators() again is called in \a _setupusb() long before calling devmain().
/// So this mutex is locked when we start the function as the old comment says.\n
/// Before reading from the FIFO and direct afterwards an unlock..lock sequence is implemented here.
/// Even if only the function readlines() should be surrounded by the unlock..lock,
/// the variable definition of the line pointer is also included here. Not nice, but does not bother either.
/// Probably the Unlock..lock is needed so that now another process can change the control structure \a linectx while we wait in readlines().
/// \todo Hope to find the need for dmutex usage later.
/// \n Should this function be declared as pthread_t* function, because of the defintion of pthread-create? But void* works also...
///
static void* devmain(usbdevice* kb){
/// \attention dmutex should still be locked when this is called
int kbfifo = kb->infifo - 1;
///
/// First a \a readlines_ctx buffer structure is initialized by \c readlines_ctx_init().
readlines_ctx linectx;
readlines_ctx_init(&linectx);
///
/// After some setup functions, beginning in _setupusb() which has called devmain(),
/// we read the command input-Fifo designated to that device in an endless loop.
/// This loop has two possible exits (plus reaction to signals, not mentioned here).
const int kbfifo = kb->infifo - 1;
readlines_ctx* linectx = calloc(1, sizeof(readlines_ctx));
while(1){
///
/// If the reading via readlines() is successful (we might have read multiple lines),
/// the interpretation is done by readcmd() if the connection to the device is still available
/// This is true if the kb-structure has a handle and an event pointer both != Null).
/// If not, the loop is left (the first exit point).
queued_mutex_unlock(dmutex(kb));
// Read from FIFO
const char* line;
int lines = readlines(kbfifo, linectx, &line);
// Read from cmd FIFO
int ret = readline_fifo(kbfifo, linectx);
wait_until_suspend_processed();
queued_mutex_lock(dmutex(kb));
// End thread when the handle is removed
if(kb->status == DEV_STATUS_DISCONNECTING || kb->status == DEV_STATUS_DISCONNECTED)
break;
///
/// if nothing is in the line buffer (some magic interrupt?),
/// continue in the endless while without any reaction.
if(lines){
/// \todo readcmd() gets a \b line, not \b lines. Have a look on that later.
if(readcmd(kb, line)){
///
/// If interpretation and communication with the usb device got errors,
/// they are signalled by readcmd() (non zero retcode).
/// In this case the usb device is closed via closeusb()
/// and the endless loop is left (the second exit point).
// USB transfer failed; destroy device
closeusb(kb);
break;
}
if(ret == -1) {
// EOF
break;
} else if(ret < -1) {
// Retry
continue;
} else {
// Keep consuming the buffered lines
// We break when readline_fifo is about to block so that it blocks above with dmutex unlocked
do {
if(readcmd(kb, linectx->buf)){
// USB transfer failed or command requested disconnect; destroy device
closeusb(kb);
goto cleanup;
}
} while(ret > 0 && (ret = readline_fifo(kbfifo, linectx)) >= 0);
}
}
cleanup:
queued_mutex_unlock(dmutex(kb));
///
/// After leaving the endless loop the readlines-ctx structure and its buffers are freed by readlines_ctx_free().
readlines_ctx_free(linectx);
free(linectx);
return 0;
}