Reputation: 9940
I have a USB protocol that I want to implement but I'm a little lost on the best way to do it.
The USB protocol involves exchanging data and acknowledgement packets back and forth like so:
Device: data
Host: ACK
Host: reply
Device: ACK
But sometimes, packets might come in asynchronously like this:
Device: data #1
Device: data #2
Host: ACK #1
...
I want to have an API that will abstract away all the details of USB and have the program just work with the actual data and not have to worry about packet headers or acknowledging packets or anything like that. Ideally, there will be a write_to_device
function that blocks until the device acknowledges the packet, a read_from_device
that will block until a packet is received and a is_data_available
function that returns immediately whether there is any data on the queue.
I'm thinking of running a separate thread that handles USB events. This thread will handle all the data encapsulation and acknowledging.
When a packet comes in, the processing thread will send an ACK packet then extract and write the raw data into a pipe. The read_from_device
function (called from the main thread) will simply read from this pipe and naturally block until there is data. But if I use this scheme, I won't have a clean way of implementing a is_data_available
function - there's no way to check if there is data in a pipe without reading it.
Something like this:
[ Main thread ][ Processing thread ]
| Read from pipe || |
| || USB packet comes in |
| || Send ACK packet |
| || Extract data |
| || Write data to pipe |
| Read succeeds || |
| Return data || |
The real issue is implementing a write_to_device
function.
[ Main thread ][ Processing thread ]
| Somehow signal write || |
| Wait for write to complete || |
| || Send the data |
| || Wait for ACK packet |
| || Somehow signal that write completed
| Return || |
How can I cleanly implement a way to send the packet, wait for an acknowledgement packet then return?
Upvotes: 5
Views: 1919
Reputation: 20738
I would suggest that you create a custom pipe class or structure or something. For that, you define a write method, which also holds waits for a semaphore to trigger. If you're on linux, sem_wait
(from the semaphore function family, sem_*
) is what you want to look at.
The write function would then write the data to the FIFO and wait for the semaphore to be flagged. However, how does the writing thread know when all the data arrived through the pipe you wanted to send? If the thread has to read blockingly, problems can occur here.
So I suggest that you use a microformat inside the pipe from the main thread to the processing thread, sending an integer size which defines how many bytes you are going to write. The processing thread will then read that amount of bytes, forward it to the device and flag the semaphore as soon as all data is flagged. The write
function will wait for the semaphore, thus non-busy blocking until the processing thread finished.
This is how a custom pipe struct and the outlined write function could be drafted:
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>
typedef struct {
int write_to_pipe, read_from_pipe;
sem_t *write_sem;
} MyPipe;
MyPipe *pipe_new() {
int fds[2];
if (pipe(fds)) {
// handle error here
return NULL;
}
sem_t *sem = NULL;
if (sem_init(sem, 0, 0)) {
// handle error here
close(fds[0]);
close(fds[1]);
return NULL;
}
MyPipe *result = malloc(sizeof(MyPipe));
result->write_to_pipe = fds[1];
result->read_from_pipe = fds[0];
result->write_sem = sem;
return result;
}
void pipe_write(MyPipe *pipe, const unsigned char *buf, const int size) {
write(pipe->write_to_pipe, &size, sizeof(int));
write(pipe->write_to_pipe, buf, size);
sem_wait(pipe->write_sem);
}
The processing thread would know the MyPipe
instance and read from read_from_pipe
whenever it desires. It first reads the amount of bytes the main thread wrote to the pipe and afterwards all the bytes an arbitrary chunks. After all data has been sent to the device and was ACK'd by it, it can sem_post
the semaphore, so that pipe_write
will return.
Optionally, one can add another semaphore, which pipe_write
posts to make the processing thread only read data when there actually is data available.
Disclaimer: Have not tested the code, only checked that it compiles. Needs to be built with -pthread
, to have sem_*
available.
Upvotes: 1
Reputation: 7153
libusb
more or less already does everything you have described. Each USB endpoint can be seen as a datagram socket, that you can write to with libusb_interrupt_transfer
(or libusb_control_transfer
). In those functions, you pass an array that functions as either the input or output. There is no need to send acknowledgements and such. The direction of the input or output depends on the endpoint's configuration.
There is also an asynchronous API where you initiate the transfer and add some file descriptors to your main select
or poll
loop, and eventually get a call back when the I/O completes.
Upvotes: 1