mirror of
https://git.planet-casio.com/Lephenixnoir/gint.git
synced 2024-12-28 20:43:36 +01:00
usb: consolidate reading mode (basic multi-segment reads now working)
This commit is contained in:
parent
c59b2f90fb
commit
fc1f510288
6 changed files with 722 additions and 395 deletions
|
@ -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 */
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
//---
|
//---
|
||||||
|
|
|
@ -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);
|
||||||
|
op->dma = false;
|
||||||
/* Generally clean up the operation; for a write, keep relevant states
|
op->data_r = NULL;
|
||||||
until the transaction finishes with an fsync(2); for a read, keep info
|
op->callback = GINT_CALL_NULL;
|
||||||
needed for further read(2) calls. */
|
op->realized_size_r = NULL;
|
||||||
if(op->type == ASYNCIO_WRITE) {
|
op->size = 0;
|
||||||
op->dma = false;
|
}
|
||||||
op->data_w = NULL;
|
|
||||||
op->size = 0;
|
void asyncio_op_start_read_hwseg(asyncio_op_t *op, size_t size, bool cont)
|
||||||
op->callback = GINT_CALL_NULL;
|
{
|
||||||
op->round_size = 0;
|
op->type = ASYNCIO_READ;
|
||||||
}
|
op->buffer_used = size;
|
||||||
else if(op->type == ASYNCIO_READ) {
|
op->cont_r = cont;
|
||||||
op->dma = false;
|
}
|
||||||
op->data_r = NULL;
|
|
||||||
op->size -= op->round_size;
|
void asyncio_op_finish_read_hwseg(asyncio_op_t *op)
|
||||||
op->callback = GINT_CALL_NULL;
|
{
|
||||||
op->round_size = 0;
|
if(!op->cont_r)
|
||||||
}
|
op->type = ASYNCIO_NONE;
|
||||||
else {
|
op->buffer_used = 0;
|
||||||
asyncio_op_clear(op);
|
}
|
||||||
}
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
return (op->buffer_used == 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
if(rc < 0) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
USB_LOG("[ff-bulk %d.%d] New %.16s.%.16s (%d bytes)\n", major, minor,
|
||||||
recv_header.application, recv_header.type, recv_header.size);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void notify_read(int endpoint, int size)
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
295
src/usb/pipes.c
295
src/usb/pipes.c
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 *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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue