usb: FIFO controllers and fxlink API

This changes fixes the way gint uses the FIFO controllers D0F and D1F
to access the FIFO. It previously used D0F in the main thread and D1F
during interrupt handling, but this is incorrect for several reasons,
mainly the possible change of controllers between a write and a commit,
and numerous instances of two FIFOs managing the same pipe caused by
the constant switching.

gint now treats FIFO controllers as resources allocated to pipes for
the duration of a commit-terminated sequence of writes. The same
controller is used for a single pipe in both normal and interrupt
modes, and released when the pipe is committed. If no controller is
available, asynchronous writes fail and synchronous ones wait.

The fxlink API is also added with a small amount of functions, namely
to transfer screenshots and raw text. Currently these are synchronous
and do not use the DMA, this will be improved later.

Finally:
* Removed pipe logic from src/usb/setup.c, instead letting pipes.c
  handle the special case of the DCP (which might be regularized later)
* Removed the usb_pipe_mode_{read,write} functions as they're actually
  about FIFo controllers and it's not clear yet how a pipe with both
  read and write should be handled. This is left for the future.
* Clarified end-of-sequence semantics after a successful commit.
This commit is contained in:
Lephe 2021-05-07 15:22:46 +02:00
parent 7aa86235a3
commit a547235f8f
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
10 changed files with 511 additions and 187 deletions

View file

@ -111,6 +111,7 @@ set(SOURCES_FX
src/render-fx/topti-asm.s
src/render-fx/topti.c
src/t6k11/t6k11.c
src/usb/classes/ff-bulk-gray.c
)
set(SOURCES_CG
src/r61524/r61524.c

View file

@ -20,41 +20,148 @@
extern usb_interface_t const usb_ff_bulk;
//---
// fxlink protocol
// 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_fxlink_header_t: Packet header for fxlink
/* usb_ff_bulk_output(): Pipe for calculator -> host communication */
int usb_ff_bulk_output(void);
//---
// fxlink protocol
//
// 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 minimalistic 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 packet is categorized with an (application, type) pair; both are UTF-8
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 packet).
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 packets will be in trouble).
side (and guessing wrong means all further messages will be in trouble).
Most of the time you don't need to create custom packets yourself since the
convenience functions below will create them for you. */
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, including this header */
/* 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];
/* Packet type */
/* 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 *application,
char *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
This function takes a screenshot. If (onscreen = false), it sends the
contents of the VRAM. This mode is best used just before dupdate(), since
the VRAM contents are exactly as they will appear on screen a moment later.
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
currently visible on-screen, with the following heuristic:
* If there is only one VRAM, the VRAM contents are used in the hope they
haven't changed since the last frame was presented with dupdate().
* 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);
#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);
#endif /* GINT_USB_FF_BULK */

View file

