Design a program for implementing the USB protocol

I have a USB protocol that I want to implement, but I lost it a little in the best way.

The USB protocol enables the exchange of data and confirmation packets back and forth as follows:

Device: data Host: ACK Host: reply Device: ACK 

But sometimes packets can arrive asynchronously:

 Device: data #1 Device: data #2 Host: ACK #1 ... 

I want to have an API that will abstract all the details of USB and have a program that just works with the actual data and does not worry about packet headers or packet acknowledgment or anything like that. Ideally, there will be a write_to_device function that blocks until the device confirms the packet, read_from_device , which will block until the packet is received, and is_data_available , which will immediately return if there is any data in the queue .

I am going to start a separate thread that processes USB events. This thread will handle all data encapsulation and acknowledgment.

When a packet arrives, the processing thread sends an ACK packet and then extracts and writes raw data to the channel. The read_from_device function (called from the main thread) will simply be read from this channel and, of course, blocked until there is data. But if I use this scheme, I will not have a clean way to implement the is_data_available function - there is no way to check if there is data in the pipe without reading it.

Something like that:

 [ 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 problem is the implementation of the 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 clearly implement the method of sending a packet, wait for a confirmation packet to be received?

+4
source share
2 answers

I would suggest creating your own class or structure or something else. To do this, you define a recording method that also contains expectations for the semaphore to start. If you are using Linux, sem_wait (from the semaphore family of functions, sem_* ) is what you want to see.

Then the write function will write the data to FIFO and wait until the semaphore is marked. However, how does a stream of letters write when all the data comes through the channel you want to send? If the stream should read in a locked way, problems can occur here.

Therefore, I suggest you use microformat inside the channel from the main stream to the processing stream, sending an integer size that determines how many bytes you are going to write. Then the processing stream will read the number of bytes, redirect it to the device and mark the semaphore as soon as all the data is marked. The write function will wait for the semaphore, so the lock is not busy until the processing flow is complete.

In this way, a custom channel structure and a dedicated recording function can be programmed:

 #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 will know the instance of MyPipe and read with read_from_pipe whenever it wishes. First, it reads the number of bytes that the main stream wrote in the pipe, and then all bytes are arbitrary pieces. After all the data has been sent to the device and was ACK'd by it, it can sem_post semaphore, so pipe_write will return.

Additionally, you can add another semaphore that pipe_write sends so that the processing thread only processes data when there is actually data.

Disclaimer: I did not check the code, I checked only its compilation. It is required to build with -pthread in order to have sem_* .

+1
source

libusb more or less already does everything you described. Each USB endpoint can be thought of as a datagram socket with which you can write using libusb_interrupt_transfer (or libusb_control_transfer ). In these functions, you pass an array that functions as input or output. No need to send confirmations, etc. The direction of entry or exit depends on the configuration of the endpoint.

There is also an asynchronous API where you initiate the transfer and add some file descriptors to your main select or poll loop and end up with a callback when I / O completion is complete.

+1
source

All Articles