2023-01-31 16:04:14 +01:00
|
|
|
//---
|
|
|
|
// gint:usb:asyncio - Asynchronous I/O common definitions
|
|
|
|
//---
|
|
|
|
|
|
|
|
#ifndef GINT_USB_ASYNCIO
|
|
|
|
#define GINT_USB_ASYNCIO
|
|
|
|
|
|
|
|
#include <gint/defs/types.h>
|
|
|
|
#include <gint/defs/call.h>
|
|
|
|
|
|
|
|
/* Data tracking the progress of a multi-part multi-round async I/O operation.
|
|
|
|
|
2023-03-12 17:33:55 +01:00
|
|
|
* Writes are multi-part because they are constructed over several calls to
|
|
|
|
write(2) followed by a "commit" with fynsc(2). They are additionally
|
|
|
|
multi-round because each call to write(2) requires mutiple rounds of
|
|
|
|
hardware communication when the hardware buffer is smaller than the data.
|
|
|
|
|
|
|
|
* Reads are multi-part because each transaction from the host requires
|
|
|
|
several calls to read(2) ("user segments") if the user's buffer is shorter
|
|
|
|
than the full transaction. In addition, reads are multi-round because a
|
|
|
|
single read(2) to a user buffer takes multiple rounds of hardware
|
|
|
|
communication ("hardware segments") whenever the user buffer is larger
|
|
|
|
than the hardware buffer.
|
2023-01-31 16:04:14 +01:00
|
|
|
|
|
|
|
The process of performing such an I/O operation, as tracked by this
|
2023-03-12 17:33:55 +01:00
|
|
|
structure and use throughout gint, is as follows.
|
|
|
|
|
|
|
|
## Writes
|
2023-01-31 16:04:14 +01:00
|
|
|
|
|
|
|
WRITING ---------------------.
|
|
|
|
^ | | HW buffer
|
|
|
|
Start writing | | Not full | full: start
|
|
|
|
| | | transmission
|
|
|
|
write(2) | v v
|
|
|
|
--> IDLE ------------------> PENDING <------------- FLYING-WRITE
|
|
|
|
^ ^ | DONE interrupt
|
|
|
|
| DONE write(2) | |
|
|
|
|
| interrupt | |
|
|
|
|
| | | Data exhausted
|
2023-02-04 21:02:59 +01:00
|
|
|
| fsync(2): start | v
|
|
|
|
FLYING-SYNC <------------ IN-PROGRESS
|
|
|
|
transmission
|
2023-01-31 16:04:14 +01:00
|
|
|
|
|
|
|
Initially the operation is in the IDLE state. When a write(2) is issued, it
|
|
|
|
interacts with hardware then transitions to the IN-PROGRESS state, where it
|
2023-02-04 21:02:59 +01:00
|
|
|
remains for any subsequent write(2). A fsync(2) will properly commit data to
|
2023-01-31 16:04:14 +01:00
|
|
|
the hardware, finish the operation and return to the IDLE state.
|
|
|
|
|
2023-02-04 21:02:59 +01:00
|
|
|
The FLYING-WRITE and FLYING-SYNC states refer to waiting periods, after
|
2023-01-31 16:04:14 +01:00
|
|
|
issuing hardware commands, during which hardware communicates. Usually an
|
|
|
|
interrupt signals when hardware is ready to resume work.
|
|
|
|
|
|
|
|
Note that in a series of write(2), hardware is only instructed to send data
|
|
|
|
once the hardware buffer is full. Therefore, a write(2) might transition
|
|
|
|
directly from IDLE or IN-PROGRESS, to PENDING, to IN-PROGRESS, without
|
|
|
|
actually communicating with the outside world.
|
|
|
|
|
2023-02-04 21:02:59 +01:00
|
|
|
An asynchronous write(2) might return to the caller as soon as writing is
|
|
|
|
finished even if the operation is left in the FLYING-WRITE state, and it may
|
|
|
|
even return while the operation is in the WRITING state if the DMA is used.
|
|
|
|
|
2023-01-31 16:04:14 +01:00
|
|
|
The invariants and meaning for each state are as follow:
|
|
|
|
|
2023-02-16 16:09:53 +01:00
|
|
|
State(s) Characterization Description
|
2023-01-31 16:04:14 +01:00
|
|
|
============================================================================
|
|
|
|
IDLE type == ASYNCIO_NONE No I/O operation
|
2023-02-16 16:09:53 +01:00
|
|
|
----------------------------------------------------------------------------
|
|
|
|
PENDING data_w && round_size == 0 Ready to write pending data
|
|
|
|
----------------------------------------------------------------------------
|
|
|
|
WRITING, round_size > 0 CPU/DMA write to HW in progress
|
|
|
|
FLYING-WRITE HW transmission in progress
|
|
|
|
----------------------------------------------------------------------------
|
2023-02-04 21:02:59 +01:00
|
|
|
IN-PROGRESS !data_w && type == WRITE Waiting for write(2) or fsync(2)
|
2023-02-16 16:09:53 +01:00
|
|
|
----------------------------------------------------------------------------
|
|
|
|
FLYING-SYNC type == ASYNCIO_SYNC HW commit in progress
|
2023-01-31 16:04:14 +01:00
|
|
|
============================================================================
|
|
|
|
|
2023-03-12 17:33:55 +01:00
|
|
|
The series of asyncio_op function calls for a write is as follows:
|
|
|
|
|
|
|
|
transaction ::= write* fsync
|
|
|
|
write ::= asyncio_op_start_write round+ asyncio_op_finish_write
|
|
|
|
round ::= asyncio_op_start_write_round asyncio_op_finish_write_round
|
|
|
|
fsync ::= asyncio_op_start_sync asyncio_op_finish_sync
|
|
|
|
|
|
|
|
Each write(2) (with a single user-provided buffer) is split into multiple
|
|
|
|
rounds that each fill the (small) hardware buffer. More writes can follow
|
|
|
|
until an fynsc(2) commits the pipe.
|
|
|
|
|
|
|
|
## Reads
|
|
|
|
|
2023-01-31 16:04:14 +01:00
|
|
|
IN interrupt
|
|
|
|
--> IDLE-EMPTY --------------> IDLE-READY
|
2023-03-12 17:33:55 +01:00
|
|
|
| \ read(2) | ^
|
|
|
|
read(2) | \ Transaction | | User buffer
|
|
|
|
| \ exhausted* | | filled
|
2023-01-31 16:04:14 +01:00
|
|
|
| '----<----------. | |
|
|
|
|
| \ | |
|
2023-03-12 17:33:55 +01:00
|
|
|
| IN interrupt \ | |
|
|
|
|
v .--------->--------. \ v | .---. Read from
|
|
|
|
WAITING READING v hardware
|
|
|
|
'---------<--------' '---'
|
|
|
|
HW buffer exhausted with
|
|
|
|
user buffer not full
|
2023-01-31 16:04:14 +01:00
|
|
|
|
|
|
|
On this diagram, the right side indicates the presence of data to read from
|
|
|
|
hardware while the bottom side indicates a read(2) request by the user.
|
2023-03-04 18:06:21 +01:00
|
|
|
Notice the diagonal arrow back to IDLE-EMPTY insteaf of WAITING, which
|
|
|
|
highlights that read(2) will always return at the end of a transaction even
|
|
|
|
if the user-provided buffer is not full (to avoid waiting).
|
|
|
|
|
2023-03-12 17:33:55 +01:00
|
|
|
A read(2) request (a "user segment") might consume several full hardware
|
|
|
|
buffers ("hardware segments") if the user buffer is large, thus looping
|
|
|
|
repeatedly between WAITING and READING. Conversely, each hardware segment
|
|
|
|
might fulfill many read(2) requests if the user buffer is small, thus
|
|
|
|
looping between IDLE-READY and READING.
|
|
|
|
|
|
|
|
* Note that if the transaction finishes right as the user buffer fills up,
|
|
|
|
we return to IDLE-READY and the next call to read(2) will successfully read
|
|
|
|
0 bytes and transition back to IDLE-EMPTY. This allows the user to read the
|
|
|
|
entire transaction by reading data until they get fewer bytes than requested
|
|
|
|
without running the risk of blocking on the next transaction.
|
2023-01-31 16:04:14 +01:00
|
|
|
|
|
|
|
The invariants and meaning for each state are as follow:
|
|
|
|
|
|
|
|
State Characterization Description
|
|
|
|
============================================================================
|
|
|
|
IDLE-EMPTY type == ASYNCIO_NONE No I/O operation
|
2023-02-16 16:09:53 +01:00
|
|
|
----------------------------------------------------------------------------
|
2023-03-12 17:33:55 +01:00
|
|
|
IDLE-READY !data_r Hardware segment pending
|
2023-02-16 16:09:53 +01:00
|
|
|
----------------------------------------------------------------------------
|
2023-03-12 17:33:55 +01:00
|
|
|
WAITING data_r && !round_size User segment pending
|
2023-02-16 16:09:53 +01:00
|
|
|
----------------------------------------------------------------------------
|
2023-03-12 17:33:55 +01:00
|
|
|
READING round_size > 0 DMA/CPU read round in progress
|
2023-01-31 16:04:14 +01:00
|
|
|
============================================================================
|
|
|
|
|
2023-03-12 17:33:55 +01:00
|
|
|
The series of asyncio_op function calls for a read is a bit more complicated
|
|
|
|
because transactions are divided into two non-comparable sequences of
|
|
|
|
segments: one for packets received by the hardware buffer (on BRDY), one for
|
|
|
|
the data being copied to user buffers (on read(2)).
|
|
|
|
|
|
|
|
|<------ Transaction from the host (no size limit) ------>|
|
|
|
|
|
|
|
|
v BRDY v BRDY v BRDY v BRDY v BRDY
|
|
|
|
+-----------+-----------+-----------+-----------+---------+
|
|
|
|
| HW buffer | HW buffer | HW buffer | HW buffer | (short) | HW segments
|
|
|
|
+-----------+------+----+-----------+-----------+---------+
|
|
|
|
| R1 | R2 | R3 | R4 | R5 | R6 | Read rounds
|
|
|
|
+-----------+------+----+-----------+-----------+---------+
|
|
|
|
| User buffer #1 | User buffer #2 | (short) | User segments
|
|
|
|
+-----------+------+----+-----------+-----------+---------+
|
|
|
|
^ read(2) ^ read(2) ^ read(2)
|
|
|
|
|
|
|
|
Reads rounds are exactly the intersections between hardware segments and
|
|
|
|
read(2) user segments.
|
|
|
|
|
2023-01-31 16:04:14 +01:00
|
|
|
States can be checked and transitioned with the API functions below. */
|
|
|
|
|
2023-02-04 21:02:59 +01:00
|
|
|
enum { ASYNCIO_NONE, ASYNCIO_READ, ASYNCIO_WRITE, ASYNCIO_SYNC };
|
2023-01-31 16:04:14 +01:00
|
|
|
|
2023-02-04 21:02:59 +01:00
|
|
|
typedef volatile struct
|
2023-01-31 16:04:14 +01:00
|
|
|
{
|
|
|
|
/** User-facing information **/
|
|
|
|
|
|
|
|
union {
|
|
|
|
/* Address of data to transfer, incremented gradually [write] */
|
|
|
|
void const *data_w;
|
|
|
|
/* Address of buffer to store data to, incremented gradually [read] */
|
|
|
|
void *data_r;
|
|
|
|
};
|
2023-03-12 17:33:55 +01:00
|
|
|
/* Size of data left to transfer to satisfy the complete request */
|
2023-01-31 16:04:14 +01:00
|
|
|
int size;
|
|
|
|
/* Callback at the end of the current write, final commit, or read */
|
|
|
|
gint_call_t callback;
|
2023-03-12 17:33:55 +01:00
|
|
|
/* For reading operations, pointer to total amount of transferred data */
|
|
|
|
int *realized_size_r;
|
|
|
|
|
|
|
|
/* Type of I/O operation (read/write/fsync) */
|
|
|
|
uint8_t type;
|
|
|
|
/* Whether the DMA should be used for hardware access */
|
|
|
|
bool dma;
|
2023-01-31 16:04:14 +01:00
|
|
|
|
|
|
|
/** 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;
|
2023-03-12 17:33:55 +01:00
|
|
|
/* 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;
|
2023-01-31 16:04:14 +01:00
|
|
|
/* Hardware resource being used for access (meaning depends on hardware).
|
2023-02-04 21:02:59 +01:00
|
|
|
Usually, this is assigned for the duration of hardware transaction.
|
|
|
|
This value is user-managed and not modified by asyncio_op functions. */
|
2023-01-31 16:04:14 +01:00
|
|
|
uint8_t controller;
|
|
|
|
|
2023-02-04 21:02:59 +01:00
|
|
|
/** Internal information **/
|
|
|
|
|
|
|
|
/* Number of bytes in short buffer (0..3) */
|
|
|
|
uint8_t shbuf_size;
|
|
|
|
/* Short buffer */
|
|
|
|
uint32_t shbuf;
|
|
|
|
|
2023-01-31 16:04:14 +01:00
|
|
|
} asyncio_op_t;
|
|
|
|
|
2023-02-04 21:02:59 +01:00
|
|
|
//---
|
|
|
|
// Initialization and query functions
|
|
|
|
//---
|
|
|
|
|
|
|
|
/* asyncio_op_clear(): Initialize/clear the storage for an I/O operation */
|
|
|
|
void asyncio_op_clear(asyncio_op_t *op);
|
|
|
|
|
|
|
|
/* asyncio_op_busy(): Check whether the transfer is busy for syscalls
|
|
|
|
|
|
|
|
This function checks whether the transfer is in a state where the CPU is
|
|
|
|
busy wrt. starting a new syscall, ie. read(2), write(2) or fsync(2). Returns
|
|
|
|
true if the CPU is busy and the call has to wait, false if the call can
|
|
|
|
proceed immediately. */
|
|
|
|
bool asyncio_op_busy(asyncio_op_t const *op);
|
|
|
|
|
|
|
|
//---
|
2023-03-12 17:33:55 +01:00
|
|
|
// I/O functions
|
2023-02-04 21:02:59 +01:00
|
|
|
//---
|
|
|
|
|
2023-03-12 17:33:55 +01:00
|
|
|
/* Start/finish a write(2) call. */
|
|
|
|
void asyncio_op_start_write(asyncio_op_t *op,
|
|
|
|
void const *data, size_t size, bool use_dma, gint_call_t const *callback);
|
|
|
|
void asyncio_op_finish_write(asyncio_op_t *op);
|
2023-02-04 21:02:59 +01:00
|
|
|
|
2023-03-12 17:33:55 +01:00
|
|
|
/* Start/finish a single-block write to hardware. */
|
2023-02-04 21:02:59 +01:00
|
|
|
void asyncio_op_start_write_round(asyncio_op_t *op, size_t size);
|
|
|
|
void asyncio_op_finish_write_round(asyncio_op_t *op);
|
2023-01-31 16:04:14 +01:00
|
|
|
|
2023-03-12 17:33:55 +01:00
|
|
|
/* Start an fsync(2) operation (after one or more writes) and finish it. */
|
|
|
|
void asyncio_op_start_sync(asyncio_op_t *op, gint_call_t const *callback);
|
|
|
|
void asyncio_op_finish_sync(asyncio_op_t *op);
|
|
|
|
|
|
|
|
/* Start/finish a read(2) call. */
|
|
|
|
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);
|
|
|
|
void asyncio_op_finish_read(asyncio_op_t *op);
|
|
|
|
|
|
|
|
/* Start/finish a hardware segment `cont` should be true if there will be
|
|
|
|
another segment in the same transaction. */
|
|
|
|
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
|
|
|
|
true if the current hardware segment finished with the round (op->cont_r
|
|
|
|
indicates whether more segments will follow). */
|
|
|
|
void asyncio_op_start_read_round(asyncio_op_t *op, size_t size);
|
|
|
|
bool asyncio_op_finish_read_round(asyncio_op_t *op);
|
2023-03-04 18:06:21 +01:00
|
|
|
|
2023-01-31 16:04:14 +01:00
|
|
|
#endif /* GINT_USB_ASYNCIO */
|