mirror of
https://github.com/ckb-next/ckb-next.git
synced 2026-10-13 22:04:00 -04:00
Rewrite command fifo reader
This commit is contained in:
+7
-19
@@ -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)
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user