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:
Lephe 2023-03-04 18:06:21 +01:00
parent eb1f9a35a1
commit c59b2f90fb
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
7 changed files with 374 additions and 37 deletions

View file

@ -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 */

View file

@ -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
//--- //---

View file

@ -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);
} }

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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);