mirror of
https://git.planet-casio.com/Lephenixnoir/gint.git
synced 2024-12-29 13:03:36 +01:00
usb: prototype reading mode
Currently only tested with short messages using the fxlink interface. There is much to be expanded upon, but this is a worthy start.
This commit is contained in:
parent
eb1f9a35a1
commit
c59b2f90fb
7 changed files with 374 additions and 37 deletions
|
@ -11,10 +11,11 @@
|
||||||
/* 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
|
* Multi-part refers to writes being constructed over several calls to
|
||||||
write(2) followed by a "commit" with fsync(2) (for async file descriptors;
|
write(2) followed by a "commit" with fsync(2), and reads on a single data
|
||||||
synchronous file descriptors are committed at every write).
|
transfer being made over several calls to read(2) (for asynchronous file
|
||||||
* Multi-round refers to the operation interacting multiple times with
|
descriptors at least).
|
||||||
hardware in order to communicate the complete data.
|
* Multi-round refers to the writes interacting multiple times with hardware
|
||||||
|
in order to communicate the complete data.
|
||||||
|
|
||||||
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. For a write:
|
||||||
|
@ -71,8 +72,8 @@
|
||||||
IN interrupt
|
IN interrupt
|
||||||
--> IDLE-EMPTY --------------> IDLE-READY
|
--> IDLE-EMPTY --------------> IDLE-READY
|
||||||
| \ | ^
|
| \ | ^
|
||||||
read(2) | \ Transaction read(2) | | Buffer full with
|
read(2) | \ Transaction read(2) | | Buffer full
|
||||||
| \ exhausted | | transaction not exhausted
|
| \ exhausted* | |
|
||||||
| '----<----------. | |
|
| '----<----------. | |
|
||||||
| \ | |
|
| \ | |
|
||||||
v IN interrupt \ v | .---. Read from
|
v IN interrupt \ v | .---. Read from
|
||||||
|
@ -81,9 +82,17 @@
|
||||||
|
|
||||||
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.
|
||||||
Notice the diagonal arrow back to IDLE-EMPTY, which means that read(2) will
|
Notice the diagonal arrow back to IDLE-EMPTY insteaf of WAITING, which
|
||||||
always return at the end of a transaction even if the user-provided buffer
|
highlights that read(2) will always return at the end of a transaction even
|
||||||
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
|
||||||
|
the buffer is not full. This is to ensure that the caller can detect the end
|
||||||
|
of a data transfer by waiting for a read(2) which returns less bytes than
|
||||||
|
requested. In the case where a read(2) consumes exactly all remaining data,
|
||||||
|
we do not transition immediately to IDLE-EMPTY because we return as many
|
||||||
|
bytes as were requested. Instead, we return to IDLE-EMPTY after successfully
|
||||||
|
yielding 0 bytes in the next call.
|
||||||
|
|
||||||
The invariants and meaning for each state are as follow:
|
The invariants and meaning for each state are as follow:
|
||||||
|
|
||||||
|
@ -91,9 +100,9 @@
|
||||||
============================================================================
|
============================================================================
|
||||||
IDLE-EMPTY type == ASYNCIO_NONE No I/O operation
|
IDLE-EMPTY type == ASYNCIO_NONE No I/O operation
|
||||||
----------------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
IDLE-READY !data_r && buffer_size > 0 Hardware waiting for us to read
|
IDLE-READY !data_r && buffer_used > 0 Hardware waiting for us to read
|
||||||
----------------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
WAITING data_r && !buffer_size Waiting for further HW data
|
WAITING data_r && !buffer_used Waiting for further HW data
|
||||||
----------------------------------------------------------------------------
|
----------------------------------------------------------------------------
|
||||||
READING round_size > 0 DMA/CPU read from HW in progress
|
READING round_size > 0 DMA/CPU read from HW in progress
|
||||||
============================================================================
|
============================================================================
|
||||||
|
@ -159,7 +168,12 @@ 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);
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// State transition functions
|
// Operations and call 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 */
|
/* asyncio_op_start_write(): Start a write call */
|
||||||
|
@ -169,6 +183,11 @@ void asyncio_op_start_write(asyncio_op_t *op, void const *data, size_t size,
|
||||||
/* asyncio_op_start_sync(): Transition a write I/O operation to a fsync call */
|
/* asyncio_op_start_sync(): Transition a write I/O operation to a fsync call */
|
||||||
void asyncio_op_start_sync(asyncio_op_t *op, gint_call_t const *callback);
|
void asyncio_op_start_sync(asyncio_op_t *op, gint_call_t const *callback);
|
||||||
|
|
||||||
|
/* 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
|
/* 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
|
This function should be called when the read(2)/write(2)/fsync(2) call last
|
||||||
|
@ -178,7 +197,7 @@ void asyncio_op_start_sync(asyncio_op_t *op, gint_call_t const *callback);
|
||||||
void asyncio_op_finish_call(asyncio_op_t *op);
|
void asyncio_op_finish_call(asyncio_op_t *op);
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// Write call functions
|
// Write round functions
|
||||||
//---
|
//---
|
||||||
|
|
||||||
/* asyncio_op_start_write_round(): Start a single-block write to hardware */
|
/* asyncio_op_start_write_round(): Start a single-block write to hardware */
|
||||||
|
@ -187,4 +206,14 @@ 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 */
|
/* 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);
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Read group functions
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* asyncio_op_start_read_group(): Start a read call */
|
||||||
|
void asyncio_op_start_read_group(asyncio_op_t *op, size_t total_size);
|
||||||
|
|
||||||
|
/* asyncio_op_fininsh_read_group(): Reset operation after a read group */
|
||||||
|
void asyncio_op_finish_read_group(asyncio_op_t *op);
|
||||||
|
|
||||||
#endif /* GINT_USB_ASYNCIO */
|
#endif /* GINT_USB_ASYNCIO */
|
||||||
|
|
|
@ -137,8 +137,8 @@ struct usb_interface {
|
||||||
/* Answer class-specific SETUP requests */
|
/* Answer class-specific SETUP requests */
|
||||||
/* TODO */
|
/* TODO */
|
||||||
|
|
||||||
/* Receive data from an endpoint */
|
/* Notification that an endpoint has data read to be read */
|
||||||
/* TODO */
|
void (*notify_read)(int endpoint, int size);
|
||||||
};
|
};
|
||||||
|
|
||||||
/* usb_interface_endpoint_t: Parameters for an interface endpoint
|
/* usb_interface_endpoint_t: Parameters for an interface endpoint
|
||||||
|
@ -266,6 +266,48 @@ int usb_commit_sync_timeout(int pipe, timeout_t const *timeout);
|
||||||
of the remaining data completes. */
|
of the remaining data completes. */
|
||||||
int usb_commit_async(int pipe, gint_call_t callback);
|
int usb_commit_async(int pipe, gint_call_t callback);
|
||||||
|
|
||||||
|
/* usb_read_sync(): Synchronously read from a USB 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
|
||||||
|
soon as any data gets read, even if it's less than `size`. Thus, in order to
|
||||||
|
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
|
||||||
|
the pipe was consumed by a previous read call which perfectly filled the
|
||||||
|
provided buffer, the pipe will still be considered in the "data available"
|
||||||
|
state with 0 bytes left to read. The next read call will then successfully
|
||||||
|
return 0 bytes. This makes it possible to consistently detect the end of a
|
||||||
|
transfer as the first read which returns less bytes than requested.
|
||||||
|
|
||||||
|
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
|
||||||
|
current transaction.
|
||||||
|
|
||||||
|
This function will use a FIFO to access the pipe. The FIFO will only be
|
||||||
|
released once the full buffer.
|
||||||
|
|
||||||
|
Returns the number of bytes read or a negative error code. */
|
||||||
|
int usb_read_sync(int pipe, void *data, int size, bool use_dma);
|
||||||
|
|
||||||
|
/* usb_read_sync_timeout(): Synchronous read with a timeout */
|
||||||
|
int usb_read_sync_timeout(int pipe, void *data, int size, bool use_dma,
|
||||||
|
timeout_t const *timeout);
|
||||||
|
|
||||||
|
/* usb_read_async(): Asynchronously read from a USB pipe
|
||||||
|
|
||||||
|
This function is similar to usb_read_sync() except that it is non-blocking;
|
||||||
|
it returns USB_READ_INACTIVE if there is no active transfer on the pipe. It
|
||||||
|
will also read 0 bytes under the same conditions as usb_read_sync().
|
||||||
|
|
||||||
|
Being asynchronous, this function starts the read process and returns
|
||||||
|
instantly; 0 on success, an error code otherwise. When the read finishes,
|
||||||
|
the provided callback is called with `*read_size` set to the number of bytes
|
||||||
|
read. */
|
||||||
|
int usb_read_async(int pipe, void *data, int size, bool use_dma,
|
||||||
|
int *read_size, gint_call_t callback);
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// USB debugging functions
|
// USB debugging functions
|
||||||
//---
|
//---
|
||||||
|
|
|
@ -53,12 +53,40 @@ 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)
|
||||||
|
{
|
||||||
|
op->type = ASYNCIO_READ;
|
||||||
|
op->size = total_size;
|
||||||
|
/* 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,
|
||||||
|
bool use_dma, gint_call_t const *callback)
|
||||||
|
{
|
||||||
|
op->dma = use_dma;
|
||||||
|
op->data_r = data;
|
||||||
|
op->callback = *callback;
|
||||||
|
op->round_size = size;
|
||||||
|
|
||||||
|
return ((int)size < op->size ? (int)size : op->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void asyncio_op_finish_read_group(asyncio_op_t *op)
|
||||||
|
{
|
||||||
|
asyncio_op_clear(op);
|
||||||
|
}
|
||||||
|
|
||||||
void asyncio_op_finish_call(asyncio_op_t *op)
|
void asyncio_op_finish_call(asyncio_op_t *op)
|
||||||
{
|
{
|
||||||
gint_call(op->callback);
|
gint_call(op->callback);
|
||||||
|
|
||||||
/* Clean up the operation, unless it is a write, in which case keep
|
/* Generally clean up the operation; for a write, keep relevant states
|
||||||
relevant states until the transaction finishes after a fsync(2). */
|
until the transaction finishes with an fsync(2); for a read, keep info
|
||||||
|
needed for further read(2) calls. */
|
||||||
if(op->type == ASYNCIO_WRITE) {
|
if(op->type == ASYNCIO_WRITE) {
|
||||||
op->dma = false;
|
op->dma = false;
|
||||||
op->data_w = NULL;
|
op->data_w = NULL;
|
||||||
|
@ -66,6 +94,13 @@ void asyncio_op_finish_call(asyncio_op_t *op)
|
||||||
op->callback = GINT_CALL_NULL;
|
op->callback = GINT_CALL_NULL;
|
||||||
op->round_size = 0;
|
op->round_size = 0;
|
||||||
}
|
}
|
||||||
|
else if(op->type == ASYNCIO_READ) {
|
||||||
|
op->dma = false;
|
||||||
|
op->data_r = NULL;
|
||||||
|
op->size -= op->round_size;
|
||||||
|
op->callback = GINT_CALL_NULL;
|
||||||
|
op->round_size = 0;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
asyncio_op_clear(op);
|
asyncio_op_clear(op);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
#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 <string.h>
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static void notify_read(int endpoint, int size);
|
||||||
|
|
||||||
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),
|
||||||
.bDescriptorType = USB_DC_INTERFACE,
|
.bDescriptorType = USB_DC_INTERFACE,
|
||||||
.bInterfaceNumber = -1 /* Set by driver */,
|
.bInterfaceNumber = -1 /* Set by driver */,
|
||||||
.bAlternateSetting = 0,
|
.bAlternateSetting = 0,
|
||||||
.bNumEndpoints = 1,
|
.bNumEndpoints = 2,
|
||||||
.bInterfaceClass = 0xff, /* Vendor-Specific */
|
.bInterfaceClass = 0xff, /* Vendor-Specific */
|
||||||
.bInterfaceSubClass = 0x77, /* (not recognized by Casio tools?) */
|
.bInterfaceSubClass = 0x77, /* (not recognized by Casio tools?) */
|
||||||
.bInterfaceProtocol = 0x00,
|
.bInterfaceProtocol = 0x00,
|
||||||
|
@ -22,7 +24,15 @@ static usb_dc_endpoint_t dc_endpoint1i = {
|
||||||
.bDescriptorType = USB_DC_ENDPOINT,
|
.bDescriptorType = USB_DC_ENDPOINT,
|
||||||
.bEndpointAddress = 0x81, /* 1 IN */
|
.bEndpointAddress = 0x81, /* 1 IN */
|
||||||
.bmAttributes = 0x02, /* Bulk transfer */
|
.bmAttributes = 0x02, /* Bulk transfer */
|
||||||
/* TODO: Additional transactions per µframe ?! */
|
.wMaxPacketSize = htole16(512),
|
||||||
|
.bInterval = 1,
|
||||||
|
};
|
||||||
|
/* Endpoint for PC -> calculator communication */
|
||||||
|
static usb_dc_endpoint_t dc_endpoint1o = {
|
||||||
|
.bLength = sizeof(usb_dc_endpoint_t),
|
||||||
|
.bDescriptorType = USB_DC_ENDPOINT,
|
||||||
|
.bEndpointAddress = 0x02, /* 2 OUT */
|
||||||
|
.bmAttributes = 0x02, /* Bulk transfer */
|
||||||
.wMaxPacketSize = htole16(512),
|
.wMaxPacketSize = htole16(512),
|
||||||
.bInterval = 1,
|
.bInterval = 1,
|
||||||
};
|
};
|
||||||
|
@ -32,14 +42,18 @@ usb_interface_t const usb_ff_bulk = {
|
||||||
.dc = (void const *[]){
|
.dc = (void const *[]){
|
||||||
&dc_interface,
|
&dc_interface,
|
||||||
&dc_endpoint1i,
|
&dc_endpoint1i,
|
||||||
|
&dc_endpoint1o,
|
||||||
NULL,
|
NULL,
|
||||||
},
|
},
|
||||||
/* Parameters for each endpoint */
|
/* Parameters for each endpoint */
|
||||||
.params = (usb_interface_endpoint_t []){
|
.params = (usb_interface_endpoint_t []){
|
||||||
{ .endpoint = 0x81, /* 1 IN */
|
{ .endpoint = 0x81, /* 1 IN */
|
||||||
.buffer_size = 2048, },
|
.buffer_size = 2048, },
|
||||||
|
{ .endpoint = 0x02, /* 2 OUT */
|
||||||
|
.buffer_size = 2048, },
|
||||||
{ 0 },
|
{ 0 },
|
||||||
},
|
},
|
||||||
|
.notify_read = notify_read,
|
||||||
};
|
};
|
||||||
|
|
||||||
GCONSTRUCTOR static void set_strings(void)
|
GCONSTRUCTOR static void set_strings(void)
|
||||||
|
@ -56,6 +70,11 @@ int usb_ff_bulk_output(void)
|
||||||
return usb_interface_pipe(&usb_ff_bulk, 0x81);
|
return usb_interface_pipe(&usb_ff_bulk, 0x81);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int usb_ff_bulk_input(void)
|
||||||
|
{
|
||||||
|
return usb_interface_pipe(&usb_ff_bulk, 0x02);
|
||||||
|
}
|
||||||
|
|
||||||
//---
|
//---
|
||||||
// fxlink protocol
|
// fxlink protocol
|
||||||
//---
|
//---
|
||||||
|
@ -68,7 +87,7 @@ bool usb_fxlink_fill_header(usb_fxlink_header_t *header,
|
||||||
memset(header, 0, sizeof *header);
|
memset(header, 0, sizeof *header);
|
||||||
header->version = htole32(0x00000100);
|
header->version = htole32(0x00000100);
|
||||||
header->size = htole32(data_size);
|
header->size = htole32(data_size);
|
||||||
/* TODO: usb_fxlink_header: avoid sync with interace definition */
|
/* TODO: usb_fxlink_fill_header: avoid harcoded transfer size */
|
||||||
header->transfer_size = htole32(2048);
|
header->transfer_size = htole32(2048);
|
||||||
|
|
||||||
strncpy(header->application, application, 16);
|
strncpy(header->application, application, 16);
|
||||||
|
@ -136,3 +155,51 @@ void usb_fxlink_videocapture(bool onscreen)
|
||||||
{
|
{
|
||||||
capture_vram(onscreen, "video");
|
capture_vram(onscreen, "video");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//---
|
||||||
|
// Data reception
|
||||||
|
//---
|
||||||
|
|
||||||
|
/* Copy of the header for received BULK messages */
|
||||||
|
static usb_fxlink_header_t recv_header;
|
||||||
|
/* Size of transfer not yet read (implies valid header when non-zero) */
|
||||||
|
static int recv_size = 0;
|
||||||
|
|
||||||
|
static void header_finished(void)
|
||||||
|
{
|
||||||
|
recv_header.version = le32toh(recv_header.version);
|
||||||
|
recv_header.size = le32toh(recv_header.size);
|
||||||
|
recv_header.transfer_size = le32toh(recv_header.transfer_size);
|
||||||
|
|
||||||
|
int major = (recv_header.version >> 8) & 0xff;
|
||||||
|
int minor = (recv_header.version & 0xff);
|
||||||
|
|
||||||
|
USB_LOG("[ff-bulk %d.%d] New %.16s.%.16s (%d bytes)\n", major, minor,
|
||||||
|
recv_header.application, recv_header.type, recv_header.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void notify_read(int endpoint, int size)
|
||||||
|
{
|
||||||
|
/* We only have one endpoint for reading, the bulk OUT */
|
||||||
|
(void)endpoint;
|
||||||
|
|
||||||
|
USB_LOG("[ff-bulk] Data available on %02x (%d bytes)\n", endpoint, size);
|
||||||
|
|
||||||
|
if(recv_size <= 0) {
|
||||||
|
usb_read_sync(usb_ff_bulk_input(), &recv_header, sizeof recv_header,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
170
src/usb/pipes.c
170
src/usb/pipes.c
|
@ -33,7 +33,7 @@ void usb_pipe_configure(int address, endpoint_t const *ep)
|
||||||
|
|
||||||
USB.PIPESEL.PIPESEL = ep->pipe;
|
USB.PIPESEL.PIPESEL = ep->pipe;
|
||||||
USB.PIPECFG.TYPE = type;
|
USB.PIPECFG.TYPE = type;
|
||||||
USB.PIPECFG.BFRE = dir_receiving;
|
USB.PIPECFG.BFRE = 0;
|
||||||
/* Enable continuous mode on all bulk transfer pipes
|
/* Enable continuous mode on all bulk transfer pipes
|
||||||
TODO: Also make it double mode*/
|
TODO: Also make it double mode*/
|
||||||
USB.PIPECFG.DBLB = 0;
|
USB.PIPECFG.DBLB = 0;
|
||||||
|
@ -54,6 +54,12 @@ void usb_pipe_configure(int address, endpoint_t const *ep)
|
||||||
USB.PIPETR[ep->pipe-1].TRE.TRCLR = 1;
|
USB.PIPETR[ep->pipe-1].TRE.TRCLR = 1;
|
||||||
USB.PIPETR[ep->pipe-1].TRE.TRENB = 0;
|
USB.PIPETR[ep->pipe-1].TRE.TRENB = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Keep receiving pipes open all the time */
|
||||||
|
if(dir_receiving) {
|
||||||
|
USB.PIPECTR[ep->pipe-1].PID = PID_BUF;
|
||||||
|
USB.BRDYENB |= (1 << ep->pipe);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void usb_pipe_clear(int pipe)
|
void usb_pipe_clear(int pipe)
|
||||||
|
@ -281,7 +287,7 @@ static void finish_call(asyncio_op_t *t, int pipe)
|
||||||
{
|
{
|
||||||
/* Unbind the USB controller used for the call, except for writes since
|
/* Unbind the USB controller used for the call, except for writes since
|
||||||
the USB module requires us to keep it until the final commit */
|
the USB module requires us to keep it until the final commit */
|
||||||
if(t->type != ASYNCIO_WRITE) {
|
if(t->type != ASYNCIO_WRITE && t->type != ASYNCIO_READ) {
|
||||||
fifo_unbind(t->controller);
|
fifo_unbind(t->controller);
|
||||||
t->controller = NOF;
|
t->controller = NOF;
|
||||||
}
|
}
|
||||||
|
@ -292,31 +298,46 @@ static void finish_call(asyncio_op_t *t, int pipe)
|
||||||
|
|
||||||
asyncio_op_finish_call(t);
|
asyncio_op_finish_call(t);
|
||||||
USB_TRACE("finish_call()");
|
USB_TRACE("finish_call()");
|
||||||
|
|
||||||
|
if(t->type == ASYNCIO_READ && t->size < 0) {
|
||||||
|
if(t->controller == CF) USB.CFIFOCTR.BCLR = 1;
|
||||||
|
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
|
||||||
hardware interactions. If the FIFO got filled by the writing, this is after
|
hardware interactions. If the FIFO got filled by the writing, this is after
|
||||||
the transmission and BEMP interrupt; otherwise this is when the CPU/DMA
|
the transmission and BEMP interrupt; otherwise this is when the CPU/DMA
|
||||||
finished writing. */
|
finished writing. */
|
||||||
static void finish_round(asyncio_op_t *t, int pipe)
|
static void finish_write_round(asyncio_op_t *t, int pipe)
|
||||||
{
|
{
|
||||||
// USB_LOG("[PIPE%d] finish_round() for %d bytes\n", pipe, t->round_size);
|
// USB_LOG("[PIPE%d] finish_write_round() for %d bytes\n",
|
||||||
|
// pipe, t->round_size);
|
||||||
asyncio_op_finish_write_round(t);
|
asyncio_op_finish_write_round(t);
|
||||||
|
|
||||||
/* Account for auto-transfers */
|
/* Account for auto-transfers */
|
||||||
if(t->buffer_used == pipe_bufsize(pipe))
|
if(t->buffer_used == pipe_bufsize(pipe))
|
||||||
t->buffer_used = 0;
|
t->buffer_used = 0;
|
||||||
|
|
||||||
USB_TRACE("finish_round()");
|
USB_TRACE("finish_write_round()");
|
||||||
|
|
||||||
if(t->size == 0)
|
if(t->size == 0)
|
||||||
finish_call(t, pipe);
|
finish_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
|
||||||
|
|
||||||
If this is a partial round (FIFO not going to be full), finish_round() is
|
If this is a partial round (FIFO not going to be full), finish_write_round()
|
||||||
invoked after the write. Otherwise the FIFO is transmitted automatically and
|
is invoked after the write. Otherwise the FIFO is transmitted automatically
|
||||||
the BEMP handler will call finish_round() after the transfer. */
|
and the BEMP handler will call finish_write_round() after the transfer. */
|
||||||
static void write_round(asyncio_op_t *t, int pipe)
|
static void write_round(asyncio_op_t *t, int pipe)
|
||||||
{
|
{
|
||||||
fifo_t ct = t->controller;
|
fifo_t ct = t->controller;
|
||||||
|
@ -330,7 +351,7 @@ static void write_round(asyncio_op_t *t, int pipe)
|
||||||
int available = pipe_bufsize(pipe) - t->buffer_used;
|
int available = pipe_bufsize(pipe) - t->buffer_used;
|
||||||
int size = min(t->size, available);
|
int size = min(t->size, available);
|
||||||
|
|
||||||
/* If this is a partial write (size < available), call finish_round()
|
/* If we write partially (size < available), call finish_write_round()
|
||||||
after the copy to notify the user that the pipe is ready. Otherwise,
|
after the copy to notify the user that the pipe is ready. Otherwise,
|
||||||
a USB transfer will occur and the BEMP handler will do it. */
|
a USB transfer will occur and the BEMP handler will do it. */
|
||||||
bool partial = (size < available);
|
bool partial = (size < available);
|
||||||
|
@ -340,7 +361,7 @@ static void write_round(asyncio_op_t *t, int pipe)
|
||||||
if(t->dma)
|
if(t->dma)
|
||||||
{
|
{
|
||||||
gint_call_t callback = partial ?
|
gint_call_t callback = partial ?
|
||||||
GINT_CALL(finish_round, (void *)t, pipe) :
|
GINT_CALL(finish_write_round, (void *)t, pipe) :
|
||||||
GINT_CALL_NULL;
|
GINT_CALL_NULL;
|
||||||
|
|
||||||
/* Use DMA channel 3 for D0F and 4 for D1F */
|
/* Use DMA channel 3 for D0F and 4 for D1F */
|
||||||
|
@ -355,7 +376,7 @@ static void write_round(asyncio_op_t *t, int pipe)
|
||||||
{
|
{
|
||||||
usb_pipe_write4(t->data_w, size, &t->shbuf, &t->shbuf_size,
|
usb_pipe_write4(t->data_w, size, &t->shbuf, &t->shbuf_size,
|
||||||
FIFO);
|
FIFO);
|
||||||
if(partial) finish_round(t, pipe);
|
if(partial) finish_write_round(t, pipe);
|
||||||
}
|
}
|
||||||
|
|
||||||
USB_TRACE("write_round()");
|
USB_TRACE("write_round()");
|
||||||
|
@ -506,7 +527,132 @@ void usb_pipe_write_bemp(int pipe)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Finish a round; if there is more data, keep going */
|
/* Finish a round; if there is more data, keep going */
|
||||||
finish_round(t, pipe);
|
finish_write_round(t, pipe);
|
||||||
if(t->data_w) write_round(t, pipe);
|
if(t->data_w) write_round(t, pipe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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];
|
||||||
|
if(asyncio_op_busy(t))
|
||||||
|
return USB_BUSY;
|
||||||
|
if(t->type == ASYNCIO_NONE)
|
||||||
|
return USB_READ_IDLE;
|
||||||
|
|
||||||
|
int actual_size = asyncio_op_start_read(t, data, size, use_dma,
|
||||||
|
&callback);
|
||||||
|
if(*read_size)
|
||||||
|
*read_size = actual_size;
|
||||||
|
|
||||||
|
USB_LOG("async read request for %d/%d bytes\n", size, t->size);
|
||||||
|
|
||||||
|
/* No data to read: finish the call immediately */
|
||||||
|
if(actual_size == 0) {
|
||||||
|
finish_call(t, pipe);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Read stuff (TODO: Smart reads + DMA) */
|
||||||
|
uint32_t volatile *FIFO = NULL;
|
||||||
|
if(t->controller == CF) FIFO = &USB.CFIFO;
|
||||||
|
if(t->controller == D0F) FIFO = &USB.D0FIFO;
|
||||||
|
if(t->controller == D1F) FIFO = &USB.D1FIFO;
|
||||||
|
|
||||||
|
void *dataptr = data;
|
||||||
|
for(int i = 0; i < size / 4; i++) {
|
||||||
|
*(uint32_t *)dataptr = *FIFO;
|
||||||
|
dataptr += 4;
|
||||||
|
}
|
||||||
|
if(size & 2) {
|
||||||
|
*(uint16_t *)dataptr = *(uint16_t volatile *)FIFO;
|
||||||
|
dataptr += 2;
|
||||||
|
}
|
||||||
|
if(size & 1) {
|
||||||
|
*(uint8_t *)dataptr = *(uint8_t volatile *)FIFO;
|
||||||
|
dataptr += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
finish_call(t, pipe);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int usb_read_sync_timeout(int pipe, void *data, int size, bool use_dma,
|
||||||
|
timeout_t const *timeout)
|
||||||
|
{
|
||||||
|
int volatile flag = 0;
|
||||||
|
int read_size = 0;
|
||||||
|
|
||||||
|
/* Wait until there is stuff to read, then read it */
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
int rc = usb_read_async(pipe, data, size, use_dma, &read_size,
|
||||||
|
GINT_CALL_SET(&flag));
|
||||||
|
if(rc == 0)
|
||||||
|
break;
|
||||||
|
if(rc != USB_BUSY && rc != USB_READ_IDLE)
|
||||||
|
return rc;
|
||||||
|
if(timeout_elapsed(timeout))
|
||||||
|
return USB_TIMEOUT;
|
||||||
|
sleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Wait until the read completes */
|
||||||
|
while(!flag)
|
||||||
|
{
|
||||||
|
if(timeout_elapsed(timeout))
|
||||||
|
return USB_TIMEOUT;
|
||||||
|
sleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Finish the read if there are 0 bytes left to read */
|
||||||
|
asyncio_op_t *t = &pipe_transfers[pipe];
|
||||||
|
if(t->size == 0) {
|
||||||
|
t->size = -1;
|
||||||
|
finish_call(t, pipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
return read_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int usb_read_sync(int pipe, void *data, int size, bool use_dma)
|
||||||
|
{
|
||||||
|
return usb_read_sync_timeout(pipe, data, size, use_dma, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.PIPECTR[pipe-1].word);
|
||||||
|
if(!USB.PIPECTR[pipe-1].BSTS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* Prepare a FIFO and the transfer structure so the read can be done
|
||||||
|
TODO: FIXME: Prevent race accesses during USB interrupts */
|
||||||
|
if(t->controller != NOF)
|
||||||
|
USB_LOG("pipe %d BRDY bound while not busy?!\n", pipe);
|
||||||
|
else {
|
||||||
|
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 */
|
||||||
|
endpoint_t *ep = usb_get_endpoint_by_pipe(pipe);
|
||||||
|
ep->intf->notify_read(ep->dc->bEndpointAddress, data_available);
|
||||||
|
}
|
||||||
|
|
|
@ -193,12 +193,15 @@ int usb_open(usb_interface_t const **interfaces, gint_call_t callback)
|
||||||
USB.CFIFOSEL.REW = 0;
|
USB.CFIFOSEL.REW = 0;
|
||||||
USB.CFIFOSEL.BIGEND = 1;
|
USB.CFIFOSEL.BIGEND = 1;
|
||||||
|
|
||||||
/* VBSE=1 RSME=0 SOFE=0 DVSE=1 CTRE=1 BEMPE=1 NRDYE=0 BRDYE=0 */
|
/* VBSE=1 RSME=0 SOFE=0 DVSE=1 CTRE=1 BEMPE=1 NRDYE=0 BRDYE=1 */
|
||||||
USB.INTENB0.word = 0x9c00;
|
USB.INTENB0.word = 0x9d00;
|
||||||
USB.INTENB1.word = 0x0000;
|
USB.INTENB1.word = 0x0000;
|
||||||
USB.BRDYENB = 0x0000;
|
USB.BRDYENB = 0x0000;
|
||||||
USB.NRDYENB = 0x0000;
|
USB.NRDYENB = 0x0000;
|
||||||
USB.BEMPENB = 0x0000;
|
USB.BEMPENB = 0x0000;
|
||||||
|
USB.BRDYSTS = 0x0000;
|
||||||
|
USB.NRDYSTS = 0x0000;
|
||||||
|
USB.BEMPSTS = 0x0000;
|
||||||
|
|
||||||
intc_handler_function(0xa20, GINT_CALL(usb_interrupt_handler));
|
intc_handler_function(0xa20, GINT_CALL(usb_interrupt_handler));
|
||||||
intc_priority(INTC_USB, 8);
|
intc_priority(INTC_USB, 8);
|
||||||
|
@ -278,12 +281,24 @@ static void usb_interrupt_handler(void)
|
||||||
else if(USB.INTSTS0.BEMP)
|
else if(USB.INTSTS0.BEMP)
|
||||||
{
|
{
|
||||||
/* Invoke callbacks for each buffer-empty interrupt */
|
/* Invoke callbacks for each buffer-empty interrupt */
|
||||||
uint16_t status = USB.BEMPSTS;
|
uint16_t status = USB.BEMPSTS & USB.BEMPENB;
|
||||||
USB.BEMPSTS = 0;
|
USB.BEMPSTS = 0;
|
||||||
|
|
||||||
for(int i = 0; i <= 9; i++)
|
for(int i = 0; i <= 9; i++)
|
||||||
{
|
{
|
||||||
if(status & (1 << i)) usb_pipe_write_bemp(i);
|
if(status & (1 << i))
|
||||||
|
usb_pipe_write_bemp(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(USB.INTSTS0.BRDY)
|
||||||
|
{
|
||||||
|
uint16_t status = USB.BRDYSTS & USB.BRDYENB;
|
||||||
|
USB.BRDYSTS = 0;
|
||||||
|
|
||||||
|
for(int i = 0; i <= 9; i++)
|
||||||
|
{
|
||||||
|
if(status & (1 << i))
|
||||||
|
usb_pipe_read_brdy(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else USB_LOG("<%04X> -> ???\n", USB.INTSTS0.word);
|
else USB_LOG("<%04X> -> ???\n", USB.INTSTS0.word);
|
||||||
|
|
|
@ -129,6 +129,9 @@ void usb_pipe_reset_fifos(void);
|
||||||
/* usb_pipe_write_bemp(): Callback for the BEMP interrupt on a pipe */
|
/* usb_pipe_write_bemp(): Callback for the BEMP interrupt on a pipe */
|
||||||
void usb_pipe_write_bemp(int pipe);
|
void usb_pipe_write_bemp(int pipe);
|
||||||
|
|
||||||
|
/* usb_pipe_read_brdy(): Callback for the BRDY interrupt on a read pipe */
|
||||||
|
void usb_pipe_read_brdy(int pipe);
|
||||||
|
|
||||||
/* usb_pipe_init_transfers(): Initialize transfer information */
|
/* usb_pipe_init_transfers(): Initialize transfer information */
|
||||||
void usb_pipe_init_transfers(void);
|
void usb_pipe_init_transfers(void);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue