Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
Sylvain PILLOT 2024-01-19 20:10:27 +01:00
commit dcf34833c5
30 changed files with 1703 additions and 492 deletions

View File

@ -1,7 +1,7 @@
# Build system for the gint unikernel
cmake_minimum_required(VERSION 3.15)
project(Gint VERSION 2.9.0 LANGUAGES C ASM)
project(Gint VERSION 2.10.0 LANGUAGES C ASM)
include(GitVersionNumber)
include(Fxconv)
@ -102,6 +102,8 @@ set(SOURCES_COMMON
# MMU driver
src/mmu/mmu.c
# Rendering
src/render/dcircle.c
src/render/dellipse.c
src/render/dhline.c
src/render/dimage.c
src/render/dline.c
@ -127,6 +129,7 @@ set(SOURCES_COMMON
src/usb/classes/ff-bulk.c
src/usb/configure.c
src/usb/pipes.c
src/usb/read4.S
src/usb/setup.c
src/usb/string.c
src/usb/usb.c

1
TODO
View File

@ -17,7 +17,6 @@ Extensions on existing code:
* core: run destructors when a task-switch results in leaving the app
* fs: support read-only files backed with GetBlockAddress() on fx-CG
* kernel: SH4- or G-III-specific linker scripts?
* keysc: global shortcut SHIFT+0+EXIT for abort() as an infinite loop break
Future directions:
* Audio playback using TSWilliamson's libsnd method

View File

@ -212,6 +212,38 @@ void dhline(int y, int color);
@color Line color (same values as dline() are allowed) */
void dvline(int x, int color);
//---
// Other geometric shapes
//---
/* dcircle(): Circle with border
This function renders a circle defined by its middle point (xm, ym) and a
radius r. The border and fill can be set separately. This function uses
Bresenham's algorithm and only renders circles of odd diameter; if you want
an even diameter, use dellipse() with a bounding box.
@xm @ym Coordinates of center
@r Radius (integer, >= 0)
@fill_color Color of the disc inside the circle, C_NONE to disable
@border_color Color of the circle itself, C_NONE to disable */
void dcircle(int xm, int ym, int r, int fill_color, int border_color);
/* dellipse(): Ellipse with border
This function renders a non-rotated ellipse, defined by its bounding box.
The border and fill can be set separately. The border is as 1-pixel thin
border given by Bresenham's algorithm.
To render an ellipse from its center coordinates (x,y) and semi-major/minor
axes a/b, use dellipse(x-a, y-b, x+a, y+b, fill_color, border_color).
@x1 @y1 @x2 @y2 Ellipse's bounding box (both bounds included, like drect())
@fill_color Color of the surface inside the ellipse, C_NONE to disable
@border_color Color of the ellipse itself, C_NONE to disable */
void dellipse(int x1, int y1, int x2, int y2, int fill_color,
int border_color);
//---
// Text rendering (topti)
//---
@ -413,6 +445,28 @@ void dprint_opt(int x, int y, int fg, int bg, int halign, int valign,
Calls dprint_opt() with bg=C_NONE, halign=DTEXT_LEFT and valign=DTEXT_TOP */
void dprint(int x, int y, int fg, char const *format, ...);
//---
// Text rendering utilities
//---
/* dfont_glyph_index(): Obtain the glyph index of a Unicode code point
Returns the position of code_point in the character table of the given font,
or -1 if code_point is not part of that set. */
int dfont_glyph_index(font_t const *font, uint32_t code_point);
/* topti_glyph_offset(): Use a font index to find the location of a glyph
The provided glyph value (usuall obtained by dfont_glyph_index()) must be
nonnegative. Returns the offset the this glyph's data in the font's data
array. When using a proportional font, the size array is ignored. */
int dfont_glyph_offset(font_t const *font, uint glyph_number);
/* dtext_utf8_next(): Read the next UTF-8 code point of a string
Returns the next code point and advances the string. Returns 0 (NUL) at the
end of the string. */
uint32_t dtext_utf8_next(uint8_t const **str_pointer);
//---
// Image rendering (bopti)
//---

View File

@ -10,15 +10,22 @@
/* 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
write(2) followed by a "commit" with fsync(2), and reads on a single data
transfer being made over several calls to read(2) (for asynchronous file
descriptors at least).
* Multi-round refers to the writes interacting multiple times with hardware
in order to communicate the complete data.
* Writes are multi-part because they are constructed over several calls to
write(2) followed by a "commit" with fynsc(2). They are additionally
multi-round because each call to write(2) requires mutiple rounds of
hardware communication when the hardware buffer is smaller than the data.
* Reads are multi-part because each transaction from the host requires
several calls to read(2) ("user segments") if the user's buffer is shorter
than the full transaction. In addition, reads are multi-round because a
single read(2) to a user buffer takes multiple rounds of hardware
communication ("hardware segments") whenever the user buffer is larger
than the hardware buffer.
The process of performing such an I/O operation, as tracked by this
structure and use throughout gint, is as follows. For a write:
structure and use throughout gint, is as follows.
## Writes
WRITING ---------------------.
^ | | HW buffer
@ -68,17 +75,32 @@
FLYING-SYNC type == ASYNCIO_SYNC HW commit in progress
============================================================================
For a read:
The series of asyncio_op function calls for a write is as follows:
transaction ::= write* fsync
write ::= asyncio_op_start_write round+ asyncio_op_finish_write
round ::= asyncio_op_start_write_round asyncio_op_finish_write_round
fsync ::= asyncio_op_start_sync asyncio_op_finish_sync
Each write(2) (with a single user-provided buffer) is split into multiple
rounds that each fill the (small) hardware buffer. More writes can follow
until an fynsc(2) commits the pipe.
## Reads
IN interrupt
--> IDLE-EMPTY --------------> IDLE-READY
| \ | ^
read(2) | \ Transaction read(2) | | Buffer full
| \ exhausted* | |
| \ read(2) | ^
read(2) | \ Transaction | | User buffer
| \ exhausted* | | filled
| '----<----------. | |
| \ | |
v IN interrupt \ v | .---. Read from
WAITING ------------------> READING v hardware
'---'
| IN interrupt \ | |
v .--------->--------. \ v | .---. Read from
WAITING READING v hardware
'---------<--------' '---'
HW buffer exhausted with
user buffer not full
On this diagram, the right side indicates the presence of data to read from
hardware while the bottom side indicates a read(2) request by the user.
@ -86,39 +108,102 @@
highlights that read(2) will always return at the end of a transaction even
if the user-provided buffer is not full (to avoid waiting).
*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.
A read(2) request (a "user segment") might consume several full hardware
buffers ("hardware segments") if the user buffer is large, thus looping
repeatedly between WAITING and READING. Conversely, each hardware segment
might fulfill many read(2) requests if the user buffer is small, thus
looping between IDLE-READY and READING.
The invariants and meaning for each state are as follow:
* Note that if the transaction finishes right as the user buffer fills up,
we return to IDLE-READY and the next call to read(2) will successfully read
0 bytes and transition back to IDLE-EMPTY. This allows the user to read the
entire transaction by reading data until they get fewer bytes than requested
without running the risk of blocking on the next transaction.
State Characterization Description
The invariants and meaning for each state are as follow. The right side
(presence of a hardware segment) is indicated by `buffer_used >= 0`, while
the bottom side (presence of a read(2) request) is indicated by `size > 0`.
As an exception, IDLE-EMPTY uses `buffer_used == 0` so that the default
zero-initialization of transfer data is sufficient.
State Invariant characterization Description
============================================================================
IDLE-EMPTY type == ASYNCIO_NONE No I/O operation
IDLE-EMPTY type == ASYNCIO_NONE No I/O operation, the pipe is
&& buffer_used == 0 idle with no request and no
&& size == 0 hardware segment (but we might
&& round_size == 0 still be mid-transaction)
----------------------------------------------------------------------------
IDLE-READY !data_r && buffer_used > 0 Hardware waiting for us to read
IDLE-READY type == ASYNCIO_READ There is a hardware segment not
&& buffer_used >= 0 marked as complete, but no
&& size == 0 read(2) request to consume it
&& round_size == 0
----------------------------------------------------------------------------
WAITING data_r && !buffer_used Waiting for further HW data
WAITING type == ASYNCIO_READ There is a read(2) request but
&& buffer_used < 0 no hardware segment, and either
&& size > 0 the request is new or the
&& round_size == 0 transaction isn't exhausted
----------------------------------------------------------------------------
READING round_size > 0 DMA/CPU read from HW in progress
READING type == ASYNCIO_READ A read round in progress and
&& buffer_used >= 0 either the read(2) request or
&& size > 0 hardware segment will be
&& round_size > 0 exhausted when it ends
============================================================================
The series of asyncio_op function calls for a read is a bit more complicated
because transactions are divided into two non-comparable sequences of
segments: one for packets received by the hardware buffer (on BRDY), one for
the data being copied to user buffers (on read(2)).
|<------ Transaction from the host (no size limit) ------>|
v BRDY v BRDY v BRDY v BRDY v BRDY
+-----------+-----------+-----------+-----------+---------+
| HW buffer | HW buffer | HW buffer | HW buffer | (short) | HW segments
+-----------+------+----+-----------+-----------+---------+
| R1 | R2 | R3 | R4 | R5 | R6 | Read rounds
+-----------+------+----+-----------+-----------+---------+
| User buffer #1 | User buffer #2 | (short) | User segments
+------------------+----------------------------+---------+
^ read(2) ^ read(2) ^ read(2)
Reads rounds are exactly the intersections between hardware segments and
read(2) user segments.
States can be checked and transitioned with the API functions below. */
enum { ASYNCIO_NONE, ASYNCIO_READ, ASYNCIO_WRITE, ASYNCIO_SYNC };
typedef volatile struct
{
/** User-facing information **/
/* Type of I/O operation (read/write/fsync) */
/* Type of I/O operation (NONE/WRITE/SYNC/READ) */
uint8_t type;
/* Whether the DMA should be used for hardware access */
bool dma;
bool dma :1;
/* For reading pipes, whether the transaction is expected to continue with
another hardware segment after the current one */
bool cont_r :1;
/* For reading pipes, interrupt flag signaling an incoming hardware segment
not yet added to the operation */
bool interrupt_r :1;
/* For reading pipes, whether the current read call should close the
current hardware segment if all the data is read even if the read call
is not partial */
bool autoclose_r :1;
/* Hardware resource being used for access (meaning depends on hardware).
Usually, this is assigned for the duration of hardware transaction.
This value is user-managed and not modified by asyncio_op functions. */
uint8_t controller;
/* Number of bytes in short buffer (0..3) */
uint8_t shbuf_size;
/* Short buffer */
uint32_t shbuf;
/* Size of data currently in the hardware buffer */
int16_t buffer_used;
/* Size of data being read/written in the current round (which may itself
be asynchronous if it's using the DMA) */
uint16_t round_size;
union {
/* Address of data to transfer, incremented gradually [write] */
@ -126,30 +211,13 @@ typedef volatile struct
/* Address of buffer to store data to, incremented gradually [read] */
void *data_r;
};
/* Size of data left to transfer / buffer space available */
/* Size of data left to transfer to satisfy the complete request */
int size;
/* For reading operations, pointer to total amount of transferred data */
int *realized_size_r;
/* Callback at the end of the current write, final commit, or read */
gint_call_t callback;
/** Hardware state information **/
/* Size of data currently in the hardware buffer */
uint16_t buffer_used;
/* Size of data being read/written in the current round (which may itself
be asynchronous if it's using the DMA) */
uint16_t round_size;
/* Hardware resource being used for access (meaning depends on hardware).
Usually, this is assigned for the duration of hardware transaction.
This value is user-managed and not modified by asyncio_op functions. */
uint8_t controller;
/** Internal information **/
/* Number of bytes in short buffer (0..3) */
uint8_t shbuf_size;
/* Short buffer */
uint32_t shbuf;
} asyncio_op_t;
//---
@ -168,52 +236,58 @@ void asyncio_op_clear(asyncio_op_t *op);
bool asyncio_op_busy(asyncio_op_t const *op);
//---
// 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().
// I/O functions
//---
/* asyncio_op_start_write(): Start a write call */
void asyncio_op_start_write(asyncio_op_t *op, void const *data, size_t size,
bool use_dma, gint_call_t const *callback);
/* Start/finish a write(2) call. */
void asyncio_op_start_write(asyncio_op_t *op,
void const *data, size_t size, bool use_dma, gint_call_t const *callback);
void asyncio_op_finish_write(asyncio_op_t *op);
/* asyncio_op_start_sync(): Transition a write I/O operation to a fsync call */
void asyncio_op_start_sync(asyncio_op_t *op, gint_call_t const *callback);
/* asyncio_op_start_read(): Start a single-block read from hardware
Returns the size that will actually be read (may be smaller than `size`). */
size_t asyncio_op_start_read(asyncio_op_t *op, void *data, size_t size,
bool use_dma, gint_call_t const *callback);
/* asyncio_op_finish_call(): Update state after a read/write/fsync call
This function should be called when the read(2)/write(2)/fsync(2) call last
started on the operation has concluded, including all of the hardware
effects. This isn't the moment when the syscall returns, rather it is the
moment when it completes its work. */
void asyncio_op_finish_call(asyncio_op_t *op);
//---
// Write round functions
//---
/* asyncio_op_start_write_round(): Start a single-block write to hardware */
/* Start/finish a single-block write to hardware. */
void asyncio_op_start_write_round(asyncio_op_t *op, size_t size);
/* asyncio_op_finish_write_round(): Finish a write round and advance data */
void asyncio_op_finish_write_round(asyncio_op_t *op);
//---
// Read group functions
//---
/* Start an fsync(2) operation (after one or more writes) and finish it. */
void asyncio_op_start_sync(asyncio_op_t *op, gint_call_t const *callback);
void asyncio_op_finish_sync(asyncio_op_t *op);
/* asyncio_op_start_read_group(): Start a read call */
void asyncio_op_start_read_group(asyncio_op_t *op, size_t total_size);
/* Start a read(2) call. The call will finish automatically when the final
round finishes. If `autoclose` is set, the current hardware segment will
be marked as completed if the round reads it entirely, even if the request
is fulfilled. */
void asyncio_op_start_read(asyncio_op_t *op, void *data, size_t size,
bool use_dma, int *realized_size, bool autoclose,
gint_call_t const *callback);
/* asyncio_op_fininsh_read_group(): Reset operation after a read group */
void asyncio_op_finish_read_group(asyncio_op_t *op);
/* Start a hardware segment. `cont` should be true if there will be another
segment in the same transaction. The segment will finish automatically when
it is completely consumed by a read round. */
void asyncio_op_start_read_hwseg(asyncio_op_t *op, size_t size, bool cont);
bool asyncio_op_has_read_call(asyncio_op_t const *op);
bool asyncio_op_has_read_hwseg(asyncio_op_t const *op);
/* Start a single-block read from hardware. The requested size is automatically
t->size, however the round may of course be smaller depending on how much
data is available. Returns the round size. */
int asyncio_op_start_read_round(asyncio_op_t *op);
enum {
ASYNCIO_HWSEG_EXHAUSTED = 0x01,
ASYNCIO_REQUEST_FINISHED = 0x02,
ASYNCIO_TRANSACTION_EXHAUSTED = 0x04,
};
/* Finish a single-block read from hardware. This function also finishes the
current hardware segment and read call if appropriate, *except* that it
doesn't invoke the read(2) callback. You should make a copy of it before
calling and invoke it manually after. Returns a combination of the above
flags indicating what finished along with the round. */
int asyncio_op_finish_read_round(asyncio_op_t *op);
/* Cancel a read call. This keeps the hardware segment part intact. */
void asyncio_op_cancel_read(asyncio_op_t *op);
#endif /* GINT_USB_ASYNCIO */

View File

@ -207,6 +207,9 @@ typedef struct {
/* Parameters for the standard repeat function */
int rep_standard_first, rep_standard_next;
/* State of keys that have changes since the last flip monitoring reset. */
GALIGNED(4) uint8_t state_flips[12];
} keydev_t;
/* keydev_std(): Standard keyboard input device
@ -274,6 +277,17 @@ void keydev_set_async_filter(keydev_t *d, keydev_async_filter_t filter);
/* keydev_keydown(): Check if a key is down according to generated events */
bool keydev_keydown(keydev_t *d, int key);
/* keydev_keypressed(): Check if a key was pressed
This compares to the state at the time of the last keydev_clear_flips(). */
bool keydev_keypressed(keydev_t *d, int key);
/* keydev_keyreleased(): Check if a key was released
This compares to the state at the time of the last keydev_clear_flips(). */
bool keydev_keyreleased(keydev_t *d, int key);
/* keydev_clear_flips(): Reset flip info used by keypressed()/keyreleased() */
void keydev_clear_flips(keydev_t *d);
/* keydev_transform(): Obtain current transform parameters */
keydev_transform_t keydev_transform(keydev_t *d);

View File

@ -80,6 +80,56 @@ void gint_osmenu_native(void);
@restart 0 to exit, 1 to restart by using gint_osmenu() */
void gint_setrestart(int restart);
/* gint_poweroff(): World switch and power off the calculator
Shows the CASIO logo / poweroff screen if show_logo is true. The program
will resume execution normally after powering on again. Don't call this
function with show_logo=false as a result of pressing AC/ON because the
calculator will restart immediately unless the user releases the AC/ON key
extremely quickly. */
void gint_poweroff(bool show_logo);
/* gint_set_onchip_save_mode(): Specify memory save policy for world switches
World switches can cause corruption in on-chip memory in two ways. First, if
the calculator powers off while in the OS world, on-chip memory will be
wiped because it's not powered when the calculator is off. Second, the OS
may run code that overwrites on-chip memory (we don't have any examples of
that but it's not part of the add-in interface).
As a result, the best option is to backup on-chip memory and restore it when
loading back into the add-in. The issue is that there's 20 kB of on-chip
memory (4 kB ILRAM + 16 kB XYRAM) and on some machines (fx-9860G-like) or in
some applications we don't have that much memory lying around.
This function selects between three modes for handling this save:
* [Reinitialization mode]
Don't save on-chip memory at all, instead just reinitialize the globals
and leave the rest corrupted. This is useful if on-chip memory is used
only for temporaries (e.g. frames in Azur), for which we don't care if
they're corrupted, or code (e.g gint interrupt handling code), which will
be reloaded and is otherwise a constant.
* [Backup mode]
Save on-chip memory to a user-provided buffer of size GINT_ONCHIP_BUFSIZE.
* [Nothing mode]
Don't do anything, and let the world switch function choose its preferred
saving method. This allows application-specific compromises.
This is not a problem on SH3 because on-chip memory is only used on SH4. */
enum {
GINT_ONCHIP_REINITIALIZE = 0,
GINT_ONCHIP_BACKUP = 1,
GINT_ONCHIP_NOTHING = 2,
};
#define GINT_ONCHIP_BUFSIZE (20 << 10)
void gint_set_onchip_save_mode(int mode, void *ptr);
/* gint_get_onchip_save_mode(): Get the current on-chip memory save policy */
int gint_get_onchip_save_mode(void **ptr);
/* This function has been moved to the INTC driver */
__attribute__((deprecated("Use intc_handler() instead")))
static GINLINE void *gint_inthandler(int code, void const *h, size_t size) {

View File

@ -414,7 +414,7 @@ image_t *image_sub(image_t const *src, int x, int y, int w, int h,
/* Make the last parameter optional */
#define image_sub1(src, x, y, w, h, dst, ...) image_sub(src, x, y, w, h, dst)
#define image_sub(...) image_sub(__VA_ARGS__, NULL)
#define image_sub(...) image_sub1(__VA_ARGS__, NULL)
/* image_at(): Build a reference to a position within a sub-image */
#define image_at(img, x, y) image_sub(img, x, y, -1, -1)
@ -434,9 +434,9 @@ image_t *image_sub(image_t const *src, int x, int y, int w, int h,
// that allocation can fail, so you need to check whether the returned image is
// valid.
//
// (You can still pass an invalid image to libimg functions when chaining
// transforms. The invalid image will be ignored or returned unchanged, so you
// can check for it at the end of any large chain.)
// (You can still pass invalid images to transform functions. The invalid image
// will be ignored or returned unchanged, so you can chain calls and check for
// validity at the end of the chain.)
//
// Some functions support in-place transforms. This means they can be called
// with the source as destination, and will transform the image without needing

View File

@ -60,6 +60,24 @@ extern "C" {
* clearevents() reads all pending events from the input queue.
Games are often also interested in whether keys were pressed or released
_since the last frame_. gint doesn't know what a frame is, but you can track
key state flips by using cleareventflips() before reading events and
keypressed() and keyreleased() afterwards.
* keypressed() checks if the specified key is currently down and was up at
the time of the last cleareventflips().
* keyreleased() checks if the specified key is currently up and was down at
the time of the last cleareventflips().
A typical game loop might look like. cleareventflips() must be called
_before_ clearevents().
cleareventflips();
clearevents(); // or pollevent(), waitevent()
if(keydown(KEY_RIGHT)) move();
if(keypressed(KEY_F6)) open_inventory();
The previous functions are quite low-level. GUI programs that look like the
system applications will prefer using a GetKey()-like functions that return
a single key press at a time, heeds for releases, for SHIFT and ALPHA
@ -98,13 +116,13 @@ typedef struct
{
uint time :16; /* Time of event, unique over short periods */
uint :3; /* Reserved for future use */
uint :2; /* Reserved for future use */
uint mod :1; /* Whether modifiers are used */
uint shift :1; /* If mod=1, whether SHIFT was pressed */
uint alpha :1; /* If mod=1, whether ALPHA was pressed */
uint type :2; /* Type of key event */
uint type :3; /* Type of key event */
uint key :8; /* Hit key */
} GPACKED(4) key_event_t;
@ -112,10 +130,11 @@ typedef struct
/* Keyboard event types, as in the [type] field of key_event_t */
enum
{
KEYEV_NONE = 0, /* No event available (poll() only) */
KEYEV_DOWN = 1, /* Key was pressed */
KEYEV_UP = 2, /* Key was released */
KEYEV_HOLD = 3, /* A key that was pressed has been held down */
KEYEV_NONE = 0, /* No event available (poll() only) */
KEYEV_DOWN = 1, /* Key was pressed */
KEYEV_UP = 2, /* Key was released */
KEYEV_HOLD = 3, /* A key that was pressed has been held down */
KEYEV_OSMENU = 4, /* We went to the main menu and back */
};
/* Keyboard frequency analysis is a runtime setting since gint 2.4. This macro
@ -162,6 +181,12 @@ key_event_t waitevent(volatile int *timeout);
/* clearevents(): Read all events waiting in the queue */
void clearevents(void);
/* cleareventflips(): Set the time reference for keypressed()/keyreleased()
The two functions keypressed() and keyreleased() will use the keyboard state
at the time this function was called to determine whether any given key was
just pressed or jut released. */
void cleareventflips(void);
//---
// Key state functions
//---
@ -181,6 +206,16 @@ int keydown_all(int key1, ...);
sequence should be terminated by a 0 integer. */
int keydown_any(int key1, ...);
/* keypressed(): Check if a key was just pressed
This function returns non-zero if the specified key is currently down, *and*
it was up at the time of the last call to cleareventflips(). */
int keypressed(int key);
/* keyreleased(): Check if a key was just released
This function returns non-zero if the specified key is currently up, *and*
it was down at the time of the last call to cleareventflips(). */
int keyreleased(int key);
//---
// High-level functions
//---
@ -205,24 +240,31 @@ key_event_t getkey(void);
/* The following are the option bits for getkey_opt(). */
enum {
/* Enable modifiers keys */
GETKEY_MOD_SHIFT = 0x01,
GETKEY_MOD_ALPHA = 0x02,
GETKEY_MOD_SHIFT = 0x0001,
GETKEY_MOD_ALPHA = 0x0002,
/* SHIFT + OPTN (requires GETKEY_MOD_SHIFT) or LIGHT toggles backlight */
GETKEY_BACKLIGHT = 0x04,
GETKEY_BACKLIGHT = 0x0004,
/* MENU triggers a task switch and displays the main menu */
GETKEY_MENU = 0x08,
GETKEY_MENU = 0x0008,
/* Repeat arrow keys, or even all keys */
GETKEY_REP_ARROWS = 0x10,
GETKEY_REP_ALL = 0x20,
GETKEY_REP_ARROWS = 0x0010,
GETKEY_REP_ALL = 0x0020,
/* Enable custom repeat profiles; see getkey_set_repeat_profile() */
GETKEY_REP_PROFILE = 0x40,
GETKEY_REP_PROFILE = 0x0040,
/* Enable application shortcuts; see getkey_set_feature_function() */
GETKEY_FEATURES = 0x80,
GETKEY_FEATURES = 0x0080,
/* After coming back from the main menu, redraw the screen with dupdate
(has an effect on fx-CG 50 only) */
GETKEY_MENU_DUPDATE = 0x0100,
/* After coming back from the main menu, send a KEYEV_OSMENU event */
GETKEY_MENU_EVENT = 0x0200,
/* Enable power off with SHIFT + AC/ON */
GETKEY_POWEROFF = 0x0400,
/* No modifiers */
GETKEY_NONE = 0x00,
GETKEY_NONE = 0x0000,
/* Default settings of getkey() */
GETKEY_DEFAULT = 0xdf,
GETKEY_DEFAULT = 0x05df,
};
/* getkey_feature_t: Custom feature function

View File

@ -1,5 +1,27 @@
//---
// gint:usb-ff-bulk - A trivial bulk-based transfer class
// gint:usb-ff-bulk - A bulk-based transfer class using the fxlink protocol
//
// This interface (class code 0xff/0x77) implements a simple bidirectional
// communication channel running the fxlink protocol. It basically talks to
// fxlink's interactive mode on the host, and can broadly be used for 4 things:
//
// 1. Sending standard messages to fxlink (screenshots, logs...). Convenient
// functions are provided in this header to minimize the overhead. fxlink
// running in interactive mode (fxlink -t) will handle these messages in a
// suitable way automatically (eg. save screenshots as PNG).
//
// 2. Sending custom messages to fxlink (bench results, memory dumps, crash
// reports...). fxlink running in interactive mode will forward the data to
// appropriate user-provided external programs to handle the messages.
//
// 3. Applications based around on data streams coming from the host (screen
// projector, web browser...). This can be achieved by modifying fxlink or
// using it as a library.
//
// 4. Remote control from the fxlink interactive mode (fxlink -t). This is
// based on fxlink's 'command' and 'keyboard' protocols. This module will
// automatically handle commands and respond to them as long as the message
// handling function is called regularly.
//---
#ifndef GINT_USB_FF_BULK
@ -10,19 +32,93 @@ extern "C" {
#endif
#include <gint/usb.h>
struct usb_fxlink_header;
/* The bulk transfer interface with class code 0xff provides a very simple
communication channel between the calculator and a host. There is
(currently) a single IN pipe that sends data from the calculator to the
host, at high-speed (USB 2.0).
The class code of this interface is 0xff, which means that the protocol is
custom and requires a custom program on the host to receive the data (unlike
for instance LINK on a Graph 90+E which behaves as a USB stick and can be
used with the file browser). fxlink can be used to the effect. */
/* This bulk transfer interface with class code 0xff/0x77 implements a simple
bidirectional communication channel between the calculator and a host,
running at high-speed USB 2.0 and using the fxlink protocol. You can use it
to communicate with fxlink's interactive mode on the host. */
extern usb_interface_t const usb_ff_bulk;
//---
// Sending standard messages
//---
/* usb_fxlink_text(): Send raw text
Send a string; fxlink will display it in the terminal. This can be used to
back stdout/stderr. Sending lots of small messages can be slow; if that's a
problem, fill in message manually. If size is 0, uses strlen(text). */
void usb_fxlink_text(char const *text, int size);
/* usb_fxlink_screenshot(): Take a screenshot
This function sends a copy of the VRAM to fxlink. This is best used just
before dupdate() since this ensures the image sent by USB is identical to
the one displayed on screen.
If `onscreen` is set and there are two VRAMs (on fx-CG or when using the
gray engine on fx-9860G), sends a copy of the other VRAM. This is a bit more
intuitive when taking a screenshot of the last shown image as a result of a
key press. Note that this function never reads pixels directly from the
display (it's usually slow and currently not even implemented). */
void usb_fxlink_screenshot(bool onscreen);
/* usb_fxlink_videocapture(): Send a frame for a video recording
This function is essentially the same as usb_fxlink_screenshot(). It sends a
capture of the VRAM to fxlink but uses the "video" type, which fxlink
displays in real-time or saves as a video file. The meaning of the onscreen
setting is identical to usb_fxlink_screenshot().
This function can be called with onscreen=false as a dupdate() hook to
automatically send new frames to fxlink. */
void usb_fxlink_videocapture(bool onscreen);
#ifdef FX9860G
/* Similar to usb_fxlink_screenshot(), but takes a gray screenshot if the gray
engine is currently running. */
void usb_fxlink_screenshot_gray(bool onscreen);
/* Like usb_fxlink_videocapture(), but uses VRAM data from the gray engine. */
void usb_fxlink_videocapture_gray(bool onscreen);
#endif
//---
// Receiving messages
//---
/* usb_fxlink_handle_messages(): Process and return incoming messages
This function processes incoming messages. It handles some types of messages
internally (eg. fxlink commands) and returns the others to the caller by
loading the provided header structure and returning true.
When this function returns true, the caller should read the message contents
on the interface's input pipe. Usually every read will return just a portion
of the message (eg. 2048 bytes) so the contents should be read with a loop.
As long as this function returns true it should be called againt to process
any other messages. When there is no more messages to process, this function
returns false. It is important to call this function regularly from the main
thread when quick responses are expected. */
bool usb_fxlink_handle_messages(struct usb_fxlink_header *header);
/* usb_fxlink_drop_transaction(): Drop incoming data until end of transaction
When a message arrives on the USB port incoming data *must* be processed in
order for the pipe to accept any further data in the future. This function
can be used in error situations to drop data until the end of the
transaction (which is usually the end of the message, at least with the
fxSDK's implementation of fxlink). */
void usb_fxlink_drop_transaction(void);
/* usb_fxlink_set_notifier(): Set up a notification for incoming messages
Registers the provided function to be called when data arrives on the fxlink
input pipe. This signals that usb_fxlink_handle_messages() should be called
later from the main thread. Pass NULL to disable the notification. */
void usb_fxlink_set_notifier(void (*notifier_function)(void));
//---
// Direct bulk access
//
@ -31,98 +127,75 @@ extern usb_interface_t const usb_ff_bulk;
// functions.
//---
/* usb_ff_bulk_output(): Pipe for calculator -> host communication */
/* usb_ff_bulk_output(): Pipe number for calculator -> host communication */
int usb_ff_bulk_output(void);
/* usb_ff_bulk_input(): Pipe number for host -> calculator communication */
int usb_ff_bulk_input(void);
//---
// fxlink protocol
// Construction and analysis of fxlink protocol messages
//
// 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.
// fxlink messages consist of a simple header followed by message contents
// (sometimes with a subheader). In addition to the functions above, it is
// possible to craft custom fxlink messages and send them manually.
//
// 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.
// To send a message manually, simple write an fxlink header to the output
// pipe, followed by the contents. The message can be built from any number of
// writes to the pipe. After the last write, commit the pipe.
//---
/* usb_fxlink_header_t: Message header for fxlink
fxlink supports a minimalist protocol to receive data sent from the
calculator and automatically process it (such as save it to file, convert
to an image, etc). It is designed as a convenience feature, and it can be
extended with custom types rather easily.
Messages are categorized with an (application, type) pair; both are UTF-8
strings of up to 16 bytes (NUL not required). The application name "fxlink"
is reserved for built-in types of messages. Other names can be used freely,
and fxlink's interactive mode on the host side will forward messages to
external programs based on the application name.
A message is categorized with an (application, type) pair; both are UTF-8
strings of up to 16 characters. The application name "fxlink" is reserved
for built-in types supported by fxlink; any other custom application name
can be set (in which case fxlink will call a user-provided program to handle
the message).
The size of the data to be transferred must be specified in order of the
transfer to proceed correctly, as it cannot reliably be guessed on the other
side (and guessing wrong means all further messages will be in trouble).
As with the rest of the USB protocol, all the multi-byte integer fields in
this header are encoded as *little-endian*. */
typedef struct
{
/* Protocol version = 0x00000100 */
uint32_t version;
/* Size of the data to transfer (excluding this header) */
uint32_t size;
/* Size of individual transfers (related to the size of the FIFO) */
uint32_t transfer_size;
/* Application name, UTF-8 (might not be zero-terminated) */
char application[16];
/* Message type */
char type[16];
The size of the data to be transferred must be specified upfront, but this
restriction might be lifted in the future. As with the rest of the USB
protocol, all the integer are encoded as *little-endian*. */
typedef struct usb_fxlink_header {
/* Protocol version = 0x00000100 */
uint32_t version;
/* Size of the data to transfer (excluding this header) */
uint32_t size;
/* Size of individual transfers (related to the size of the FIFO) */
uint32_t transfer_size;
/* Application name and message type, UTF-8 (need not be zero-terminated) */
char application[16];
char type[16];
} usb_fxlink_header_t;
/* usb_fxlink_fill_header(): Fill an fxlink message header
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. */
Fills an fxlink header's fields with the provided. You need to specify the
exact amount of data that the fxlink message will contain. Returns false if
parameters are invalid or don't fit the fields. */
bool usb_fxlink_fill_header(usb_fxlink_header_t *header,
char const *application, char const *type, uint32_t data_size);
//---
// Short functions for fxlink built-in types
//---
char const *application, char const *type,
uint32_t data_size);
/* Subheader for the fxlink built-in "image" type */
typedef struct
{
uint32_t width;
uint32_t height;
/* Pixel format, see below */
int pixel_format;
typedef struct {
/* Image size; storage is row-major */
uint32_t width;
uint32_t height;
/* Pixel format, see below */
int pixel_format;
} usb_fxlink_image_t;
/* Pixel formats */
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,
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;
@ -147,7 +220,7 @@ typedef enum
usually slow and currently not even implemented. */
void usb_fxlink_screenshot(bool onscreen);
#if defined(FX9860G) || defined(FX9860G_AS_CG)
#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
@ -179,7 +252,7 @@ void usb_fxlink_text(char const *text, int size);
automatically send new frames to fxlink. */
void usb_fxlink_videocapture(bool onscreen);
#if defined(FX9860G) || defined(FX9860G_AS_CG)
#ifdef FX9860G
/* usb_fxlink_videocapture_gray(): Send a gray frame for a video recording
Like usb_fxlink_videocapture(), but uses VRAM data from the gray engine. */
void usb_fxlink_videocapture_gray(bool onscreen);

View File

@ -55,6 +55,9 @@ enum {
USB_READ_IDLE = -12,
/* No FIFO controller is available */
USB_READ_NOFIFO = -13,
/* (Internal codes) */
USB_ERROR_ZERO_LENGTH = -100,
};
/* usb_open(): Open the USB link
@ -137,8 +140,12 @@ struct usb_interface {
/* Answer class-specific SETUP requests */
/* TODO */
/* Notification that an endpoint has data read to be read */
void (*notify_read)(int endpoint, int size);
/* Notification that an endpoint has data to be read. This function is
called frequently when data is being transmitted; the particular
timings depend on low-level details. The notification should be
passed down to cause the main thread to read later. Do not read in
the notification function! */
void (*notify_read)(int endpoint);
};
/* usb_interface_endpoint_t: Parameters for an interface endpoint
@ -269,25 +276,32 @@ 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.
and then reads up to `size` bytes into `data`. It is "synchronized" with USB
transactions on the pipe in the following ways:
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.
1. If there is no active transaction, it waits for one to arrive.
2. If the active transaction has no data left to read, it is skipped.
3. If the transaction's data is completely read, the transaction is closed
even if `data` is full. (See usb_read_async() for the alternative.)
Basically usb_read_sync() returns the next `size` bytes of data that the
calculator receives on the pipe, with a single exception: it doesn't read
across transactions, so if the active transaction has 100 bytes left and you
request 200, you will only get 100. usb_read_sync() only ever returns 0
bytes if there is an empty transaction, which is rare.
Because this is a blocking function, a call to usb_read_sync() will *FREEZE*
if there is no data to read on the pipe and the host doesn't send any. In
addition, it's not possible to detect how much data is left to read with
usb_read_sync()'s interface. If you want to avoid or debug a freeze, use
use usb_read_async() or set a timeout with usb_read_sync_timeout(). If you
want to read a transaction until the end without knowing its size in
advance, use usb_read_async().
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);
@ -295,29 +309,86 @@ int usb_read_sync(int pipe, void *data, int size, bool use_dma);
int usb_read_sync_timeout(int pipe, void *data, int size, bool use_dma,
timeout_t const *timeout);
/* Flags for usb_read_async(), see below. */
enum {
/* Use the DMA for data transfer (requires full 4-byte alignment). */
USB_READ_USE_DMA = 0x01,
/* Ignore reads of 0 bytes from exhausted transactions. */
USB_READ_IGNORE_ZEROS = 0x02,
/* Close transactions when exhausted even by non-partial reads. */
USB_READ_AUTOCLOSE = 0x04,
/* Wait for the read to finish before returning. */
USB_READ_WAIT = 0x08,
/* Block for a new transaction if none is currenty active. */
USB_READ_BLOCK = 0x10,
};
/* 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().
This function is similar to usb_read_sync() but it is asynchronous, ie. it
returns after starting the read and then runs in the background. Without
options, usb_read_async() is guaranteed to return quickly without waiting
for communications. The read itself also never blocks unless there is a
disagreement with the host on the size of transactions.
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_read_async() is also slightly lower-level than usb_read_sync(). In
particular, it only closes transactions when the data is fully read *and*
the user buffer is not full. If the user requests exactly the number of
bytes left in the transaction, the entire contents will be read but the
transaction will be left in an "active with 0 bytes left" state. Only on the
next read will the transaction be closed.
This behavior is designed so that a read closes a transaction if and only if
it returns fewer bytes than requested, which is useful for detecting the end
of transfers when their size isn't known in advance.
This function returns a preliminary error code. If it is zero, then the
callback will be invoked later with *rc set to the final return value of the
read, which is itself either an error (< 0) or the number of bytes read.
Due to its low-level nature, raw usb_read_async() is a bit impractical to
use and almost always requires repeated calls and callback waits. The
following flags are provided to make it more convenient by incorporating
features of usb_read_sync():
* USB_READ_IGNORE_ZEROS causes usb_read_async() to ignore reads of 0 bytes
from transactions that were exhausted but not closed.
* USB_READ_AUTOCLOSE causes usb_read_async() to always close transactions
when exhausted even if the read is not partial. This is useful when the
size of the transaction is in fact known in advance.
* USB_READ_WAIT causes usb_read_async() to wait for the end of the transfer
before returning. Unless there is a driver bug or USB_READ_BLOCK is also
set, this cannot cause the function to freeze since the read will always
complete in finite time (returning USB_READ_IDLE if no transaction is
active). When USB_READ_WAIT it set, `callback` and `rc` are ignored; the
callback is not invoked, and the status of the entire read is returned.
* USB_READ_BLOCK causes usb_read_async() to wait for a transaction if none
is active. This can stall the transfer indefinitely if the host doesn't
transmit any data, and freeze the call entirely if USB_READ_WAIT is set.
This option really makes usb_read_async() a synchronous function.
* A timeout is supported (pass NULL to ignore).
With all options enabled, usb_read_async() becomes functionally identical to
usb_read_sync_timeout(). */
int usb_read_async(int pipe, void *data, int size, int flags, int *rc,
timeout_t const *timeout, gint_call_t callback);
/* usb_read_cancel(): Cancel an asynchronous read
Once started, an async read will run in the background and keep writing to
the provided buffer. This function cancels the operation so that no further
writes to the buffer are made and the associated memory can be safely
deallocated. */
void usb_read_cancel(int pipe);
//---
// USB debugging functions
//---
#ifdef GINT_USB_DEBUG
#define USB_LOG(...) usb_log(__VA_ARGS__)
#define USB_TRACE(...) usb_trace(__VA_ARGS__)
/* usb_set_log(): Set the logging function for the USB driver */
void usb_set_log(void (*logger)(char const *format, va_list args));
/* usb_get_log(): Get the logging function */
void (*usb_get_log(void))(char const *format, va_list args);
/* usb_log(): Send a message to the USB log */
void usb_log(char const *format, ...);
@ -325,9 +396,14 @@ void usb_log(char const *format, ...);
The function is called atomically, thus cannot be interrupted, therefore it
is safe to call usb_trace() in interrupt handlers. */
void usb_set_trace(void (*tracer)(char const *message));
/* usb_get_trace(): Get the tracing function */
void (*usb_get_trace(void))(char const *message);
/* usb_trace(): Trace the current state of the driver */
void usb_trace(char const *message);
#ifdef GINT_USB_DEBUG
#define USB_LOG(...) usb_log(__VA_ARGS__)
#define USB_TRACE(...) usb_trace(__VA_ARGS__)
#else
#define USB_LOG(...) do {} while(0)
#define USB_TRACE(...) do {} while(0)

View File

@ -5,6 +5,13 @@
#ifndef GINT_CORE_KERNEL
#define GINT_CORE_KERNEL
/* gint_load_onchip_sections(): Initialize on-chip memory sections */
void gint_load_onchip_sections(void);
/* gint_copy_vram(): Copy gint's VRAM to the OS to avoid flickering during
certain world switches. */
void gint_copy_vram(void);
/* kinit(): Install and start gint */
void kinit(void);

View File

@ -1,6 +1,8 @@
#include <gint/gint.h>
#include <gint/display.h>
#include <gint/hardware.h>
#include <gint/keyboard.h>
#include "kernel.h"
#include <string.h>
@ -11,7 +13,6 @@ int __Timer_Deinstall(int id);
int __PutKeyCode(int row, int column, int keycode);
int __GetKeyWait(int *col,int *row,int type,int time,int menu,uint16_t *key);
void __ClearKeyBuffer(void); /* ? */
void *__GetVRAMAddress(void);
void __ConfigureStatusArea(int mode);
void __SetQuitHandler(void (*callback)(void));
@ -28,29 +29,92 @@ static void __osmenu_handler(void)
__Timer_Deinstall(__osmenu_id);
}
#ifdef FXCG50
typedef void (os_menu_function_t)(void);
/* This method is possible thanks to reverse-engineering by Dr-Carlos.
<https://www.cemetech.net/forum/viewtopic.php?t=18944> */
static os_menu_function_t *find_os_menu_function(void)
{
/* Get syscall table address */
uint32_t addr = *(uint32_t *)0x8002007c;
if(addr < 0x80020070 || addr >= 0x81000000 - 0x1e58 * 4)
return NULL;
/* Get pointer to %1e58 SwitchToMainMenu() */
uint16_t const *insns = *(uint16_t const **)(addr + 0x1e58 * 4);
if(addr < 0x80020070 || addr >= 0x81000000)
return NULL;
/* Check up to 150 instructions to find the call to the internal function
SaveAndOpenMainMenu(). This call is in a widget of the shape
mov.l GetkeyToMainFunctionReturnFlag, rX
mov #3, rY
bsr SaveAndOpenMainMenu
mov.b rY, @rX
bra <start of widget>
nop */
for(int i = 0; i < 150; i++) {
/* Match: mov.l @(disp, pc), rX */
if((insns[i] & 0xf000) != 0xd000)
continue;
int rX = (insns[i] >> 8) & 0x0f;
/* Match: mov #3, rY */
if((insns[i+1] & 0xf0ff) != 0xe003)
continue;
int rY = (insns[i+1] >> 8) & 0x0f;
/* Match: bsr @(disp, pc) */
if((insns[i+2] & 0xf000) != 0xb000)
continue;
int disp = (insns[i+2] & 0x0fff);
/* Match: mov.b rX, @rY */
if((insns[i+3] != 0x2000 + (rX << 8) + (rY << 4)))
continue;
/* Match: bra @(_, pc) */
if((insns[i+4] & 0xf000) != 0xa000)
continue;
/* Match: nop */
if(insns[i+5] != 0x0009)
continue;
/* Return the target of the bsr instruction */
uint32_t fun_addr = (uint32_t)&insns[i+2] + 4 + disp * 2;
return (os_menu_function_t *)fun_addr;
}
return NULL;
}
#endif
void gint_osmenu_native(void)
{
__ClearKeyBuffer();
#ifdef FX9860G
memcpy(__GetVRAMAddress(), gint_vram, 1024);
#endif
gint_copy_vram();
#ifdef FXCG50
/* Unfortunately ineffective (main menu probably reenables it)
__ConfigureStatusArea(3); */
/* TODO: Improve copied VRAM behavior in gint_osmenu() on fxcg50 */
uint16_t *vram1, *vram2;
dgetvram(&vram1, &vram2);
/* Try to use the internal function directly if we could figure out its
address by dynamically disassembling */
os_menu_function_t *fun = find_os_menu_function();
if(fun) {
fun();
uint16_t *dst = __GetVRAMAddress();
uint16_t *src = (gint_vram == vram1) ? vram2 + 6 : vram1 + 6;
/* Run an immediate keyboard update, and clear the events so that the
key pressed in order to re-enter the add-in is not also processed in
the application */
extern int keysc_tick(void);
keysc_tick();
clearevents();
for(int y = 0; y < 216; y++, dst+=384, src+=396)
for(int x = 0; x < 384; x++)
{
dst[x] = src[x];
return;
}
#endif

View File

@ -11,6 +11,7 @@
#include <gint/exc.h>
#include <setjmp.h>
#include <stdlib.h>
#include <string.h>
#include "kernel.h"
@ -94,6 +95,21 @@ static void callarray(void (**f)(void), void (**l)(void))
while(f < l) (*(*f++))();
}
void gint_load_onchip_sections(void)
{
/* Do not load data to ILRAM, XRAM or YRAM on SH3 - the areas don't
exist. If you use them, you're responsible! */
if(!isSH3())
{
/* Clear the areas so that we have less to save in case of a
return to menu leading to a poweroff. */
memset((void *)0xe5200000, 0, 4096);
regcpy(&lilram, &silram, &rilram);
memset((void *)0xe500e000, 0, 16384);
regcpy(&lxyram, &sxyram, &rxyram);
}
}
static int start2(int isappli, int optnum)
{
/* We are currently in a dynamic userspace mapping of an add-in run
@ -141,13 +157,7 @@ static int start2(int isappli, int optnum)
regcpy(&ldata, &sdata, &rdata);
regclr(&rbss, &sbss);
/* Do not load data to ILRAM, XRAM or YRAM on SH3 - the areas don't
exist. If you use them, you're responsible! */
if(!isSH3())
{
regcpy(&lilram, &silram, &rilram);
regcpy(&lxyram, &sxyram, &rxyram);
}
gint_load_onchip_sections();
#if defined(FX9860G) && !defined(FX9860G_AS_CG)
/* Copy permanently-mapped code to start of user RAM (on fx-CG 50 it

View File

@ -45,6 +45,7 @@
.global ___SetQuitHandler
/* Reset */
.global ___PowerOff
.global ___Reset
#define syscall_(id, syscall_table) \
@ -120,6 +121,8 @@ ___SetQuitHandler:
/* Reset */
___PowerOff:
syscall(0x3f4)
___Reset:
syscall(0x236)
@ -198,6 +201,8 @@ ___SpecialMatrixCodeProcessing:
/* Reset */
___PowerOff:
syscall(0x1839)
___Reset:
syscall(0x1187)

View File

@ -3,8 +3,12 @@
#include <gint/gint.h>
#include <gint/exc.h>
#include <gint/defs/call.h>
#include <gint/hardware.h>
#include <gint/display.h>
#include "kernel.h"
#include <stdlib.h>
#include <string.h>
//---
// World buffer
@ -56,6 +60,9 @@ void gint_world_sync(void)
// World switch with driver state saves
//---
static int onchip_save_mode = GINT_ONCHIP_REINITIALIZE;
static void *onchip_save_buffer = NULL;
void gint_world_switch_in(gint_world_t world_os, gint_world_t world_addin)
{
/* Unbind from the OS driver and complete foreign asynchronous tasks */
@ -144,13 +151,41 @@ int gint_world_switch(gint_call_t call)
extern void *gint_stack_top;
gint_world_switch_out(gint_world_addin, gint_world_os);
void *ILRAM = (void *)0xe5200000;
void *XRAM = (void *)0xe500e000;
void *YRAM = (void *)0xe5010000;
/* Watch out for stack overflows */
uint32_t *canary = gint_stack_top;
if(canary)
*canary = 0xb7c0ffee;
/* Save on-chip memory if requested */
if(!isSH3() && onchip_save_mode == GINT_ONCHIP_BACKUP) {
void *ptr = onchip_save_buffer;
memcpy(ptr, ILRAM, 4096);
ptr += 4096;
memcpy(ptr, XRAM, 8192);
ptr += 8192;
memcpy(ptr, YRAM, 8192);
ptr += 8192;
}
int rc = gint_call(call);
/* Restore or reinitialize on-chip memory */
if(!isSH3() && onchip_save_mode == GINT_ONCHIP_BACKUP) {
void *ptr = onchip_save_buffer;
memcpy(ILRAM, ptr, 4096);
ptr += 4096;
memcpy(XRAM, ptr, 8192);
ptr += 8192;
memcpy(YRAM, ptr, 8192);
ptr += 8192;
}
else if(!isSH3() && onchip_save_mode == GINT_ONCHIP_REINITIALIZE)
gint_load_onchip_sections();
/* The canary check needs to occur before switching in the gint world;
otherwise we just crash due to the overflow. gint_panic() isn't
really designed to work from the OS world, but it does fine on the
@ -167,3 +202,47 @@ void gint_switch(void (*function)(void))
{
gint_world_switch(GINT_CALL(function));
}
void gint_set_onchip_save_mode(int mode, void *ptr)
{
onchip_save_mode = mode;
onchip_save_buffer = ptr;
}
int gint_get_onchip_save_mode(void **ptr)
{
if(ptr)
*ptr = onchip_save_buffer;
return onchip_save_mode;
}
void gint_copy_vram(void)
{
void *__GetVRAMAddress(void);
#ifdef FX9860G
memcpy(__GetVRAMAddress(), gint_vram, 1024);
#endif
#ifdef FXCG50
/* TODO: Improve copied VRAM behavior in gint_osmenu() on fxcg50 */
uint16_t *vram1, *vram2;
dgetvram(&vram1, &vram2);
uint16_t *dst = __GetVRAMAddress();
uint16_t *src = (gint_vram == vram1) ? vram2 + 6 : vram1 + 6;
for(int y = 0; y < 216; y++, dst+=384, src+=396)
for(int x = 0; x < 384; x++)
{
dst[x] = src[x];
}
#endif
}
void gint_poweroff(bool show_logo)
{
void __PowerOff(int show_logo);
gint_copy_vram();
gint_world_switch(GINT_CALL(__PowerOff, (int)show_logo));
}

View File

@ -4,6 +4,7 @@
#include <gint/keyboard.h>
#include <gint/drivers/keydev.h>
#include <gint/display.h>
#include <gint/gint.h>
#include <gint/defs/types.h>
@ -64,10 +65,27 @@ key_event_t getkey_opt(int opt, volatile int *timeout)
e.key == KEY_MENU && !e.shift && !e.alpha)
{
gint_osmenu();
#ifdef FXCG50
if(opt & GETKEY_MENU_DUPDATE)
dupdate();
#endif
if(!(opt & GETKEY_MENU_EVENT))
continue;
e.type = KEYEV_OSMENU;
}
/* Poweroff */
if((opt & GETKEY_POWEROFF) && e.type == KEYEV_DOWN &&
e.key == KEY_ACON && e.shift && !e.alpha)
{
gint_poweroff(true);
if(opt & GETKEY_MENU_DUPDATE)
dupdate();
continue;
}
if(e.type == KEYEV_DOWN || e.type == KEYEV_HOLD)
if(e.type != KEYEV_NONE || e.type != KEYEV_UP)
{
/* Custom global features */
bool accepted = false;

View File

@ -142,6 +142,22 @@ void keydev_tick(keydev_t *d, uint us)
d->time++;
/* Disable repeat if the repeating key was released */
if(d->rep_key != 0)
{
int row = (d->rep_key >> 4);
int col = 0x80 >> (d->rep_key & 0x7);
if(!(d->state_now[row] & col))
{
d->rep_key = 0;
d->rep_count = -1;
d->rep_time = -1;
d->rep_delay = -1;
d->delayed_shift = 0;
d->delayed_alpha = 0;
}
}
if(d->rep_key != 0)
{
if(d->rep_delay >= 0)
@ -179,6 +195,7 @@ key_event_t keydev_unqueue_event(keydev_t *d)
if(ev.type == KEYEV_DOWN)
{
d->state_queue[row] |= col;
d->state_flips[row] ^= col;
/* Mark this key as the currently repeating one */
if(d->rep_key == 0 && can_repeat(d, ev.key))
{
@ -191,16 +208,7 @@ key_event_t keydev_unqueue_event(keydev_t *d)
else if(ev.type == KEYEV_UP)
{
d->state_queue[row] &= ~col;
/* End the current repeating streak */
if(d->rep_key == ev.key)
{
d->rep_key = 0;
d->rep_count = -1;
d->rep_time = -1;
d->rep_delay = -1;
d->delayed_shift = 0;
d->delayed_alpha = 0;
}
d->state_flips[row] ^= col;
}
return ev;
@ -219,6 +227,27 @@ bool keydev_keydown(keydev_t *d, int key)
__attribute__((alias("keydev_keydown")))
bool _WEAK_keydev_keydown(keydev_t *d, int key);
bool keydev_keypressed(keydev_t *d, int key)
{
int row = (key >> 4);
int col = 0x80 >> (key & 0x7);
return (d->state_queue[row] & col) && (d->state_flips[row] & col);
}
bool keydev_keyreleased(keydev_t *d, int key)
{
int row = (key >> 4);
int col = 0x80 >> (key & 0x7);
return !(d->state_queue[row] & col) && (d->state_flips[row] & col);
}
void keydev_clear_flips(keydev_t *d)
{
memset(d->state_flips, 0, sizeof d->state_flips);
}
//---
// Event transforms
//---

View File

@ -15,6 +15,7 @@
#include <gint/hardware.h>
#include <string.h>
#include <stdlib.h>
/* Keyboard scan frequency in Hertz. Start with 128 Hz, this frequency *must
be high* for the keyboard to work! Reading at low frequencies produces a lot
@ -55,13 +56,21 @@ static void keysc_scan(uint8_t *scan)
}
/* keysc_tick(): Update the keyboard to the next state */
static int keysc_tick(void)
int keysc_tick(void)
{
uint8_t scan[12] = { 0 };
keysc_scan(scan);
keydev_process_state(&keysc_dev, scan);
keydev_tick(&keysc_dev, keysc_scan_us);
/* Freeze abort key combo: SHIFT+7+3+AC/ON */
if(keydown(KEY_SHIFT) && keydown(KEY_7) && keydown(KEY_3) &&
keydown(KEY_ACON))
{
abort();
}
return TIMER_CONTINUE;
}
@ -93,12 +102,26 @@ void clearevents(void)
while(pollevent().type != KEYEV_NONE);
}
/* keydown(): Current key state */
void cleareventflips(void)
{
keydev_clear_flips(&keysc_dev);
}
int keydown(int key)
{
return keydev_keydown(&keysc_dev, key);
}
int keypressed(int key)
{
return keydev_keypressed(&keysc_dev, key);
}
int keyreleased(int key)
{
return keydev_keyreleased(&keysc_dev, key);
}
//---
// Driver initialization
//---

View File

@ -85,10 +85,10 @@ static void topti_render(int x, int y, char const *str_char, font_t const *f,
/* Read each character from the input string */
while(1)
{
uint32_t code_point = topti_utf8_next(&str);
uint32_t code_point = dtext_utf8_next(&str);
if(!code_point || (size >= 0 && str - str0 > size)) break;
int glyph = topti_glyph_index(f, code_point);
int glyph = dfont_glyph_index(f, code_point);
if(glyph < 0) continue;
int dataw = f->prop ? f->glyph_width[glyph] : f->width;
@ -98,7 +98,7 @@ static void topti_render(int x, int y, char const *str_char, font_t const *f,
x += space;
if(x >= dwindow.right) break;
int index = topti_offset(f, glyph);
int index = dfont_glyph_offset(f, glyph);
/* Compute horizontal intersection between glyph and screen */

View File

@ -134,13 +134,13 @@ void topti_render(int x, int y, char const *str_char, font_t const *f,
/* Pull each character into the operator buffer */
while(1)
{
uint32_t code_point = topti_utf8_next(&str);
uint32_t code_point = dtext_utf8_next(&str);
if(!code_point || (size >= 0 && str - str0 > size)) break;
int glyph = topti_glyph_index(f, code_point);
int glyph = dfont_glyph_index(f, code_point);
if(glyph < 0) continue;
int index = topti_offset(f, glyph);
int index = dfont_glyph_offset(f, glyph);
/* Put glyph data into the operators */
int width = f->prop ? f->glyph_width[glyph] : f->width;

36
src/render/dcircle.c Normal file
View File

@ -0,0 +1,36 @@
#include <gint/display.h>
/* Based on <http://members.chello.at/~easyfilter/bresenham.html> */
void dcircle(int xm, int ym, int r, int fill_color, int border_color)
{
if(r < 0 || (fill_color == C_NONE && border_color == C_NONE)) return;
/* Circle is completely outside the rendering window */
if(xm-r >= dwindow.right || xm+r < dwindow.left) return;
if(ym-r >= dwindow.bottom || ym+r < dwindow.top) return;
int x = -r, y = 0, err = 2-2*r; /* II. Quadrant */
/* The iteration is slightly changed from the original. We swap x/y in
quadrants II. and IV. so that we get horizontal linesi nstead of a
single arc rotated 4 times. However this means we have to go until
x <= 0 instead of x < 0. */
do {
if(fill_color != C_NONE)
dline(xm-x, ym+y, xm+x, ym+y, fill_color);
dpixel(xm-x, ym+y, border_color); /* I. Quadrant */
dpixel(xm+x, ym+y, border_color); /* II. Quadrant */
if(fill_color != C_NONE)
dline(xm-x, ym-y, xm+x, ym-y, fill_color);
dpixel(xm+x, ym-y, border_color); /* III. Quadrant */
dpixel(xm-x, ym-y, border_color); /* IV. Quadrant */
r = err;
if (r <= y) /* e_xy+e_y < 0 */
err += ++y*2+1;
if (r > x || err > y) /* e_xy+e_x > 0 or no 2nd y-step */
err += ++x*2+1;
}
while (x <= 0);
}

57
src/render/dellipse.c Normal file
View File

@ -0,0 +1,57 @@
#include <gint/display.h>
#include <gint/defs/util.h>
/* Based on <http://members.chello.at/~easyfilter/bresenham.html> */
void dellipse(int x1, int y1, int x2, int y2, int fill_color, int border_color)
{
if(fill_color == C_NONE && border_color == C_NONE) return;
if(x1 > x2) swap(x1, x2);
if(y1 > y2) swap(y1, y2);
/* Ellipse is completely outside the rendering window */
if(x1 >= dwindow.right || x2 < dwindow.left) return;
if(y1 >= dwindow.bottom || y2 < dwindow.top) return;
int a = x2-x1, b = y2-y1, b1 = b&1; /* diameter */
int dx = 4*(1-a)*b*b, dy = 4*(b1+1)*a*a; /* error increment */
int err = dx + dy + b1 * a * a, e2; /* error of 1.step */
y1 += (b + 1) / 2;
y2 = y1-b1; /* starting pixel */
a = 8 * a * a;
b1 = 8 * b * b;
do {
if(fill_color != C_NONE)
dline(x1, y1, x2, y1, fill_color);
dpixel(x2, y1, border_color); /* I. Quadrant */
dpixel(x1, y1, border_color); /* II. Quadrant */
if(fill_color != C_NONE)
dline(x1, y2, x2, y2, fill_color);
dpixel(x1, y2, border_color); /* III. Quadrant */
dpixel(x2, y2, border_color); /* IV. Quadrant */
e2 = 2 * err;
if (e2 <= dy) {
y1++;
y2--;
err += (dy += a);
}
if (e2 >= dx || 2 * err > dy) {
x1++;
x2--;
err += (dx += b1);
}
}
while (x1 <= x2);
while (y1 - y2 <= b) /* to early stop of flat ellipses a=1 */
{
dpixel(x1 - 1, y1, border_color); /* finish tip of ellipse */
dpixel(x2 + 1, y1++, border_color);
dpixel(x1 - 1, y2, border_color);
dpixel(x2 + 1, y2--, border_color);
}
}

View File

@ -26,23 +26,4 @@ extern font_t const *topti_font;
/* Default font */
extern font_t const *gint_default_font;
/* topti_utf8_next(): Read the next UTF-8 code point of a string
Returns the next code point and advances the string. Returns 0 (NUL) at the
end of the string. */
uint32_t topti_utf8_next(uint8_t const **str_pointer);
/* topti_glyph_index(): Obtain the glyph index of a Unicode code point
Returns the position of code_point in the character table of the given font,
or -1 if code_point is not part of that set.
@f Font object
@code_point Unicode code point to locate the glyph for */
int topti_glyph_index(font_t const *f, uint32_t code_point);
/* topti_offset(): Use a font index to find the location of a glyph
@f Font object
@glyph Glyph number obtained by charset_decode(), must be nonnegative.
Returns the offset the this glyph's data in the font's data array. When
using a proportional font, the size array is not heeded for. */
int topti_offset(font_t const *f, uint glyph);
#endif /* RENDER_COMMON */

View File

@ -3,7 +3,6 @@
#include "../render/render.h"
/* dfont(): Set the default font for text rendering */
font_t const *dfont(font_t const * font)
{
font_t const *old_font = topti_font;
@ -12,14 +11,12 @@ font_t const *dfont(font_t const * font)
return old_font;
}
/* dfont_default(): Get gint's default font */
font_t const *dfont_default(void)
{
return gint_default_font;
}
/* topti_glyph_index(): Obtain the glyph index of a Unicode code point */
int topti_glyph_index(font_t const *f, uint32_t code_point)
int dfont_glyph_index(font_t const *f, uint32_t code_point)
{
int glyph_start = 0;
@ -37,8 +34,7 @@ int topti_glyph_index(font_t const *f, uint32_t code_point)
return -1;
}
/* topti_offset(): Use a font index to find the location of a glyph */
int topti_offset(font_t const *f, uint glyph)
int dfont_glyph_offset(font_t const *f, uint glyph)
{
/* Non-proportional fonts don't need an index */
if(!f->prop) return glyph * f->storage_size;
@ -57,8 +53,7 @@ int topti_offset(font_t const *f, uint glyph)
return offset;
}
/* topti_utf8_next(): Read the next UTF-8 code point of a string */
uint32_t topti_utf8_next(uint8_t const **str_pointer)
uint32_t dtext_utf8_next(uint8_t const **str_pointer)
{
uint8_t const *str = *str_pointer;
uint8_t lead = *str++;
@ -103,7 +98,6 @@ uint32_t topti_utf8_next(uint8_t const **str_pointer)
return 0x20;
}
/* dnsize(): Get the width and height of rendered text, with character limit */
void dnsize(char const *str_char, int size, font_t const *f, int *w, int *h)
{
uint8_t const *str = (void *)str_char;
@ -121,7 +115,7 @@ void dnsize(char const *str_char, int size, font_t const *f, int *w, int *h)
int length = 0;
while(1)
{
code_point = topti_utf8_next(&str);
code_point = dtext_utf8_next(&str);
if(!code_point || (size >= 0 && str - str0 > size))
break;
length++;
@ -136,23 +130,21 @@ void dnsize(char const *str_char, int size, font_t const *f, int *w, int *h)
while(1)
{
code_point = topti_utf8_next(&str);
code_point = dtext_utf8_next(&str);
if(!code_point || (size >= 0 && str - str0 > size)) break;
int glyph = topti_glyph_index(f, code_point);
int glyph = dfont_glyph_index(f, code_point);
if(glyph >= 0)
width += f->glyph_width[glyph] + f->char_spacing;
}
*w = width - f->char_spacing;
}
/* dsize(): Get the width and height of rendered text */
void dsize(char const *str_char, font_t const *f, int *w, int *h)
{
return dnsize(str_char, -1, f, w, h);
}
/* drsize(): Get width of rendered text with reverse size limit */
char const *drsize(char const *str_char, font_t const *f, int width, int *w)
{
uint8_t const *str = (void *)str_char;
@ -166,7 +158,7 @@ char const *drsize(char const *str_char, font_t const *f, int width, int *w)
/* Record that last glyph considered fits */
str_char = (void *)str;
code_point = topti_utf8_next(&str);
code_point = dtext_utf8_next(&str);
if(!code_point)
{
break;
@ -179,7 +171,7 @@ char const *drsize(char const *str_char, font_t const *f, int width, int *w)
}
else
{
int glyph = topti_glyph_index(f, code_point);
int glyph = dfont_glyph_index(f, code_point);
if(glyph >= 0) used_width += f->glyph_width[glyph];
}
}

View File

@ -1,5 +1,6 @@
#include <gint/drivers/asyncio.h>
#include <string.h>
#include <assert.h>
void asyncio_op_clear(asyncio_op_t *op)
{
@ -8,9 +9,9 @@ void asyncio_op_clear(asyncio_op_t *op)
bool asyncio_op_busy(asyncio_op_t const *op)
{
/* WAITING and READING states are busy */
/* WAITING and READING states are busy (ie. read(2) call in progress) */
if(op->type == ASYNCIO_READ)
return op->round_size || op->data_r != NULL;
return op->size > 0;
/* WRITING, FLYING-WRITE and PENDING states are busy */
if(op->type == ASYNCIO_WRITE)
return op->data_w != NULL;
@ -31,6 +32,18 @@ void asyncio_op_start_write(asyncio_op_t *op, void const *data, size_t size,
op->callback = *callback;
}
void asyncio_op_finish_write(asyncio_op_t *op)
{
gint_call(op->callback);
/* Keep relevant states until the transaction finishes with an fsync(2) */
op->dma = false;
op->data_w = NULL;
op->size = 0;
op->callback = GINT_CALL_NULL;
op->round_size = 0;
}
void asyncio_op_start_write_round(asyncio_op_t *op, size_t size)
{
op->round_size = size;
@ -53,55 +66,108 @@ void asyncio_op_start_sync(asyncio_op_t *op, gint_call_t const *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)
void asyncio_op_finish_sync(asyncio_op_t *op)
{
gint_call(op->callback);
asyncio_op_clear(op);
}
void asyncio_op_finish_call(asyncio_op_t *op)
void asyncio_op_start_read(asyncio_op_t *op, void *data, size_t size,
bool use_dma, int *realized_size, bool autoclose,
gint_call_t const *callback)
{
gint_call(op->callback);
assert(!asyncio_op_has_read_call(op) && size > 0 && !op->round_size);
/* Generally clean up the operation; for a write, keep relevant states
until the transaction finishes with an fsync(2); for a read, keep info
needed for further read(2) calls. */
if(op->type == ASYNCIO_WRITE) {
op->type = ASYNCIO_READ;
op->dma = use_dma;
op->autoclose_r = autoclose;
op->data_r = data;
op->size = size;
op->realized_size_r = realized_size;
op->callback = *callback;
if(realized_size)
*realized_size = 0;
}
bool asyncio_op_has_read_call(asyncio_op_t const *op)
{
/* WAITING and READING states */
return (op->type == ASYNCIO_READ) && (op->size > 0);
}
bool asyncio_op_has_read_hwseg(asyncio_op_t const *op)
{
/* IDLE-READY and READING states */
return (op->type == ASYNCIO_READ) && (op->buffer_used >= 0);
}
void asyncio_op_start_read_hwseg(asyncio_op_t *op, size_t size, bool cont)
{
op->type = ASYNCIO_READ;
op->buffer_used = size;
op->cont_r = cont;
}
int asyncio_op_start_read_round(asyncio_op_t *op)
{
op->round_size = (op->size < op->buffer_used ? op->size : op->buffer_used);
return op->round_size;
}
int asyncio_op_finish_read_round(asyncio_op_t *op)
{
int status = 0;
if(op->realized_size_r)
*op->realized_size_r += op->round_size;
op->buffer_used -= op->round_size;
if(op->data_r)
op->data_r += op->round_size;
op->size -= op->round_size;
op->round_size = 0;
bool read_fulfilled = (op->size == 0);
bool hwseg_exhausted =
(op->buffer_used == 0 && (op->autoclose_r || op->size > 0));
bool transaction_exhausted = hwseg_exhausted && !op->cont_r;
if(read_fulfilled || transaction_exhausted) {
op->dma = false;
op->data_w = NULL;
op->autoclose_r = false;
op->data_r = NULL;
op->size = 0;
op->callback = GINT_CALL_NULL;
op->round_size = 0;
op->realized_size_r = NULL;
status |= ASYNCIO_REQUEST_FINISHED;
}
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;
if(hwseg_exhausted) {
op->buffer_used = -1;
status |= ASYNCIO_HWSEG_EXHAUSTED;
if(status & ASYNCIO_REQUEST_FINISHED) {
op->type = ASYNCIO_NONE;
op->buffer_used = 0;
}
}
else {
asyncio_op_clear(op);
if(transaction_exhausted) {
status |= ASYNCIO_TRANSACTION_EXHAUSTED;
}
return status;
}
void asyncio_op_cancel_read(asyncio_op_t *op)
{
op->dma = false;
op->data_r = NULL;
op->autoclose_r = false;
op->size = 0;
op->callback = GINT_CALL_NULL;
op->realized_size_r = NULL;
if(!asyncio_op_has_read_hwseg(op)) {
op->type = ASYNCIO_NONE;
op->buffer_used = 0;
}
}

View File

@ -1,15 +1,18 @@
#include <gint/usb.h>
#include <gint/usb-ff-bulk.h>
#include <gint/display.h>
#include <gint/hardware.h>
#include <gint/cpu.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
static void notify_read(int endpoint, int size);
static void notify_read(int endpoint);
static usb_dc_interface_t dc_interface = {
.bLength = sizeof(usb_dc_interface_t),
.bDescriptorType = USB_DC_INTERFACE,
.bInterfaceNumber = -1 /* Set by driver */,
.bInterfaceNumber = -1, /* Set by driver */
.bAlternateSetting = 0,
.bNumEndpoints = 2,
.bInterfaceClass = 0xff, /* Vendor-Specific */
@ -156,50 +159,151 @@ void usb_fxlink_videocapture(bool onscreen)
capture_vram(onscreen, "video");
}
//---
// Built-in command execution
//---
static char const * const str_MPU[] = {
[HWMPU_UNKNOWN] = "Unknown MPU",
[HWMPU_SH7337] = "SH7737",
[HWMPU_SH7305] = "SH7305",
[HWMPU_SH7355] = "SH7355",
[HWMPU_SH7724] = "SH7724",
};
static char const * const str_CALC[] = {
[HWCALC_FX9860G_SH3] = "SH3-based fx-9860G-like",
[HWCALC_FX9860G_SH4] = "SH4-based fx-9860G-like",
[HWCALC_G35PE2] = "fx-9860G III/Graph 35+E II",
[HWCALC_PRIZM] = "Prizm fx-CG 10/20",
[HWCALC_FXCG50] = "fx-CG 50/Graph 90+E",
[HWCALC_FXCG_MANAGER] = "fx-CG Manager",
[HWCALC_FX9860G_SLIM] = "fx-9860G Slim",
};
static void execute_command(char const *cmd)
{
if(!strncmp(cmd, "echo", 4)) {
char const *text = cmd+4 + strspn(cmd+4, " \t\n");
usb_fxlink_text(text, 0);
}
if(!strncmp(cmd, "identify", 8)) {
#if defined(FX9860G)
char const *serial_number = (void *)0x8000ffd0;
char const *OS_version = (void *)0x80010020;
char const *BC_version = (void *)0x8000ffb0;
#elif defined(FXCG50)
char const *serial_number = (void *)0x8001ffd0;
char const *OS_version = (void *)0x80020020;
char const *BC_version = (void *)0x8001ffb0;
#endif
char str[256];
sprintf(str, "gint %.8s %s (0x%07x) on %s (%s) %.10s %.14s\n",
serial_number,
GINT_VERSION, GINT_HASH,
str_MPU[gint[HWMPU]], str_CALC[gint[HWCALC]],
OS_version, BC_version);
usb_fxlink_text(str, 0);
}
}
//---
// Data reception
//---
/* 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;
/* User notification function */
static void (*recv_handler)(void) = NULL;
static void header_finished(void)
bool usb_fxlink_handle_messages(usb_fxlink_header_t *header_ptr)
{
recv_header.version = le32toh(recv_header.version);
recv_header.size = le32toh(recv_header.size);
recv_header.transfer_size = le32toh(recv_header.transfer_size);
if(!usb_is_open())
return false;
int major = (recv_header.version >> 8) & 0xff;
int minor = (recv_header.version & 0xff);
/* Read a message header first */
usb_fxlink_header_t header;
timeout_t tm = timeout_make_ms(1000);
int rc = usb_read_async(usb_ff_bulk_input(), &header, sizeof header,
USB_READ_IGNORE_ZEROS | USB_READ_AUTOCLOSE | USB_READ_WAIT, NULL,
&tm, GINT_CALL_NULL);
if(rc == USB_READ_IDLE)
return false;
if(rc < 0) {
USB_LOG("[ff-bulk] Header read failed: %d\n", rc);
return false;
}
if(rc < (int)sizeof header) {
USB_LOG("[ff-bulk] Incomplete header (%d/%d bytes)\n", rc,
sizeof header);
return false;
}
header.version = le32toh(header.version);
header.size = le32toh(header.size);
header.transfer_size = le32toh(header.transfer_size);
int major = (header.version >> 8) & 0xff;
int minor = (header.version & 0xff);
(void)minor;
(void)major;
if(major != 1 || minor != 0) {
USB_LOG("[ff-bulk] Invalid message header!\n");
return usb_fxlink_handle_messages(header_ptr);
}
USB_LOG("[ff-bulk %d.%d] New %.16s.%.16s (%d bytes)\n", major, minor,
recv_header.application, recv_header.type, recv_header.size);
header.application, header.type, header.size);
/* Handle built-in fxlink messages */
if(!strncmp(header.application, "fxlink", 16)
&& !strncmp(header.type, "command", 16)) {
USB_LOG("[ff-bulk] Receiving command (%d bytes)\n", header.size);
char *cmd = malloc(header.size + 1);
if(!cmd)
return false;
usb_read_sync(usb_ff_bulk_input(), cmd, header.size, false);
cmd[header.size] = 0;
USB_LOG("[ff-bulk] Command is: '%s'\n", cmd);
execute_command(cmd);
free(cmd);
return usb_fxlink_handle_messages(header_ptr);
}
*header_ptr = header;
return true;
}
static void notify_read(int endpoint, int size)
void usb_fxlink_set_notifier(void (*notifier_function)(void))
{
recv_handler = notifier_function;
}
void usb_fxlink_drop_transaction(void)
{
int block = USB_READ_BLOCK;
char buf[512];
while(1) {
timeout_t tm = timeout_make_ms(1000);
int rc = usb_read_async(usb_ff_bulk_input(), buf, 512,
USB_READ_WAIT | block, NULL, &tm, GINT_CALL_NULL);
/* Break on error or short read (end of transaction) */
if(rc != 512)
break;
block = 0;
}
}
static void notify_read(int endpoint)
{
/* 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);
// USB_LOG("[ff-bulk] Data available on %02x\n", endpoint);
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;
}
if(recv_handler)
recv_handler();
}

View File

@ -5,6 +5,7 @@
#include <gint/defs/util.h>
#include <string.h>
#include <stdio.h>
#include <gint/drivers/asyncio.h>
#include "usb_private.h"
@ -35,7 +36,7 @@ void usb_pipe_configure(int address, endpoint_t const *ep)
USB.PIPECFG.TYPE = type;
USB.PIPECFG.BFRE = 0;
/* 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.CNTMD = (type == TYPE_BULK);
USB.PIPECFG.SHTNAK = 1;
@ -129,9 +130,10 @@ static fifo_t fifo_find_available_controller(int pipe)
if(pipe == 0) return CF;
if(USB.D0FIFOSEL.CURPIPE == 0) return D0F;
USB_LOG("D0 is unavailable!\n");
// USB_LOG("D0 is unavailable!\n");
if(USB.D1FIFOSEL.CURPIPE == 0) return D1F;
USB_LOG("D1 is unavailable!\n");
// USB_LOG("D1 is unavailable!\n");
USB_LOG("No controller is unavailable!\n");
return NOF;
}
@ -159,10 +161,10 @@ static void fifo_bind(fifo_t ct, int pipe, int mode)
usb_while(!USB.D1FIFOCTR.FRDY || USB.PIPECFG.DIR != mode);
}
/* Enable USB comunication! */
if(pipe == 0 && writing)
/* Enable USB comunication when writing */
if(writing && pipe == 0)
USB.DCPCTR.PID = PID_BUF;
if(pipe != 0)
if(writing && pipe != 0)
USB.PIPECTR[pipe-1].PID = PID_BUF;
}
@ -181,7 +183,7 @@ static void fifo_unbind(fifo_t ct)
if(pipe <= 0)
return;
/* Disbable communication on the pipe */
/* Disable communication on the pipe */
USB.PIPECTR[pipe-1].PID = PID_NAK;
usb_while(USB.PIPECTR[pipe-1].PBUSY);
@ -234,16 +236,16 @@ void usb_pipe_reset_fifos(void)
Note that the "<Finish writing>" event is asynchronous if the DMA is used. A
full write will take zero or more complete rounds followed by zero or one
partial round before finish_call() is called:
partial round before finish_write_call() is called:
usb_write_async ::= complete_round* partial_round? finish_call
usb_write_async ::= complete_round* partial_round? finish_write_call
And a commit will trigger a transmission of whatever is left in the buffer
(including nothing) and wait for the BEMP interrupt.
usb_commit_async ::= <Manually commit pipe>
<BEMP interrut after transmission>
finish_call
finish_write_call
Most functions can execute either in the main thread or within an interrupt
handler. */
@ -281,36 +283,24 @@ static int pipe_bufsize(int pipe)
return (USB.PIPEBUF.BUFSIZE + 1) * 64;
}
/* This function is called when a read/write/fsync call and its associated
hardware interactions all complete. */
static void finish_call(asyncio_op_t *t, int pipe)
/* Called when a write/fsync call and its hardware interactions all complete */
static void finish_write_call(asyncio_op_t *t, int pipe)
{
/* Unbind the USB controller used for the call, except for writes since
the USB module requires us to keep it until the final commit */
if(t->type != ASYNCIO_WRITE && t->type != ASYNCIO_READ) {
/* Unbind the FIFO after a sync */
if(t->type == ASYNCIO_SYNC) {
fifo_unbind(t->controller);
t->controller = NOF;
}
/* Disable interrupts */
if((t->type == ASYNCIO_WRITE || t->type == ASYNCIO_SYNC) && pipe != 0)
if(pipe != 0)
USB.BEMPENB &= ~(1 << pipe);
asyncio_op_finish_call(t);
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;
}
if(t->type == ASYNCIO_WRITE)
asyncio_op_finish_write(t);
else if(t->type == ASYNCIO_SYNC)
asyncio_op_finish_sync(t);
USB_TRACE("finish_write_call()");
}
/* This function is called when a round of writing has completed, including all
@ -330,7 +320,7 @@ static void finish_write_round(asyncio_op_t *t, int pipe)
USB_TRACE("finish_write_round()");
if(t->size == 0)
finish_call(t, pipe);
finish_write_call(t, pipe);
}
/* write_round(): Write up to a FIFO's worth of data to a pipe
@ -463,23 +453,23 @@ int usb_commit_async(int pipe, gint_call_t callback)
usb_pipe_flush4(t->shbuf, t->shbuf_size, FIFO);
/* Switch from WRITE to SYNC type; this influences the BEMP handler and
the final finish_call() */
the final finish_write_call() */
asyncio_op_start_sync(t, &callback);
/* TODO: Figure out why previous attempts to use BEMP to finish commit
TODO| calls on the DCP failed with a freeze */
if(pipe == 0) {
USB.CFIFOCTR.BVAL = 1;
finish_call(t, pipe);
finish_write_call(t, pipe);
return 0;
}
/* Set BVAL=1 and inform the BEMP handler of the commitment with the
SYNC type; the handler will invoke finish_call() */
SYNC type; the handler will invoke finish_write_call() */
USB.BEMPENB |= (1 << pipe);
if(t->controller == D0F) USB.D0FIFOCTR.BVAL = 1;
if(t->controller == D1F) USB.D1FIFOCTR.BVAL = 1;
USB_LOG("[PIPE%d] Committed transfer\n", pipe);
// USB_LOG("[PIPE%d] Committed transfer\n", pipe);
return 0;
}
@ -522,7 +512,7 @@ void usb_pipe_write_bemp(int pipe)
if(t->type == ASYNCIO_SYNC)
{
finish_call(t, pipe);
finish_write_call(t, pipe);
}
else
{
@ -532,26 +522,108 @@ void usb_pipe_write_bemp(int pipe)
}
}
int usb_read_async(int pipe, void *data, int size, bool use_dma,
int *read_size, gint_call_t callback)
//---
// Reading operations
//---
#ifdef GINT_USB_DEBUG
static void USB_LOG_TR(char const *p, asyncio_op_t *t, char const *fmt, ...)
{
asyncio_op_t *t = &pipe_transfers[pipe];
if(asyncio_op_busy(t))
return USB_BUSY;
if(t->type == ASYNCIO_NONE)
return USB_READ_IDLE;
if(!usb_get_log())
return;
int actual_size = asyncio_op_start_read(t, data, size, use_dma,
&callback);
if(*read_size)
*read_size = actual_size;
int E = USB.INTENB0.BRDYE;
USB.INTENB0.BRDYE = 0;
USB_LOG("async read request for %d/%d bytes\n", size, t->size);
char shbuf[16];
if(t->shbuf_size >= 4)
sprintf(shbuf, "!!%d", t->shbuf_size);
else
snprintf(shbuf, t->shbuf_size * 2 + 1, "%08x", t->shbuf);
/* No data to read: finish the call immediately */
if(actual_size == 0) {
finish_call(t, pipe);
return 0;
char str[128];
snprintf(str, sizeof str - 1, "%s: %s buf=%d%s%s req=%d/%d%s |%s| ",
p, t->type == ASYNCIO_READ ? "READ" : "NONE",
t->buffer_used, t->cont_r ? "+":"", t->interrupt_r ? "!":"",
t->round_size, t->size, t->autoclose_r ? "#" : "", shbuf);
va_list args;
va_start(args, fmt);
int l = strlen(str);
vsnprintf(str + l, sizeof str - 1 - l, fmt, args);
va_end(args);
strcat(str, "\n");
USB_LOG("%s", str);
/* if(cpu_getSR().IMASK == 0) {
extern void usb_fxlink_text(char const *str, int size);
usb_fxlink_text(str, 0);
} */
USB.INTENB0.BRDYE = E;
}
#else
#define USB_LOG_TR(PREFIX, OP, FMT, ...)
#endif
static void finish_read_round(asyncio_op_t *t, int pipe)
{
USB_LOG("[PIPE%d] read %d/(r%d,b%d) bytes\n", pipe,
t->round_size, t->size, t->buffer_used);
/* This call will propagate all changes to the op, including finishing
the hardware segment and call (if appropriate). The only thing it
doesn't do is invoke the callback, so we have a chance to switch to
PID=BUF before doing it manually */
gint_call_t cb = t->callback;
int status = asyncio_op_finish_read_round(t);
USB_LOG_TR("frr", t, "finished=%c%c%c",
status & ASYNCIO_HWSEG_EXHAUSTED ? 'H' : '-',
status & ASYNCIO_REQUEST_FINISHED ? 'R' : '-',
status & ASYNCIO_TRANSACTION_EXHAUSTED ? 'T' : '-');
if(status & ASYNCIO_HWSEG_EXHAUSTED) {
#ifdef GINT_USB_DEBUG
/* Log DTLN to help identify data loss bugs */
int DTLN = -1;
if(t->controller == CF) DTLN = USB.CFIFOCTR.DTLN;
if(t->controller == D0F) DTLN = USB.D0FIFOCTR.DTLN;
if(t->controller == D1F) DTLN = USB.D1FIFOCTR.DTLN;
#endif
if(t->controller == CF) USB.CFIFOCTR.BCLR = 1;
if(t->controller == D0F) USB.D0FIFOCTR.BCLR = 1;
if(t->controller == D1F) USB.D1FIFOCTR.BCLR = 1;
USB_LOG("frr[seg]: DTLN=%d cont=%d interrupt=%d\n",
DTLN, t->cont_r, t->interrupt_r);
USB_TRACE("finish_read_round() [seg]");
if(status & ASYNCIO_TRANSACTION_EXHAUSTED) {
fifo_unbind(t->controller);
t->controller = NOF;
}
/* Re-enable communication for the next segment */
USB_LOG_TR("frr[seg]", t, "--> PID=BUF");
USB.PIPECTR[pipe-1].PID = PID_BUF;
}
if(status & ASYNCIO_REQUEST_FINISHED) {
gint_call(cb);
}
}
static bool read_round(asyncio_op_t *t, int pipe)
{
int round_size = asyncio_op_start_read_round(t);
USB_LOG_TR("rr", t, "");
/* No data to read: finish the round immediately */
if(round_size == 0 || t->data_r == NULL) {
finish_read_round(t, pipe);
return true;
}
/* Read stuff (TODO: Smart reads + DMA) */
@ -560,60 +632,155 @@ int usb_read_async(int pipe, void *data, int size, bool use_dma,
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;
int fifosize = t->buffer_used - t->shbuf_size;
usb_pipe_read4(t->data_r, round_size, FIFO, fifosize, &t->shbuf,
&t->shbuf_size);
finish_read_round(t, pipe);
return false;
}
static int handle_incoming_hwseg(asyncio_op_t *t, int pipe)
{
/* Do nothing if no interrupt is waiting or there is a hwseg */
if(!t->interrupt_r || asyncio_op_has_read_hwseg(t))
return 0;
/* PID will stay at NAK for the entire duration of the segment */
USB.PIPECTR[pipe-1].PID = PID_NAK;
if(t->controller == NOF) {
fifo_t ct = fifo_find_available_controller(pipe);
if(ct == NOF)
return USB_READ_NOFIFO;
fifo_bind(ct, pipe, FIFO_READ);
t->controller = ct;
}
t->interrupt_r = false;
int data_available = 0;
if(t->controller == CF) data_available = USB.CFIFOCTR.DTLN;
if(t->controller == D0F) data_available = USB.D0FIFOCTR.DTLN;
if(t->controller == D1F) data_available = USB.D1FIFOCTR.DTLN;
/* USB requires a zero-length or short packet to finish a transaction,
which equates to a partially-full buffer */
bool cont = (data_available == pipe_bufsize(pipe));
asyncio_op_start_read_hwseg(t, data_available, cont);
USB_LOG_TR("nseg", t, "PIPECTR=%04x", USB.PIPECTR[pipe-1].word);
/* Continue an ongoing transfer */
if(asyncio_op_busy(t)) {
USB_LOG("PIPE %d new hwseg while busy -> new round\n", pipe);
read_round(t, pipe);
}
finish_call(t, pipe);
return 0;
}
int usb_read_sync_timeout(int pipe, void *data, int size, bool use_dma,
timeout_t const *timeout)
/* Performs a single asynchronous read. This function implements support for
the USE_DMA, IGNORE_ZEROS, and AUTOCLOSE flags. Returns a non-blocking error
code and invokes *callback after finite time. */
static int read_once(int pipe, void *data, int size, int flags, int *rc_ptr,
gint_call_t const *cb)
{
asyncio_op_t *t = &pipe_transfers[pipe];
int rc;
if((rc = handle_incoming_hwseg(t, pipe)))
return rc;
if(asyncio_op_busy(t))
return USB_BUSY;
if(t->type == ASYNCIO_NONE)
return USB_READ_IDLE;
/* Handle 0-byte reads immediately because size == 0 is a special case
for the asyncio structure (meaning no read request) */
if(!size) {
if(rc_ptr)
*rc_ptr = 0;
gint_call(*cb);
return 0;
}
bool USE_DMA = (flags & USB_READ_USE_DMA) != 0;
bool AUTOCLOSE = (flags & USB_READ_AUTOCLOSE) != 0;
bool IGNORE_ZEROS = (flags & USB_READ_IGNORE_ZEROS) != 0;
asyncio_op_start_read(t, data, size, USE_DMA, rc_ptr, AUTOCLOSE, cb);
/* Start the first round; others will follow from BRDY interrupts. When
dealing with a 0-byte read due to an exhausted transaction, this
function will finish the round immediately and return true. The user
callback will not be invoked since size > 0, so we can let the round
ru and emit USB_ERROR_ZERO_LENGTH afterwards. */
bool zero_length = read_round(t, pipe);
return (zero_length && IGNORE_ZEROS) ? USB_ERROR_ZERO_LENGTH : 0;
}
/* Implements a generic sync or async read. This function implements support
for the WAIT and BLOCK flags, and the timeout. */
int read_core(int pipe, void *data, int size, int flags, int *rc_ptr,
timeout_t const *timeout, gint_call_t cb)
{
int volatile flag = 0;
int read_size = 0;
int async_rc = 0;
if(flags & USB_READ_WAIT) {
cb = GINT_CALL_SET(&flag);
rc_ptr = &async_rc;
}
/* 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)
/* Perform only a single read, unless USB_READ_BLOCK is set */
while(1) {
int rc = read_once(pipe, data, size, flags, rc_ptr, &cb);
/* On blocked timing errors, try again later */
if((rc == USB_BUSY || rc == USB_READ_IDLE)
&& (flags & USB_READ_BLOCK)) {
if(timeout_elapsed(timeout)) {
usb_read_cancel(pipe);
return USB_TIMEOUT;
}
sleep();
continue;
}
/* On ignored zero-length reads, try again immediately */
else if(rc == USB_ERROR_ZERO_LENGTH)
continue;
/* Other errors are fatal */
else if(rc < 0 || !(flags & USB_READ_WAIT))
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();
/* Wait until the read completes */
while(!flag) {
if(timeout_elapsed(timeout)) {
usb_read_cancel(pipe);
return USB_TIMEOUT;
}
sleep();
}
return async_rc;
}
}
/* 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);
}
int usb_read_async(int pipe, void *data, int size, int flags, int *rc_ptr,
timeout_t const *timeout, gint_call_t cb)
{
return read_core(pipe, data, size, flags, rc_ptr, timeout, cb);
}
return read_size;
int usb_read_sync_timeout(int pipe, void *data, int size, bool dma,
timeout_t const *tm)
{
int flags = (dma ? USB_READ_USE_DMA : 0)
| USB_READ_IGNORE_ZEROS
| USB_READ_AUTOCLOSE
| USB_READ_WAIT
| USB_READ_BLOCK;
return read_core(pipe, data, size, flags, NULL, tm, GINT_CALL_NULL);
}
int usb_read_sync(int pipe, void *data, int size, bool use_dma)
@ -621,38 +788,41 @@ 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)
void usb_read_cancel(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)
if(pipe < 0 || pipe > 9)
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;
asyncio_op_cancel_read(&pipe_transfers[pipe]);
// TODO: usb_read_cancel: Also cancel DMA if it's running!
}
void usb_pipe_read_brdy(int pipe)
{
USB_LOG("[PIPE%d] BRDY with PIPECTR %04x\n", pipe,
USB.PIPECTR[pipe-1].word);
asyncio_op_t *t = &pipe_transfers[pipe];
/* If a transfer is ongoing and stalled waiting for a new segment,
perform the round right now. This is acceptable because in that case
we are guarantees that a FIFO is bound is the round will succeed. */
if(asyncio_op_has_read_call(t) && !asyncio_op_has_read_hwseg(t)) {
t->interrupt_r = true;
handle_incoming_hwseg(t, pipe);
return;
}
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);
/* Signal the arrival to the main thread but don't do anything yet.
This is both for proper error handling and because changing transfer
data asynchronously would require disabling the interrupt in other
sections of the driver, which seems to cause data loss. */
t->interrupt_r = true;
/* 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);
if(ep->intf->notify_read)
ep->intf->notify_read(ep->dc->bEndpointAddress);
USB_LOG_TR("int", t, "");
}

158
src/usb/read4.S Normal file
View File

@ -0,0 +1,158 @@
.global _usb_pipe_read4
/* User buffer and round size */
#define _data r4
#define _datasize r5
/* FIFO address and amount of data available in there */
#define _fifo r6
#define _fifosize r7
/* Short buffer address and *pointer to* its size */
#define _buf r8
#define _bufsize r9
/* Copy _datasize bytes from _fifo to _data, using USB FIFO access rules and
storing excess data in the short buffer.
Requires: 1 _datasize _fifosize + *_bufsize */
_usb_pipe_read4:
mov.l @(4, r15), r1
mov.l r8, @-r15
mov.l r9, @-r15
mov r1, _bufsize
mov.b @_bufsize, r1
/* Step #1: If _datasize *_bufsize < 4, then we fill user data from the short
buffer, and return immediately. */
/* 1 cycle lost here due to early use of r1 */
mov.l @(8, r15), _buf
cmp/ge _datasize, r1
bt .short_buffer_only
tst r1, r1
/* Step #2: If *_bufsize > 0, copy *_bufsize bytes from the short buffer to
_data, emptying the short buffer. */
bt 2f
mov _buf, r3
1: mov.b @r3+, r2
dt _datasize
dt r1
mov.b r2, @_data
bf.s 1b
add #1, _data
mov.b r1, @_bufsize
nop
/* Step #3: Copy longwords from the FIFO to the user buffer as long as there is
at least 4 bytes in both. Since at this stage _datasize _fifosize we can
simply check _datasize. */
2: /* Update _datasize and _fifosize in advance */
mov #-4, r0
and _datasize, r0
/* If _datasize ≤ 3, skip this step */
mov #3, r3
cmp/ge _datasize, r3
bt.s 3f
sub r0, _fifosize
tst r3, _data
nop
/* r2 is the number of 4-byte reads; since _datasize > 3, r2 ≥ 1 */
mov _datasize, r2
shlr2 r2
bf.s .unaligned
and r3, _datasize
.aligned:
ldrs .al_b
ldre .al_e
ldrc r2
mov _fifo, r3
.al_b: movs.l @r3, x0
.al_e: movs.l x0, @_data+
bra 3f
nop
.unaligned:
mov.l @_fifo, r0
dt r2
mov.b r0, @(3, _data)
nop
shlr8 r0
mov.b r0, @(2, _data)
shlr8 r0
mov.b r0, @(1, _data)
shlr8 r0
mov.b r0, @_data
bf.s .unaligned
add #4, _data
/* Step #4: Load the final bytes of the round from the FIFO into the short
buffer (can be either nothing or a standard 4-byte read), then finish with a
copy to user data. */
3: /* If we finished the read, don't load anything */
tst _datasize, _datasize
mov #3, r3
bt .epilogue
cmp/hi r3, _fifosize
bf 4f
mov #4, _fifosize
/* Make a 4-byte read from the FIFO. If there are less than 4 bytes
left this will pad with zeros. */
4: mov.l @_fifo, r3
mov _fifosize, r1
mov.l r3, @_buf
nop
/* Step #1/#5: Copy 0 < _datasize *_bufsize bytes from the short buffer to
_data, then return. r1 must be *_bufsize. */
.short_buffer_only:
mov.l @_buf, r3
mov #0, r0
sub _datasize, r1
nop
/* The loop accesses memory with @(r0, _) but also shifts the contents
of _buf in r3 (it's more convenient to use the available EX slot
than shift by 8*_datasize outside the loop) */
5: mov.b @(r0, _buf), r2
dt _datasize
mov.b r2, @(r0, _data)
add #1, r0
bf.s 5b
shll8 r3
.epilogue:
mov.l r3, @_buf
mov.b r1, @_bufsize
mov.l @r15+, r9
rts
mov.l @r15+, r8

View File

@ -40,6 +40,11 @@ void usb_set_log(void (*logger)(char const *format, va_list args))
usb_logger = logger;
}
void (*usb_get_log(void))(char const *format, va_list args)
{
return usb_logger;
}
void usb_log(char const *format, ...)
{
if(!usb_logger) return;
@ -54,6 +59,11 @@ void usb_set_trace(void (*tracer)(char const *message))
usb_tracer = tracer;
}
void (*usb_get_trace(void))(char const *message)
{
return usb_tracer;
}
void usb_trace(char const *message)
{
if(usb_tracer) {

View File

@ -175,6 +175,23 @@ void usb_pipe_write4(void const *data, int size, uint32_t volatile *buffer,
void usb_pipe_flush4(uint32_t buffer, int buffer_size,
uint32_t volatile *FIFO);
/* usb_pipe_read4(): Copy arbitrary ranges of memory from a 4-byte USB FIFO
This function performs arbitrarily-aligned reads of any size from a 4-byte
USB FIFO register to regular memory. It performs only 4-byte reads on the
FIFO (except when reading the last few bytes in the buffer) and copies read
data to the supplied buffer. Any excess bytes read from the FIFO are stored
in a short buffer to be used on the next call.
@data Buffer to read into
@data_size Read size (1 data_size fifo_size + *buffer_size)
@fifo USB FIFO register to read from
@fifo_size Amount of data left in the FIFO (excluding short buffer!)
@buffer Address of short buffer
@buffer_size Address of short buffer's size tracker */
void usb_pipe_read4(void *data, int size, uint32_t volatile *FIFO,
int fifo_size, uint32_t volatile *buffer, uint8_t volatile *buffer_size);
//---
// Timeout waits
//---