4

我有一个想要实现的 USB 协议,但我对实现它的最佳方式有点迷茫。

USB 协议涉及来回交换数据和确认数据包,如下所示:

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

但有时,数据包可能会像这样异步传入:

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

我希望有一个 API 可以抽象出 USB 的所有细节,让程序只处理实际数据,而不必担心数据包标头或确认数据包或类似的事情。理想情况下,会有一个write_to_device函数在设备确认数据包之前阻塞,一个函数read_from_device会阻塞直到接收到数据包,以及一个is_data_available函数会立即返回队列中是否有任何数据。

我正在考虑运行一个单独的线程来处理 USB 事件。该线程将处理所有数据封装和确认。

当数据包进来时,处理线程将发送一个 ACK​​ 数据包,然后提取原始数据并将其写入管道。该read_from_device函数(从主线程调用)将简单地从此管道读取并自然阻塞,直到有数据。但是如果我使用这个方案,我将没有一种干净的方式来实现一个is_data_available函数——没有办法在不读取管道的情况下检查管道中是否有数据。

像这样的东西:

[ Main thread    ][ Processing thread   ]
| Read from pipe ||                     |
|                || USB packet comes in |
|                || Send ACK packet     |
|                || Extract data        |
|                || Write data to pipe  |
| Read succeeds  ||                     |
| Return data    ||                     |

真正的问题是实现一个write_to_device功能。

[ 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                     ||                        |

我怎样才能干净地实现一种发送数据包的方法,等待确认数据包然后返回?

4

2 回答 2

1

我建议您创建一个自定义管道类或结构或其他东西。为此,您定义了一个 write 方法,该方法还等待信号量触发。如果您在 linux 上,sem_wait(来自信号量函数系列,sem_*)是您想要查看的内容。

然后 write 函数将数据写入 FIFO 并等待信号量被标记。但是,写入线程如何知道所有数据何时通过您要发送的管道到达?如果线程必须以阻塞方式读取,则此处可能会出现问题。

所以我建议你在从主线程到处理线程的管道内使用微格式,发送一个整数大小,它定义了你要写入的字节数。然后,处理线程将读取该数量的字节,将其转发到设备并在标记所有数据后立即标记信号量。该write函数将等待信号量,因此在处理线程完成之前不会忙阻塞。

这就是可以起草自定义管道结构和概述的写入函数的方式:

#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);
}

处理线程将知道MyPipe实例并在需要时读取read_from_pipe。它首先读取主线程写入管道的字节数,然后读取所有字节任意块。在所有数据都已发送到设备并被它确认后,它可以发送sem_post信号量,因此pipe_write将返回。

可选地,可以添加另一个信号量,该信号量pipe_write发布以使处理线程仅在实际有数据可用时才读取数据。

免责声明:没有测试代码,只检查它是否可以编译。需要用 构建-pthread,才能sem_*使用。

于 2013-01-05T11:19:11.773 回答
1

libusb或多或少已经完成了您所描述的一切。每个 USB 端点都可以看作是一个数据报套接字,您可以使用libusb_interrupt_transfer(或libusb_control_transfer)对其进行写入。在这些函数中,您传递一个用作输入或输出的数组。无需发送确认等。输入或输出的方向取决于端点的配置。

还有一个异步 API,您可以在其中启动传输并将一些文件描述符添加到您的主循环selectpoll循环中,并最终在 I/O 完成时获得回调。

于 2013-01-05T15:31:23.537 回答