Merge remote-tracking branch 'upstream/dev' into dev
This commit is contained in:
commit
dcf34833c5
|
@ -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
1
TODO
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
//---
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
//---
|
||||
|
|
|
@ -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
|
||||
//---
|
||||
|
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 */
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
412
src/usb/pipes.c
412
src/usb/pipes.c
|
@ -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, "");
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
//---
|
||||
|
|
Loading…
Reference in New Issue