@ -10,6 +10,83 @@
#include <gint/gint.h>
#include <stdarg.h>
/* See "Interfaces to communicate with USB transfers" below for details */
typedef struct usb_interface usb_interface_t;
typedef struct usb_interface_endpoint usb_interface_endpoint_t;
//---
// General functions
//---
/* Error codes for USB functions */
enum {
/* There are no interfaces */
USB_OPEN_NO_INTERFACE = 1,
/* There are more interfaces than supported (16) */
USB_OPEN_TOO_MANY_INTERFACES,
/* There are not enough endpoint numbers for every interface, or there
are not enough pipes to set them up */
USB_OPEN_TOO_MANY_ENDPOINTS,
/* There is not enough FIFO memory to use the requested buffer sizes */
USB_OPEN_NOT_ENOUGH_MEMORY,
/* Information is missing, such as buffer size for some endpoints */
USB_OPEN_MISSING_DATA,
/* Invalid parameters: bad endpoint numbers, bad buffer sizes... */
USB_OPEN_INVALID_PARAMS,
/* This pipe is busy (returned by usb_write_async()) */
USB_WRITE_BUSY,
/* Both FIFO controlles are busy, none is available to transfer */
USB_WRITE_NOFIFO,
/* This pipe is busy (returned by usb_commit_async()) */
USB_COMMIT_BUSY,
/* This pipe has no ongoing transfer to commit */
USB_COMMIT_INACTIVE,
};
/* usb_open(): Open the USB link
This function opens the USB link and notifies the host that the device is
ready to connect. Usually the host immediately queries the device, and after
some exchanges the device can be used. The USB link might not be ready when
this function returns, use the callback or usb_open_wait() for that.
The first parameters is a NULL-terminated array of interfaces to open. To
see available interfaces, please see header files in <gint/usb-*.h>. Each
interface can be used independently, however if there are not enough USB
resources (buffer memory, pipes or endpoints) for all of them, usb_open()
will return an error.
The second parameter is a callback to be (asynchronously) invoked when the
USB link is ready. Use GINT_CALL() to create one, or pass GINT_CALL_NULL for
no callback. You can also use usb_open_wait() to synchronously wait for the
link to be ready.
@interfaces NULL-terminate list of interfaces to open
@callback Optional function to be called when the USB link opens */
int usb_open(usb_interface_t const **interfaces, gint_call_t callback);
/* usb_open_wait(): Wait until the USB link is ready
When called after usb_open(), this function waits until the communication is
established. You should only call this if usb_open() returns 0. */
void usb_open_wait(void);
/* usb_is_open(): Check whether the USB link is active */
bool usb_is_open(void);
/* usb_close(): Close the USB link
This function closes the link opened by usb_open(), and notifies the host of
the disconnection (if any was established). The USB link can be reopened
later to perform more tasks.
There are two reasons to close the USB link: to save battery power and to
return to the calculator's main menu. You should thus close it if (1) the
USB link might not be used for a while, or (2) you want to return to the
main menu before using it again. */
void usb_close(void);
//---
// Interfaces to communicate with USB transfers
//
@ -39,7 +116,7 @@
Instead, the supplied descriptors should use arbitrary endpoint numbers; the
driver will use them to communicate with the interface, and transparently
use concrete endpoint numbers internally. */
typedef struct {
struct usb_interface {
/* NULL-terminated array of descriptors for the interface */
void const **dc;
/* Array of endpoint parameters, see below */
@ -50,92 +127,40 @@ typedef struct {
/* Receive data from an endpoint */
/* TODO */
} usb_interface_t;
};
/* usb_interface_endpoint_t: Parameters for an interface endpoint
This structure mainly specifies the settings for the pipe associated to the
endpoint. There 10 pipes, but not all can be used with any transfer type,
and not all have access to the same amount of memory. */
typedef struct usb_interface_endpoint {
struct usb_interface_endpoint {
/* Endpoint number as specified in the interface's descriptors
(including the IN/OUT bit) */
uint8_t endpoint;
/* Requested buffer size, should be a multiple of 64 and not more than
2048. Valid only for bulk and isochronous endpoints. */
uint16_t buffer_size;
} usb_interface_endpoint_t;
//---
// General functions
//---
/* Error codes for USB functions */
enum {
/* There are no interfaces */
USB_OPEN_NO_INTERFACE = 1,
/* There are more interfaces than supported (16) */
USB_OPEN_TOO_MANY_INTERFACES,
/* There are not enough endpoint numbers for every interface, or there
are not enough pipes to set them up */
USB_OPEN_TOO_MANY_ENDPOINTS,
/* There is not enough FIFO memory to use the requested buffer sizes */
USB_OPEN_NOT_ENOUGH_MEMORY,
/* Information is missing, such as buffer size for some endpoints */
USB_OPEN_MISSING_DATA,
/* Invalid parameters: bad endpoint numbers, bad buffer sizes... */
USB_OPEN_INVALID_PARAMS,
/* This pipe is busy (returned by usb_write_async()) */
USB_WRITE_BUSY,
/* This pipe is busy (returned by usb_commit_async()) */
USB_COMMIT_BUSY,
};
/* usb_open(): Open the USB link
/* usb_interface_pipe(): Get the pipe associated to an interface endpoint
This function opens the USB link and notifies the host that the device is
ready to connect. Usually the host immediately queries the device, and after
some exchanges the device can be used. The USB link might not be ready when
this function returns, use the callback or usb_open_wait() for that.
The first parameters is a NULL-terminated array of interfaces to open. To
see available interfaces, please see header files in <gint/usb-*.h>. Each
interface can be used independently, however if there are not enough USB
resources (buffer memory, pipes or endpoints) for all of them, usb_open()
will return an error.
The second parameter is a callback to be (asynchronously) invoked when the
USB link is ready. Use GINT_CALL() to create one, or pass GINT_CALL_NULL for
no callback. You can also use usb_open_wait() to synchronously wait for the
link to be ready.
@interfaces NULL-terminate list of interfaces to open
@callback Optional function to be called when the USB link opens */
int usb_open(usb_interface_t const **interfaces, gint_call_t callback);
/* usb_open_wait(): Wait until the USB link is ready
When called after usb_open(), this function waits until the communication is
established. You should only call this if usb_open() returns 0. */
void usb_open_wait(void);
/* usb_close(): Close the USB link
This function closes the link opened by usb_open(), and notifies the host of
the disconnection (if any was established). The USB link can be reopened
later to perform more tasks.
There are two reasons to close the USB link: to save battery power and to
return to the calculator's main menu. You should thus close it if (1) the
USB link might not be used for a while, or (2) you want to return to the
main menu before using it again. */
void usb_close(void);
This function returns the pipe number that backs the specified endpoint
number (using the local value of the interface, not the concrete one). This
function is intended for interface implementations, not users. */
int usb_interface_pipe(usb_interface_t const *interface, int endpoint);
//---
// Pipe writing API
// Pipe access API
//
// The following functions provide access to USB pipes. Normally the add-in
// will not know which pipe is allocated to each interface, so there is no way
// to reliably access a pipe directly. Instead you should use functions
// provided by the interfaces in <gint/usb-*.h>.
//
// The functions below are useful for interface implementations; an interface
// can get the pipe for an endpoint with usb_interface_pipe() and then use
// direct pipe access.
//---
/* usb_write_sync(): Synchronously write to a USB pipe
@ -155,15 +180,18 @@ void usb_close(void);
other writes to follow. After the last write in a sequence, use
usb_commit_sync() or usb_commit_async() to transmit the last bytes.
If (use_dma=true), the write is performed wita the DMA instead of the CPU,
If (use_dma=true), the write is performed with the DMA instead of the CPU,
which is generally faster.
*WARNING*: Due to a current limitation in the DMA API, the same DMA channel
is used for all DMA-based writes to USB pipes. Do not write to two USB pipes
with DMA at the same time!
This function will use a FIFO controller to access the pipe. The FIFO
controller will be reserved for further writes until the contents of the
pipe are commited with usb_commit_sync() or usb_commit_async(); when more
than two pipes need to operate in parallel, keep the write sequences short
and commit regularly to avoid holding the controllers.
If the pipe is busy due to an ongoing asynchronous write or commit, this
function waits for the operation to complete and proceeds normally.
If the pipe is busy due to an ongoing asynchronous write or commit, or there
is no FIFO controller available to perform the operation, this function
waits for the ressources to become available then proceeds normally.
@pipe Pipe to write into
@data Source data (unit_size-aligned)
@ -183,14 +211,15 @@ int usb_write_sync(int pipe, void const *data, int size, int unit_size,
GINT_CALL_NULL.
If the pipe is busy due to a previous asynchronous write, this function
returns USB_PIPE_BUSY. When called with (use_dma=true), it returns as soon
as the DMA starts, without even a guarantee that the first few bytes have
been written.
returns USB_WRITE_BUSY. If no FIFO controller is available for the transfer,
it returns USB_WRITE_NOFIFO. When called with (use_dma=true), it returns as
soon as the DMA starts, without even a guarantee that the first few bytes
have been written.
There is no guarantee that the write is complete until the callback is
called, however calling again with data=NULL and size=0 can be used to
determine whether the write has finished, since it will return 0 if the pipe
is idle and USB_PIPE_BUSY otherwise.
is idle and USB_WRITE_BUSY otherwise.
@pipe Pipe to write into
@data Source data (unit_size-aligned)
@ -203,6 +232,7 @@ int usb_write_async(int pipe, void const *data, int size, int unit_size,
bool use_dma, gint_call_t callback);
/* usb_commit_sync(): Synchronously commit a write
This function waits for any pending write on the pipe to finish, then
transfers whatever data is left, and returns when the transfer completes. */
void usb_commit_sync(int pipe);

View file

@ -0,0 +1,32 @@
#ifdef FX9860G
#include <gint/usb.h>
#include <gint/usb-ff-bulk.h>
#include <gint/display.h>
#include <gint/gray.h>
void usb_fxlink_screenshot_gray(GUNUSED bool onscreen)
{
uint32_t *light, *dark;
if(onscreen) dgray_getscreen(&light, &dark);
else dgray_getvram(&light, &dark);
usb_fxlink_header_t header;
usb_fxlink_image_t subheader;
usb_fxlink_fill_header(&header, "fxlink", "image",
2048 + sizeof subheader);
subheader.width = htole32(DWIDTH);
subheader.height = htole32(DHEIGHT);
subheader.pixel_format = htole32(USB_FXLINK_IMAGE_GRAY);
int pipe = usb_ff_bulk_output();
usb_write_sync(pipe, &header, sizeof header, 4, false);
usb_write_sync(pipe, &subheader, sizeof subheader, 4, false);
usb_write_sync(pipe, light, 1024, 4, false);
usb_write_sync(pipe, dark, 1024, 4, false);
usb_commit_sync(pipe);
}
#endif /* FX9860G */

View file

@ -1,4 +1,7 @@
#include <gint/usb.h>
#include <gint/usb-ff-bulk.h>
#include <gint/display.h>
#include <gint/std/string.h>
static usb_dc_interface_t dc_interface = {
.bLength = sizeof(usb_dc_interface_t),
@ -23,7 +26,7 @@ static usb_dc_endpoint_t dc_endpoint1i = {
.bInterval = 1,
};
usb_interface_t usb_ff_bulk = {
usb_interface_t const usb_ff_bulk = {
/* List of descriptors */
.dc = (void const *[]){
&dc_interface,
@ -42,3 +45,87 @@ GCONSTRUCTOR static void set_strings(void)
{
dc_interface.iInterface = usb_dc_string(u"Bulk Input", 0);
}
//---
// Direct bulk access
//---
int usb_ff_bulk_output(void)
{
return usb_interface_pipe(&usb_ff_bulk, 0x81);
}
//---
// fxlink protocol
//---
bool usb_fxlink_fill_header(usb_fxlink_header_t *header, char *application,
char *type, uint32_t data_size)
{
if(strlen(application) > 16 || strlen(type) > 16) return false;
memset(header, 0, sizeof *header);
header->version = htole32(0x00000100);
header->size = htole32(data_size);
/* TODO: usb_fxlink_header: avoid sync with interace definition */
header->transfer_size = htole32(2048);
strncpy(header->application, application, 16);
strncpy(header->type, type, 16);
return true;
}
void usb_fxlink_screenshot(GUNUSED bool onscreen)
{
void *source = gint_vram;
int size, format;
#ifdef FX9860G
size = 1024;
format = USB_FXLINK_IMAGE_MONO;
#endif
#ifdef FXCG50
if(onscreen) {
uint16_t *main, *secondary;
dgetvram(&main, &secondary);
source = (gint_vram == main) ? secondary : main;
}
size = DWIDTH * DHEIGHT * 2;
format = USB_FXLINK_IMAGE_RGB565;
#endif
usb_fxlink_header_t header;
usb_fxlink_image_t subheader;
usb_fxlink_fill_header(&header, "fxlink", "image",
size + sizeof subheader);
subheader.width = htole32(DWIDTH);
subheader.height = htole32(DHEIGHT);
subheader.pixel_format = htole32(format);
int pipe = usb_ff_bulk_output();
usb_write_sync(pipe, &header, sizeof header, 4, false);
usb_write_sync(pipe, &subheader, sizeof subheader, 4, false);
usb_write_sync(pipe, source, size, 4, false);
usb_commit_sync(pipe);
}
void usb_fxlink_text(char const *text, int size)
{
if(size == 0) size = strlen(text);
int unit_size = 4;
if((uint32_t)text & 3 || size & 3) unit_size = 2;
if((uint32_t)text & 1 || size & 1) unit_size = 1;
usb_fxlink_header_t header;
usb_fxlink_fill_header(&header, "fxlink", "text", size);
int pipe = usb_ff_bulk_output();
usb_write_sync(pipe, &header, sizeof header, unit_size, false);
usb_write_sync(pipe, text, size, unit_size, false);
usb_commit_sync(pipe);
}

View file

@ -209,3 +209,16 @@ usb_interface_t const * const *usb_configure_interfaces(void)
{
return conf_if;
}
//---
// API for interfaces
//---
int usb_interface_pipe(usb_interface_t const *interface, int endpoint)
{
int concrete_address = usb_configure_address(interface, endpoint);
endpoint_t const *ep = usb_configure_endpoint(concrete_address);
if(!ep) return -1;
return ep->pipe;
}

View file

@ -9,30 +9,9 @@
#define USB SH7305_USB
//---
// Operations on pipe controllers
// Operations on pipes
//---
/* pipect_t: Pipe controllers used to access pipe configuration and contents */
typedef enum {
CF, /* Used for the Default Control Pipe */
D0F, /* Used for main-thread access to other pipes */
D1F, /* Used for interrupt-thread access to other pipes */
} pipect_t;
/* pipect(): Determine which controller to use to access a pipe */
pipect_t pipect(int pipe)
{
if(pipe == 0) return CF;
/* In normal program flow, use D0FIF0; in interrupt mode, use D1FIF0.
This avoids access races */
uint32_t sr, IMASK;
__asm__("stc sr, %0": "=r"(sr));
IMASK = (sr >> 4) & 0xf;
return (IMASK ? D1F : D0F);
}
/* usb_pipe_configure(): Configure a pipe when opening the connection */
void usb_pipe_configure(int address, endpoint_t const *ep)
{
@ -61,24 +40,54 @@ void usb_pipe_clear(int pipe)
/* Set PID=NAK then use ACLRM to clear the pipe */
USB.PIPECTR[pipe-1].PID = 0;
while(USB.PIPECTR[pipe-1].PBUSY) {}
usb_while(USB.PIPECTR[pipe-1].PBUSY);
USB.PIPECTR[pipe-1].ACLRM = 1;
USB.PIPECTR[pipe-1].ACLRM = 0;
while(!USB.PIPECTR[pipe-1].BSTS) {}
usb_while(!USB.PIPECTR[pipe-1].BSTS);
USB.PIPECTR[pipe-1].PID = 0;
USB.PIPECTR[pipe-1].SQCLR = 1;
}
/* pipe_mode(): Set the pipe in reading or writing mode */
static void pipe_mode(pipect_t ct, int pipe, int mode, int size)
//---
// Operation on FIFO controllers
//---
/* fifoct_t: FIFO controllers to access pipe queues */
typedef enum {
NOF = 0, /* No FIFO controller */
CF, /* Used for the Default Control Pipe */
D0F, /* FIFO Controller 0 */
D1F, /* FIFO Controller 1 */
} fifo_t;
enum {
FIFO_READ = 0, /* Read mode */
FIFO_WRITE = 1, /* Write mode */
};
/* fifo_access(): Get a FIFO controller for a pipe */
static fifo_t fifo_access(int pipe)
{
/* TODO: USB: fifo_access(): Possibly use CFIFO for all pipes? */
if(pipe == 0) return CF;
/* Find a free controller */
if(USB.D0FIFOSEL.CURPIPE == 0) return D0F;
usb_log("Wait D0 is unavailable\n");
if(USB.D1FIFOSEL.CURPIPE == 0) return D1F;
return NOF;
}
/* fifo_bind(): Bind a FIFO to a pipe in reading or writing mode */
static void fifo_bind(fifo_t ct, int pipe, int mode, int size)
{
size = (size - (size == 4) - 1) & 3;
if(ct == CF)
{
if(mode == 1) USB.DCPCTR.PID = 1;
if(mode == FIFO_WRITE) USB.DCPCTR.PID = 1;
/* RCNT=0 REW=0 MBW=size BIGEND=1 ISEL=mode CURPIPE=0 */
USB.CFIFOSEL.word = 0x0100 | (mode << 5) | (size << 10);
usb_while(!USB.CFIFOCTR.FRDY || USB.CFIFOSEL.ISEL != mode);
@ -95,22 +104,37 @@ static void pipe_mode(pipect_t ct, int pipe, int mode, int size)
if(ct == D0F) usb_while(!USB.D0FIFOCTR.FRDY || USB.PIPECFG.DIR!=mode);
if(ct == D1F) usb_while(!USB.D1FIFOCTR.FRDY || USB.PIPECFG.DIR!=mode);
}
void usb_pipe_mode_read(int pipe, int read_size)
/* fifo_unbind(): Free a FIFO */
static void fifo_unbind(fifo_t ct)
{
return pipe_mode(pipect(pipe), pipe, 0, read_size);
}
void usb_pipe_mode_write(int pipe, int write_size)
{
return pipe_mode(pipect(pipe), pipe, 1, write_size);
if(ct == D0F)
{
USB.D0FIFOSEL.word = 0x0000;
usb_while(USB.D0FIFOSEL.CURPIPE != 0);
}
if(ct == D1F)
{
USB.D1FIFOSEL.word = 0x0000;
usb_while(USB.D1FIFOSEL.CURPIPE != 0);
}
}
//---
// Writing operations
//---
/* Current operation waiting to be performed on each pipe */
/* Current operation waiting to be performed on each pipe. There are two
possible states for a pipe's transfer data:
-> Either there is a transfer going on, in which case (data != NULL),
(size != 0), (controller != NOF), and (used) has no meaning.
-> Either there is no transfer going on, and (data = NULL), (size = 0), and
(controller = NOF).
Additionally, between a call to write_round() and the corresponding
finish_write(), the (flying) attribute is set to a non-zero value indicating
how many bytes are waiting for write completion. */
struct transfer {
/* Address of data to transfer next; NULL if no transfer */
/* Address of data to transfer next */
void const *data;
/* Size of data left to transfer */
int size;
@ -124,6 +148,8 @@ struct transfer {
bool committed;
/* Whether to use the DMA */
bool dma;
/* FIFO controller being used for this transfer */
fifo_t ct;
/* Callback to be invoked at the end of the current write or commit
(both cannot exist at the same time) */
gint_call_t callback;
@ -171,6 +197,27 @@ static int pipe_bufsize(int pipe)
return (USB.PIPEBUF.BUFSIZE + 1) * 64;
}
/* finish_transfer(): Finish a multi-round write transfer
This function is called when the final round of a transfer has completed,
either by the handler of the BEMP interrupt or by the usb_commit_async()
function if the pipe is being committed when empty. */
static void finish_transfer(struct transfer volatile *t, int pipe)
{
/* Free the FIFO controller */
fifo_unbind(t->ct);
t->ct = NOF;
/* Mark the transfer as unused */
t->committed = false;
t->used = 0;
/* Disable the interrupt */
if(pipe) USB.BEMPENB.word &= ~(1 << pipe);
if(t->callback.function) gint_call(t->callback);
}
/* finish_round(): Update transfer logic after a write round completes
This function is called when a write round completes, either by the handler
@ -187,10 +234,13 @@ static void finish_round(struct transfer volatile *t, int pipe)
t->size -= t->flying;
t->flying = 0;
if(pipe) usb_log("%d left\n", t->size);
/* Account for auto-transfers */
if(t->used == pipe_bufsize(pipe)) t->used = 0;
/* Invoke the callback at the end */
/* At the end, free the FIFO and invoke the callback. Hold the
controller until the pipe is committed */
if(t->size == 0)
{
t->data = NULL;
@ -199,19 +249,25 @@ static void finish_round(struct transfer volatile *t, int 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
invoked after the write. Otherwise the FIFO is transmitted automatically and
the BEMP handler will call finish_round() after the transfer. */
static void write_round(struct transfer volatile *t, int pipe)
{
pipect_t ct = pipect(pipe);
fifo_t ct = t->ct;
void volatile *FIFO = NULL;
if(ct == CF) FIFO = &USB.CFIFO;
if(ct == D0F) FIFO = &USB.D0FIFO;
if(ct == D1F) FIFO = &USB.D1FIFO;
if(pipe) pipe_mode(ct, pipe, 1, t->unit_size);
if(pipe == 0)
{
if(USB.CFIFOSEL.ISEL != 1 || USB.DCPCTR.PID != 1)
fifo_bind(ct, 0, FIFO_WRITE, 1);
}
else fifo_bind(ct, pipe, FIFO_WRITE, t->unit_size);
/* Amount of data that can be transferred in a single run */
int available = pipe_bufsize(pipe) - (pipe == 0 ? 0 : t->used);
@ -230,13 +286,16 @@ static void write_round(struct transfer volatile *t, int pipe)
if(t->unit_size == 2) block_size = DMA_2B, size >>= 1;
if(t->unit_size == 4) block_size = DMA_4B, size >>= 2;
gint_call_t callback = !partial ? GINT_CALL_NULL :
GINT_CALL(finish_round, (void *)t, pipe);
gint_call_t callback = partial ?
GINT_CALL(finish_round, (void *)t, pipe) :
GINT_CALL_NULL;
/* TODO: DMA support in usb_write_async()/write_round() */
/* TODO: USB: Don't use a fixed DMA channel */
dma_transfer_async(3, block_size, size,
/* Use DMA channel 3 for D0F and 4 for D1F */
int channel = (ct == D0F) ? 3 : 4;
int rc = dma_transfer_async(channel, block_size, size,
t->data, DMA_INC, (void *)FIFO, DMA_FIXED, callback);
usb_log("dma_transfer_async: %d, bs=%d, size=%d, fifo=%d\n", rc, block_size, size, ct);
}
else
{
@ -255,11 +314,19 @@ int usb_write_async(int pipe, void const *data, int size, int unit_size,
struct transfer volatile *t = &pipe_transfers[pipe];
if(!data || !size) return 0;
/* Re-use the controller from a previous write if there is one,
otherwise try to get a new free one */
/* TODO: usb_write_async(): TOC/TOU race on controller being free */
fifo_t ct = t->ct;
if(ct == NOF) ct = fifo_access(pipe);
if(ct == NOF) return USB_WRITE_NOFIFO;
t->data = data;
t->size = size;
t->unit_size = unit_size;
t->dma = use_dma;
t->committed = false;
t->ct = ct;
t->callback = callback;
/* Set up the Buffer Empty interrupt to refill the buffer when it gets
@ -273,42 +340,52 @@ int usb_write_async(int pipe, void const *data, int size, int unit_size,
int usb_write_sync(int pipe, void const *data, int size, int unit_size,
bool use_dma)
{
/* Wait for a previous write and/or transfer to finish */
while(pipe_busy(pipe)) sleep();
volatile int flag = 0;
usb_write_async(pipe, data, size, unit_size, use_dma,
GINT_CALL_SET(&flag));
int rc;
while(1)
{
rc = usb_write_async(pipe, data, size, unit_size, use_dma,
GINT_CALL_SET(&flag));
if(rc == 0) break;
if(rc == USB_WRITE_NOFIFO)
usb_log("USB_WRITE_NOFIFO\n");
sleep();
}
while(!flag) sleep();
return 0;
}
int usb_commit_async(int pipe, gint_call_t callback)
{
struct transfer volatile *t = &pipe_transfers[pipe];
if(pipe_busy(pipe)) return USB_COMMIT_BUSY;
/* TODO: USB: Commit on the DCP? */
if(pipe == 0) return 0;
if(t->ct == NOF) return USB_COMMIT_INACTIVE;
/* Commiting an empty pipe is a no-op */
if(t->used == 0)
{
if(callback.function) gint_call(callback);
return 0;
}
/* Set BVAL=1 and inform the BMEP handler of the commitment with the
committed flag; the handler will invoke the commit callback */
t->committed = true;
t->callback = callback;
pipect_t ct = pipect(pipe);
if(ct == D0F) USB.D0FIFOCTR.BVAL = 1;
if(ct == D1F) USB.D1FIFOCTR.BVAL = 1;
/* TODO: Handle complex commits on the DCP */
if(pipe == 0)
{
finish_transfer(t, pipe);
USB.CFIFOCTR.BVAL = 1;
return 0;
}
/* Commiting an empty pipe ends the transfer on the spot */
if(t->used == 0)
{
finish_transfer(t, pipe);
return 0;
}
/* Set BVAL=1 and inform the BEMP handler of the commitment with the
committed flag; the handler will invoke finish_transfer() */
if(t->ct == D0F) USB.D0FIFOCTR.BVAL = 1;
if(t->ct == D1F) USB.D1FIFOCTR.BVAL = 1;
usb_log("[PIPE%d] Committed transfer\n", pipe);
return 0;
@ -334,12 +411,7 @@ void usb_pipe_write_bemp(int pipe)
if(t->committed)
{
/* Finish transfer, disable interrupt, reset logic */
t->committed = false;
t->used = 0;
USB.BEMPENB.word &= ~(1 << pipe);
if(t->callback.function) gint_call(t->callback);
finish_transfer(t, pipe);
}
else
{

View file

@ -6,16 +6,7 @@
#define USB SH7305_USB
/* Write a response to the DCP, and automatically set write mode */
static void dcp_write(void const *data, size_t size)
{
/* Automatically enable write mode (heuristic condition) */
/* TODO: dcp_write(): Try to set the mode in usb_pipe_write() */
if(USB.CFIFOSEL.ISEL != 1 || USB.DCPCTR.PID != 1)
usb_pipe_mode_write(0, 1);
usb_write_sync(0, data, size, 1, false);
}
#define dcp_write(data, size) usb_write_sync(0, data, size, 1, false)
//---
// SETUP requests
@ -177,8 +168,8 @@ void usb_req_setup(void)
int wIndex = USB.USBINDX.word;
int wLength = USB.USBLENG.word;
do USB.INTSTS0.VALID = 0;
while(USB.INTSTS0.VALID);
USB.INTSTS0.VALID = 0;
usb_while(USB.INTSTS0.VALID);
/* Standard requests */
@ -198,7 +189,7 @@ void usb_req_setup(void)
/* Push the buffer when responding to an IN request with a BUF */
if((bmRequestType & 0x80) && USB.DCPCTR.PID == 1)
USB.CFIFOCTR.BVAL = 1;
usb_commit_sync(0);
/* Finalize request */
USB.DCPCTR.CCPL = 1;

View file

@ -172,10 +172,14 @@ int usb_open(usb_interface_t const **interfaces, gint_call_t callback)
intc_handler_function(0xa20, GINT_CALL(usb_interrupt_handler));
intc_priority(INTC_USB, 15);
usb_open_status = true;
return 0;
}
bool usb_is_open(void)
{
return usb_open_status;
}
void usb_open_wait(void)
{
while(!usb_open_status) sleep();
@ -204,10 +208,6 @@ static void usb_interrupt_handler(void)
};
/* Save PIPESEL to avoid concurrent access issues */
uint16_t pipesel = USB.PIPESEL.word;
/* Change D0FIFOSEL to make sure that the same pipe can't be specified
in D0FIFOSEL and D1FIFOSEL simultaneously */
uint16_t d0fifosel = USB.D0FIFOSEL.word;
USB.D0FIFOSEL.word = 0x0000;
if(USB.INTSTS0.VBINT)
{
@ -250,9 +250,6 @@ static void usb_interrupt_handler(void)
}
else usb_log("<%04X> -> ???\n", USB.INTSTS0.word);
/* Disable D1FIFO so that concurrent access does not occur */
USB.D1FIFOSEL.word = 0x0000;
USB.D0FIFOSEL.word = d0fifosel;
/* Restore PIPESEL which can have been used for transfers */
USB.PIPESEL.word = pipesel;
}

View file

@ -113,12 +113,6 @@ void usb_pipe_configure(int address, endpoint_t const *ep);
/* usb_pipe_clear(): Clear all data in the pipe */
void usb_pipe_clear(int pipe);
/* usb_pipe_mode_read(): Set a pipe in read mode */
void usb_pipe_mode_read(int pipe, int read_size);
/* usb_pipe_mode_write(): Set a pipe in write mode */
void usb_pipe_mode_write(int pipe, int write_size);
/* usb_pipe_write_bemp(): Callback for the BEMP interrupt on a pipe */
void usb_pipe_write_bemp(int pipe);