SSブログ

macOSからPi Picoを使う - その32 [Pi Pico]

まだこれを引きずってる。core0とcore1はちょっと大きめのqueueふたつをpipeとして共有しているだけで他の接点はないのに、core1が忙しくなるとcore0がUSBのデータを取りこぼす。queueにロックがあるのでそこでストールするのかと思ってデバガで見てもそんな様子はない。不思議だ。よくわからん。ってどっか間違ってるんだろうけど。

それはいいとして、前回の続きでlibusbの非同期転送について。前回は別threadで呼ぶなら非同期転送でなくてもいいんじゃね?だったけど、今回はmain threadでlibusbに非同期転送のための作業時間をどうやって割り当てるか....

ということで前回書いたように、専用の独立threadで非同期転送を回す必要性はそれほど高くない(専用threadを立てたらそこで同期転送でもかまわない)、と僕は考えている。

ひとつ、前回書き忘れたので追加するけど、もしUSB deviceからデータを受けるだけなら独立した専用のthreadで
    while (running)
        libusb_handle_events(ctx);
でかまわない。することがなければlibusb_handle_events()の中でブロックして出てこないし、もしデータが到着すればすぐにlibusbが処理してコールバックを呼んでくれる。レイテンシはlibusbの内部だけなのでバッファがオーバーランすることもない。
ホストからは小さなコマンドだけで、デバイスからデータをたくさん受け取る、なんて言う場合に非同期転送を使うにはこれが一番簡単。しかし大きなデータや低レイテンシのデータをやり取りする場合、これはあまりに非対称すぎる。また、ドキュメントにあるように、threadを止めるにはなんらかの別の手段が必要(libusb_handle_events()がブロックしたままなので)になる。

さらに、isochronous転送を使いたい場合や、ホットプラグに対応したい場合以外でも、効率を考えるなら非同期転送を使う方が望ましい。そのときは同じthreadからlibusbを呼ぶことになる。そのthreadがman threadかそうでないか、は置いといて。

libusbでは内部でthreadを起こしていないので、外部で何らかのループを回して、その中でlibusbが動作するタイミングを与えてやらないといけない。libusbにとって何か作業する対象が発生することを「イベント」と呼んでいる。例えば、デバイスからデータが来た、USBケーブルが抜けた刺さった、などである。

そのイベント処理の関数がドキュメントの「Polling and timing」セクションに書いてある。しかしこれがイマイチわかりにくい。

基本はlibusb_handle_events()を適切なタイミングで呼んでやればいいんだけど、何度も書いているようにこの関数は処理するイベントがない場合、イベントが発生するまでブロックする。単一threadでこれでは何もできなくなる。

そこでPOSIXのpoll()システムコールを使って監視できるようになっている。libusb_contextに関連づけられたファイルディスクリプタを得ることができる。
struct libusb_pollfd {
	int fd;
	short events;
};

const struct libusb_pollfd  **libusb_get_pollfds(libusb_context *ctx);
void libusb_free_pollfds(const struct libusb_pollfd **pollfds);
これはファイルディスクリプタが複数の場合があるので(USB interfaceごとか?)NULL終端のリストになっていて、不要になったらfreeしないといけない。struct libusb\_pollfdのfdはlibusbがdeviceのinterfaceごと(endpointごとかもしれない)に割り当てたファイルディスクリプタで、eventsはこのあとのstruct pollfdのeventsと同じ監視したいイベントの種類を表す。

これをpoll()に渡して監視する。poll()はPOSIXのシステムコールで
//#include <poll.h>
struct pollfd {
    int   fd;         /* file descriptor */
    short events;     /* requested events */
    short revents;    /* returned events */
};

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
で、これもnfds個のファイルディスクリプタを監視して、何かあるまでブロックする。struct libusb_pollfdからfdとeventsはコピーすればいい。timeoutはmsec単位である。タイムアウトが発生すると0が返る。正の数の場合、いくつのファイルディスクリプタにイベントが発生したかを表す。どのファイルディスクリプタに何のイベントがあったかはstrut pollfdのreventsにビットマップで上書きされて返ってくる(イベントのななかったファイルディスクリプタのreventsは0になる)。

でもpoll()も結局ブロックするので、timeoutを適切に設定して、何もないと判断できたら他のことをやる、ということになる。

しかし、そういうやりかただとlibusbにも用意されていて
int libusb_handle_events_timeout_completed(libusb_context *ctx,
                                           struct timeval *tv,
                                           int *completed);
int libusb_handle_events_timeout(libusb_context *ctx,
                                 struct timeval *tv);
はタイムアウトが設定できて、それを超えると返ってくる。struct timevalはsys/time.hに定義があって
struct timeval {
    time_t      tv_sec;
    suseconds_t tv_usec;
};
となっている。tv_secは秒、tv_usecはマイクロ秒である。POSIXに書いたことのある人なら見かけたことのある構造体で、longにすればいいのに、といつも思うけど昔のCPUだとlongもlong longも32ビットなんていうのがあったかららしい。

これらのlibusb関数でもpoll()でも、どのくらい待てばいいか、つまりどのくらいのタイムアウトに設定すればいいかよくわからない。そこで
int libusb_get_next_timeout(libusb_context *ctx,
                            struct timeval *tv);
という関数がある。これは0が返れば処理する必要のあるイベントはないということで1だと、いつごろまでにイベントを処理する必要が起こるかを引数に書く。これを目安にpoll()などのタイムアウトを決めればいい、ということらしい。

ところが、少なくともmacOSのlibusb1.0.24ではこの関数は常にtvに0が入って返るようである。これでは話が違う、ということになるんだよな。
nice!(0)  コメント(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。