mirror of
https://git.planet-casio.com/Lephenixnoir/gint.git
synced 2025-07-18 18:07:33 +02:00
usb: consolidate reading logic again, now beta-worthy
This looks like it could work in the long term. The only issue that really hasn't been addressed is how to use packet counters to cut transactions when there's no ZLP, but we can leave that for later.
This commit is contained in:
parent
fc1f510288
commit
4983849510
6 changed files with 468 additions and 282 deletions
|
@ -120,17 +120,33 @@
|
||||||
entire transaction by reading data until they get fewer bytes than requested
|
entire transaction by reading data until they get fewer bytes than requested
|
||||||
without running the risk of blocking on the next transaction.
|
without running the risk of blocking on the next transaction.
|
||||||
|
|
||||||
The invariants and meaning for each state are as follow:
|
The invariants and meaning for each state are as follow. The right side
|
||||||
|
(presence of a hardware segment) is indicated by `buffer_used >= 0`, while
|
||||||
|
the bottom side (presence of a read(2) request) is indicated by `size > 0`.
|
||||||
|
As an exception, IDLE-EMPTY uses `buffer_used == 0` so that the default
|
||||||
|
zero-initialization of transfer data is sufficient.
|
||||||
|
|
||||||
State Characterization Description
|
State Invariant characterization Description
|
||||||
============================================================================
|
============================================================================
|
||||||
IDLE-EMPTY type == ASYNCIO_NONE No I/O operation
|
IDLE-EMPTY type == ASYNCIO_NONE No I/O operation, the pipe is
|
||||||
|
&& buffer_used == 0 idle with no request and no
|
||||||
|
&& size == 0 hardware segment (but we might
|
||||||
|
&& round_size == 0 still be mid-transaction)
|
||||||
----------------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
IDLE-READY !data_r Hardware segment pending
|
IDLE-READY type == ASYNCIO_READ There is a hardware segment not
|
||||||
|
&& buffer_used >= 0 marked as complete, but no
|
||||||
|
&& size == 0 read(2) request to consume it
|
||||||
|
&& round_size == 0
|
||||||
----------------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
WAITING data_r && !round_size User segment pending
|
WAITING type == ASYNCIO_READ There is a read(2) request but
|
||||||
|
&& buffer_used < 0 no hardware segment, and either
|
||||||
|
&& size > 0 the request is new or the
|
||||||
|
&& round_size == 0 transaction isn't exhausted
|
||||||
----------------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
READING round_size > 0 DMA/CPU read round in progress
|
READING type == ASYNCIO_READ A read round in progress and
|
||||||
|
&& buffer_used >= 0 either the read(2) request or
|
||||||
|
&& size > 0 hardware segment will be
|
||||||
|
&& round_size > 0 exhausted when it ends
|
||||||
============================================================================
|
============================================================================
|
||||||
|
|
||||||
The series of asyncio_op function calls for a read is a bit more complicated
|
The series of asyncio_op function calls for a read is a bit more complicated
|
||||||
|
@ -147,7 +163,7 @@
|
||||||
| R1 | R2 | R3 | R4 | R5 | R6 | Read rounds
|
| R1 | R2 | R3 | R4 | R5 | R6 | Read rounds
|
||||||
+-----------+------+----+-----------+-----------+---------+
|
+-----------+------+----+-----------+-----------+---------+
|
||||||
| User buffer #1 | User buffer #2 | (short) | User segments
|
| User buffer #1 | User buffer #2 | (short) | User segments
|
||||||
+-----------+------+----+-----------+-----------+---------+
|
+------------------+----------------------------+---------+
|
||||||
^ read(2) ^ read(2) ^ read(2)
|
^ read(2) ^ read(2) ^ read(2)
|
||||||
|
|
||||||
Reads rounds are exactly the intersections between hardware segments and
|
Reads rounds are exactly the intersections between hardware segments and
|
||||||
|
@ -159,7 +175,35 @@ enum { ASYNCIO_NONE, ASYNCIO_READ, ASYNCIO_WRITE, ASYNCIO_SYNC };
|
||||||
|
|
||||||
typedef volatile struct
|
typedef volatile struct
|
||||||
{
|
{
|
||||||
/** User-facing information **/
|
/* Type of I/O operation (NONE/WRITE/SYNC/READ) */
|
||||||
|
uint8_t type;
|
||||||
|
/* Whether the DMA should be used for hardware access */
|
||||||
|
bool dma :1;
|
||||||
|
/* For reading pipes, whether the transaction is expected to continue with
|
||||||
|
another hardware segment after the current one */
|
||||||
|
bool cont_r :1;
|
||||||
|
/* For reading pipes, interrupt flag signaling an incoming hardware segment
|
||||||
|
not yet added to the operation */
|
||||||
|
bool interrupt_r :1;
|
||||||
|
/* For reading pipes, whether the current read call should close the
|
||||||
|
current hardware segment if all the data is read even if the read call
|
||||||
|
is not partial */
|
||||||
|
bool autoclose_r :1;
|
||||||
|
/* Hardware resource being used for access (meaning depends on hardware).
|
||||||
|
Usually, this is assigned for the duration of hardware transaction.
|
||||||
|
This value is user-managed and not modified by asyncio_op functions. */
|
||||||
|
uint8_t controller;
|
||||||
|
|
||||||
|
/* Number of bytes in short buffer (0..3) */
|
||||||
|
uint8_t shbuf_size;
|
||||||
|
/* Short buffer */
|
||||||
|
uint32_t shbuf;
|
||||||
|
|
||||||
|
/* Size of data currently in the hardware buffer */
|
||||||
|
int16_t buffer_used;
|
||||||
|
/* Size of data being read/written in the current round (which may itself
|
||||||
|
be asynchronous if it's using the DMA) */
|
||||||
|
uint16_t round_size;
|
||||||
|
|
||||||
union {
|
union {
|
||||||
/* Address of data to transfer, incremented gradually [write] */
|
/* Address of data to transfer, incremented gradually [write] */
|
||||||
|
@ -169,40 +213,10 @@ typedef volatile struct
|
||||||
};
|
};
|
||||||
/* Size of data left to transfer to satisfy the complete request */
|
/* Size of data left to transfer to satisfy the complete request */
|
||||||
int size;
|
int size;
|
||||||
/* Callback at the end of the current write, final commit, or read */
|
|
||||||
gint_call_t callback;
|
|
||||||
/* For reading operations, pointer to total amount of transferred data */
|
/* For reading operations, pointer to total amount of transferred data */
|
||||||
int *realized_size_r;
|
int *realized_size_r;
|
||||||
|
/* Callback at the end of the current write, final commit, or read */
|
||||||
/* Type of I/O operation (read/write/fsync) */
|
gint_call_t callback;
|
||||||
uint8_t type;
|
|
||||||
/* Whether the DMA should be used for hardware access */
|
|
||||||
bool dma;
|
|
||||||
|
|
||||||
/** Hardware state information **/
|
|
||||||
|
|
||||||
/* Size of data currently in the hardware buffer */
|
|
||||||
uint16_t buffer_used;
|
|
||||||
/* Size of data being read/written in the current round (which may itself
|
|
||||||
be asynchronous if it's using the DMA) */
|
|
||||||
uint16_t round_size;
|
|
||||||
/* For reading operations, whether the transaction is expected to continue
|
|
||||||
with another hardware segment after the current one */
|
|
||||||
bool cont_r;
|
|
||||||
/* For reading operations, whether there is a new hardware segment just
|
|
||||||
signaled by an interrupt not yet added to the operation */
|
|
||||||
bool interrupt_r;
|
|
||||||
/* Hardware resource being used for access (meaning depends on hardware).
|
|
||||||
Usually, this is assigned for the duration of hardware transaction.
|
|
||||||
This value is user-managed and not modified by asyncio_op functions. */
|
|
||||||
uint8_t controller;
|
|
||||||
|
|
||||||
/** Internal information **/
|
|
||||||
|
|
||||||
/* Number of bytes in short buffer (0..3) */
|
|
||||||
uint8_t shbuf_size;
|
|
||||||
/* Short buffer */
|
|
||||||
uint32_t shbuf;
|
|
||||||
|
|
||||||
} asyncio_op_t;
|
} asyncio_op_t;
|
||||||
|
|
||||||
|
@ -238,20 +252,42 @@ void asyncio_op_finish_write_round(asyncio_op_t *op);
|
||||||
void asyncio_op_start_sync(asyncio_op_t *op, gint_call_t const *callback);
|
void asyncio_op_start_sync(asyncio_op_t *op, gint_call_t const *callback);
|
||||||
void asyncio_op_finish_sync(asyncio_op_t *op);
|
void asyncio_op_finish_sync(asyncio_op_t *op);
|
||||||
|
|
||||||
/* Start/finish a read(2) call. */
|
/* Start a read(2) call. The call will finish automatically when the final
|
||||||
|
round finishes. If `autoclose` is set, the current hardware segment will
|
||||||
|
be marked as completed if the round reads it entirely, even if the request
|
||||||
|
is fulfilled. */
|
||||||
void asyncio_op_start_read(asyncio_op_t *op, void *data, size_t size,
|
void asyncio_op_start_read(asyncio_op_t *op, void *data, size_t size,
|
||||||
bool use_dma, int *realized_size, gint_call_t const *callback);
|
bool use_dma, int *realized_size, bool autoclose,
|
||||||
void asyncio_op_finish_read(asyncio_op_t *op);
|
gint_call_t const *callback);
|
||||||
|
|
||||||
/* Start/finish a hardware segment `cont` should be true if there will be
|
/* Start a hardware segment. `cont` should be true if there will be another
|
||||||
another segment in the same transaction. */
|
segment in the same transaction. The segment will finish automatically when
|
||||||
|
it is completely consumed by a read round. */
|
||||||
void asyncio_op_start_read_hwseg(asyncio_op_t *op, size_t size, bool cont);
|
void asyncio_op_start_read_hwseg(asyncio_op_t *op, size_t size, bool cont);
|
||||||
void asyncio_op_finish_read_hwseg(asyncio_op_t *op);
|
|
||||||
|
|
||||||
/* Start/finish a single-block read from hardware. The finish function returns
|
bool asyncio_op_has_read_call(asyncio_op_t const *op);
|
||||||
true if the current hardware segment finished with the round (op->cont_r
|
|
||||||
indicates whether more segments will follow). */
|
bool asyncio_op_has_read_hwseg(asyncio_op_t const *op);
|
||||||
void asyncio_op_start_read_round(asyncio_op_t *op, size_t size);
|
|
||||||
bool asyncio_op_finish_read_round(asyncio_op_t *op);
|
/* Start a single-block read from hardware. The requested size is automatically
|
||||||
|
t->size, however the round may of course be smaller depending on how much
|
||||||
|
data is available. Returns the round size. */
|
||||||
|
int asyncio_op_start_read_round(asyncio_op_t *op);
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ASYNCIO_HWSEG_EXHAUSTED = 0x01,
|
||||||
|
ASYNCIO_REQUEST_FINISHED = 0x02,
|
||||||
|
ASYNCIO_TRANSACTION_EXHAUSTED = 0x04,
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Finish a single-block read from hardware. This function also finishes the
|
||||||
|
current hardware segment and read call if appropriate, *except* that it
|
||||||
|
doesn't invoke the read(2) callback. You should make a copy of it before
|
||||||
|
calling and invoke it manually after. Returns a combination of the above
|
||||||
|
flags indicating what finished along with the round. */
|
||||||
|
int asyncio_op_finish_read_round(asyncio_op_t *op);
|
||||||
|
|
||||||
|
/* Cancel a read call. This keeps the hardware segment part intact. */
|
||||||
|
void asyncio_op_cancel_read(asyncio_op_t *op);
|
||||||
|
|
||||||
#endif /* GINT_USB_ASYNCIO */
|
#endif /* GINT_USB_ASYNCIO */
|
||||||
|
|
|
@ -130,8 +130,8 @@ void usb_fxlink_set_notifier(void (*notifier_function)(void));
|
||||||
/* usb_ff_bulk_output(): Pipe number for calculator -> host communication */
|
/* usb_ff_bulk_output(): Pipe number for calculator -> host communication */
|
||||||
int usb_ff_bulk_output(void);
|
int usb_ff_bulk_output(void);
|
||||||
|
|
||||||
/* usb_ff_bulk_in(): Pipe number for host -> calculator communication */
|
/* usb_ff_bulk_input(): Pipe number for host -> calculator communication */
|
||||||
int usb_ff_bulk_in(void);
|
int usb_ff_bulk_input(void);
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Construction and analysis of fxlink protocol messages
|
// Construction and analysis of fxlink protocol messages
|
||||||
|
|
|
@ -55,6 +55,9 @@ enum {
|
||||||
USB_READ_IDLE = -12,
|
USB_READ_IDLE = -12,
|
||||||
/* No FIFO controller is available */
|
/* No FIFO controller is available */
|
||||||
USB_READ_NOFIFO = -13,
|
USB_READ_NOFIFO = -13,
|
||||||
|
|
||||||
|
/* (Internal codes) */
|
||||||
|
USB_ERROR_ZERO_LENGTH = -100,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* usb_open(): Open the USB link
|
/* usb_open(): Open the USB link
|
||||||
|
@ -273,47 +276,32 @@ int usb_commit_async(int pipe, gint_call_t callback);
|
||||||
/* usb_read_sync(): Synchronously read from a USB pipe
|
/* usb_read_sync(): Synchronously read from a USB pipe
|
||||||
|
|
||||||
This function waits for data to become available on the specified `pipe`,
|
This function waits for data to become available on the specified `pipe`,
|
||||||
and then reads up to `size` bytes into `data`. It synchronizes with USB
|
and then reads up to `size` bytes into `data`. It is "synchronized" with USB
|
||||||
transactions on the pipe in the following ways:
|
transactions on the pipe in the following ways:
|
||||||
|
|
||||||
1. If there is no active transaction when it is first called, it blocks
|
1. If there is no active transaction, it waits for one to arrive.
|
||||||
until one starts (which can freeze).
|
2. If the active transaction has no data left to read, it is skipped.
|
||||||
2. If there is an active transaction but all of its contents were consumed
|
3. If the transaction's data is completely read, the transaction is closed
|
||||||
by a previous read, it is marked as complete before usb_read_sync()
|
even if `data` is full. (See usb_read_async() for the alternative.)
|
||||||
starts waiting.
|
|
||||||
|
|
||||||
The second point has a subtle implication. In an application that always
|
Basically usb_read_sync() returns the next `size` bytes of data that the
|
||||||
reads synchronously from a pipe, the "resting" state between transactions
|
calculator receives on the pipe, with a single exception: it doesn't read
|
||||||
can show the last processed transaction as active with 0 bytes remaining to
|
across transactions, so if the active transaction has 100 bytes left and you
|
||||||
read, as processed transactions are not marked as completed until a read
|
request 200, you will only get 100. usb_read_sync() only ever returns 0
|
||||||
request is fulfilled *partially*. This is for three reasons:
|
bytes if there is an empty transaction, which is rare.
|
||||||
|
|
||||||
* Only marking transactions as complete upon partial reads makes it possible
|
Because this is a blocking function, a call to usb_read_sync() will *FREEZE*
|
||||||
to properly detect the end of transactions in asynchronous mode. See
|
if there is no data to read on the pipe and the host doesn't send any. In
|
||||||
usb_read_async() for more details.
|
addition, it's not possible to detect how much data is left to read with
|
||||||
|
usb_read_sync()'s interface. If you want to avoid or debug a freeze, use
|
||||||
* It further allows detecting the end of transactions in synchronous mode by
|
use usb_read_async() or set a timeout with usb_read_sync_timeout(). If you
|
||||||
checking for partial reads *or* usb_poll() returning false.
|
want to read a transaction until the end without knowing its size in
|
||||||
|
advance, use usb_read_async().
|
||||||
* Marking transactions with 0 bytes left to read as complete *before* a
|
|
||||||
synchronous read (instead of after) avoids complications for sync reads
|
|
||||||
when switching between sync and async reads. It makes async reads handle
|
|
||||||
the switch with a 0-byte read, instead of the other way round. Thus, sync
|
|
||||||
reads don't need to worry about switches and can never return 0 bytes.
|
|
||||||
|
|
||||||
It is not an error for usb_read_sync() to return fewer bytes than requested.
|
|
||||||
If the active USB transaction has fewer bytes left than specified in `size`,
|
|
||||||
the remaining data will be returned without waiting for another transaction.
|
|
||||||
(Reads never carry across transactions.) Because usb_read_sync() will ignore
|
|
||||||
transactions with 0 bytes left, it can never return 0 bytes.
|
|
||||||
|
|
||||||
If `use_dma=true`, uses the DMA for transfers. This requires 4-byte
|
If `use_dma=true`, uses the DMA for transfers. This requires 4-byte
|
||||||
alignment on the buffer, size, and number of bytes previously read in the
|
alignment on the buffer, size, and number of bytes previously read in the
|
||||||
current transaction.
|
current transaction.
|
||||||
|
|
||||||
This function will use a FIFO to access the pipe. The FIFO will only be
|
|
||||||
released once the transaction is completely consumed.
|
|
||||||
|
|
||||||
Returns the number of bytes read or a negative error code. */
|
Returns the number of bytes read or a negative error code. */
|
||||||
int usb_read_sync(int pipe, void *data, int size, bool use_dma);
|
int usb_read_sync(int pipe, void *data, int size, bool use_dma);
|
||||||
|
|
||||||
|
@ -321,46 +309,77 @@ int usb_read_sync(int pipe, void *data, int size, bool use_dma);
|
||||||
int usb_read_sync_timeout(int pipe, void *data, int size, bool use_dma,
|
int usb_read_sync_timeout(int pipe, void *data, int size, bool use_dma,
|
||||||
timeout_t const *timeout);
|
timeout_t const *timeout);
|
||||||
|
|
||||||
|
/* Flags for usb_read_async(), see below. */
|
||||||
|
enum {
|
||||||
|
/* Use the DMA for data transfer (requires full 4-byte alignment). */
|
||||||
|
USB_READ_USE_DMA = 0x01,
|
||||||
|
/* Ignore reads of 0 bytes from exhausted transactions. */
|
||||||
|
USB_READ_IGNORE_ZEROS = 0x02,
|
||||||
|
/* Close transactions when exhausted even by non-partial reads. */
|
||||||
|
USB_READ_AUTOCLOSE = 0x04,
|
||||||
|
/* Wait for the read to finish before returning. */
|
||||||
|
USB_READ_WAIT = 0x08,
|
||||||
|
/* Block for a new transaction if none is currenty active. */
|
||||||
|
USB_READ_BLOCK = 0x10,
|
||||||
|
};
|
||||||
|
|
||||||
/* usb_read_async(): Asynchronously read from a USB pipe
|
/* usb_read_async(): Asynchronously read from a USB pipe
|
||||||
|
|
||||||
This function is similar to usb_read_sync() except that it doesn't
|
This function is similar to usb_read_sync() but it is asynchronous, ie. it
|
||||||
synchronize with transactions, resulting in two differences:
|
returns after starting the read and then runs in the background. Without
|
||||||
|
options, usb_read_async() is guaranteed to return quickly without waiting
|
||||||
|
for communications. The read itself also never blocks unless there is a
|
||||||
|
disagreement with the host on the size of transactions.
|
||||||
|
|
||||||
1. It returns USB_READ_INACTIVE if there is no active transaction.
|
usb_read_async() is also slightly lower-level than usb_read_sync(). In
|
||||||
2. If there is an active transaction with 0 bytes left to read, it returns 0
|
particular, it only closes transactions when the data is fully read *and*
|
||||||
bytes and marks the transaction as complete (unless size == 0).
|
the user buffer is not full. If the user requests exactly the number of
|
||||||
|
bytes left in the transaction, the entire contents will be read but the
|
||||||
|
transaction will be left in an "active with 0 bytes left" state. Only on the
|
||||||
|
next read will the transaction be closed.
|
||||||
|
|
||||||
Like usb_read_sync(), if the exact amount of data left in the transaction is
|
This behavior is designed so that a read closes a transaction if and only if
|
||||||
requested, the transaction will remain active with 0 bytes left to read and
|
it returns fewer bytes than requested, which is useful for detecting the end
|
||||||
the *next* read will return 0 bytes and mark it as complete. This way, the
|
of transfers when their size isn't known in advance.
|
||||||
end of the transaction can be detected consistently: it is sufficient to
|
|
||||||
loop until a read call returns fewer bytes than requested.
|
|
||||||
|
|
||||||
Being asynchronous, this function starts the read process and returns
|
This function returns a preliminary error code. If it is zero, then the
|
||||||
instantly; 0 on success, an error code otherwise. When the read finishes,
|
callback will be invoked later with *rc set to the final return value of the
|
||||||
the provided callback is called with `*read_size` set to the number of bytes
|
read, which is itself either an error (< 0) or the number of bytes read.
|
||||||
read. */
|
|
||||||
int usb_read_async(int pipe, void *data, int size, bool use_dma,
|
|
||||||
int *read_size, gint_call_t callback);
|
|
||||||
|
|
||||||
/* usb_poll(): Check whether there is data to read in a pipe
|
Due to its low-level nature, raw usb_read_async() is a bit impractical to
|
||||||
|
use and almost always requires repeated calls and callback waits. The
|
||||||
|
following flags are provided to make it more convenient by incorporating
|
||||||
|
features of usb_read_sync():
|
||||||
|
|
||||||
This function checks whether a transaction is active on the pipe with more
|
* USB_READ_IGNORE_ZEROS causes usb_read_async() to ignore reads of 0 bytes
|
||||||
than 0 bytes left to read. Its main purpose is to simplify reading patterns
|
from transactions that were exhausted but not closed.
|
||||||
with usb_read_sync(), in the following ways:
|
* USB_READ_AUTOCLOSE causes usb_read_async() to always close transactions
|
||||||
|
when exhausted even if the read is not partial. This is useful when the
|
||||||
|
size of the transaction is in fact known in advance.
|
||||||
|
* USB_READ_WAIT causes usb_read_async() to wait for the end of the transfer
|
||||||
|
before returning. Unless there is a driver bug or USB_READ_BLOCK is also
|
||||||
|
set, this cannot cause the function to freeze since the read will always
|
||||||
|
complete in finite time (returning USB_READ_IDLE if no transaction is
|
||||||
|
active). When USB_READ_WAIT it set, `callback` and `rc` are ignored; the
|
||||||
|
callback is not invoked, and the status of the entire read is returned.
|
||||||
|
* USB_READ_BLOCK causes usb_read_async() to wait for a transaction if none
|
||||||
|
is active. This can stall the transfer indefinitely if the host doesn't
|
||||||
|
transmit any data, and freeze the call entirely if USB_READ_WAIT is set.
|
||||||
|
This option really makes usb_read_async() a synchronous function.
|
||||||
|
* A timeout is supported (pass NULL to ignore).
|
||||||
|
|
||||||
1. When usb_poll() returns true, usb_read_sync() is guaranteed to not block
|
With all options enabled, usb_read_async() becomes functionally identical to
|
||||||
(unless the host fails to complete a transaction).
|
usb_read_sync_timeout(). */
|
||||||
2. After a synchronous read which was completely fulfilled (ie. returned as
|
int usb_read_async(int pipe, void *data, int size, int flags, int *rc,
|
||||||
many bytes as were requested), usb_poll() will tell whether there is any
|
timeout_t const *timeout, gint_call_t callback);
|
||||||
more data left. This is useful because if there is no data left, the next
|
|
||||||
call to usb_read_sync() would block. Thus, it is possible to detect the
|
|
||||||
end of the current transaction during synchronous reads by checking for
|
|
||||||
(1) partial reads, and (2) usb_poll() returning false after a full read.
|
|
||||||
|
|
||||||
usb_poll() is of little interest to asynchronous applications since it is
|
/* usb_read_cancel(): Cancel an asynchronous read
|
||||||
easier to check the return status of usb_read_async(). */
|
|
||||||
bool usb_poll(int pipe);
|
Once started, an async read will run in the background and keep writing to
|
||||||
|
the provided buffer. This function cancels the operation so that no further
|
||||||
|
writes to the buffer are made and the associated memory can be safely
|
||||||
|
deallocated. */
|
||||||
|
void usb_read_cancel(int pipe);
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// USB debugging functions
|
// USB debugging functions
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include <gint/drivers/asyncio.h>
|
#include <gint/drivers/asyncio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
void asyncio_op_clear(asyncio_op_t *op)
|
void asyncio_op_clear(asyncio_op_t *op)
|
||||||
{
|
{
|
||||||
|
@ -8,9 +9,9 @@ void asyncio_op_clear(asyncio_op_t *op)
|
||||||
|
|
||||||
bool asyncio_op_busy(asyncio_op_t const *op)
|
bool asyncio_op_busy(asyncio_op_t const *op)
|
||||||
{
|
{
|
||||||
/* WAITING and READING states are busy */
|
/* WAITING and READING states are busy (ie. read(2) call in progress) */
|
||||||
if(op->type == ASYNCIO_READ)
|
if(op->type == ASYNCIO_READ)
|
||||||
return op->round_size || op->data_r != NULL;
|
return op->size > 0;
|
||||||
/* WRITING, FLYING-WRITE and PENDING states are busy */
|
/* WRITING, FLYING-WRITE and PENDING states are busy */
|
||||||
if(op->type == ASYNCIO_WRITE)
|
if(op->type == ASYNCIO_WRITE)
|
||||||
return op->data_w != NULL;
|
return op->data_w != NULL;
|
||||||
|
@ -72,28 +73,33 @@ void asyncio_op_finish_sync(asyncio_op_t *op)
|
||||||
}
|
}
|
||||||
|
|
||||||
void asyncio_op_start_read(asyncio_op_t *op, void *data, size_t size,
|
void asyncio_op_start_read(asyncio_op_t *op, void *data, size_t size,
|
||||||
bool use_dma, int *realized_size, gint_call_t const *callback)
|
bool use_dma, int *realized_size, bool autoclose,
|
||||||
|
gint_call_t const *callback)
|
||||||
{
|
{
|
||||||
|
assert(!asyncio_op_has_read_call(op) && size > 0 && !op->round_size);
|
||||||
|
|
||||||
|
op->type = ASYNCIO_READ;
|
||||||
op->dma = use_dma;
|
op->dma = use_dma;
|
||||||
|
op->autoclose_r = autoclose;
|
||||||
op->data_r = data;
|
op->data_r = data;
|
||||||
op->callback = *callback;
|
|
||||||
op->realized_size_r = realized_size;
|
|
||||||
op->size = size;
|
op->size = size;
|
||||||
|
op->realized_size_r = realized_size;
|
||||||
|
op->callback = *callback;
|
||||||
|
|
||||||
if(realized_size)
|
if(realized_size)
|
||||||
*realized_size = 0;
|
*realized_size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void asyncio_op_finish_read(asyncio_op_t *op)
|
bool asyncio_op_has_read_call(asyncio_op_t const *op)
|
||||||
{
|
{
|
||||||
/* Note: In this function the type may be either READ or NONE depending of
|
/* WAITING and READING states */
|
||||||
whether there is a hardware segment being processed. */
|
return (op->type == ASYNCIO_READ) && (op->size > 0);
|
||||||
gint_call(op->callback);
|
}
|
||||||
op->dma = false;
|
|
||||||
op->data_r = NULL;
|
bool asyncio_op_has_read_hwseg(asyncio_op_t const *op)
|
||||||
op->callback = GINT_CALL_NULL;
|
{
|
||||||
op->realized_size_r = NULL;
|
/* IDLE-READY and READING states */
|
||||||
op->size = 0;
|
return (op->type == ASYNCIO_READ) && (op->buffer_used >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void asyncio_op_start_read_hwseg(asyncio_op_t *op, size_t size, bool cont)
|
void asyncio_op_start_read_hwseg(asyncio_op_t *op, size_t size, bool cont)
|
||||||
|
@ -103,26 +109,65 @@ void asyncio_op_start_read_hwseg(asyncio_op_t *op, size_t size, bool cont)
|
||||||
op->cont_r = cont;
|
op->cont_r = cont;
|
||||||
}
|
}
|
||||||
|
|
||||||
void asyncio_op_finish_read_hwseg(asyncio_op_t *op)
|
int asyncio_op_start_read_round(asyncio_op_t *op)
|
||||||
{
|
{
|
||||||
if(!op->cont_r)
|
op->round_size = (op->size < op->buffer_used ? op->size : op->buffer_used);
|
||||||
op->type = ASYNCIO_NONE;
|
return op->round_size;
|
||||||
op->buffer_used = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void asyncio_op_start_read_round(asyncio_op_t *op, size_t size)
|
int asyncio_op_finish_read_round(asyncio_op_t *op)
|
||||||
{
|
{
|
||||||
op->round_size = size;
|
int status = 0;
|
||||||
}
|
|
||||||
|
|
||||||
bool asyncio_op_finish_read_round(asyncio_op_t *op)
|
|
||||||
{
|
|
||||||
if(op->realized_size_r)
|
if(op->realized_size_r)
|
||||||
*op->realized_size_r += op->round_size;
|
*op->realized_size_r += op->round_size;
|
||||||
op->buffer_used -= op->round_size;
|
op->buffer_used -= op->round_size;
|
||||||
|
if(op->data_r)
|
||||||
op->data_r += op->round_size;
|
op->data_r += op->round_size;
|
||||||
op->size -= op->round_size;
|
op->size -= op->round_size;
|
||||||
op->round_size = 0;
|
op->round_size = 0;
|
||||||
|
|
||||||
return (op->buffer_used == 0);
|
bool read_fulfilled = (op->size == 0);
|
||||||
|
bool hwseg_exhausted =
|
||||||
|
(op->buffer_used == 0 && (op->autoclose_r || op->size > 0));
|
||||||
|
bool transaction_exhausted = hwseg_exhausted && !op->cont_r;
|
||||||
|
|
||||||
|
if(read_fulfilled || transaction_exhausted) {
|
||||||
|
op->dma = false;
|
||||||
|
op->autoclose_r = false;
|
||||||
|
op->data_r = NULL;
|
||||||
|
op->size = 0;
|
||||||
|
op->callback = GINT_CALL_NULL;
|
||||||
|
op->realized_size_r = NULL;
|
||||||
|
status |= ASYNCIO_REQUEST_FINISHED;
|
||||||
|
}
|
||||||
|
if(hwseg_exhausted) {
|
||||||
|
op->buffer_used = -1;
|
||||||
|
status |= ASYNCIO_HWSEG_EXHAUSTED;
|
||||||
|
|
||||||
|
if(status & ASYNCIO_REQUEST_FINISHED) {
|
||||||
|
op->type = ASYNCIO_NONE;
|
||||||
|
op->buffer_used = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(transaction_exhausted) {
|
||||||
|
status |= ASYNCIO_TRANSACTION_EXHAUSTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void asyncio_op_cancel_read(asyncio_op_t *op)
|
||||||
|
{
|
||||||
|
op->dma = false;
|
||||||
|
op->data_r = NULL;
|
||||||
|
op->autoclose_r = false;
|
||||||
|
op->size = 0;
|
||||||
|
op->callback = GINT_CALL_NULL;
|
||||||
|
op->realized_size_r = NULL;
|
||||||
|
|
||||||
|
if(!asyncio_op_has_read_hwseg(op)) {
|
||||||
|
op->type = ASYNCIO_NONE;
|
||||||
|
op->buffer_used = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,12 +216,18 @@ static void (*recv_handler)(void) = NULL;
|
||||||
|
|
||||||
bool usb_fxlink_handle_messages(usb_fxlink_header_t *header_ptr)
|
bool usb_fxlink_handle_messages(usb_fxlink_header_t *header_ptr)
|
||||||
{
|
{
|
||||||
if(!usb_poll(usb_ff_bulk_input()))
|
if(!usb_is_open())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
/* Read a message header first */
|
/* Read a message header first */
|
||||||
usb_fxlink_header_t header;
|
usb_fxlink_header_t header;
|
||||||
int rc = usb_read_sync(usb_ff_bulk_input(), &header, sizeof header, false);
|
timeout_t tm = timeout_make_ms(1000);
|
||||||
|
|
||||||
|
int rc = usb_read_async(usb_ff_bulk_input(), &header, sizeof header,
|
||||||
|
USB_READ_IGNORE_ZEROS | USB_READ_AUTOCLOSE | USB_READ_WAIT, NULL,
|
||||||
|
&tm, GINT_CALL_NULL);
|
||||||
|
if(rc == USB_READ_IDLE)
|
||||||
|
return false;
|
||||||
|
|
||||||
if(rc < 0) {
|
if(rc < 0) {
|
||||||
USB_LOG("[ff-bulk] Header read failed: %d\n", rc);
|
USB_LOG("[ff-bulk] Header read failed: %d\n", rc);
|
||||||
|
@ -233,7 +239,6 @@ bool usb_fxlink_handle_messages(usb_fxlink_header_t *header_ptr)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
header.version = le32toh(header.version);
|
header.version = le32toh(header.version);
|
||||||
header.size = le32toh(header.size);
|
header.size = le32toh(header.size);
|
||||||
header.transfer_size = le32toh(header.transfer_size);
|
header.transfer_size = le32toh(header.transfer_size);
|
||||||
|
@ -243,6 +248,11 @@ bool usb_fxlink_handle_messages(usb_fxlink_header_t *header_ptr)
|
||||||
(void)minor;
|
(void)minor;
|
||||||
(void)major;
|
(void)major;
|
||||||
|
|
||||||
|
if(major != 1 || minor != 0) {
|
||||||
|
USB_LOG("[ff-bulk] Invalid message header!\n");
|
||||||
|
return usb_fxlink_handle_messages(header_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
USB_LOG("[ff-bulk %d.%d] New %.16s.%.16s (%d bytes)\n", major, minor,
|
USB_LOG("[ff-bulk %d.%d] New %.16s.%.16s (%d bytes)\n", major, minor,
|
||||||
header.application, header.type, header.size);
|
header.application, header.type, header.size);
|
||||||
|
|
||||||
|
@ -272,11 +282,17 @@ void usb_fxlink_set_notifier(void (*notifier_function)(void))
|
||||||
|
|
||||||
void usb_fxlink_drop_transaction(void)
|
void usb_fxlink_drop_transaction(void)
|
||||||
{
|
{
|
||||||
|
int block = USB_READ_BLOCK;
|
||||||
|
|
||||||
while(1) {
|
while(1) {
|
||||||
int rc = usb_read_sync(usb_ff_bulk_input(), NULL, 512, false);
|
timeout_t tm = timeout_make_ms(1000);
|
||||||
/* Break on error or short read */
|
int rc = usb_read_async(usb_ff_bulk_input(), NULL, 512,
|
||||||
|
USB_READ_WAIT | block, NULL, &tm, GINT_CALL_NULL);
|
||||||
|
|
||||||
|
/* Break on error or short read (end of transaction) */
|
||||||
if(rc != 512)
|
if(rc != 512)
|
||||||
break;
|
break;
|
||||||
|
block = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
270
src/usb/pipes.c
270
src/usb/pipes.c
|
@ -5,6 +5,7 @@
|
||||||
#include <gint/defs/util.h>
|
#include <gint/defs/util.h>
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <gint/drivers/asyncio.h>
|
#include <gint/drivers/asyncio.h>
|
||||||
#include "usb_private.h"
|
#include "usb_private.h"
|
||||||
|
@ -129,9 +130,10 @@ static fifo_t fifo_find_available_controller(int pipe)
|
||||||
if(pipe == 0) return CF;
|
if(pipe == 0) return CF;
|
||||||
|
|
||||||
if(USB.D0FIFOSEL.CURPIPE == 0) return D0F;
|
if(USB.D0FIFOSEL.CURPIPE == 0) return D0F;
|
||||||
USB_LOG("D0 is unavailable!\n");
|
// USB_LOG("D0 is unavailable!\n");
|
||||||
if(USB.D1FIFOSEL.CURPIPE == 0) return D1F;
|
if(USB.D1FIFOSEL.CURPIPE == 0) return D1F;
|
||||||
USB_LOG("D1 is unavailable!\n");
|
// USB_LOG("D1 is unavailable!\n");
|
||||||
|
USB_LOG("No controller is unavailable!\n");
|
||||||
|
|
||||||
return NOF;
|
return NOF;
|
||||||
}
|
}
|
||||||
|
@ -467,7 +469,7 @@ int usb_commit_async(int pipe, gint_call_t callback)
|
||||||
USB.BEMPENB |= (1 << pipe);
|
USB.BEMPENB |= (1 << pipe);
|
||||||
if(t->controller == D0F) USB.D0FIFOCTR.BVAL = 1;
|
if(t->controller == D0F) USB.D0FIFOCTR.BVAL = 1;
|
||||||
if(t->controller == D1F) USB.D1FIFOCTR.BVAL = 1;
|
if(t->controller == D1F) USB.D1FIFOCTR.BVAL = 1;
|
||||||
USB_LOG("[PIPE%d] Committed transfer\n", pipe);
|
// USB_LOG("[PIPE%d] Committed transfer\n", pipe);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -524,34 +526,58 @@ void usb_pipe_write_bemp(int pipe)
|
||||||
// Reading operations
|
// Reading operations
|
||||||
//---
|
//---
|
||||||
|
|
||||||
static int handle_incoming_hwseg(asyncio_op_t *t, int pipe);
|
|
||||||
|
|
||||||
#ifdef GINT_USB_DEBUG
|
#ifdef GINT_USB_DEBUG
|
||||||
static void USB_LOG_TR(char const *prefix, asyncio_op_t *t)
|
static void USB_LOG_TR(char const *p, asyncio_op_t *t, char const *fmt, ...)
|
||||||
{
|
{
|
||||||
USB_LOG("%s: %s buf=%d%s%s req=%d/%d",
|
int E = USB.INTENB0.BRDYE;
|
||||||
prefix,
|
USB.INTENB0.BRDYE = 0;
|
||||||
t->type == ASYNCIO_READ ? "READ" : "NONE",
|
|
||||||
|
char str[128];
|
||||||
|
snprintf(str, sizeof str - 1, "%s: %s buf=%d%s%s req=%d/%d%s ",
|
||||||
|
p, t->type == ASYNCIO_READ ? "READ" : "NONE",
|
||||||
t->buffer_used, t->cont_r ? "+":"", t->interrupt_r ? "!":"",
|
t->buffer_used, t->cont_r ? "+":"", t->interrupt_r ? "!":"",
|
||||||
t->round_size, t->size);
|
t->round_size, t->size, t->autoclose_r ? "#" : "");
|
||||||
|
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
int l = strlen(str);
|
||||||
|
vsnprintf(str + l, sizeof str - 1 - l, fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
strcat(str, "\n");
|
||||||
|
USB_LOG("%s", str);
|
||||||
|
|
||||||
|
/* if(cpu_getSR().IMASK == 0) {
|
||||||
|
extern void usb_fxlink_text(char const *str, int size);
|
||||||
|
usb_fxlink_text(str, 0);
|
||||||
|
} */
|
||||||
|
|
||||||
|
USB.INTENB0.BRDYE = E;
|
||||||
}
|
}
|
||||||
#define USB_LOG_TR(PREFIX, OP, ...) do { \
|
|
||||||
int __E = USB.INTENB0.BRDYE; \
|
|
||||||
USB.INTENB0.BRDYE = 0; \
|
|
||||||
USB_LOG_TR(PREFIX, OP); \
|
|
||||||
__VA_OPT__(USB_LOG(" " __VA_ARGS__)); \
|
|
||||||
USB_LOG("\n"); \
|
|
||||||
USB.INTENB0.BRDYE = __E; \
|
|
||||||
} while(0)
|
|
||||||
#else
|
#else
|
||||||
#define USB_LOG_TR(PREFIX, OP, ...)
|
#define USB_LOG_TR(PREFIX, OP, FMT, ...)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Unbind resources after a hardware segment is finished reading. */
|
static void finish_read_round(asyncio_op_t *t, int pipe)
|
||||||
static void finish_read_hwseg(asyncio_op_t *t, int pipe)
|
|
||||||
{
|
{
|
||||||
|
USB_LOG("[PIPE%d] read %d/(r%d,b%d) bytes\n", pipe,
|
||||||
|
t->round_size, t->size, t->buffer_used);
|
||||||
|
|
||||||
|
/* This call will propagate all changes to the op, including finishing
|
||||||
|
the hardware segment and call (if appropriate). The only thing it
|
||||||
|
doesn't do is invoke the callback, so we have a chance to switch to
|
||||||
|
PID=BUF before doing it manually */
|
||||||
|
gint_call_t cb = t->callback;
|
||||||
|
int status = asyncio_op_finish_read_round(t);
|
||||||
|
|
||||||
|
USB_LOG_TR("frr", t, "finished=%c%c%c",
|
||||||
|
status & ASYNCIO_HWSEG_EXHAUSTED ? 'H' : '-',
|
||||||
|
status & ASYNCIO_REQUEST_FINISHED ? 'R' : '-',
|
||||||
|
status & ASYNCIO_TRANSACTION_EXHAUSTED ? 'T' : '-');
|
||||||
|
|
||||||
|
if(status & ASYNCIO_HWSEG_EXHAUSTED) {
|
||||||
#ifdef GINT_USB_DEBUG
|
#ifdef GINT_USB_DEBUG
|
||||||
/* Log DTLN to help identify bugs where we clear data we didn't read */
|
/* Log DTLN to help identify data loss bugs */
|
||||||
int DTLN = -1;
|
int DTLN = -1;
|
||||||
if(t->controller == CF) DTLN = USB.CFIFOCTR.DTLN;
|
if(t->controller == CF) DTLN = USB.CFIFOCTR.DTLN;
|
||||||
if(t->controller == D0F) DTLN = USB.D0FIFOCTR.DTLN;
|
if(t->controller == D0F) DTLN = USB.D0FIFOCTR.DTLN;
|
||||||
|
@ -562,55 +588,33 @@ static void finish_read_hwseg(asyncio_op_t *t, int pipe)
|
||||||
if(t->controller == D0F) USB.D0FIFOCTR.BCLR = 1;
|
if(t->controller == D0F) USB.D0FIFOCTR.BCLR = 1;
|
||||||
if(t->controller == D1F) USB.D1FIFOCTR.BCLR = 1;
|
if(t->controller == D1F) USB.D1FIFOCTR.BCLR = 1;
|
||||||
|
|
||||||
USB_LOG("frhwseg: DTLN=%d cont=%d interrupt=%d\n", DTLN, t->cont_r,
|
USB_LOG("frr[seg]: DTLN=%d cont=%d interrupt=%d\n",
|
||||||
t->interrupt_r);
|
DTLN, t->cont_r, t->interrupt_r);
|
||||||
|
USB_TRACE("finish_read_round() [seg]");
|
||||||
|
|
||||||
asyncio_op_finish_read_hwseg(t);
|
if(status & ASYNCIO_TRANSACTION_EXHAUSTED) {
|
||||||
USB_TRACE("finish_read_hwseg()");
|
|
||||||
|
|
||||||
if(!t->cont_r) {
|
|
||||||
fifo_unbind(t->controller);
|
fifo_unbind(t->controller);
|
||||||
t->controller = NOF;
|
t->controller = NOF;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Re-enable communication for the next segment */
|
/* Re-enable communication for the next segment */
|
||||||
USB_LOG_TR("frhwseg", t, "--> PID=BUF");
|
USB_LOG_TR("frr[seg]", t, "--> PID=BUF");
|
||||||
USB.PIPECTR[pipe-1].PID = PID_BUF;
|
USB.PIPECTR[pipe-1].PID = PID_BUF;
|
||||||
}
|
|
||||||
|
|
||||||
static void finish_read_round(asyncio_op_t *t, int pipe)
|
|
||||||
{
|
|
||||||
USB_LOG("[PIPE%d] finished reading %d/%d bytes\n", pipe,
|
|
||||||
t->round_size, t->buffer_used);
|
|
||||||
|
|
||||||
bool hwseg_finished = asyncio_op_finish_read_round(t);
|
|
||||||
USB_LOG_TR("frr1", t, "hwseg finished = %d", hwseg_finished);
|
|
||||||
if(hwseg_finished) {
|
|
||||||
finish_read_hwseg(t, pipe);
|
|
||||||
handle_incoming_hwseg(t, pipe);
|
|
||||||
}
|
}
|
||||||
|
if(status & ASYNCIO_REQUEST_FINISHED) {
|
||||||
USB_LOG("req. progress: size=%d cont=%d interrupt=%d\n",
|
gint_call(cb);
|
||||||
t->size, t->cont_r, t->interrupt_r);
|
|
||||||
|
|
||||||
/* End the call if we exhausted either the buffer or the transaction */
|
|
||||||
if(t->size == 0 || !t->cont_r) {
|
|
||||||
asyncio_op_finish_read(t);
|
|
||||||
USB_LOG_TR("fc", t);
|
|
||||||
USB_TRACE("finish_read_round() finish call");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void read_round(asyncio_op_t *t, int pipe)
|
static bool read_round(asyncio_op_t *t, int pipe)
|
||||||
{
|
{
|
||||||
int round_size = (t->size < t->buffer_used ? t->size : t->buffer_used);
|
int round_size = asyncio_op_start_read_round(t);
|
||||||
asyncio_op_start_read_round(t, round_size);
|
USB_LOG_TR("rr", t, "");
|
||||||
USB_LOG_TR("rr", t);
|
|
||||||
|
|
||||||
/* No data to read: finish the round immediately */
|
/* No data to read: finish the round immediately */
|
||||||
if(round_size == 0 || t->data_r == NULL) {
|
if(round_size == 0 || t->data_r == NULL) {
|
||||||
finish_read_round(t, pipe);
|
finish_read_round(t, pipe);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read stuff (TODO: Smart reads + DMA) */
|
/* Read stuff (TODO: Smart reads + DMA) */
|
||||||
|
@ -620,6 +624,7 @@ static void read_round(asyncio_op_t *t, int pipe)
|
||||||
if(t->controller == D1F) FIFO = &USB.D1FIFO;
|
if(t->controller == D1F) FIFO = &USB.D1FIFO;
|
||||||
|
|
||||||
void *dataptr = t->data_r;
|
void *dataptr = t->data_r;
|
||||||
|
if(dataptr) {
|
||||||
for(int i = 0; i < round_size / 4; i++) {
|
for(int i = 0; i < round_size / 4; i++) {
|
||||||
*(uint32_t *)dataptr = *FIFO;
|
*(uint32_t *)dataptr = *FIFO;
|
||||||
dataptr += 4;
|
dataptr += 4;
|
||||||
|
@ -632,15 +637,26 @@ static void read_round(asyncio_op_t *t, int pipe)
|
||||||
*(uint8_t *)dataptr = *(uint8_t volatile *)FIFO;
|
*(uint8_t *)dataptr = *(uint8_t volatile *)FIFO;
|
||||||
dataptr += 1;
|
dataptr += 1;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
volatile int x;
|
||||||
|
for(int i = 0; i < round_size / 4; i++)
|
||||||
|
x = *FIFO;
|
||||||
|
if(round_size & 2)
|
||||||
|
x = *(uint16_t volatile *)FIFO;
|
||||||
|
if(round_size & 1)
|
||||||
|
x = *(uint8_t volatile *)FIFO;
|
||||||
|
(void)x;
|
||||||
|
}
|
||||||
|
|
||||||
finish_read_round(t, pipe);
|
finish_read_round(t, pipe);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int handle_incoming_hwseg(asyncio_op_t *t, int pipe)
|
static int handle_incoming_hwseg(asyncio_op_t *t, int pipe)
|
||||||
{
|
{
|
||||||
/* Do nothing if no interrupt is waiting or if the pipe is already
|
/* Do nothing if no interrupt is waiting or there is a hwseg */
|
||||||
processing a previous hardware segment */
|
if(!t->interrupt_r || asyncio_op_has_read_hwseg(t))
|
||||||
if(!t->interrupt_r || t->buffer_used > 0)
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* PID will stay at NAK for the entire duration of the segment */
|
/* PID will stay at NAK for the entire duration of the segment */
|
||||||
|
@ -656,13 +672,6 @@ static int handle_incoming_hwseg(asyncio_op_t *t, int pipe)
|
||||||
|
|
||||||
t->interrupt_r = false;
|
t->interrupt_r = false;
|
||||||
|
|
||||||
/* Continue an ongoing transfer */
|
|
||||||
if(asyncio_op_busy(t)) {
|
|
||||||
USB_LOG("PIPE %d new hwseg while busy -> new round\n", pipe);
|
|
||||||
read_round(t, pipe);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int data_available = 0;
|
int data_available = 0;
|
||||||
if(t->controller == CF) data_available = USB.CFIFOCTR.DTLN;
|
if(t->controller == CF) data_available = USB.CFIFOCTR.DTLN;
|
||||||
if(t->controller == D0F) data_available = USB.D0FIFOCTR.DTLN;
|
if(t->controller == D0F) data_available = USB.D0FIFOCTR.DTLN;
|
||||||
|
@ -674,11 +683,21 @@ static int handle_incoming_hwseg(asyncio_op_t *t, int pipe)
|
||||||
|
|
||||||
asyncio_op_start_read_hwseg(t, data_available, cont);
|
asyncio_op_start_read_hwseg(t, data_available, cont);
|
||||||
USB_LOG_TR("nseg", t, "PIPECTR=%04x", USB.PIPECTR[pipe-1].word);
|
USB_LOG_TR("nseg", t, "PIPECTR=%04x", USB.PIPECTR[pipe-1].word);
|
||||||
|
|
||||||
|
/* Continue an ongoing transfer */
|
||||||
|
if(asyncio_op_busy(t)) {
|
||||||
|
USB_LOG("PIPE %d new hwseg while busy -> new round\n", pipe);
|
||||||
|
read_round(t, pipe);
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int usb_read_async(int pipe, void *data, int size, bool use_dma,
|
/* Performs a single asynchronous read. This function implements support for
|
||||||
int *read_size, gint_call_t callback)
|
the USE_DMA, IGNORE_ZEROS, and AUTOCLOSE flags. Returns a non-blocking error
|
||||||
|
code and invokes *callback after finite time. */
|
||||||
|
static int read_once(int pipe, void *data, int size, int flags, int *rc_ptr,
|
||||||
|
gint_call_t const *cb)
|
||||||
{
|
{
|
||||||
asyncio_op_t *t = &pipe_transfers[pipe];
|
asyncio_op_t *t = &pipe_transfers[pipe];
|
||||||
int rc;
|
int rc;
|
||||||
|
@ -690,42 +709,91 @@ int usb_read_async(int pipe, void *data, int size, bool use_dma,
|
||||||
if(t->type == ASYNCIO_NONE)
|
if(t->type == ASYNCIO_NONE)
|
||||||
return USB_READ_IDLE;
|
return USB_READ_IDLE;
|
||||||
|
|
||||||
asyncio_op_start_read(t, data, size, use_dma, read_size, &callback);
|
/* Handle 0-byte reads immediately because size == 0 is a special case
|
||||||
read_round(t, pipe);
|
for the asyncio structure (meaning no read request) */
|
||||||
|
if(!size) {
|
||||||
|
if(rc_ptr)
|
||||||
|
*rc_ptr = 0;
|
||||||
|
gint_call(*cb);
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool USE_DMA = (flags & USB_READ_USE_DMA) != 0;
|
||||||
|
bool AUTOCLOSE = (flags & USB_READ_AUTOCLOSE) != 0;
|
||||||
|
bool IGNORE_ZEROS = (flags & USB_READ_IGNORE_ZEROS) != 0;
|
||||||
|
|
||||||
|
asyncio_op_start_read(t, data, size, USE_DMA, rc_ptr, AUTOCLOSE, cb);
|
||||||
|
|
||||||
|
/* Start the first round; others will follow from BRDY interrupts. When
|
||||||
|
dealing with a 0-byte read due to an exhausted transaction, this
|
||||||
|
function will finish the round immediately and return true. The user
|
||||||
|
callback will not be invoked since size > 0, so we can let the round
|
||||||
|
ru and emit USB_ERROR_ZERO_LENGTH afterwards. */
|
||||||
|
bool zero_length = read_round(t, pipe);
|
||||||
|
|
||||||
|
return (zero_length && IGNORE_ZEROS) ? USB_ERROR_ZERO_LENGTH : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int usb_read_sync_timeout(int pipe, void *data, int size, bool dma,
|
/* Implements a generic sync or async read. This function implements support
|
||||||
timeout_t const *timeout)
|
for the WAIT and BLOCK flags, and the timeout. */
|
||||||
|
int read_core(int pipe, void *data, int size, int flags, int *rc_ptr,
|
||||||
|
timeout_t const *timeout, gint_call_t cb)
|
||||||
{
|
{
|
||||||
int volatile flag = 0;
|
int volatile flag = 0;
|
||||||
int read_size = 0;
|
int async_rc = 0;
|
||||||
|
if(flags & USB_READ_WAIT) {
|
||||||
/* Wait until there is stuff to read, then read it */
|
cb = GINT_CALL_SET(&flag);
|
||||||
while(1) {
|
rc_ptr = &async_rc;
|
||||||
int rc = usb_read_async(pipe, data, size, dma, &read_size,
|
|
||||||
GINT_CALL_SET(&flag));
|
|
||||||
if(rc == 0)
|
|
||||||
break;
|
|
||||||
if(rc != USB_BUSY && rc != USB_READ_IDLE)
|
|
||||||
return rc;
|
|
||||||
if(timeout_elapsed(timeout))
|
|
||||||
return USB_TIMEOUT;
|
|
||||||
sleep();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Perform only a single read, unless USB_READ_BLOCK is set */
|
||||||
|
while(1) {
|
||||||
|
int rc = read_once(pipe, data, size, flags, rc_ptr, &cb);
|
||||||
|
|
||||||
|
/* On blocked timing errors, try again later */
|
||||||
|
if((rc == USB_BUSY || rc == USB_READ_IDLE)
|
||||||
|
&& (flags & USB_READ_BLOCK)) {
|
||||||
|
if(timeout_elapsed(timeout)) {
|
||||||
|
usb_read_cancel(pipe);
|
||||||
|
return USB_TIMEOUT;
|
||||||
|
}
|
||||||
|
sleep();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* On ignored zero-length reads, try again immediately */
|
||||||
|
else if(rc == USB_ERROR_ZERO_LENGTH)
|
||||||
|
continue;
|
||||||
|
/* Other errors are fatal */
|
||||||
|
else if(rc < 0 || !(flags & USB_READ_WAIT))
|
||||||
|
return rc;
|
||||||
|
|
||||||
/* Wait until the read completes */
|
/* Wait until the read completes */
|
||||||
while(!flag) {
|
while(!flag) {
|
||||||
if(timeout_elapsed(timeout))
|
if(timeout_elapsed(timeout)) {
|
||||||
|
usb_read_cancel(pipe);
|
||||||
return USB_TIMEOUT;
|
return USB_TIMEOUT;
|
||||||
|
}
|
||||||
sleep();
|
sleep();
|
||||||
}
|
}
|
||||||
|
return async_rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Ignore 0-length reads */
|
int usb_read_async(int pipe, void *data, int size, int flags, int *rc_ptr,
|
||||||
if(read_size == 0)
|
timeout_t const *timeout, gint_call_t cb)
|
||||||
return usb_read_sync_timeout(pipe, data, size, dma, timeout);
|
{
|
||||||
|
return read_core(pipe, data, size, flags, rc_ptr, timeout, cb);
|
||||||
|
}
|
||||||
|
|
||||||
return read_size;
|
int usb_read_sync_timeout(int pipe, void *data, int size, bool dma,
|
||||||
|
timeout_t const *tm)
|
||||||
|
{
|
||||||
|
int flags = (dma ? USB_READ_USE_DMA : 0)
|
||||||
|
| USB_READ_IGNORE_ZEROS
|
||||||
|
| USB_READ_AUTOCLOSE
|
||||||
|
| USB_READ_WAIT
|
||||||
|
| USB_READ_BLOCK;
|
||||||
|
return read_core(pipe, data, size, flags, NULL, tm, GINT_CALL_NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
int usb_read_sync(int pipe, void *data, int size, bool use_dma)
|
int usb_read_sync(int pipe, void *data, int size, bool use_dma)
|
||||||
|
@ -733,16 +801,13 @@ int usb_read_sync(int pipe, void *data, int size, bool use_dma)
|
||||||
return usb_read_sync_timeout(pipe, data, size, use_dma, NULL);
|
return usb_read_sync_timeout(pipe, data, size, use_dma, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool usb_poll(int pipe)
|
void usb_read_cancel(int pipe)
|
||||||
{
|
{
|
||||||
if(pipe < 0)
|
if(pipe < 0 || pipe > 9)
|
||||||
return false;
|
return;
|
||||||
|
|
||||||
asyncio_op_t *t = &pipe_transfers[pipe];
|
asyncio_op_cancel_read(&pipe_transfers[pipe]);
|
||||||
if(handle_incoming_hwseg(t, pipe))
|
// TODO: usb_read_cancel: Also cancel DMA if it's running!
|
||||||
return false;
|
|
||||||
|
|
||||||
return (t->type == ASYNCIO_READ) && (t->buffer_used > 0 || t->cont_r);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void usb_pipe_read_brdy(int pipe)
|
void usb_pipe_read_brdy(int pipe)
|
||||||
|
@ -752,9 +817,14 @@ void usb_pipe_read_brdy(int pipe)
|
||||||
|
|
||||||
asyncio_op_t *t = &pipe_transfers[pipe];
|
asyncio_op_t *t = &pipe_transfers[pipe];
|
||||||
|
|
||||||
/* The BRDY interrupt also occurs after a pipe clear */
|
/* If a transfer is ongoing and stalled waiting for a new segment,
|
||||||
if(!USB.PIPECTR[pipe-1].BSTS)
|
perform the round right now. This is acceptable because in that case
|
||||||
|
we are guarantees that a FIFO is bound is the round will succeed. */
|
||||||
|
if(asyncio_op_has_read_call(t) && !asyncio_op_has_read_hwseg(t)) {
|
||||||
|
t->interrupt_r = true;
|
||||||
|
handle_incoming_hwseg(t, pipe);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Signal the arrival to the main thread but don't do anything yet.
|
/* Signal the arrival to the main thread but don't do anything yet.
|
||||||
This is both for proper error handling and because changing transfer
|
This is both for proper error handling and because changing transfer
|
||||||
|
@ -767,5 +837,5 @@ void usb_pipe_read_brdy(int pipe)
|
||||||
if(ep->intf->notify_read)
|
if(ep->intf->notify_read)
|
||||||
ep->intf->notify_read(ep->dc->bEndpointAddress);
|
ep->intf->notify_read(ep->dc->bEndpointAddress);
|
||||||
|
|
||||||
USB_LOG_TR("int", t);
|
USB_LOG_TR("int", t, "");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue