usb: consolidate reading mode (basic multi-segment reads now working)

This commit is contained in:
Lephe 2023-03-12 17:33:55 +01:00
parent c59b2f90fb
commit fc1f510288
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
6 changed files with 722 additions and 395 deletions

View file

@ -10,15 +10,22 @@
/* Data tracking the progress of a multi-part multi-round async I/O operation. /* Data tracking the progress of a multi-part multi-round async I/O operation.
* Multi-part refers to writes being constructed over several calls to * Writes are multi-part because they are constructed over several calls to
write(2) followed by a "commit" with fsync(2), and reads on a single data write(2) followed by a "commit" with fynsc(2). They are additionally
transfer being made over several calls to read(2) (for asynchronous file multi-round because each call to write(2) requires mutiple rounds of
descriptors at least). hardware communication when the hardware buffer is smaller than the data.
* Multi-round refers to the writes interacting multiple times with hardware
in order to communicate the complete 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.
The process of performing such an I/O operation, as tracked by this The process of performing such an I/O operation, as tracked by this
structure and use throughout gint, is as follows. For a write: structure and use throughout gint, is as follows.
## Writes
WRITING ---------------------. WRITING ---------------------.
^ | | HW buffer ^ | | HW buffer
@ -68,17 +75,32 @@
FLYING-SYNC type == ASYNCIO_SYNC HW commit in progress FLYING-SYNC type == ASYNCIO_SYNC HW commit in progress
============================================================================ ============================================================================
For a read: 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
IN interrupt IN interrupt
--> IDLE-EMPTY --------------> IDLE-READY --> IDLE-EMPTY --------------> IDLE-READY
| \ | ^ | \ read(2) | ^
read(2) | \ Transaction read(2) | | Buffer full read(2) | \ Transaction | | User buffer
| \ exhausted* | | | \ exhausted* | | filled
| '----<----------. | | | '----<----------. | |
| \ | | | \ | |
v IN interrupt \ v | .---. Read from | IN interrupt \ | |
WAITING ------------------> READING v hardware v .--------->--------. \ v | .---. Read from
'---' WAITING READING v hardware
'---------<--------' '---'
HW buffer exhausted with
user buffer not full
On this diagram, the right side indicates the presence of data to read from 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. hardware while the bottom side indicates a read(2) request by the user.
@ -86,13 +108,17 @@
highlights that read(2) will always return at the end of a transaction even 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). if the user-provided buffer is not full (to avoid waiting).
*The state returns to IDLE-EMPTY only if the transaction was exhausted while A read(2) request (a "user segment") might consume several full hardware
the buffer is not full. This is to ensure that the caller can detect the end buffers ("hardware segments") if the user buffer is large, thus looping
of a data transfer by waiting for a read(2) which returns less bytes than repeatedly between WAITING and READING. Conversely, each hardware segment
requested. In the case where a read(2) consumes exactly all remaining data, might fulfill many read(2) requests if the user buffer is small, thus
we do not transition immediately to IDLE-EMPTY because we return as many looping between IDLE-READY and READING.
bytes as were requested. Instead, we return to IDLE-EMPTY after successfully
yielding 0 bytes in the next call. * 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.
The invariants and meaning for each state are as follow: The invariants and meaning for each state are as follow:
@ -100,13 +126,33 @@
============================================================================ ============================================================================
IDLE-EMPTY type == ASYNCIO_NONE No I/O operation IDLE-EMPTY type == ASYNCIO_NONE No I/O operation
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
IDLE-READY !data_r && buffer_used > 0 Hardware waiting for us to read IDLE-READY !data_r Hardware segment pending
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
WAITING data_r && !buffer_used Waiting for further HW data WAITING data_r && !round_size User segment pending
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
READING round_size > 0 DMA/CPU read from HW in progress READING round_size > 0 DMA/CPU read round in progress
============================================================================ ============================================================================
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.
States can be checked and transitioned with the API functions below. */ States can be checked and transitioned with the API functions below. */
enum { ASYNCIO_NONE, ASYNCIO_READ, ASYNCIO_WRITE, ASYNCIO_SYNC }; enum { ASYNCIO_NONE, ASYNCIO_READ, ASYNCIO_WRITE, ASYNCIO_SYNC };
@ -115,21 +161,23 @@ typedef volatile struct
{ {
/** User-facing information **/ /** User-facing information **/
/* Type of I/O operation (read/write/fsync) */
uint8_t type;
/* Whether the DMA should be used for hardware access */
bool dma;
union { union {
/* Address of data to transfer, incremented gradually [write] */ /* Address of data to transfer, incremented gradually [write] */
void const *data_w; void const *data_w;
/* Address of buffer to store data to, incremented gradually [read] */ /* Address of buffer to store data to, incremented gradually [read] */
void *data_r; void *data_r;
}; };
/* Size of data left to transfer / buffer space available */ /* 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 */ /* Callback at the end of the current write, final commit, or read */
gint_call_t callback; gint_call_t callback;
/* 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;
/** Hardware state information **/ /** Hardware state information **/
@ -138,6 +186,12 @@ typedef volatile struct
/* Size of data being read/written in the current round (which may itself /* Size of data being read/written in the current round (which may itself
be asynchronous if it's using the DMA) */ be asynchronous if it's using the DMA) */
uint16_t round_size; 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). /* Hardware resource being used for access (meaning depends on hardware).
Usually, this is assigned for the duration of hardware transaction. Usually, this is assigned for the duration of hardware transaction.
This value is user-managed and not modified by asyncio_op functions. */ This value is user-managed and not modified by asyncio_op functions. */
@ -168,52 +222,36 @@ 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);
//--- //---
// Operations and call functions // I/O functions
//
// Notice that for a write, the process is a single write call containing many
// write rounds and only a single finish_call(), whereas for a read the process
// is a single read reception contaning many read calls each with their own
// finish_call(), and only a single finish_read_reception().
//--- //---
/* asyncio_op_start_write(): Start a write call */ /* Start/finish a write(2) call. */
void asyncio_op_start_write(asyncio_op_t *op, void const *data, size_t size, void asyncio_op_start_write(asyncio_op_t *op,
bool use_dma, gint_call_t const *callback); void const *data, size_t size, bool use_dma, gint_call_t const *callback);
void asyncio_op_finish_write(asyncio_op_t *op);
/* asyncio_op_start_sync(): Transition a write I/O operation to a fsync call */ /* Start/finish a single-block write to hardware. */
void asyncio_op_start_sync(asyncio_op_t *op, gint_call_t const *callback);
/* asyncio_op_start_read(): Start a single-block read from hardware
Returns the size that will actually be read (may be smaller than `size`). */
size_t asyncio_op_start_read(asyncio_op_t *op, void *data, size_t size,
bool use_dma, gint_call_t const *callback);
/* asyncio_op_finish_call(): Update state after a read/write/fsync call
This function should be called when the read(2)/write(2)/fsync(2) call last
started on the operation has concluded, including all of the hardware
effects. This isn't the moment when the syscall returns, rather it is the
moment when it completes its work. */
void asyncio_op_finish_call(asyncio_op_t *op);
//---
// Write round functions
//---
/* asyncio_op_start_write_round(): Start a single-block write to hardware */
void asyncio_op_start_write_round(asyncio_op_t *op, size_t size); void asyncio_op_start_write_round(asyncio_op_t *op, size_t size);
/* asyncio_op_finish_write_round(): Finish a write round and advance data */
void asyncio_op_finish_write_round(asyncio_op_t *op); void asyncio_op_finish_write_round(asyncio_op_t *op);
//--- /* Start an fsync(2) operation (after one or more writes) and finish it. */
// Read group functions void asyncio_op_start_sync(asyncio_op_t *op, gint_call_t const *callback);
//--- void asyncio_op_finish_sync(asyncio_op_t *op);
/* asyncio_op_start_read_group(): Start a read call */ /* Start/finish a read(2) call. */
void asyncio_op_start_read_group(asyncio_op_t *op, size_t total_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);
void asyncio_op_finish_read(asyncio_op_t *op);
/* asyncio_op_fininsh_read_group(): Reset operation after a read group */ /* Start/finish a hardware segment `cont` should be true if there will be
void asyncio_op_finish_read_group(asyncio_op_t *op); 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);
#endif /* GINT_USB_ASYNCIO */ #endif /* GINT_USB_ASYNCIO */

View file

@ -1,5 +1,27 @@
//--- //---
// gint:usb-ff-bulk - A trivial bulk-based transfer class // gint:usb-ff-bulk - A bulk-based transfer class using the fxlink protocol
//
// This interface (class code 0xff/0x77) implements a simple bidirectional
// communication channel running the fxlink protocol. It basically talks to
// fxlink's interactive mode on the host, and can broadly be used for 4 things:
//
// 1. Sending standard messages to fxlink (screenshots, logs...). Convenient
// functions are provided in this header to minimize the overhead. fxlink
// running in interactive mode (fxlink -t) will handle these messages in a
// suitable way automatically (eg. save screenshots as PNG).
//
// 2. Sending custom messages to fxlink (bench results, memory dumps, crash
// reports...). fxlink running in interactive mode will forward the data to
// appropriate user-provided external programs to handle the messages.
//
// 3. Applications based around on data streams coming from the host (screen
// projector, web browser...). This can be achieved by modifying fxlink or
// using it as a library.
//
// 4. Remote control from the fxlink interactive mode (fxlink -t). This is
// based on fxlink's 'command' and 'keyboard' protocols. This module will
// automatically handle commands and respond to them as long as the message
// handling function is called regularly.
//--- //---
#ifndef GINT_USB_FF_BULK #ifndef GINT_USB_FF_BULK
@ -10,164 +32,37 @@ extern "C" {
#endif #endif
#include <gint/usb.h> #include <gint/usb.h>
struct usb_fxlink_header;
/* The bulk transfer interface with class code 0xff provides a very simple /* This bulk transfer interface with class code 0xff/0x77 implements a simple
communication channel between the calculator and a host. There is bidirectional communication channel between the calculator and a host,
(currently) a single IN pipe that sends data from the calculator to the running at high-speed USB 2.0 and using the fxlink protocol. You can use it
host, at high-speed (USB 2.0). to communicate with fxlink's interactive mode on the host. */
The class code of this interface is 0xff, which means that the protocol is
custom and requires a custom program on the host to receive the data (unlike
for instance LINK on a Graph 90+E which behaves as a USB stick and can be
used with the file browser). fxlink can be used to the effect. */
extern usb_interface_t const usb_ff_bulk; extern usb_interface_t const usb_ff_bulk;
//--- //---
// Direct bulk access // Sending standard messages
//
// The following functions can be used to access the bulk pipes directly. They
// provide the pipe numbers, which can be passed for instance to usb_write
// functions.
//--- //---
/* usb_ff_bulk_output(): Pipe for calculator -> host communication */ /* usb_fxlink_text(): Send raw text
int usb_ff_bulk_output(void); Send a string; fxlink will display it in the terminal. This can be used to
back stdout/stderr. Sending lots of small messages can be slow; if that's a
//--- problem, fill in message manually. If size is 0, uses strlen(text). */
// fxlink protocol void usb_fxlink_text(char const *text, int size);
//
// fxlink is a bulk-based communication tool in the fxSDK that can be used to
// conveniently transfer or manipulate information during the execution of an
// add-in. For instance, screenshots can be saved, files can be transferred,
// and text can be logged.
//
// Each communication with fxlink is a message that consists of:
// * A first write with a message header;
// * One or more writes with message data (of a size announced in the header);
// * A commit on the USB pipe (to ensure that everything is sent).
//
// There are two ways to use the fxlink protocol in this header; you can either
// use one of the convenience functions like usb_fxlink_screenshot() that do
// all the steps for you, or you can perform the writes and commits yourself to
// send custom messages.
//---
/* usb_fxlink_header_t: Message header for fxlink
fxlink supports a minimalist protocol to receive data sent from the
calculator and automatically process it (such as save it to file, convert
to an image, etc). It is designed as a convenience feature, and it can be
extended with custom types rather easily.
A message is categorized with an (application, type) pair; both are UTF-8
strings of up to 16 characters. The application name "fxlink" is reserved
for built-in types supported by fxlink; any other custom application name
can be set (in which case fxlink will call a user-provided program to handle
the message).
The size of the data to be transferred must be specified in order of the
transfer to proceed correctly, as it cannot reliably be guessed on the other
side (and guessing wrong means all further messages will be in trouble).
As with the rest of the USB protocol, all the multi-byte integer fields in
this header are encoded as *little-endian*. */
typedef struct
{
/* Protocol version = 0x00000100 */
uint32_t version;
/* Size of the data to transfer (excluding this header) */
uint32_t size;
/* Size of individual transfers (related to the size of the FIFO) */
uint32_t transfer_size;
/* Application name, UTF-8 (might not be zero-terminated) */
char application[16];
/* Message type */
char type[16];
} usb_fxlink_header_t;
/* usb_fxlink_fill_header(): Fill an fxlink message header
This function will fill the specified fxlink header. You need to specify the
exact amount of data that the fxlink message will contain; if you cannot
determine it, consider splitting the message into several messages each with
their own header, and use the application-specific script on the host to
recombine them.
Returns false if the parameters are invalid or don't fit, in this case the
contents of the header are unchanged. */
bool usb_fxlink_fill_header(usb_fxlink_header_t *header,
char const *application, char const *type, uint32_t data_size);
//---
// Short functions for fxlink built-in types
//---
/* Subheader for the fxlink built-in "image" type */
typedef struct
{
uint32_t width;
uint32_t height;
/* Pixel format, see below */
int pixel_format;
} usb_fxlink_image_t;
/* Pixel formats */
typedef enum
{
/* Image is an array of *big-endian* uint16_t with RGB565 format */
USB_FXLINK_IMAGE_RGB565 = 0,
/* Image is an array of bits in mono format */
USB_FXLINK_IMAGE_MONO,
/* Image is two consecutive mono arrays, one for light, one for dark */
USB_FXLINK_IMAGE_GRAY,
} usb_fxlink_image_format_t;
/* usb_fxlink_screenshot(): Take a screenshot /* usb_fxlink_screenshot(): Take a screenshot
This function takes a screenshot. If (onscreen = false), it sends the This function sends a copy of the VRAM to fxlink. This is best used just
contents of the VRAM. This mode is best used just before dupdate(), since before dupdate() since this ensures the image sent by USB is identical to
the VRAM contents are exactly as they will appear on screen a moment later. the one displayed on screen.
However, this is somewhat unintuitive for interactive GUI as the user has to
press a button after the option is present on-screen, therefore the contents
of the *next* frame are captured.
If (onscreen = true), this function tries to send the pixels that are If `onscreen` is set and there are two VRAMs (on fx-CG or when using the
currently visible on-screen, with the following heuristic: gray engine on fx-9860G), sends a copy of the other VRAM. This is a bit more
intuitive when taking a screenshot of the last shown image as a result of a
* If there is only one VRAM, the VRAM contents are used in the hope they key press. Note that this function never reads pixels directly from the
haven't changed since the last frame was presented with dupdate(). display (it's usually slow and currently not even implemented). */
* If there are two VRAMs (on fx-CG 50 or using the gray engine) the contents
of the VRAM not currently being draw to are sent.
This function does not read pixels directly from the display, as this is
usually slow and currently not even implemented. */
void usb_fxlink_screenshot(bool onscreen); void usb_fxlink_screenshot(bool onscreen);
#ifdef FX9860G
/* usb_fxlink_screenshot_gray(): Take a gray screenshot on fx-9860G
This function is similar to usb_fxlink_screenshot(), but it takes a gray
screenshot. It depends on the gray engine so if you use your add-in will
automatically have the gray engine, that's why it's separate. */
void usb_fxlink_screenshot_gray(bool onscreen);
#endif
/* usb_fxlink_text(): Send raw text
This function sends a string with the "text" type, which fxlink prints on
the terminal. It will send a full fxlink message (with a commit), which is
inefficient if there is a lot of text to send. For better speed, send the
message manually by filling the header and doing the writes and commit
manually.
This function sends the text by blocks of 4 bytes or 2 bytes when alignment
and size allow, and 1 byte otherwise. If size is 0, strlen(text) is used. */
void usb_fxlink_text(char const *text, int size);
/* usb_fxlink_videocapture(): Send a frame for a video recording /* usb_fxlink_videocapture(): Send a frame for a video recording
This function is essentially the same as usb_fxlink_screenshot(). It sends a This function is essentially the same as usb_fxlink_screenshot(). It sends a
@ -180,11 +75,130 @@ void usb_fxlink_text(char const *text, int size);
void usb_fxlink_videocapture(bool onscreen); void usb_fxlink_videocapture(bool onscreen);
#ifdef FX9860G #ifdef FX9860G
/* usb_fxlink_videocapture_gray(): Send a gray frame for a video recording /* Similar to usb_fxlink_screenshot(), but takes a gray screenshot if the gray
Like usb_fxlink_videocapture(), but uses VRAM data from the gray engine. */ engine is currently running. */
void usb_fxlink_screenshot_gray(bool onscreen);
/* Like usb_fxlink_videocapture(), but uses VRAM data from the gray engine. */
void usb_fxlink_videocapture_gray(bool onscreen); void usb_fxlink_videocapture_gray(bool onscreen);
#endif #endif
//---
// Receiving messages
//---
/* usb_fxlink_handle_messages(): Process and return incoming messages
This function processes incoming messages. It handles some types of messages
internally (eg. fxlink commands) and returns the others to the caller by
loading the provided header structure and returning true.
When this function returns true, the caller should read the message contents
on the interface's input pipe. Usually every read will return just a portion
of the message (eg. 2048 bytes) so the contents should be read with a loop.
As long as this function returns true it should be called againt to process
any other messages. When there is no more messages to process, this function
returns false. It is important to call this function regularly from the main
thread when quick responses are expected. */
bool usb_fxlink_handle_messages(struct usb_fxlink_header *header);
/* usb_fxlink_drop_transaction(): Drop incoming data until end of transaction
When a message arrives on the USB port incoming data *must* be processed in
order for the pipe to accept any further data in the future. This function
can be used in error situations to drop data until the end of the
transaction (which is usually the end of the message, at least with the
fxSDK's implementation of fxlink). */
void usb_fxlink_drop_transaction(void);
/* usb_fxlink_set_notifier(): Set up a notification for incoming messages
Registers the provided function to be called when data arrives on the fxlink
input pipe. This signals that usb_fxlink_handle_messages() should be called
later from the main thread. Pass NULL to disable the notification. */
void usb_fxlink_set_notifier(void (*notifier_function)(void));
//---
// Direct bulk access
//
// The following functions can be used to access the bulk pipes directly. They
// provide the pipe numbers, which can be passed for instance to usb_write
// functions.
//---
/* usb_ff_bulk_output(): Pipe number for calculator -> host communication */
int usb_ff_bulk_output(void);
/* usb_ff_bulk_in(): Pipe number for host -> calculator communication */
int usb_ff_bulk_in(void);
//---
// Construction and analysis of fxlink protocol messages
//
// fxlink messages consist of a simple header followed by message contents
// (sometimes with a subheader). In addition to the functions above, it is
// possible to craft custom fxlink messages and send them manually.
//
// To send a message manually, simple write an fxlink header to the output
// pipe, followed by the contents. The message can be built from any number of
// writes to the pipe. After the last write, commit the pipe.
//---
/* usb_fxlink_header_t: Message header for fxlink
Messages are categorized with an (application, type) pair; both are UTF-8
strings of up to 16 bytes (NUL not required). The application name "fxlink"
is reserved for built-in types of messages. Other names can be used freely,
and fxlink's interactive mode on the host side will forward messages to
external programs based on the application name.
The size of the data to be transferred must be specified upfront, but this
restriction might be lifted in the future. As with the rest of the USB
protocol, all the integer are encoded as *little-endian*. */
typedef struct usb_fxlink_header
{
/* Protocol version = 0x00000100 */
uint32_t version;
/* Size of the data to transfer (excluding this header) */
uint32_t size;
/* Size of individual transfers (related to the size of the FIFO) */
uint32_t transfer_size;
/* Application name and message type, UTF-8 (need not be zero-terminated) */
char application[16];
char type[16];
} usb_fxlink_header_t;
/* usb_fxlink_fill_header(): Fill an fxlink message header
Fills an fxlink header's fields with the provided. You need to specify the
exact amount of data that the fxlink message will contain. Returns false if
parameters are invalid or don't fit the fields. */
bool usb_fxlink_fill_header(usb_fxlink_header_t *header,
char const *application, char const *type, uint32_t data_size);
/* Subheader for the fxlink built-in "image" type */
typedef struct
{
/* Image size; storage is row-major */
uint32_t width;
uint32_t height;
/* Pixel format, see below */
int pixel_format;
} usb_fxlink_image_t;
/* Pixel formats */
enum {
/* Image is an array of *big-endian* uint16_t with RGB565 format */
USB_FXLINK_IMAGE_RGB565 = 0,
/* Image is an array of bits in mono format */
USB_FXLINK_IMAGE_MONO,
/* Image is two consecutive mono arrays, one for light, one for dark */
USB_FXLINK_IMAGE_GRAY,
};
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -137,8 +137,12 @@ struct usb_interface {
/* Answer class-specific SETUP requests */ /* Answer class-specific SETUP requests */
/* TODO */ /* TODO */
/* Notification that an endpoint has data read to be read */ /* Notification that an endpoint has data to be read. This function is
void (*notify_read)(int endpoint, int size); called frequently when data is being transmitted; the particular
timings depend on low-level details. The notification should be
passed down to cause the main thread to read later. Do not read in
the notification function! */
void (*notify_read)(int endpoint);
}; };
/* usb_interface_endpoint_t: Parameters for an interface endpoint /* usb_interface_endpoint_t: Parameters for an interface endpoint
@ -269,24 +273,46 @@ 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`. This function will return as and then reads up to `size` bytes into `data`. It synchronizes with USB
soon as any data gets read, even if it's less than `size`. Thus, in order to transactions on the pipe in the following ways:
read all available data you should call this function in a loop until you
get less bytes than you requested.
It is possible for this function to return 0 bytes. If the data available on 1. If there is no active transaction when it is first called, it blocks
the pipe was consumed by a previous read call which perfectly filled the until one starts (which can freeze).
provided buffer, the pipe will still be considered in the "data available" 2. If there is an active transaction but all of its contents were consumed
state with 0 bytes left to read. The next read call will then successfully by a previous read, it is marked as complete before usb_read_sync()
return 0 bytes. This makes it possible to consistently detect the end of a starts waiting.
transfer as the first read which returns less bytes than requested.
The second point has a subtle implication. In an application that always
reads synchronously from a pipe, the "resting" state between transactions
can show the last processed transaction as active with 0 bytes remaining to
read, as processed transactions are not marked as completed until a read
request is fulfilled *partially*. This is for three reasons:
* Only marking transactions as complete upon partial reads makes it possible
to properly detect the end of transactions in asynchronous mode. See
usb_read_async() for more details.
* It further allows detecting the end of transactions in synchronous mode by
checking for partial reads *or* usb_poll() returning false.
* 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 This function will use a FIFO to access the pipe. The FIFO will only be
released once the full buffer. 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);
@ -297,9 +323,18 @@ int usb_read_sync_timeout(int pipe, void *data, int size, bool use_dma,
/* 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 is non-blocking; This function is similar to usb_read_sync() except that it doesn't
it returns USB_READ_INACTIVE if there is no active transfer on the pipe. It synchronize with transactions, resulting in two differences:
will also read 0 bytes under the same conditions as usb_read_sync().
1. It returns USB_READ_INACTIVE if there is no active transaction.
2. If there is an active transaction with 0 bytes left to read, it returns 0
bytes and marks the transaction as complete (unless size == 0).
Like usb_read_sync(), if the exact amount of data left in the transaction is
requested, the transaction will remain active with 0 bytes left to read and
the *next* read will return 0 bytes and mark it as complete. This way, the
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 Being asynchronous, this function starts the read process and returns
instantly; 0 on success, an error code otherwise. When the read finishes, instantly; 0 on success, an error code otherwise. When the read finishes,
@ -308,6 +343,25 @@ int usb_read_sync_timeout(int pipe, void *data, int size, bool use_dma,
int usb_read_async(int pipe, void *data, int size, bool use_dma, int usb_read_async(int pipe, void *data, int size, bool use_dma,
int *read_size, gint_call_t callback); int *read_size, gint_call_t callback);
/* usb_poll(): Check whether there is data to read in a pipe
This function checks whether a transaction is active on the pipe with more
than 0 bytes left to read. Its main purpose is to simplify reading patterns
with usb_read_sync(), in the following ways:
1. When usb_poll() returns true, usb_read_sync() is guaranteed to not block
(unless the host fails to complete a transaction).
2. After a synchronous read which was completely fulfilled (ie. returned as
many bytes as were requested), usb_poll() will tell whether there is any
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
easier to check the return status of usb_read_async(). */
bool usb_poll(int pipe);
//--- //---
// USB debugging functions // USB debugging functions
//--- //---

View file

@ -31,6 +31,18 @@ void asyncio_op_start_write(asyncio_op_t *op, void const *data, size_t size,
op->callback = *callback; op->callback = *callback;
} }
void asyncio_op_finish_write(asyncio_op_t *op)
{
gint_call(op->callback);
/* Keep relevant states until the transaction finishes with an fsync(2) */
op->dma = false;
op->data_w = NULL;
op->size = 0;
op->callback = GINT_CALL_NULL;
op->round_size = 0;
}
void asyncio_op_start_write_round(asyncio_op_t *op, size_t size) void asyncio_op_start_write_round(asyncio_op_t *op, size_t size)
{ {
op->round_size = size; op->round_size = size;
@ -53,55 +65,64 @@ void asyncio_op_start_sync(asyncio_op_t *op, gint_call_t const *callback)
op->callback = *callback; op->callback = *callback;
} }
void asyncio_op_start_read_group(asyncio_op_t *op, size_t total_size) void asyncio_op_finish_sync(asyncio_op_t *op)
{ {
op->type = ASYNCIO_READ; gint_call(op->callback);
op->size = total_size; asyncio_op_clear(op);
/* These are specified differently on each read(2) call */
op->dma = false;
op->data_r = NULL;
op->callback = GINT_CALL_NULL;
op->round_size = 0;
} }
size_t 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, gint_call_t const *callback) bool use_dma, int *realized_size, gint_call_t const *callback)
{ {
op->dma = use_dma; op->dma = use_dma;
op->data_r = data; op->data_r = data;
op->callback = *callback; op->callback = *callback;
op->round_size = size; op->realized_size_r = realized_size;
op->size = size;
return ((int)size < op->size ? (int)size : op->size); if(realized_size)
*realized_size = 0;
} }
void asyncio_op_finish_read_group(asyncio_op_t *op) void asyncio_op_finish_read(asyncio_op_t *op)
{
asyncio_op_clear(op);
}
void asyncio_op_finish_call(asyncio_op_t *op)
{ {
/* Note: In this function the type may be either READ or NONE depending of
whether there is a hardware segment being processed. */
gint_call(op->callback); gint_call(op->callback);
/* Generally clean up the operation; for a write, keep relevant states
until the transaction finishes with an fsync(2); for a read, keep info
needed for further read(2) calls. */
if(op->type == ASYNCIO_WRITE) {
op->dma = false;
op->data_w = NULL;
op->size = 0;
op->callback = GINT_CALL_NULL;
op->round_size = 0;
}
else if(op->type == ASYNCIO_READ) {
op->dma = false; op->dma = false;
op->data_r = NULL; op->data_r = NULL;
op->size -= op->round_size;
op->callback = GINT_CALL_NULL; op->callback = GINT_CALL_NULL;
op->realized_size_r = NULL;
op->size = 0;
}
void asyncio_op_start_read_hwseg(asyncio_op_t *op, size_t size, bool cont)
{
op->type = ASYNCIO_READ;
op->buffer_used = size;
op->cont_r = cont;
}
void asyncio_op_finish_read_hwseg(asyncio_op_t *op)
{
if(!op->cont_r)
op->type = ASYNCIO_NONE;
op->buffer_used = 0;
}
void asyncio_op_start_read_round(asyncio_op_t *op, size_t size)
{
op->round_size = size;
}
bool asyncio_op_finish_read_round(asyncio_op_t *op)
{
if(op->realized_size_r)
*op->realized_size_r += op->round_size;
op->buffer_used -= op->round_size;
op->data_r += op->round_size;
op->size -= op->round_size;
op->round_size = 0; op->round_size = 0;
}
else { return (op->buffer_used == 0);
asyncio_op_clear(op);
}
} }

View file

@ -1,10 +1,13 @@
#include <gint/usb.h> #include <gint/usb.h>
#include <gint/usb-ff-bulk.h> #include <gint/usb-ff-bulk.h>
#include <gint/display.h> #include <gint/display.h>
#include <gint/hardware.h>
#include <gint/cpu.h>
#include <string.h> #include <string.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h>
static void notify_read(int endpoint, int size); static void notify_read(int endpoint);
static usb_dc_interface_t dc_interface = { static usb_dc_interface_t dc_interface = {
.bLength = sizeof(usb_dc_interface_t), .bLength = sizeof(usb_dc_interface_t),
@ -156,50 +159,134 @@ void usb_fxlink_videocapture(bool onscreen)
capture_vram(onscreen, "video"); capture_vram(onscreen, "video");
} }
//---
// Built-in command execution
//---
static char const * const str_MPU[] = {
[HWMPU_UNKNOWN] = "Unknown MPU",
[HWMPU_SH7337] = "SH7737",
[HWMPU_SH7305] = "SH7305",
[HWMPU_SH7355] = "SH7355",
[HWMPU_SH7724] = "SH7724",
};
static char const * const str_CALC[] = {
[HWCALC_FX9860G_SH3] = "SH3-based fx-9860G-like",
[HWCALC_FX9860G_SH4] = "SH4-based fx-9860G-like",
[HWCALC_G35PE2] = "fx-9860G III/Graph 35+E II",
[HWCALC_PRIZM] = "Prizm fx-CG 10/20",
[HWCALC_FXCG50] = "fx-CG 50/Graph 90+E",
[HWCALC_FXCG_MANAGER] = "fx-CG Manager",
[HWCALC_FX9860G_SLIM] = "fx-9860G Slim",
};
static void execute_command(char const *cmd)
{
if(!strncmp(cmd, "echo", 4)) {
char const *text = cmd+4 + strspn(cmd+4, " \t\n");
usb_fxlink_text(text, 0);
}
if(!strncmp(cmd, "identify", 8)) {
#if defined(FX9860G)
char const *serial_number = (void *)0x8000ffd0;
char const *OS_version = (void *)0x80010020;
char const *BC_version = (void *)0x8000ffb0;
#elif defined(FXCG50)
char const *serial_number = (void *)0x8001ffd0;
char const *OS_version = (void *)0x80020020;
char const *BC_version = (void *)0x8001ffb0;
#endif
char str[256];
sprintf(str, "gint %.8s %s (0x%07x) on %s (%s) %.10s %.14s\n",
serial_number,
GINT_VERSION, GINT_HASH,
str_MPU[gint[HWMPU]], str_CALC[gint[HWCALC]],
OS_version, BC_version);
usb_fxlink_text(str, 0);
}
}
//--- //---
// Data reception // Data reception
//--- //---
/* Copy of the header for received BULK messages */ /* User notification function */
static usb_fxlink_header_t recv_header; static void (*recv_handler)(void) = NULL;
/* Size of transfer not yet read (implies valid header when non-zero) */
static int recv_size = 0;
static void header_finished(void) bool usb_fxlink_handle_messages(usb_fxlink_header_t *header_ptr)
{ {
recv_header.version = le32toh(recv_header.version); if(!usb_poll(usb_ff_bulk_input()))
recv_header.size = le32toh(recv_header.size); return false;
recv_header.transfer_size = le32toh(recv_header.transfer_size);
int major = (recv_header.version >> 8) & 0xff; /* Read a message header first */
int minor = (recv_header.version & 0xff); usb_fxlink_header_t header;
int rc = usb_read_sync(usb_ff_bulk_input(), &header, sizeof header, false);
USB_LOG("[ff-bulk %d.%d] New %.16s.%.16s (%d bytes)\n", major, minor, if(rc < 0) {
recv_header.application, recv_header.type, recv_header.size); USB_LOG("[ff-bulk] Header read failed: %d\n", rc);
return false;
}
if(rc < (int)sizeof header) {
USB_LOG("[ff-bulk] Incomplete header (%d/%d bytes)\n", rc,
sizeof header);
return false;
} }
static void notify_read(int endpoint, int size)
header.version = le32toh(header.version);
header.size = le32toh(header.size);
header.transfer_size = le32toh(header.transfer_size);
int major = (header.version >> 8) & 0xff;
int minor = (header.version & 0xff);
(void)minor;
(void)major;
USB_LOG("[ff-bulk %d.%d] New %.16s.%.16s (%d bytes)\n", major, minor,
header.application, header.type, header.size);
/* Handle built-in fxlink messages */
if(!strncmp(header.application, "fxlink", 16)
&& !strncmp(header.type, "command", 16)) {
USB_LOG("[ff-bulk] Receiving command (%d bytes)\n", header.size);
char *cmd = malloc(header.size + 1);
if(!cmd)
return false;
usb_read_sync(usb_ff_bulk_input(), cmd, header.size, false);
cmd[header.size] = 0;
USB_LOG("[ff-bulk] Command is: '%s'\n", cmd);
execute_command(cmd);
free(cmd);
return usb_fxlink_handle_messages(header_ptr);
}
*header_ptr = header;
return true;
}
void usb_fxlink_set_notifier(void (*notifier_function)(void))
{
recv_handler = notifier_function;
}
void usb_fxlink_drop_transaction(void)
{
while(1) {
int rc = usb_read_sync(usb_ff_bulk_input(), NULL, 512, false);
/* Break on error or short read */
if(rc != 512)
break;
}
}
static void notify_read(int endpoint)
{ {
/* We only have one endpoint for reading, the bulk OUT */ /* We only have one endpoint for reading, the bulk OUT */
(void)endpoint; (void)endpoint;
USB_LOG("[ff-bulk] Data available on %02x (%d bytes)\n", endpoint, size); // USB_LOG("[ff-bulk] Data available on %02x\n", endpoint);
if(recv_size <= 0) { if(recv_handler)
usb_read_sync(usb_ff_bulk_input(), &recv_header, sizeof recv_header, recv_handler();
false);
header_finished();
recv_size = recv_header.size;
}
else {
USB_LOG("-> malloc dropping\n");
char *data = malloc(size);
usb_read_sync(usb_ff_bulk_input(), data, size, false);
free(data);
recv_size -= size;
if(recv_size < 0)
recv_size = 0;
}
} }

View file

@ -159,10 +159,10 @@ static void fifo_bind(fifo_t ct, int pipe, int mode)
usb_while(!USB.D1FIFOCTR.FRDY || USB.PIPECFG.DIR != mode); usb_while(!USB.D1FIFOCTR.FRDY || USB.PIPECFG.DIR != mode);
} }
/* Enable USB comunication! */ /* Enable USB comunication when writing */
if(pipe == 0 && writing) if(writing && pipe == 0)
USB.DCPCTR.PID = PID_BUF; USB.DCPCTR.PID = PID_BUF;
if(pipe != 0) if(writing && pipe != 0)
USB.PIPECTR[pipe-1].PID = PID_BUF; USB.PIPECTR[pipe-1].PID = PID_BUF;
} }
@ -181,7 +181,7 @@ static void fifo_unbind(fifo_t ct)
if(pipe <= 0) if(pipe <= 0)
return; return;
/* Disbable communication on the pipe */ /* Disable communication on the pipe */
USB.PIPECTR[pipe-1].PID = PID_NAK; USB.PIPECTR[pipe-1].PID = PID_NAK;
usb_while(USB.PIPECTR[pipe-1].PBUSY); usb_while(USB.PIPECTR[pipe-1].PBUSY);
@ -234,16 +234,16 @@ void usb_pipe_reset_fifos(void)
Note that the "<Finish writing>" event is asynchronous if the DMA is used. A Note that the "<Finish writing>" event is asynchronous if the DMA is used. A
full write will take zero or more complete rounds followed by zero or one full write will take zero or more complete rounds followed by zero or one
partial round before finish_call() is called: partial round before finish_write_call() is called:
usb_write_async ::= complete_round* partial_round? finish_call usb_write_async ::= complete_round* partial_round? finish_write_call
And a commit will trigger a transmission of whatever is left in the buffer And a commit will trigger a transmission of whatever is left in the buffer
(including nothing) and wait for the BEMP interrupt. (including nothing) and wait for the BEMP interrupt.
usb_commit_async ::= <Manually commit pipe> usb_commit_async ::= <Manually commit pipe>
<BEMP interrut after transmission> <BEMP interrut after transmission>
finish_call finish_write_call
Most functions can execute either in the main thread or within an interrupt Most functions can execute either in the main thread or within an interrupt
handler. */ handler. */
@ -281,36 +281,24 @@ static int pipe_bufsize(int pipe)
return (USB.PIPEBUF.BUFSIZE + 1) * 64; return (USB.PIPEBUF.BUFSIZE + 1) * 64;
} }
/* This function is called when a read/write/fsync call and its associated /* Called when a write/fsync call and its hardware interactions all complete */
hardware interactions all complete. */ static void finish_write_call(asyncio_op_t *t, int pipe)
static void finish_call(asyncio_op_t *t, int pipe)
{ {
/* Unbind the USB controller used for the call, except for writes since /* Unbind the FIFO after a sync */
the USB module requires us to keep it until the final commit */ if(t->type == ASYNCIO_SYNC) {
if(t->type != ASYNCIO_WRITE && t->type != ASYNCIO_READ) {
fifo_unbind(t->controller); fifo_unbind(t->controller);
t->controller = NOF; t->controller = NOF;
} }
/* Disable interrupts */ /* Disable interrupts */
if((t->type == ASYNCIO_WRITE || t->type == ASYNCIO_SYNC) && pipe != 0) if(pipe != 0)
USB.BEMPENB &= ~(1 << pipe); USB.BEMPENB &= ~(1 << pipe);
asyncio_op_finish_call(t); if(t->type == ASYNCIO_WRITE)
USB_TRACE("finish_call()"); asyncio_op_finish_write(t);
else if(t->type == ASYNCIO_SYNC)
if(t->type == ASYNCIO_READ && t->size < 0) { asyncio_op_finish_sync(t);
if(t->controller == CF) USB.CFIFOCTR.BCLR = 1; USB_TRACE("finish_write_call()");
if(t->controller == D0F) USB.D0FIFOCTR.BCLR = 1;
if(t->controller == D1F) USB.D1FIFOCTR.BCLR = 1;
fifo_unbind(t->controller);
t->controller = NOF;
asyncio_op_finish_read_group(t);
USB_TRACE("finish_call() read group");
USB.PIPECTR[pipe-1].PID = PID_BUF;
}
} }
/* This function is called when a round of writing has completed, including all /* This function is called when a round of writing has completed, including all
@ -330,7 +318,7 @@ static void finish_write_round(asyncio_op_t *t, int pipe)
USB_TRACE("finish_write_round()"); USB_TRACE("finish_write_round()");
if(t->size == 0) if(t->size == 0)
finish_call(t, pipe); finish_write_call(t, pipe);
} }
/* write_round(): Write up to a FIFO's worth of data to a pipe /* write_round(): Write up to a FIFO's worth of data to a pipe
@ -463,19 +451,19 @@ int usb_commit_async(int pipe, gint_call_t callback)
usb_pipe_flush4(t->shbuf, t->shbuf_size, FIFO); usb_pipe_flush4(t->shbuf, t->shbuf_size, FIFO);
/* Switch from WRITE to SYNC type; this influences the BEMP handler and /* Switch from WRITE to SYNC type; this influences the BEMP handler and
the final finish_call() */ the final finish_write_call() */
asyncio_op_start_sync(t, &callback); asyncio_op_start_sync(t, &callback);
/* TODO: Figure out why previous attempts to use BEMP to finish commit /* TODO: Figure out why previous attempts to use BEMP to finish commit
TODO| calls on the DCP failed with a freeze */ TODO| calls on the DCP failed with a freeze */
if(pipe == 0) { if(pipe == 0) {
USB.CFIFOCTR.BVAL = 1; USB.CFIFOCTR.BVAL = 1;
finish_call(t, pipe); finish_write_call(t, pipe);
return 0; return 0;
} }
/* Set BVAL=1 and inform the BEMP handler of the commitment with the /* Set BVAL=1 and inform the BEMP handler of the commitment with the
SYNC type; the handler will invoke finish_call() */ SYNC type; the handler will invoke finish_write_call() */
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;
@ -522,7 +510,7 @@ void usb_pipe_write_bemp(int pipe)
if(t->type == ASYNCIO_SYNC) if(t->type == ASYNCIO_SYNC)
{ {
finish_call(t, pipe); finish_write_call(t, pipe);
} }
else else
{ {
@ -532,26 +520,97 @@ void usb_pipe_write_bemp(int pipe)
} }
} }
int usb_read_async(int pipe, void *data, int size, bool use_dma, //---
int *read_size, gint_call_t callback) // Reading operations
//---
static int handle_incoming_hwseg(asyncio_op_t *t, int pipe);
#ifdef GINT_USB_DEBUG
static void USB_LOG_TR(char const *prefix, asyncio_op_t *t)
{ {
asyncio_op_t *t = &pipe_transfers[pipe]; USB_LOG("%s: %s buf=%d%s%s req=%d/%d",
if(asyncio_op_busy(t)) prefix,
return USB_BUSY; t->type == ASYNCIO_READ ? "READ" : "NONE",
if(t->type == ASYNCIO_NONE) t->buffer_used, t->cont_r ? "+":"", t->interrupt_r ? "!":"",
return USB_READ_IDLE; t->round_size, t->size);
}
#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
#define USB_LOG_TR(PREFIX, OP, ...)
#endif
int actual_size = asyncio_op_start_read(t, data, size, use_dma, /* Unbind resources after a hardware segment is finished reading. */
&callback); static void finish_read_hwseg(asyncio_op_t *t, int pipe)
if(*read_size) {
*read_size = actual_size; #ifdef GINT_USB_DEBUG
/* Log DTLN to help identify bugs where we clear data we didn't read */
int DTLN = -1;
if(t->controller == CF) DTLN = USB.CFIFOCTR.DTLN;
if(t->controller == D0F) DTLN = USB.D0FIFOCTR.DTLN;
if(t->controller == D1F) DTLN = USB.D1FIFOCTR.DTLN;
#endif
USB_LOG("async read request for %d/%d bytes\n", size, t->size); if(t->controller == CF) USB.CFIFOCTR.BCLR = 1;
if(t->controller == D0F) USB.D0FIFOCTR.BCLR = 1;
if(t->controller == D1F) USB.D1FIFOCTR.BCLR = 1;
/* No data to read: finish the call immediately */ USB_LOG("frhwseg: DTLN=%d cont=%d interrupt=%d\n", DTLN, t->cont_r,
if(actual_size == 0) { t->interrupt_r);
finish_call(t, pipe);
return 0; asyncio_op_finish_read_hwseg(t);
USB_TRACE("finish_read_hwseg()");
if(!t->cont_r) {
fifo_unbind(t->controller);
t->controller = NOF;
}
/* Re-enable communication for the next segment */
USB_LOG_TR("frhwseg", t, "--> 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);
}
USB_LOG("req. progress: size=%d cont=%d interrupt=%d\n",
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)
{
int round_size = (t->size < t->buffer_used ? t->size : t->buffer_used);
asyncio_op_start_read_round(t, round_size);
USB_LOG_TR("rr", t);
/* No data to read: finish the round immediately */
if(round_size == 0 || t->data_r == NULL) {
finish_read_round(t, pipe);
return;
} }
/* Read stuff (TODO: Smart reads + DMA) */ /* Read stuff (TODO: Smart reads + DMA) */
@ -560,34 +619,91 @@ int usb_read_async(int pipe, void *data, int size, bool use_dma,
if(t->controller == D0F) FIFO = &USB.D0FIFO; if(t->controller == D0F) FIFO = &USB.D0FIFO;
if(t->controller == D1F) FIFO = &USB.D1FIFO; if(t->controller == D1F) FIFO = &USB.D1FIFO;
void *dataptr = data; void *dataptr = t->data_r;
for(int i = 0; i < size / 4; i++) { for(int i = 0; i < round_size / 4; i++) {
*(uint32_t *)dataptr = *FIFO; *(uint32_t *)dataptr = *FIFO;
dataptr += 4; dataptr += 4;
} }
if(size & 2) { if(round_size & 2) {
*(uint16_t *)dataptr = *(uint16_t volatile *)FIFO; *(uint16_t *)dataptr = *(uint16_t volatile *)FIFO;
dataptr += 2; dataptr += 2;
} }
if(size & 1) { if(round_size & 1) {
*(uint8_t *)dataptr = *(uint8_t volatile *)FIFO; *(uint8_t *)dataptr = *(uint8_t volatile *)FIFO;
dataptr += 1; dataptr += 1;
} }
finish_call(t, pipe); finish_read_round(t, 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
processing a previous hardware segment */
if(!t->interrupt_r || t->buffer_used > 0)
return 0;
/* PID will stay at NAK for the entire duration of the segment */
USB.PIPECTR[pipe-1].PID = PID_NAK;
if(t->controller == NOF) {
fifo_t ct = fifo_find_available_controller(pipe);
if(ct == NOF)
return USB_READ_NOFIFO;
fifo_bind(ct, pipe, FIFO_READ);
t->controller = ct;
}
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; return 0;
} }
int usb_read_sync_timeout(int pipe, void *data, int size, bool use_dma, int data_available = 0;
if(t->controller == CF) data_available = USB.CFIFOCTR.DTLN;
if(t->controller == D0F) data_available = USB.D0FIFOCTR.DTLN;
if(t->controller == D1F) data_available = USB.D1FIFOCTR.DTLN;
/* USB requires a zero-length or short packet to finish a transaction,
which equates to a partially-full buffer */
bool cont = (data_available == pipe_bufsize(pipe));
asyncio_op_start_read_hwseg(t, data_available, cont);
USB_LOG_TR("nseg", t, "PIPECTR=%04x", USB.PIPECTR[pipe-1].word);
return 0;
}
int usb_read_async(int pipe, void *data, int size, bool use_dma,
int *read_size, gint_call_t callback)
{
asyncio_op_t *t = &pipe_transfers[pipe];
int rc;
if((rc = handle_incoming_hwseg(t, pipe)))
return rc;
if(asyncio_op_busy(t))
return USB_BUSY;
if(t->type == ASYNCIO_NONE)
return USB_READ_IDLE;
asyncio_op_start_read(t, data, size, use_dma, read_size, &callback);
read_round(t, pipe);
return 0;
}
int usb_read_sync_timeout(int pipe, void *data, int size, bool dma,
timeout_t const *timeout) timeout_t const *timeout)
{ {
int volatile flag = 0; int volatile flag = 0;
int read_size = 0; int read_size = 0;
/* Wait until there is stuff to read, then read it */ /* Wait until there is stuff to read, then read it */
while(1) while(1) {
{ int rc = usb_read_async(pipe, data, size, dma, &read_size,
int rc = usb_read_async(pipe, data, size, use_dma, &read_size,
GINT_CALL_SET(&flag)); GINT_CALL_SET(&flag));
if(rc == 0) if(rc == 0)
break; break;
@ -599,19 +715,15 @@ int usb_read_sync_timeout(int pipe, void *data, int size, bool use_dma,
} }
/* Wait until the read completes */ /* Wait until the read completes */
while(!flag) while(!flag) {
{
if(timeout_elapsed(timeout)) if(timeout_elapsed(timeout))
return USB_TIMEOUT; return USB_TIMEOUT;
sleep(); sleep();
} }
/* Finish the read if there are 0 bytes left to read */ /* Ignore 0-length reads */
asyncio_op_t *t = &pipe_transfers[pipe]; if(read_size == 0)
if(t->size == 0) { return usb_read_sync_timeout(pipe, data, size, dma, timeout);
t->size = -1;
finish_call(t, pipe);
}
return read_size; return read_size;
} }
@ -621,38 +733,39 @@ 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)
{
if(pipe < 0)
return false;
asyncio_op_t *t = &pipe_transfers[pipe];
if(handle_incoming_hwseg(t, pipe))
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)
{ {
asyncio_op_t *t = &pipe_transfers[pipe];
if(asyncio_op_busy(t))
USB_LOG("pipe %d BRDY while busy?!\n", pipe);
USB_LOG("[PIPE%d] BRDY with PIPECTR %04x\n", pipe, USB_LOG("[PIPE%d] BRDY with PIPECTR %04x\n", pipe,
USB.PIPECTR[pipe-1].word); USB.PIPECTR[pipe-1].word);
asyncio_op_t *t = &pipe_transfers[pipe];
/* The BRDY interrupt also occurs after a pipe clear */
if(!USB.PIPECTR[pipe-1].BSTS) if(!USB.PIPECTR[pipe-1].BSTS)
return; return;
/* Prepare a FIFO and the transfer structure so the read can be done /* Signal the arrival to the main thread but don't do anything yet.
TODO: FIXME: Prevent race accesses during USB interrupts */ This is both for proper error handling and because changing transfer
if(t->controller != NOF) data asynchronously would require disabling the interrupt in other
USB_LOG("pipe %d BRDY bound while not busy?!\n", pipe); sections of the driver, which seems to cause data loss. */
else { t->interrupt_r = true;
fifo_t ct = fifo_find_available_controller(pipe);
/* TODO: BRDY: Delay FIFO binding until read syscall */
if(ct == NOF)
return;
fifo_bind(ct, pipe, FIFO_READ);
t->controller = ct;
}
int data_available = 0;
if(t->controller == CF) data_available = USB.CFIFOCTR.DTLN;
if(t->controller == D0F) data_available = USB.D0FIFOCTR.DTLN;
if(t->controller == D1F) data_available = USB.D1FIFOCTR.DTLN;
asyncio_op_start_read_group(t, data_available);
/* Notify the interface so it can read or schedule to read */ /* Notify the interface so it can read or schedule to read */
endpoint_t *ep = usb_get_endpoint_by_pipe(pipe); endpoint_t *ep = usb_get_endpoint_by_pipe(pipe);
ep->intf->notify_read(ep->dc->bEndpointAddress, data_available); if(ep->intf->notify_read)
ep->intf->notify_read(ep->dc->bEndpointAddress);
USB_LOG_TR("int", t);
} }