diff --git a/CMakeLists.txt b/CMakeLists.txt index fe98b79..f58f023 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/include/gint/usb-ff-bulk.h b/include/gint/usb-ff-bulk.h index 4a14c87..621c715 100644 --- a/include/gint/usb-ff-bulk.h +++ b/include/gint/usb-ff-bulk.h @@ -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 */ diff --git a/include/gint/usb.h b/include/gint/usb.h index d96664a..9a8eae3 100644 --- a/include/gint/usb.h +++ b/include/gint/usb.h @@ -10,6 +10,83 @@ #include #include +/* 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 . 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 . 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 . +// +// 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); diff --git a/src/usb/classes/ff-bulk-gray.c b/src/usb/classes/ff-bulk-gray.c new file mode 100644 index 0000000..8dac23d --- /dev/null +++ b/src/usb/classes/ff-bulk-gray.c @@ -0,0 +1,32 @@ +#ifdef FX9860G + +#include +#include +#include +#include + +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 */ diff --git a/src/usb/classes/ff-bulk.c b/src/usb/classes/ff-bulk.c index a760b70..07a8243 100644 --- a/src/usb/classes/ff-bulk.c +++ b/src/usb/classes/ff-bulk.c @@ -1,4 +1,7 @@ #include +#include +#include +#include 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); +} diff --git a/src/usb/configure.c b/src/usb/configure.c index 5f5b854..cd621c4 100644 --- a/src/usb/configure.c +++ b/src/usb/configure.c @@ -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; +} diff --git a/src/usb/pipes.c b/src/usb/pipes.c index 743cb0c..37de6f4 100644 --- a/src/usb/pipes.c +++ b/src/usb/pipes.c @@ -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 { diff --git a/src/usb/setup.c b/src/usb/setup.c index 6aadd55..7a0ce15 100644 --- a/src/usb/setup.c +++ b/src/usb/setup.c @@ -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; diff --git a/src/usb/usb.c b/src/usb/usb.c index c2a00a3..6a53efc 100644 --- a/src/usb/usb.c +++ b/src/usb/usb.c @@ -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; } diff --git a/src/usb/usb_private.h b/src/usb/usb_private.h index af6ff00..4590fa1 100644 --- a/src/usb/usb_private.h +++ b/src/usb/usb_private.h @@ -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);