mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-11-09 13:12:37 -05:00
3ds: 3ds port sources. Second set of two.
This commit adds new files written exclusively for the 3ds port. Additional comments: 1. Plugins works, but will be enabled in future commits. 2. The port has only been tested on the New 3DS. 3. Not all features of rockbox have been tested so there may be bugs or non-functional features. 4. There is a known issue where a random crash can occur when exiting the app. Change-Id: I122d0bea9aa604e04fca45ba8287cf79e6110769
This commit is contained in:
parent
a4de1195cd
commit
3b7dafb117
51 changed files with 7322 additions and 0 deletions
99
firmware/target/hosted/ctru/lib/bfile/bfile-internal.h
Normal file
99
firmware/target/hosted/ctru/lib/bfile/bfile-internal.h
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#ifndef _BFILE_INTERNAL_H_
|
||||
#define _BFILE_INTERNAL_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/stat.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <3ds/gfx.h>
|
||||
#include <3ds/svc.h>
|
||||
#include <3ds/types.h>
|
||||
#include <3ds/thread.h>
|
||||
#include <3ds/result.h>
|
||||
#include <3ds/services/fs.h>
|
||||
#include <3ds/synchronization.h>
|
||||
|
||||
/* #define MALLOC_DEBUG
|
||||
#include "rmalloc/rmalloc.h" */
|
||||
|
||||
#include "cslice.h"
|
||||
#include "cmap.h"
|
||||
|
||||
#define nil NULL
|
||||
|
||||
/* in go functions can return two values */
|
||||
#define two_type_value(type1, type2, name1, name2, type_name) \
|
||||
typedef struct { \
|
||||
type1 name1; \
|
||||
type2 name2; \
|
||||
} type_name##_t;
|
||||
|
||||
/* single mutex implementation */
|
||||
#define sync_Mutex LightLock
|
||||
static inline void sync_MutexInit(sync_Mutex* m) {
|
||||
LightLock_Init(m);
|
||||
}
|
||||
|
||||
/* mutex unlock */
|
||||
static inline void sync_MutexLock(sync_Mutex* m) {
|
||||
LightLock_Lock(m);
|
||||
}
|
||||
|
||||
/* mutex lock */
|
||||
static inline void sync_MutexUnlock(sync_Mutex* m) {
|
||||
LightLock_Unlock(m);
|
||||
}
|
||||
|
||||
/* read_write mutex implementation */
|
||||
typedef struct {
|
||||
sync_Mutex shared;
|
||||
CondVar reader_q;
|
||||
CondVar writer_q;
|
||||
int active_readers;
|
||||
int active_writers;
|
||||
int waiting_writers;
|
||||
} sync_RWMutex;
|
||||
|
||||
void sync_RWMutexInit(sync_RWMutex *m);
|
||||
void sync_RWMutexRLock(sync_RWMutex *m);
|
||||
void sync_RWMutexRUnlock(sync_RWMutex *m);
|
||||
void sync_RWMutexLock(sync_RWMutex *m);
|
||||
void sync_RWMutexUnlock(sync_RWMutex *m);
|
||||
|
||||
/* declare a two type value with name 'n_err_t' */
|
||||
two_type_value(int, const char*, n, err, int_error);
|
||||
two_type_value(struct stat, const char*, fi, err, stat_error);
|
||||
typedef const char* file_error_t;
|
||||
|
||||
typedef struct page {
|
||||
s64 num;
|
||||
struct page* prev;
|
||||
struct page* next;
|
||||
u8* data;
|
||||
} page;
|
||||
|
||||
/* the two map types used by this library */
|
||||
cmap_declare(page, s64, struct page*);
|
||||
cmap_declare(bool, s64, bool);
|
||||
|
||||
typedef struct shard {
|
||||
sync_Mutex mu;
|
||||
cmap(page) pages;
|
||||
cmap(bool) dirty;
|
||||
struct page* head;
|
||||
struct page* tail;
|
||||
} shard;
|
||||
|
||||
typedef struct Pager {
|
||||
Handle file;
|
||||
s64 pgsize;
|
||||
s64 pgmax;
|
||||
/* sync_RWMutex mu; */
|
||||
s64 size;
|
||||
cslice(shard) shards;
|
||||
} Pager;
|
||||
|
||||
#endif /* _BFILE_INTERNAL_H_ */
|
||||
471
firmware/target/hosted/ctru/lib/bfile/bfile.c
Normal file
471
firmware/target/hosted/ctru/lib/bfile/bfile.c
Normal file
|
|
@ -0,0 +1,471 @@
|
|||
/*
|
||||
* This code is based on bfile.go by Josh Baker.
|
||||
* Converted to C code by Mauricio G.
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/* IMPORTANT: this code only works for O_RDONLY and O_RDWR files. */
|
||||
|
||||
#include "bfile.h"
|
||||
|
||||
/* note: for ease of reading and ease of comparing go code
|
||||
with c implementation, function names are similar to the
|
||||
go version */
|
||||
|
||||
/* note2: sync_RWMutex calls have been moved to rockbox sys_file
|
||||
implementation. To use as standalone code please uncomment those calls. */
|
||||
|
||||
void sync_RWMutexInit(sync_RWMutex *m) {
|
||||
LightLock_Init(&m->shared);
|
||||
CondVar_Init(&m->reader_q);
|
||||
CondVar_Init(&m->writer_q);
|
||||
m->active_readers = 0;
|
||||
m->active_writers = 0;
|
||||
m->waiting_writers = 0;
|
||||
}
|
||||
|
||||
static inline LightLock *unique_lock(LightLock *lk) {
|
||||
LightLock_Lock(lk);
|
||||
return lk;
|
||||
}
|
||||
|
||||
void sync_RWMutexRLock(sync_RWMutex *m) {
|
||||
LightLock *lk = unique_lock(&m->shared);
|
||||
while (m->waiting_writers != 0) {
|
||||
CondVar_Wait(&m->reader_q, lk);
|
||||
}
|
||||
++m->active_readers;
|
||||
LightLock_Unlock(lk);
|
||||
}
|
||||
|
||||
void sync_RWMutexRUnlock(sync_RWMutex *m) {
|
||||
LightLock *lk = unique_lock(&m->shared);
|
||||
--m->active_readers;
|
||||
LightLock_Unlock(lk);
|
||||
CondVar_Signal(&m->writer_q);
|
||||
}
|
||||
|
||||
void sync_RWMutexLock(sync_RWMutex *m) {
|
||||
LightLock *lk = unique_lock(&m->shared);
|
||||
++m->waiting_writers;
|
||||
while ((m->active_readers != 0) || (m->active_writers != 0)) {
|
||||
CondVar_Wait(&m->writer_q, lk);
|
||||
}
|
||||
++m->active_writers;
|
||||
LightLock_Unlock(lk);
|
||||
}
|
||||
|
||||
void sync_RWMutexUnlock(sync_RWMutex *m) {
|
||||
LightLock *lk = unique_lock(&m->shared);
|
||||
--m->waiting_writers;
|
||||
--m->active_writers;
|
||||
if (m->waiting_writers > 0) {
|
||||
CondVar_Signal(&m->writer_q);
|
||||
} else {
|
||||
CondVar_Broadcast(&m->reader_q);
|
||||
}
|
||||
LightLock_Unlock(lk);
|
||||
}
|
||||
|
||||
void s_init(shard* s);
|
||||
|
||||
void s_push(shard* s, page* p) {
|
||||
s->head->next->prev = p;
|
||||
p->next = s->head->next;
|
||||
p->prev = s->head;
|
||||
s->head->next = p;
|
||||
}
|
||||
|
||||
void s_pop(shard* s, page* p) {
|
||||
p->prev->next = p->next;
|
||||
p->next->prev = p->prev;
|
||||
}
|
||||
|
||||
void s_bump(shard* s, page* p) {
|
||||
s_pop(s, p);
|
||||
s_push(s, p);
|
||||
}
|
||||
|
||||
/* page_pair_t destructor */
|
||||
/* page_pair_t type is defined by cmap_declare(page, s64, struct page*) */
|
||||
/* struct {
|
||||
s64 key;
|
||||
struct page* value;
|
||||
} page_pair_t;
|
||||
*/
|
||||
void page_pair_free(void* pair_ptr) {
|
||||
if (pair_ptr) {
|
||||
page_pair_t *pair = (page_pair_t*) pair_ptr;
|
||||
struct page *p = pair->value;
|
||||
if (p != nil) {
|
||||
if (p->data != nil) {
|
||||
free(p->data);
|
||||
}
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* shard destructor */
|
||||
void s_free(void* s_ptr) {
|
||||
if (s_ptr) {
|
||||
shard *s = (shard*) s_ptr;
|
||||
if (s->pages != nil) {
|
||||
cmap_set_elem_destructor(s->pages, page_pair_free);
|
||||
cmap_clear(page, s->pages);
|
||||
cmap_clear(bool, s->dirty);
|
||||
free(s->head);
|
||||
free(s->tail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Pager* NewPager(Handle file) {
|
||||
return NewPagerSize(file, 0, 0);
|
||||
}
|
||||
|
||||
Pager* NewPagerSize(Handle file, int pageSize, int bufferSize) {
|
||||
if (pageSize <= 0) {
|
||||
pageSize = defaultPageSize;
|
||||
} else if ((pageSize & 4095) != 0) {
|
||||
// must be a power of two
|
||||
int x = 1;
|
||||
while (x < pageSize) {
|
||||
x *= 2;
|
||||
}
|
||||
pageSize = x;
|
||||
}
|
||||
|
||||
if (bufferSize <= 0) {
|
||||
bufferSize = defaultBufferSize;
|
||||
} else if (bufferSize < pageSize) {
|
||||
bufferSize = pageSize;
|
||||
}
|
||||
|
||||
Pager* f = (Pager*) malloc(sizeof(Pager));
|
||||
f->file = file;
|
||||
f->size = -1;
|
||||
f->pgsize = (s64) pageSize;
|
||||
|
||||
/* sync_RWMutexInit(&f->mu); */
|
||||
|
||||
// calculate the max number of pages across all shards
|
||||
s64 pgmax = (s64) bufferSize / f->pgsize;
|
||||
if (pgmax < minPages) {
|
||||
pgmax = minPages;
|
||||
}
|
||||
|
||||
// calculate how many shards are needed, power of 2
|
||||
s64 nshards = (s64) ceil((double) pgmax / (double) pagesPerShard);
|
||||
if (nshards > maxShards) {
|
||||
nshards = maxShards;
|
||||
}
|
||||
s64 x = 1;
|
||||
while (x < nshards) {
|
||||
x *= 2;
|
||||
}
|
||||
nshards = x;
|
||||
|
||||
// calculate the max number of pages per shard
|
||||
f->pgmax = (s64) floor((double) pgmax / (double) nshards);
|
||||
cslice_make(f->shards, nshards, (shard) { 0 });
|
||||
|
||||
// initialize sync mutex
|
||||
size_t i;
|
||||
for (i = 0; i < cslice_len(f->shards); i++) {
|
||||
sync_MutexInit(&f->shards[i].mu);
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
static int_error_t read_at(Handle file, u8 *data, size_t data_len, off_t off)
|
||||
{
|
||||
u32 read_bytes = 0;
|
||||
if (R_FAILED(FSFILE_Read(file, &read_bytes, (u64) off, data, (u32) data_len))) {
|
||||
return (int_error_t) { -1, "I/O error" };
|
||||
}
|
||||
|
||||
/* io.EOF */
|
||||
if (read_bytes == 0) {
|
||||
return (int_error_t) { 0, "io.EOF" };
|
||||
}
|
||||
|
||||
return (int_error_t) { (int) read_bytes, nil };
|
||||
}
|
||||
|
||||
static int_error_t write_at(Handle file, u8 *data, size_t data_len, off_t off)
|
||||
{
|
||||
u32 written = 0;
|
||||
if (R_FAILED(FSFILE_Write(file, &written, (u64) off, data, (u32) data_len, FS_WRITE_FLUSH))) {
|
||||
return (int_error_t) { -1, "I/O error" };
|
||||
}
|
||||
|
||||
/* I/O error */
|
||||
if ((written == 0) || (written < (u32) data_len)) {
|
||||
return (int_error_t) { -1, "I/O error" };
|
||||
}
|
||||
|
||||
return (int_error_t) { (int) written, nil };
|
||||
}
|
||||
|
||||
static stat_error_t file_stat(Handle file)
|
||||
{
|
||||
u64 size = 0;
|
||||
struct stat fi = { 0 };
|
||||
if (R_FAILED(FSFILE_GetSize(file, &size))) {
|
||||
fi.st_size = 0;
|
||||
return (stat_error_t) { fi, "I/O error" };
|
||||
}
|
||||
|
||||
fi.st_size = (off_t) size;
|
||||
return (stat_error_t) { fi, nil };
|
||||
}
|
||||
|
||||
void s_init(shard* s)
|
||||
{
|
||||
if (s->pages == nil) {
|
||||
s->pages = cmap_make(/*s64*/page);
|
||||
s->dirty = cmap_make(/*s64*/bool);
|
||||
s->head = (page*) malloc(sizeof(page));
|
||||
s->tail = (page*) malloc(sizeof(page));
|
||||
s->head->next = s->tail;
|
||||
s->tail->prev = s->head;
|
||||
}
|
||||
}
|
||||
|
||||
file_error_t f_write(Pager* f, page* p) {
|
||||
s64 off = p->num * f->pgsize;
|
||||
s64 end = f->pgsize;
|
||||
if ((off + end) > f->size) {
|
||||
end = f->size - off;
|
||||
}
|
||||
int_error_t __err = write_at(f->file, p->data, end, off);
|
||||
if (__err.err != nil) {
|
||||
return __err.err;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
file_error_t f_read(Pager* f, page* p) {
|
||||
int_error_t __err = read_at(f->file, p->data, f->pgsize, p->num * f->pgsize);
|
||||
if ((__err.err != nil) && strcmp(__err.err, "io.EOF")) {
|
||||
return "I/O error";
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
const char* f_incrSize(Pager* f, s64 end, bool write)
|
||||
{
|
||||
#define defer(m) \
|
||||
sync_RWMutexUnlock(&f->mu); \
|
||||
sync_RWMutexRLock(&f->mu);
|
||||
|
||||
/* sync_RWMutexRUnlock(&f->mu);
|
||||
sync_RWMutexLock(&f->mu); */
|
||||
|
||||
if (f->size == -1) {
|
||||
stat_error_t fi_err = file_stat(f->file);
|
||||
if (fi_err.err != nil) {
|
||||
/* defer(&f->mu); */
|
||||
return nil;
|
||||
}
|
||||
f->size = fi_err.fi.st_size;
|
||||
}
|
||||
if (write && (end > f->size)) {
|
||||
f->size = end;
|
||||
}
|
||||
|
||||
/* defer(&f->mu); */
|
||||
return nil;
|
||||
}
|
||||
|
||||
int_error_t f_pio(Pager *f, u8 *b, size_t len_b, s64 pnum, s64 pstart, s64 pend, bool write);
|
||||
int_error_t f_io(Pager *f, u8 *b, size_t len_b, s64 off, bool write) {
|
||||
if (f == nil) {
|
||||
return (int_error_t) { 0, "invalid argument" };
|
||||
}
|
||||
bool eof = false;
|
||||
s64 start = off, end = off + len_b;
|
||||
if (start < 0) {
|
||||
return (int_error_t) { 0, "negative offset" };
|
||||
}
|
||||
|
||||
// Check the upper bounds of the input to the known file size.
|
||||
// Increase the file size if needed.
|
||||
/* sync_RWMutexRLock(&f->mu); */
|
||||
if (end > f->size) {
|
||||
file_error_t err = f_incrSize(f, end, write);
|
||||
if (err != nil) {
|
||||
/* sync_RWMutexRUnlock(&f->mu); */
|
||||
return (int_error_t) { 0, err };
|
||||
}
|
||||
if (!write && (end > f->size)) {
|
||||
end = f->size;
|
||||
if ((end - start) < 0) {
|
||||
end = start;
|
||||
}
|
||||
eof = true;
|
||||
len_b = end-start; /* b = b[:end-start] */
|
||||
}
|
||||
}
|
||||
/* sync_RWMutexRUnlock(&f->mu); */
|
||||
|
||||
// Perform the page I/O.
|
||||
int total = 0;
|
||||
while (len_b > 0) {
|
||||
s64 pnum = off / f->pgsize;
|
||||
s64 pstart = off & (f->pgsize - 1);
|
||||
s64 pend = pstart + (s64) len_b;
|
||||
if (pend > f->pgsize) {
|
||||
pend = f->pgsize;
|
||||
}
|
||||
|
||||
int_error_t result = f_pio(f, b, pend - pstart, pnum, pstart, pend, write);
|
||||
if (result.err != nil) {
|
||||
return (int_error_t) { total, result.err };
|
||||
}
|
||||
|
||||
off += (s64) result.n;
|
||||
total += result.n;
|
||||
b = &b[result.n]; len_b -= result.n; /* b = b[n:] */
|
||||
}
|
||||
if (eof) {
|
||||
return (int_error_t) { total, "io.EOF" };
|
||||
}
|
||||
|
||||
return (int_error_t) { total, nil };
|
||||
}
|
||||
|
||||
int_error_t f_pio(Pager *f, u8 *b, size_t len_b, s64 pnum, s64 pstart, s64 pend, bool write) {
|
||||
/* printf("pio(%p, %d, %lld, %lld, %lld, %s)\n", b, len_b, pnum, pstart, pend, write == true ? "true" : "false"); */
|
||||
shard *s = &f->shards[pnum & (s64) (cslice_len(f->shards) - 1)];
|
||||
sync_MutexLock(&s->mu);
|
||||
s_init(s);
|
||||
page *p = cmap_get_ptr(page, s->pages, pnum);
|
||||
if (p == nil) {
|
||||
// Page does not exist in memory.
|
||||
// Acquire a new one.
|
||||
if (cmap_len(s->pages) == f->pgmax) {
|
||||
// The buffer is at capacity.
|
||||
// Evict lru page and hang on to it.
|
||||
p = s->tail->prev;
|
||||
s_pop(s, p);
|
||||
cmap_delete(page, s->pages, p->num);
|
||||
if (cmap_get(bool, s->dirty, p->num)) {
|
||||
// dirty page. flush it now
|
||||
file_error_t err = f_write(f, p);
|
||||
if (err != nil) {
|
||||
sync_MutexUnlock(&s->mu);
|
||||
return (int_error_t) { 0, err };
|
||||
}
|
||||
cmap_delete(bool, s->dirty, p->num);
|
||||
}
|
||||
// Clear the previous page memory for partial page writes for
|
||||
// pages that are being partially written to.
|
||||
if (write && ((pend - pstart) < f->pgsize)) {
|
||||
memset(p->data, 0, f->pgsize);
|
||||
}
|
||||
} else {
|
||||
// Allocate an entirely new page.
|
||||
p = (page *) malloc(sizeof(page));
|
||||
p->data = (u8 *) malloc(f->pgsize);
|
||||
}
|
||||
p->num = pnum;
|
||||
// Read contents of page from file for all read operations, and
|
||||
// partial write operations. Ignore for full page writes.
|
||||
if (!write || ((pend-pstart) < f->pgsize)) {
|
||||
file_error_t err = f_read(f, p);
|
||||
if (err != nil) {
|
||||
sync_MutexUnlock(&s->mu);
|
||||
return (int_error_t) { 0, err };
|
||||
}
|
||||
}
|
||||
// Add the newly acquired page to the list.
|
||||
cmap_set(page, s->pages, p->num, p);
|
||||
s_push(s, p);
|
||||
} else {
|
||||
// Bump the page to the front of the list.
|
||||
s_bump(s, p);
|
||||
}
|
||||
if (write) {
|
||||
memcpy(p->data + pstart, b, pend - pstart);
|
||||
cmap_set(bool, s->dirty, pnum, true);
|
||||
} else {
|
||||
memcpy(b, p->data + pstart, pend - pstart);
|
||||
}
|
||||
sync_MutexUnlock(&s->mu);
|
||||
return (int_error_t) { len_b, nil };
|
||||
}
|
||||
|
||||
// Flush writes any unwritten buffered data to the underlying file.
|
||||
file_error_t PagerFlush(Pager *f) {
|
||||
if (f == nil) {
|
||||
return "invalid argument";
|
||||
}
|
||||
|
||||
/* sync_RWMutexLock(&f->mu); */
|
||||
for (size_t i = 0; i < cslice_len(f->shards); i++) {
|
||||
cmap_iterator(bool) pnum;
|
||||
if (f->shards[i].dirty != nil) {
|
||||
for (pnum = cmap_begin(f->shards[i].dirty);
|
||||
pnum != cmap_end(f->shards[i].dirty); pnum++) {
|
||||
if (pnum->value == true) {
|
||||
page *p = cmap_get_ptr(page, f->shards[i].pages, pnum->key);
|
||||
if (p != nil) {
|
||||
file_error_t err = f_write(f, p);
|
||||
if (err != nil) {
|
||||
/* sync_RWMutexUnlock(&f->mu); */
|
||||
return err;
|
||||
}
|
||||
}
|
||||
cmap_set(bool, f->shards[i].dirty, pnum->key, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* sync_RWMutexUnlock(&f->mu); */
|
||||
return nil;
|
||||
}
|
||||
|
||||
// ReadAt reads len(b) bytes from the File starting at byte offset off.
|
||||
int_error_t PagerReadAt(Pager *f, u8 *b, size_t len_b, off_t off) {
|
||||
return f_io(f, b, len_b, off, false);
|
||||
}
|
||||
|
||||
// WriteAt writes len(b) bytes to the File starting at byte offset off.
|
||||
int_error_t PagerWriteAt(Pager *f, u8 *b, size_t len_b, off_t off) {
|
||||
return f_io(f, b, len_b, off, true);
|
||||
}
|
||||
|
||||
file_error_t PagerTruncate(Pager *f, off_t length) {
|
||||
if (f == nil) {
|
||||
return "invalid argument";
|
||||
}
|
||||
|
||||
/* flush unwritten changes to disk */
|
||||
PagerFlush(f);
|
||||
|
||||
/* sync_RWMutexRLock(&f->mu); */
|
||||
/* set new file size */
|
||||
Handle handle = f->file;
|
||||
Result res = FSFILE_SetSize(handle, (u64) length);
|
||||
if (R_FAILED(res)) {
|
||||
return "I/O error";
|
||||
}
|
||||
/* sync_RWMutexRUnlock(&f->mu); */
|
||||
|
||||
/* FIXME: truncate only required pages. Remove all for now */
|
||||
PagerClear(f);
|
||||
f = NewPager(handle);
|
||||
return nil;
|
||||
}
|
||||
|
||||
void PagerClear(Pager *f) {
|
||||
if (f) {
|
||||
cslice_set_elem_destructor(f->shards, s_free);
|
||||
cslice_clear(f->shards);
|
||||
free(f);
|
||||
}
|
||||
}
|
||||
|
||||
37
firmware/target/hosted/ctru/lib/bfile/bfile.h
Normal file
37
firmware/target/hosted/ctru/lib/bfile/bfile.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef _BFILE_H_
|
||||
#define _BFILE_H_
|
||||
|
||||
#include "bfile-internal.h"
|
||||
|
||||
static const int defaultPageSize = 4096; // all pages are this size
|
||||
static const int defaultBufferSize = 0x800000; // default buffer size, 8 MB
|
||||
static const int minPages = 4; // minimum total pages per file
|
||||
static const int pagesPerShard = 32; // ideal number of pages per shard
|
||||
static const int maxShards = 128; // maximum number of shards per file
|
||||
|
||||
// NewPager returns a new Pager that is backed by the provided file.
|
||||
Pager* NewPager(Handle file);
|
||||
|
||||
// NewPagerSize returns a new Pager with a custom page size and buffer size.
|
||||
// The bufferSize is the maximum amount of memory dedicated to individual
|
||||
// pages. Setting pageSize and bufferSize to zero will use their defaults,
|
||||
// which are 4096 and 8 MB respectively. Custom values are rounded up to the
|
||||
// nearest power of 2.
|
||||
Pager* NewPagerSize(Handle file, int pageSize, int bufferSize);
|
||||
|
||||
// ReadAt reads len(b) bytes from the File starting at byte offset off.
|
||||
int_error_t PagerReadAt(Pager *f, u8 *b, size_t len_b, off_t off);
|
||||
|
||||
// WriteAt writes len(b) bytes to the File starting at byte offset off.
|
||||
int_error_t PagerWriteAt(Pager *f, u8 *b, size_t len_b, off_t off);
|
||||
|
||||
// Flush writes any unwritten buffered data to the underlying file.
|
||||
file_error_t PagerFlush(Pager *f);
|
||||
|
||||
// Truncates pager to specified length
|
||||
file_error_t PagerTruncate(Pager *f, off_t length);
|
||||
|
||||
// Free all memory associated to a Pager file
|
||||
void PagerClear(Pager *f);
|
||||
|
||||
#endif /* _B_FILE_H_ */
|
||||
209
firmware/target/hosted/ctru/lib/cmap.h
Normal file
209
firmware/target/hosted/ctru/lib/cmap.h
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
#ifndef CMAP_H_
|
||||
#define CMAP_H_
|
||||
|
||||
#define CVECTOR_LINEAR_GROWTH
|
||||
#include "cvector.h"
|
||||
|
||||
/* note: for ease of porting go code to c, many functions (macros) names
|
||||
remain similar to the ones used by go */
|
||||
|
||||
/* note2: this is a very basic map implementation. It does not do any sorting, and only works for basic types (and pointers) that can be compared with the equality operator */
|
||||
|
||||
#define nil NULL
|
||||
|
||||
#define cmap_elem_destructor_t cvector_elem_destructor_t
|
||||
|
||||
/**
|
||||
* @brief cmap_declare - The map type used in this library
|
||||
* @param name - The name associated to a map type.
|
||||
* @param key_type - The map pair key type.
|
||||
* @param val_type - The map pair value type.
|
||||
* @param compare_func - The function used to compare for key_type. Should return value < 0 when a < b, 0 when a == b and value > 0 when a > b.
|
||||
*/
|
||||
#define cmap_declare(name, key_type, val_type) \
|
||||
typedef struct { \
|
||||
key_type key; \
|
||||
val_type value; \
|
||||
} name##_pair_t; \
|
||||
\
|
||||
typedef struct { \
|
||||
cvector(name##_pair_t) tree; \
|
||||
cmap_elem_destructor_t elem_destructor; \
|
||||
} name##_map_t; \
|
||||
\
|
||||
static inline val_type name##_get_( \
|
||||
name##_map_t *this, const key_type key) \
|
||||
{ \
|
||||
if (this) { \
|
||||
size_t i; \
|
||||
for (i = 0; i < cvector_size(this->tree); i++) { \
|
||||
if (key == this->tree[i].key) { \
|
||||
return this->tree[i].value; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
static inline val_type name##_get_ptr_( \
|
||||
name##_map_t *this, const key_type key) \
|
||||
{ \
|
||||
if (this) { \
|
||||
size_t i; \
|
||||
for (i = 0; i < cvector_size(this->tree); i++) { \
|
||||
if (key == this->tree[i].key) { \
|
||||
return this->tree[i].value; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
return nil; \
|
||||
} \
|
||||
\
|
||||
static inline void name##_set_( \
|
||||
name##_map_t *this, const key_type key, val_type value) \
|
||||
{ \
|
||||
if (this) { \
|
||||
size_t i; \
|
||||
for (i = 0; i < cvector_size(this->tree); i++) { \
|
||||
if (key == this->tree[i].key) { \
|
||||
this->tree[i].value = value; \
|
||||
return; \
|
||||
} \
|
||||
} \
|
||||
name##_pair_t new_pair = (name##_pair_t) { key, value }; \
|
||||
cvector_push_back(this->tree, new_pair); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
static inline void name##_delete_( \
|
||||
name##_map_t *this, const key_type key) \
|
||||
{ \
|
||||
if (this) { \
|
||||
size_t i; \
|
||||
for (i = 0; i < cvector_size(this->tree); i++) { \
|
||||
if (key == this->tree[i].key) { \
|
||||
cvector_erase(this->tree, i); \
|
||||
return; \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
static inline name##_map_t* name##_map_make_(void) \
|
||||
{ \
|
||||
name##_map_t *map = (name##_map_t*) malloc(sizeof(name##_map_t)); \
|
||||
if (map) { \
|
||||
map->tree = nil; \
|
||||
cvector_init(map->tree, 0, nil); \
|
||||
return map; \
|
||||
} \
|
||||
return nil; \
|
||||
} \
|
||||
\
|
||||
static inline void name##_clear_(name##_map_t *this) \
|
||||
{ \
|
||||
if (this) { \
|
||||
cvector_free(this->tree); \
|
||||
free(this); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
|
||||
|
||||
/**
|
||||
* @brief cmap - The map type used in this library
|
||||
* @param name - The name associated to a map type.
|
||||
*/
|
||||
#define cmap(name) name##_map_t *
|
||||
|
||||
/**
|
||||
* @brief cmap_make - creates a new map. Automatically initializes the map.
|
||||
* @param name - the name asociated to the map type
|
||||
* @return a pointer to a new map.
|
||||
*/
|
||||
#define cmap_make(name) name##_map_make_()
|
||||
|
||||
/**
|
||||
* @brief cmap_size - gets the current size of the map
|
||||
* @param map_ptr - the map pointer
|
||||
* @return the size as a size_t
|
||||
*/
|
||||
#define cmap_len(map_ptr) cvector_size(map_ptr->tree)
|
||||
|
||||
/**
|
||||
* @brief cmap_get - gets value associated to a key.
|
||||
* @param name - the name asociated to the map type
|
||||
* @param map_ptr - the map pointer
|
||||
* @param key - the key to search for
|
||||
* @return the value associated to a key
|
||||
*/
|
||||
#define cmap_get(name, map_ptr, key) name##_get_(map_ptr, key)
|
||||
|
||||
/**
|
||||
* @brief cmap_get-ptr - gets ptr_value associated to a key. Use it to avoid assigning a ptr to 0.
|
||||
* @param name - the name asociated to the map type
|
||||
* @param map_ptr - the map pointer
|
||||
* @param key - the key to search for
|
||||
* @return the value associated to a key
|
||||
*/
|
||||
#define cmap_get_ptr(name, map_ptr, key) name##_get_ptr_(map_ptr, key)
|
||||
|
||||
|
||||
/**
|
||||
* @brief cmap_set - sets value associated to a key.
|
||||
* @param name - the name asociated to the map type
|
||||
* @param map_ptr - the map pointer
|
||||
* @param key - the key to search for
|
||||
* @param value - the new value
|
||||
* @return void
|
||||
*/
|
||||
#define cmap_set(name, map_ptr, key, val) name##_set_(map_ptr, key, val)
|
||||
|
||||
/**
|
||||
* @brief cmap_delete - deletes map entry associated to a key.
|
||||
* @param name - the name asociated to the map type
|
||||
* @param map_ptr - the map pointer
|
||||
* @param key - the key to search for
|
||||
* @return void
|
||||
*/
|
||||
#define cmap_delete(name, map_ptr, key) name##_delete_(map_ptr, key)
|
||||
|
||||
/**
|
||||
* @brief cmap_set_elem_destructor - set the element destructor function
|
||||
* used to clean up removed elements. The map must NOT be NULL for this to do anything.
|
||||
* @param map_ptr - the map pointer
|
||||
* @param elem_destructor_fn - function pointer of type cvector_elem_destructor_t used to destroy elements
|
||||
* @return void
|
||||
*/
|
||||
#define cmap_set_elem_destructor(map_ptr, elem_destructor_fn) \
|
||||
cvector_set_elem_destructor(map_ptr->tree, elem_destructor_fn)
|
||||
|
||||
/**
|
||||
* @brief cmap_clear - deletes all map entries. And frees memory is an element destructor was set previously.
|
||||
* @param name - the name asociated to the map type
|
||||
* @param map_ptr - the map pointer
|
||||
* @return void
|
||||
*/
|
||||
#define cmap_clear(name, map_ptr) name##_clear_(map_ptr)
|
||||
|
||||
/**
|
||||
* @brief cmap_iterator - The iterator type used for cmap
|
||||
* @param type The type of iterator to act on.
|
||||
*/
|
||||
#define cmap_iterator(name) cvector_iterator(name##_pair_t)
|
||||
|
||||
/**
|
||||
* @brief cmap_begin - returns an iterator to first element of the vector
|
||||
* @param map_ptr - the map pointer
|
||||
* @return a pointer to the first element (or NULL)
|
||||
*/
|
||||
#define cmap_begin(map_ptr) ((map_ptr) ? cvector_begin(map_ptr->tree) : nil)
|
||||
|
||||
/**
|
||||
* @brief cmap_end - returns an iterator to one past the last element of the vector
|
||||
* @param map_ptrs - the map pointer
|
||||
* @return a pointer to one past the last element (or NULL)
|
||||
*/
|
||||
#define cmap_end(map_ptr) ((map_ptr) ? cvector_end(map_ptr->tree) : nil)
|
||||
|
||||
#endif /* CMAP_H_ */
|
||||
63
firmware/target/hosted/ctru/lib/cslice.h
Normal file
63
firmware/target/hosted/ctru/lib/cslice.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#ifndef CSLICE_H_
|
||||
#define CSLICE_H_
|
||||
|
||||
#define CVECTOR_LINEAR_GROWTH
|
||||
#include "cvector.h"
|
||||
|
||||
/* note: for ease of porting go code to c, many functions (macros) names
|
||||
remain similar to the ones used by go */
|
||||
|
||||
#define nil NULL
|
||||
|
||||
/**
|
||||
* @brief cslice - The slice type used in this library
|
||||
* @param type The type of slice to act on.
|
||||
*/
|
||||
#define cslice(type) cvector(type)
|
||||
|
||||
/**
|
||||
* @brief cslice_make - creates a new slice. Automatically initializes the slice.
|
||||
* @param slice - the slice
|
||||
* @param count - new size of the slice
|
||||
* @param value - the value to initialize new elements with
|
||||
* @return void
|
||||
*/
|
||||
#define cslice_make(slice, capacity, value) \
|
||||
do { \
|
||||
slice = nil; \
|
||||
cvector_init(slice, capacity, nil); \
|
||||
cvector_resize(slice, capacity, value); \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* @brief cslice_size - gets the current size of the slice
|
||||
* @param slice - the slice
|
||||
* @return the size as a size_t
|
||||
*/
|
||||
#define cslice_len(slice) cvector_size(slice)
|
||||
|
||||
/**
|
||||
* @brief cslice_capacity - gets the current capacity of the slice
|
||||
* @param slice - the slice
|
||||
* @return the capacity as a size_t
|
||||
*/
|
||||
#define cslice_cap(slice) cvector_capacity(slice)
|
||||
|
||||
/**
|
||||
* @brief cslice_set_elem_destructor - set the element destructor function
|
||||
* used to clean up removed elements. The vector must NOT be NULL for this to do anything.
|
||||
* @param slice - the slice
|
||||
* @param elem_destructor_fn - function pointer of type cslice_elem_destructor_t used to destroy elements
|
||||
* @return void
|
||||
*/
|
||||
#define cslice_set_elem_destructor(slice, elem_destructor_fn) \
|
||||
cvector_set_elem_destructor(slice, elem_destructor_fn)
|
||||
|
||||
/**
|
||||
* @brief cslice_free - frees all memory associated with the slice
|
||||
* @param slice - the slice
|
||||
* @return void
|
||||
*/
|
||||
#define cslice_clear(slice) cvector_free(slice)
|
||||
|
||||
#endif /* CSLICE_H_ */
|
||||
549
firmware/target/hosted/ctru/lib/cvector.h
Normal file
549
firmware/target/hosted/ctru/lib/cvector.h
Normal file
|
|
@ -0,0 +1,549 @@
|
|||
#ifndef CVECTOR_H_
|
||||
#define CVECTOR_H_
|
||||
/**
|
||||
* @copyright Copyright (c) 2015 Evan Teran,
|
||||
* License: The MIT License (MIT)
|
||||
* @brief cvector heap implemented using C library malloc()
|
||||
* @file cvector.h
|
||||
*/
|
||||
|
||||
/* in case C library malloc() needs extra protection,
|
||||
* allow these defines to be overridden.
|
||||
*/
|
||||
/* functions for allocation and deallocation need to correspond to each other, fall back to C library functions if not all are overridden */
|
||||
#if !defined(cvector_clib_free) || !defined(cvector_clib_malloc) || !defined(cvector_clib_calloc) || !defined(cvector_clib_realloc)
|
||||
#ifdef cvector_clib_free
|
||||
#undef cvector_clib_free
|
||||
#endif
|
||||
#ifdef cvector_clib_malloc
|
||||
#undef cvector_clib_malloc
|
||||
#endif
|
||||
#ifdef cvector_clib_calloc
|
||||
#undef cvector_clib_calloc
|
||||
#endif
|
||||
#ifdef cvector_clib_realloc
|
||||
#undef cvector_clib_realloc
|
||||
#endif
|
||||
#include <stdlib.h>
|
||||
#define cvector_clib_free free
|
||||
#define cvector_clib_malloc malloc
|
||||
#define cvector_clib_calloc calloc
|
||||
#define cvector_clib_realloc realloc
|
||||
#endif
|
||||
/* functions independent of memory allocation */
|
||||
#ifndef cvector_clib_assert
|
||||
#include <assert.h> /* for assert */
|
||||
#define cvector_clib_assert assert
|
||||
#endif
|
||||
#ifndef cvector_clib_memcpy
|
||||
#include <string.h> /* for memcpy */
|
||||
#define cvector_clib_memcpy memcpy
|
||||
#endif
|
||||
#ifndef cvector_clib_memmove
|
||||
#include <string.h> /* for memmove */
|
||||
#define cvector_clib_memmove memmove
|
||||
#endif
|
||||
|
||||
/* NOTE: Similar to C's qsort and bsearch, you will receive a T*
|
||||
* for a vector of Ts. This means that you cannot use `free` directly
|
||||
* as a destructor. Instead if you have for example a cvector_vector_type(int *)
|
||||
* you will need to supply a function which casts `elem_ptr` to an `int**`
|
||||
* and then does a free on what that pointer points to:
|
||||
*
|
||||
* ex:
|
||||
*
|
||||
* void free_int(void *p) { free(*(int **)p); }
|
||||
*/
|
||||
typedef void (*cvector_elem_destructor_t)(void *elem_ptr);
|
||||
|
||||
typedef struct cvector_metadata_t {
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
cvector_elem_destructor_t elem_destructor;
|
||||
} cvector_metadata_t;
|
||||
|
||||
/**
|
||||
* @brief cvector_vector_type - The vector type used in this library
|
||||
* @param type The type of vector to act on.
|
||||
*/
|
||||
#define cvector_vector_type(type) type *
|
||||
|
||||
/**
|
||||
* @brief cvector - Syntactic sugar to retrieve a vector type
|
||||
* @param type The type of vector to act on.
|
||||
*/
|
||||
#define cvector(type) cvector_vector_type(type)
|
||||
|
||||
/**
|
||||
* @brief cvector_iterator - The iterator type used for cvector
|
||||
* @param type The type of iterator to act on.
|
||||
*/
|
||||
#define cvector_iterator(type) cvector_vector_type(type)
|
||||
|
||||
/**
|
||||
* @note you can also safely pass a pointer to a cvector iterator to a function
|
||||
* but you have to update the pointer at the end to update the original
|
||||
* iterator.
|
||||
* example:
|
||||
* void function( cvector_vector_type( type ) * p_it )
|
||||
* {
|
||||
* cvector_vector_type( type ) it = *p_it;
|
||||
* it ++;
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* *p_it = it;
|
||||
* }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief cvector_vector_type_ptr - helper to make code more "readable"
|
||||
* @param type - the vector type pointer
|
||||
*/
|
||||
#define cvector_ptr_type(type) \
|
||||
cvector_vector_type(type) *
|
||||
|
||||
/**
|
||||
* @brief cvector_vector_ptr_get_iterator/set - helpers to make code more "readable"
|
||||
* @param it - the vector iterator
|
||||
* @param ptr - the vector pointer
|
||||
*/
|
||||
#define cvector_ptr_get_iterator(ptr) \
|
||||
*(ptr)
|
||||
|
||||
#define cvector_ptr_set(ptr, it) \
|
||||
*(ptr) = it
|
||||
|
||||
/**
|
||||
* @brief cvector_vector_container_declare - defined a vector container type
|
||||
*/
|
||||
#define cvector_vector_container_declare(name, type) \
|
||||
struct cvector_vector_container_##name { \
|
||||
cvector_vector_type(type) vector; \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief cvector_vector_container - used to pass a cvector wrapped inside a container as a function parameter
|
||||
*/
|
||||
#define cvector_vector_container(name) \
|
||||
struct cvector_vector_container_##name
|
||||
|
||||
|
||||
/**
|
||||
* @brief cvector_vec_to_base - For internal use, converts a vector pointer to a metadata pointer
|
||||
* @param vec - the vector
|
||||
* @return the metadata pointer of the vector
|
||||
* @internal
|
||||
*/
|
||||
#define cvector_vec_to_base(vec) \
|
||||
(&((cvector_metadata_t *)(void *)(vec))[-1])
|
||||
|
||||
/**
|
||||
* @brief cvector_base_to_vec - For internal use, converts a metadata pointer to a vector pointer
|
||||
* @param ptr - pointer to the metadata
|
||||
* @return the vector
|
||||
* @internal
|
||||
*/
|
||||
#define cvector_base_to_vec(ptr) \
|
||||
((void *)&((cvector_metadata_t *)(ptr))[1])
|
||||
|
||||
/**
|
||||
* @brief cvector_capacity - gets the current capacity of the vector
|
||||
* @param vec - the vector
|
||||
* @return the capacity as a size_t
|
||||
*/
|
||||
#define cvector_capacity(vec) \
|
||||
((vec) ? cvector_vec_to_base(vec)->capacity : (size_t)0)
|
||||
|
||||
/**
|
||||
* @brief cvector_size - gets the current size of the vector
|
||||
* @param vec - the vector
|
||||
* @return the size as a size_t
|
||||
*/
|
||||
#define cvector_size(vec) \
|
||||
((vec) ? cvector_vec_to_base(vec)->size : (size_t)0)
|
||||
|
||||
/**
|
||||
* @brief cvector_elem_destructor - get the element destructor function used
|
||||
* to clean up elements
|
||||
* @param vec - the vector
|
||||
* @return the function pointer as cvector_elem_destructor_t
|
||||
*/
|
||||
#define cvector_elem_destructor(vec) \
|
||||
((vec) ? cvector_vec_to_base(vec)->elem_destructor : NULL)
|
||||
|
||||
/**
|
||||
* @brief cvector_empty - returns non-zero if the vector is empty
|
||||
* @param vec - the vector
|
||||
* @return non-zero if empty, zero if non-empty
|
||||
*/
|
||||
#define cvector_empty(vec) \
|
||||
(cvector_size(vec) == 0)
|
||||
|
||||
/**
|
||||
* @brief cvector_reserve - Requests that the vector capacity be at least enough
|
||||
* to contain n elements. If n is greater than the current vector capacity, the
|
||||
* function causes the container to reallocate its storage increasing its
|
||||
* capacity to n (or greater).
|
||||
* @param vec - the vector
|
||||
* @param n - Minimum capacity for the vector.
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_reserve(vec, n) \
|
||||
do { \
|
||||
size_t cv_reserve_cap__ = cvector_capacity(vec); \
|
||||
if (cv_reserve_cap__ < (n)) { \
|
||||
cvector_grow((vec), (n)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_init - Initialize a vector. The vector must be NULL for this to do anything.
|
||||
* @param vec - the vector
|
||||
* @param capacity - vector capacity to reserve
|
||||
* @param elem_destructor_fn - element destructor function
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_init(vec, capacity, elem_destructor_fn) \
|
||||
do { \
|
||||
if (!(vec)) { \
|
||||
cvector_reserve((vec), capacity); \
|
||||
cvector_set_elem_destructor((vec), (elem_destructor_fn)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_init_default - Initialize a vector with default value. The vector must be NULL for this to do anything. Does NOT work for struct types.
|
||||
* @param vec - the vector
|
||||
* @param capacity - vector capacity to reserve
|
||||
* @param elem_destructor_fn - element destructor function
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_init_default(vec, capacity, default) \
|
||||
do { \
|
||||
if (!(vec)) { \
|
||||
cvector_reserve((vec), capacity); \
|
||||
cvector_set_elem_destructor((vec), (elem_destructor_fn)); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_erase - removes the element at index i from the vector
|
||||
* @param vec - the vector
|
||||
* @param i - index of element to remove
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_erase(vec, i) \
|
||||
do { \
|
||||
if (vec) { \
|
||||
const size_t cv_erase_sz__ = cvector_size(vec); \
|
||||
if ((i) < cv_erase_sz__) { \
|
||||
cvector_elem_destructor_t cv_erase_elem_dtor__ = cvector_elem_destructor(vec); \
|
||||
if (cv_erase_elem_dtor__) { \
|
||||
cv_erase_elem_dtor__(&(vec)[i]); \
|
||||
} \
|
||||
cvector_set_size((vec), cv_erase_sz__ - 1); \
|
||||
cvector_clib_memmove( \
|
||||
(vec) + (i), \
|
||||
(vec) + (i) + 1, \
|
||||
sizeof(*(vec)) * (cv_erase_sz__ - 1 - (i))); \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_clear - erase all of the elements in the vector
|
||||
* @param vec - the vector
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_clear(vec) \
|
||||
do { \
|
||||
if (vec) { \
|
||||
cvector_elem_destructor_t cv_clear_elem_dtor__ = cvector_elem_destructor(vec); \
|
||||
if (cv_clear_elem_dtor__) { \
|
||||
size_t cv_clear_i__; \
|
||||
for (cv_clear_i__ = 0; cv_clear_i__ < cvector_size(vec); ++cv_clear_i__) { \
|
||||
cv_clear_elem_dtor__(&(vec)[cv_clear_i__]); \
|
||||
} \
|
||||
} \
|
||||
cvector_set_size(vec, 0); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_free - frees all memory associated with the vector
|
||||
* @param vec - the vector
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_free(vec) \
|
||||
do { \
|
||||
if (vec) { \
|
||||
void *cv_free_p__ = cvector_vec_to_base(vec); \
|
||||
cvector_elem_destructor_t cv_free_elem_dtor__ = cvector_elem_destructor(vec); \
|
||||
if (cv_free_elem_dtor__) { \
|
||||
size_t cv_free_i__; \
|
||||
for (cv_free_i__ = 0; cv_free_i__ < cvector_size(vec); ++cv_free_i__) { \
|
||||
cv_free_elem_dtor__(&(vec)[cv_free_i__]); \
|
||||
} \
|
||||
} \
|
||||
cvector_clib_free(cv_free_p__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_begin - returns an iterator to first element of the vector
|
||||
* @param vec - the vector
|
||||
* @return a pointer to the first element (or NULL)
|
||||
*/
|
||||
#define cvector_begin(vec) \
|
||||
(vec)
|
||||
|
||||
/**
|
||||
* @brief cvector_end - returns an iterator to one past the last element of the vector
|
||||
* @param vec - the vector
|
||||
* @return a pointer to one past the last element (or NULL)
|
||||
*/
|
||||
#define cvector_end(vec) \
|
||||
((vec) ? &((vec)[cvector_size(vec)]) : NULL)
|
||||
|
||||
/* user request to use linear growth algorithm */
|
||||
#ifdef CVECTOR_LINEAR_GROWTH
|
||||
|
||||
/**
|
||||
* @brief cvector_compute_next_grow - returns an the computed size in next vector grow
|
||||
* size is increased by 1
|
||||
* @param size - current size
|
||||
* @return size after next vector grow
|
||||
*/
|
||||
#define cvector_compute_next_grow(size) \
|
||||
((size) + 1)
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
* @brief cvector_compute_next_grow - returns an the computed size in next vector grow
|
||||
* size is increased by multiplication of 2
|
||||
* @param size - current size
|
||||
* @return size after next vector grow
|
||||
*/
|
||||
#define cvector_compute_next_grow(size) \
|
||||
((size) ? ((size) << 1) : 1)
|
||||
|
||||
#endif /* CVECTOR_LINEAR_GROWTH */
|
||||
|
||||
/**
|
||||
* @brief cvector_push_back - adds an element to the end of the vector
|
||||
* @param vec - the vector
|
||||
* @param value - the value to add
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_push_back(vec, value) \
|
||||
do { \
|
||||
size_t cv_push_back_cap__ = cvector_capacity(vec); \
|
||||
if (cv_push_back_cap__ <= cvector_size(vec)) { \
|
||||
cvector_grow((vec), cvector_compute_next_grow(cv_push_back_cap__)); \
|
||||
} \
|
||||
(vec)[cvector_size(vec)] = (value); \
|
||||
cvector_set_size((vec), cvector_size(vec) + 1); \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_insert - insert element at position pos to the vector
|
||||
* @param vec - the vector
|
||||
* @param pos - position in the vector where the new elements are inserted.
|
||||
* @param val - value to be copied (or moved) to the inserted elements.
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_insert(vec, pos, val) \
|
||||
do { \
|
||||
size_t cv_insert_cap__ = cvector_capacity(vec); \
|
||||
if (cv_insert_cap__ <= cvector_size(vec)) { \
|
||||
cvector_grow((vec), cvector_compute_next_grow(cv_insert_cap__)); \
|
||||
} \
|
||||
if ((pos) < cvector_size(vec)) { \
|
||||
cvector_clib_memmove( \
|
||||
(vec) + (pos) + 1, \
|
||||
(vec) + (pos), \
|
||||
sizeof(*(vec)) * ((cvector_size(vec)) - (pos))); \
|
||||
} \
|
||||
(vec)[(pos)] = (val); \
|
||||
cvector_set_size((vec), cvector_size(vec) + 1); \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_pop_back - removes the last element from the vector
|
||||
* @param vec - the vector
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_pop_back(vec) \
|
||||
do { \
|
||||
cvector_elem_destructor_t cv_pop_back_elem_dtor__ = cvector_elem_destructor(vec); \
|
||||
if (cv_pop_back_elem_dtor__) { \
|
||||
cv_pop_back_elem_dtor__(&(vec)[cvector_size(vec) - 1]); \
|
||||
} \
|
||||
cvector_set_size((vec), cvector_size(vec) - 1); \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_copy - copy a vector
|
||||
* @param from - the original vector
|
||||
* @param to - destination to which the function copy to
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_copy(from, to) \
|
||||
do { \
|
||||
if ((from)) { \
|
||||
cvector_grow(to, cvector_size(from)); \
|
||||
cvector_set_size(to, cvector_size(from)); \
|
||||
cvector_clib_memcpy((to), (from), cvector_size(from) * sizeof(*(from))); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_swap - exchanges the content of the vector by the content of another vector of the same type
|
||||
* @param vec - the original vector
|
||||
* @param other - the other vector to swap content with
|
||||
* @param type - the type of both vectors
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_swap(vec, other, type) \
|
||||
do { \
|
||||
if (vec && other) { \
|
||||
cvector_vector_type(type) cv_swap__ = vec; \
|
||||
vec = other; \
|
||||
other = cv_swap__; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_set_capacity - For internal use, sets the capacity variable of the vector
|
||||
* @param vec - the vector
|
||||
* @param size - the new capacity to set
|
||||
* @return void
|
||||
* @internal
|
||||
*/
|
||||
#define cvector_set_capacity(vec, size) \
|
||||
do { \
|
||||
if (vec) { \
|
||||
cvector_vec_to_base(vec)->capacity = (size); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_set_size - For internal use, sets the size variable of the vector
|
||||
* @param vec - the vector
|
||||
* @param _size - the new capacity to set
|
||||
* @return void
|
||||
* @internal
|
||||
*/
|
||||
#define cvector_set_size(vec, _size) \
|
||||
do { \
|
||||
if (vec) { \
|
||||
cvector_vec_to_base(vec)->size = (_size); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_set_elem_destructor - set the element destructor function
|
||||
* used to clean up removed elements. The vector must NOT be NULL for this to do anything.
|
||||
* @param vec - the vector
|
||||
* @param elem_destructor_fn - function pointer of type cvector_elem_destructor_t used to destroy elements
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_set_elem_destructor(vec, elem_destructor_fn) \
|
||||
do { \
|
||||
if (vec) { \
|
||||
cvector_vec_to_base(vec)->elem_destructor = (elem_destructor_fn); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_grow - For internal use, ensures that the vector is at least `count` elements big
|
||||
* @param vec - the vector
|
||||
* @param count - the new capacity to set
|
||||
* @return void
|
||||
* @internal
|
||||
*/
|
||||
#define cvector_grow(vec, count) \
|
||||
do { \
|
||||
const size_t cv_grow_sz__ = (count) * sizeof(*(vec)) + sizeof(cvector_metadata_t); \
|
||||
if (vec) { \
|
||||
void *cv_grow_p1__ = cvector_vec_to_base(vec); \
|
||||
void *cv_grow_p2__ = cvector_clib_realloc(cv_grow_p1__, cv_grow_sz__); \
|
||||
cvector_clib_assert(cv_grow_p2__); \
|
||||
(vec) = cvector_base_to_vec(cv_grow_p2__); \
|
||||
} else { \
|
||||
void *cv_grow_p__ = cvector_clib_malloc(cv_grow_sz__); \
|
||||
cvector_clib_assert(cv_grow_p__); \
|
||||
(vec) = cvector_base_to_vec(cv_grow_p__); \
|
||||
cvector_set_size((vec), 0); \
|
||||
cvector_set_elem_destructor((vec), NULL); \
|
||||
} \
|
||||
cvector_set_capacity((vec), (count)); \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_shrink_to_fit - requests the container to reduce its capacity to fit its size
|
||||
* @param vec - the vector
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_shrink_to_fit(vec) \
|
||||
do { \
|
||||
if (vec) { \
|
||||
const size_t cv_shrink_to_fit_sz__ = cvector_size(vec); \
|
||||
cvector_grow(vec, cv_shrink_to_fit_sz__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/**
|
||||
* @brief cvector_at - returns a reference to the element at position n in the vector.
|
||||
* @param vec - the vector
|
||||
* @param n - position of an element in the vector.
|
||||
* @return the element at the specified position in the vector.
|
||||
*/
|
||||
#define cvector_at(vec, n) \
|
||||
((vec) ? (((int)(n) < 0 || (size_t)(n) >= cvector_size(vec)) ? NULL : &(vec)[n]) : NULL)
|
||||
|
||||
/**
|
||||
* @brief cvector_front - returns a reference to the first element in the vector. Unlike member cvector_begin, which returns an iterator to this same element, this function returns a direct reference.
|
||||
* @param vec - the vector
|
||||
* @return a reference to the first element in the vector container.
|
||||
*/
|
||||
#define cvector_front(vec) \
|
||||
((vec) ? ((cvector_size(vec) > 0) ? cvector_at(vec, 0) : NULL) : NULL)
|
||||
|
||||
/**
|
||||
* @brief cvector_back - returns a reference to the last element in the vector.Unlike member cvector_end, which returns an iterator just past this element, this function returns a direct reference.
|
||||
* @param vec - the vector
|
||||
* @return a reference to the last element in the vector.
|
||||
*/
|
||||
#define cvector_back(vec) \
|
||||
((vec) ? ((cvector_size(vec) > 0) ? cvector_at(vec, cvector_size(vec) - 1) : NULL) : NULL)
|
||||
|
||||
/**
|
||||
* @brief cvector_resize - resizes the container to contain count elements.
|
||||
* @param vec - the vector
|
||||
* @param count - new size of the vector
|
||||
* @param value - the value to initialize new elements with
|
||||
* @return void
|
||||
*/
|
||||
#define cvector_resize(vec, count, value) \
|
||||
do { \
|
||||
if (vec) { \
|
||||
size_t cv_resize_count__ = (size_t)(count); \
|
||||
size_t cv_resize_sz__ = cvector_vec_to_base(vec)->size; \
|
||||
if (cv_resize_count__ > cv_resize_sz__) { \
|
||||
cvector_reserve((vec), cv_resize_count__); \
|
||||
cvector_set_size((vec), cv_resize_count__); \
|
||||
do { \
|
||||
(vec)[cv_resize_sz__++] = (value); \
|
||||
} while (cv_resize_sz__ < cv_resize_count__); \
|
||||
} else { \
|
||||
while (cv_resize_count__ < cv_resize_sz__--) { \
|
||||
cvector_pop_back(vec); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#endif /* CVECTOR_H_ */
|
||||
403
firmware/target/hosted/ctru/lib/sys_dir.c
Normal file
403
firmware/target/hosted/ctru/lib/sys_dir.c
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2025 Mauricio G.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
#define DIRFUNCTIONS_DEFINED
|
||||
#include "config.h"
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include "debug.h"
|
||||
#include "dir.h"
|
||||
#include "pathfuncs.h"
|
||||
#include "timefuncs.h"
|
||||
#include "system.h"
|
||||
#include "fs_defines.h"
|
||||
#include "sys_file.h"
|
||||
|
||||
#include <3ds/archive.h>
|
||||
#include <3ds/util/utf.h>
|
||||
|
||||
/* This file is based on firmware/common/dir.c */
|
||||
|
||||
/* Define LOGF_ENABLE to enable logf output in this file */
|
||||
// #define LOGF_ENABLE
|
||||
#include "logf.h"
|
||||
|
||||
/* structure used for open directory streams */
|
||||
static struct dirstr_desc
|
||||
{
|
||||
struct filestr_base stream; /* basic stream info (first!) */
|
||||
struct dirent entry; /* current parsed entry information */
|
||||
} open_streams[MAX_OPEN_DIRS] =
|
||||
{
|
||||
[0 ... MAX_OPEN_FILES-1] = { .stream = { .handle = 0 } }
|
||||
};
|
||||
|
||||
extern FS_Archive sdmcArchive;
|
||||
|
||||
/* check and return a struct dirstr_desc* from a DIR* */
|
||||
static struct dirstr_desc * get_dirstr(DIR *dirp)
|
||||
{
|
||||
struct dirstr_desc *dir = (struct dirstr_desc *)dirp;
|
||||
|
||||
if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS))
|
||||
dir = NULL;
|
||||
else if (dir->stream.handle != 0)
|
||||
return dir;
|
||||
|
||||
int errnum;
|
||||
|
||||
if (!dir)
|
||||
{
|
||||
errnum = EFAULT;
|
||||
}
|
||||
else
|
||||
{
|
||||
logf("dir #%d: dir not open\n", (int)(dir - open_streams));
|
||||
errnum = EBADF;
|
||||
}
|
||||
|
||||
errno = errnum;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define GET_DIRSTR(type, dirp) \
|
||||
({ \
|
||||
file_internal_lock_##type(); \
|
||||
struct dirstr_desc *_dir = get_dirstr(dirp); \
|
||||
if (_dir) \
|
||||
FILESTR_LOCK(type, &_dir->stream); \
|
||||
else { \
|
||||
file_internal_unlock_##type(); \
|
||||
} \
|
||||
_dir; \
|
||||
})
|
||||
|
||||
/* release the lock on the dirstr_desc* */
|
||||
#define RELEASE_DIRSTR(type, dir) \
|
||||
({ \
|
||||
FILESTR_UNLOCK(type, &(dir)->stream); \
|
||||
file_internal_unlock_##type(); \
|
||||
})
|
||||
|
||||
|
||||
/* find a free dir stream descriptor */
|
||||
static struct dirstr_desc * alloc_dirstr(void)
|
||||
{
|
||||
for (unsigned int dd = 0; dd < MAX_OPEN_DIRS; dd++)
|
||||
{
|
||||
struct dirstr_desc *dir = &open_streams[dd];
|
||||
if (dir->stream.handle == 0)
|
||||
return dir;
|
||||
}
|
||||
|
||||
logf("Too many dirs open\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
u32 fs_error(void) {
|
||||
u32 err;
|
||||
FSUSER_GetSdmcFatfsError(&err);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Initialize the base descriptor */
|
||||
static void filestr_base_init(struct filestr_base *stream)
|
||||
{
|
||||
stream->cache = nil;
|
||||
stream->handle = 0;
|
||||
stream->size = 0;
|
||||
LightLock_Init(&stream->mtx);
|
||||
}
|
||||
|
||||
/** POSIX interface **/
|
||||
|
||||
/* open a directory */
|
||||
DIR * ctru_opendir(const char *dirname)
|
||||
{
|
||||
logf("opendir(dirname=\"%s\")\n", dirname);
|
||||
|
||||
DIR *dirp = NULL;
|
||||
file_internal_lock_WRITER();
|
||||
|
||||
int rc;
|
||||
|
||||
struct dirstr_desc * const dir = alloc_dirstr();
|
||||
if (!dir)
|
||||
FILE_ERROR(EMFILE, _RC);
|
||||
|
||||
filestr_base_init(&dir->stream);
|
||||
Result res = FSUSER_OpenDirectory(&dir->stream.handle,
|
||||
sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, dirname));
|
||||
if (R_FAILED(res)) {
|
||||
logf("Open failed: %lld\n", fs_error());
|
||||
FILE_ERROR(EMFILE, -1);
|
||||
}
|
||||
|
||||
dir->stream.size = 0;
|
||||
dir->stream.flags = 0;
|
||||
|
||||
/* we will use file path to implement ctru_samedir function */
|
||||
strcpy(dir->stream.path, dirname);
|
||||
|
||||
dirp = (DIR *)dir;
|
||||
file_error:
|
||||
file_internal_unlock_WRITER();
|
||||
return dirp;
|
||||
}
|
||||
|
||||
/* close a directory stream */
|
||||
int ctru_closedir(DIR *dirp)
|
||||
{
|
||||
int rc;
|
||||
|
||||
file_internal_lock_WRITER();
|
||||
|
||||
/* needs to work even if marked "nonexistant" */
|
||||
struct dirstr_desc * const dir = (struct dirstr_desc *)dirp;
|
||||
if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS))
|
||||
FILE_ERROR(EFAULT, -1);
|
||||
|
||||
logf("closedir(dirname=\"%s\")\n", dir->stream.path);
|
||||
|
||||
if (dir->stream.handle == 0)
|
||||
{
|
||||
logf("dir #%d: dir not open\n", (int)(dir - open_streams));
|
||||
FILE_ERROR(EBADF, -2);
|
||||
}
|
||||
|
||||
Result res = FSDIR_Close(dir->stream.handle);
|
||||
if (R_FAILED(res))
|
||||
FILE_ERROR(ERRNO, -3);
|
||||
|
||||
dir->stream.handle = 0;
|
||||
dir->stream.path[0] = '\0';
|
||||
|
||||
rc = 0;
|
||||
file_error:
|
||||
file_internal_unlock_WRITER();
|
||||
return rc;
|
||||
}
|
||||
|
||||
void dirstr_entry_init(FS_DirectoryEntry *dirEntry, struct dirent *entry)
|
||||
{
|
||||
/* clear */
|
||||
memset(entry, 0, sizeof(struct dirent));
|
||||
|
||||
/* attributes */
|
||||
if (dirEntry->attributes & FS_ATTRIBUTE_DIRECTORY)
|
||||
entry->info.attr |= ATTR_DIRECTORY;
|
||||
if (dirEntry->attributes & FS_ATTRIBUTE_HIDDEN)
|
||||
entry->info.attr |= ATTR_HIDDEN;
|
||||
if (dirEntry->attributes & FS_ATTRIBUTE_ARCHIVE)
|
||||
entry->info.attr |= ATTR_ARCHIVE;
|
||||
if (dirEntry->attributes & FS_ATTRIBUTE_READ_ONLY)
|
||||
entry->info.attr |= ATTR_READ_ONLY;
|
||||
|
||||
/* size */
|
||||
entry->info.size = dirEntry->fileSize;
|
||||
|
||||
/* name */
|
||||
uint8_t d_name[0xA0 + 1];
|
||||
memset(d_name, '\0', 0xA0);
|
||||
utf16_to_utf8(d_name, (uint16_t *) &dirEntry->name, 0xA0);
|
||||
memcpy(entry->d_name, d_name, 0xA0);
|
||||
}
|
||||
|
||||
/* read a directory */
|
||||
struct dirent * ctru_readdir(DIR *dirp)
|
||||
{
|
||||
struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp);
|
||||
if (!dir)
|
||||
FILE_ERROR_RETURN(ERRNO, NULL);
|
||||
|
||||
int rc;
|
||||
struct dirent *res = NULL;
|
||||
|
||||
logf("readdir(dirname=\"%s\")\n", dir->stream.path);
|
||||
|
||||
u32 dataRead = 0;
|
||||
FS_DirectoryEntry dirEntry;
|
||||
Result result = FSDIR_Read(dir->stream.handle,
|
||||
&dataRead,
|
||||
1,
|
||||
&dirEntry);
|
||||
if (R_FAILED(result))
|
||||
FILE_ERROR(EIO, _RC);
|
||||
|
||||
if (dataRead == 0) {
|
||||
/* directory end. return NULL value, no errno */
|
||||
res = NULL;
|
||||
goto file_error;
|
||||
}
|
||||
|
||||
res = &dir->entry;
|
||||
dirstr_entry_init(&dirEntry, res);
|
||||
|
||||
/* time */
|
||||
char full_path[MAX_PATH+1];
|
||||
|
||||
if (!strcmp(PATH_ROOTSTR, dir->stream.path))
|
||||
snprintf(full_path, MAX_PATH, "%s%s", dir->stream.path, res->d_name);
|
||||
else
|
||||
snprintf(full_path, MAX_PATH, "%s/%s", dir->stream.path, res->d_name);
|
||||
|
||||
u64 mtime;
|
||||
archive_getmtime(full_path, &mtime);
|
||||
|
||||
/* DEBUGF("archive_getmtime(%s): %lld\n", full_path, mtime); */
|
||||
|
||||
uint16_t dosdate, dostime;
|
||||
dostime_localtime(mtime, &dosdate, &dostime);
|
||||
res->info.wrtdate = dosdate;
|
||||
res->info.wrttime = dostime;
|
||||
|
||||
file_error:
|
||||
RELEASE_DIRSTR(READER, dir);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* make a directory */
|
||||
int ctru_mkdir(const char *path)
|
||||
{
|
||||
logf("mkdir(path=\"%s\")\n", path);
|
||||
|
||||
int rc;
|
||||
|
||||
file_internal_lock_WRITER();
|
||||
|
||||
Result res = FSUSER_CreateDirectory(sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, path),
|
||||
0);
|
||||
if (R_FAILED(res))
|
||||
FILE_ERROR(ERRNO, -1);
|
||||
|
||||
rc = 0;
|
||||
file_error:
|
||||
file_internal_unlock_WRITER();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* remove a directory */
|
||||
int ctru_rmdir(const char *name)
|
||||
{
|
||||
logf("rmdir(name=\"%s\")\n", name);
|
||||
|
||||
int rc;
|
||||
|
||||
if (name)
|
||||
{
|
||||
/* path may not end with "." */
|
||||
const char *basename;
|
||||
size_t len = path_basename(name, &basename);
|
||||
if (basename[0] == '.' && len == 1)
|
||||
{
|
||||
logf("Invalid path; last component is \".\"\n");
|
||||
FILE_ERROR_RETURN(EINVAL, -9);
|
||||
}
|
||||
}
|
||||
|
||||
file_internal_lock_WRITER();
|
||||
Result res = FSUSER_DeleteDirectory(sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, name));
|
||||
if (R_FAILED(res))
|
||||
FILE_ERROR(ERRNO, -1);
|
||||
|
||||
rc = 0;
|
||||
file_error:
|
||||
file_internal_unlock_WRITER();
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/** Extended interface **/
|
||||
|
||||
/* return if two directory streams refer to the same directory */
|
||||
int ctru_samedir(DIR *dirp1, DIR *dirp2)
|
||||
{
|
||||
struct dirstr_desc * const dir1 = GET_DIRSTR(WRITER, dirp1);
|
||||
if (!dir1)
|
||||
FILE_ERROR_RETURN(ERRNO, -1);
|
||||
|
||||
int rc = -2;
|
||||
|
||||
struct dirstr_desc * const dir2 = get_dirstr(dirp2);
|
||||
if (dir2) {
|
||||
rc = strcmp(dir1->stream.path, dir2->stream.path) == 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
RELEASE_DIRSTR(WRITER, dir1);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* test directory existence (returns 'false' if a file) */
|
||||
bool ctru_dir_exists(const char *dirname)
|
||||
{
|
||||
file_internal_lock_WRITER();
|
||||
|
||||
int rc;
|
||||
|
||||
Handle handle;
|
||||
Result res = FSUSER_OpenDirectory(&handle,
|
||||
sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, dirname));
|
||||
if (R_FAILED(res)) {
|
||||
logf("Directory not found: %ld\n", fs_error());
|
||||
FILE_ERROR(EMFILE, -1);
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
file_error:
|
||||
if (rc == 0) {
|
||||
FSDIR_Close(handle);
|
||||
}
|
||||
file_internal_unlock_WRITER();
|
||||
return rc == 0 ? true : false;
|
||||
}
|
||||
|
||||
/* get the portable info from the native entry */
|
||||
struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry)
|
||||
{
|
||||
int rc;
|
||||
if (!dirp || !entry)
|
||||
FILE_ERROR(EFAULT, _RC);
|
||||
|
||||
if (entry->d_name[0] == '\0')
|
||||
FILE_ERROR(ENOENT, _RC);
|
||||
|
||||
if ((file_size_t)entry->info.size > FILE_SIZE_MAX)
|
||||
FILE_ERROR(EOVERFLOW, _RC);
|
||||
|
||||
return (struct dirinfo)
|
||||
{
|
||||
.attribute = entry->info.attr,
|
||||
.size = entry->info.size,
|
||||
.mtime = dostime_mktime(entry->info.wrtdate, entry->info.wrttime),
|
||||
};
|
||||
|
||||
file_error:
|
||||
return (struct dirinfo){ .attribute = 0 };
|
||||
}
|
||||
|
||||
const char* ctru_root_realpath(void)
|
||||
{
|
||||
/* Native only, for APP and SIM see respective filesystem-.c files */
|
||||
return PATH_ROOTSTR; /* rb_namespace.c */
|
||||
}
|
||||
777
firmware/target/hosted/ctru/lib/sys_file.c
Normal file
777
firmware/target/hosted/ctru/lib/sys_file.c
Normal file
|
|
@ -0,0 +1,777 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2025 Mauricio G.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
#define RB_FILESYSTEM_OS
|
||||
#include "config.h"
|
||||
#include "system.h"
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <utime.h>
|
||||
#include "file.h"
|
||||
#include "debug.h"
|
||||
#include "string-extra.h"
|
||||
#include "fs_defines.h"
|
||||
#include "sys_file.h"
|
||||
|
||||
/* This file is based on firmware/common/file.c */
|
||||
|
||||
/* Define LOGF_ENABLE to enable logf output in this file */
|
||||
// #define LOGF_ENABLE
|
||||
#include "logf.h"
|
||||
|
||||
/**
|
||||
* These functions provide a roughly POSIX-compatible file I/O API.
|
||||
* Important: the bufferio I/O library (bfile) used in the 3ds does not work
|
||||
* with write-only files due to an internal limitation.
|
||||
* So all files will be opened with the read flag by default.
|
||||
*/
|
||||
|
||||
/* structure used for open file descriptors */
|
||||
static struct filestr_desc
|
||||
{
|
||||
struct filestr_base stream; /* basic stream info (first!) */
|
||||
file_size_t offset; /* current offset for stream */
|
||||
u64 *sizep; /* shortcut to file size in fileobj */
|
||||
} open_streams[MAX_OPEN_FILES] =
|
||||
{
|
||||
[0 ... MAX_OPEN_FILES-1] = { .stream = { .cache = nil, .flags = 0 } }
|
||||
};
|
||||
|
||||
extern FS_Archive sdmcArchive;
|
||||
|
||||
/* check and return a struct filestr_desc* from a file descriptor number */
|
||||
static struct filestr_desc * get_filestr(int fildes)
|
||||
{
|
||||
struct filestr_desc *file = &open_streams[fildes];
|
||||
|
||||
if ((unsigned int)fildes >= MAX_OPEN_FILES)
|
||||
file = NULL;
|
||||
else if (file->stream.cache != nil)
|
||||
return file;
|
||||
|
||||
logf("fildes %d: bad file number\n", fildes);
|
||||
errno = (file && (file->stream.cache == nil)) ? ENXIO : EBADF;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define GET_FILESTR(type, fildes) \
|
||||
({ \
|
||||
file_internal_lock_##type(); \
|
||||
struct filestr_desc * _file = get_filestr(fildes); \
|
||||
if (_file) \
|
||||
FILESTR_LOCK(type, &_file->stream); \
|
||||
else { \
|
||||
file_internal_unlock_##type(); \
|
||||
}\
|
||||
_file; \
|
||||
})
|
||||
|
||||
/* release the lock on the filestr_desc* */
|
||||
#define RELEASE_FILESTR(type, file) \
|
||||
({ \
|
||||
FILESTR_UNLOCK(type, &(file)->stream); \
|
||||
file_internal_unlock_##type(); \
|
||||
})
|
||||
|
||||
/* find a free file descriptor */
|
||||
static int alloc_filestr(struct filestr_desc **filep)
|
||||
{
|
||||
for (int fildes = 0; fildes < MAX_OPEN_FILES; fildes++)
|
||||
{
|
||||
struct filestr_desc *file = &open_streams[fildes];
|
||||
if (file->stream.cache == nil)
|
||||
{
|
||||
*filep = file;
|
||||
return fildes;
|
||||
}
|
||||
}
|
||||
|
||||
logf("Too many files open\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* check for file existence */
|
||||
int test_stream_exists_internal(const char *path)
|
||||
{
|
||||
int rc;
|
||||
bool is_dir = false;
|
||||
|
||||
Handle handle;
|
||||
Result res = FSUSER_OpenFile(&handle,
|
||||
sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, path),
|
||||
FS_OPEN_READ,
|
||||
0);
|
||||
if (R_FAILED(res)) {
|
||||
/* not a file, try to open a directory */
|
||||
res = FSUSER_OpenDirectory(&handle,
|
||||
sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, path));
|
||||
if (R_FAILED(res)) {
|
||||
logf("File does not exist\n");
|
||||
FILE_ERROR(ERRNO, -1);
|
||||
}
|
||||
|
||||
is_dir = true;
|
||||
}
|
||||
|
||||
rc = 1;
|
||||
file_error:
|
||||
if (handle > 0) {
|
||||
if (is_dir)
|
||||
FSDIR_Close(handle);
|
||||
else
|
||||
FSFILE_Close(handle);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* set the file pointer */
|
||||
static off_t lseek_internal(struct filestr_desc *file, off_t offset,
|
||||
int whence)
|
||||
{
|
||||
off_t rc;
|
||||
off_t pos;
|
||||
|
||||
off_t size = MIN(*file->sizep, FILE_SIZE_MAX);
|
||||
off_t file_offset = AtomicLoad(&file->offset);
|
||||
|
||||
switch (whence)
|
||||
{
|
||||
case SEEK_SET:
|
||||
if (offset < 0 || (off_t)offset > size)
|
||||
FILE_ERROR(EINVAL, -1);
|
||||
|
||||
pos = offset;
|
||||
break;
|
||||
|
||||
case SEEK_CUR:
|
||||
if ((offset < 0 && (off_t)-offset > file_offset) ||
|
||||
(offset > 0 && (off_t)offset > size - file_offset))
|
||||
FILE_ERROR(EINVAL, -1);
|
||||
|
||||
pos = file_offset + offset;
|
||||
break;
|
||||
|
||||
case SEEK_END:
|
||||
if (offset > 0 || (off_t)-offset > size)
|
||||
FILE_ERROR(EINVAL, -1);
|
||||
|
||||
pos = size + offset;
|
||||
break;
|
||||
|
||||
default:
|
||||
FILE_ERROR(EINVAL, -1);
|
||||
}
|
||||
|
||||
AtomicSwap(&file->offset, pos);
|
||||
|
||||
return pos;
|
||||
file_error:
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* read from or write to the file; back end to read() and write() */
|
||||
static ssize_t readwrite(struct filestr_desc *file, void *buf, size_t nbyte,
|
||||
bool write)
|
||||
{
|
||||
#ifndef LOGF_ENABLE /* wipes out log before you can save it */
|
||||
/* DEBUGF("readwrite(%p,%lx,%lu,%s)\n",
|
||||
file, (long)buf, (unsigned long)nbyte, write ? "write" : "read"); */
|
||||
#endif
|
||||
|
||||
const file_size_t size = *file->sizep;
|
||||
size_t filerem;
|
||||
|
||||
if (write)
|
||||
{
|
||||
/* if opened in append mode, move pointer to end */
|
||||
if (file->stream.flags & O_APPEND)
|
||||
AtomicSwap(&file->offset, MIN(size, FILE_SIZE_MAX));
|
||||
|
||||
filerem = FILE_SIZE_MAX - AtomicLoad(&file->offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* limit to maximum possible offset (EOF or FILE_SIZE_MAX) */
|
||||
filerem = MIN(size, FILE_SIZE_MAX) - AtomicLoad(&file->offset);
|
||||
}
|
||||
|
||||
if (nbyte > filerem)
|
||||
{
|
||||
nbyte = filerem;
|
||||
if (nbyte > 0)
|
||||
{}
|
||||
else if (write)
|
||||
FILE_ERROR_RETURN(EFBIG, -1); /* would get too large */
|
||||
else if (AtomicLoad(&file->offset) >= FILE_SIZE_MAX)
|
||||
FILE_ERROR_RETURN(EOVERFLOW, -2); /* can't read here */
|
||||
}
|
||||
|
||||
if (nbyte == 0)
|
||||
return 0;
|
||||
|
||||
int rc = 0;
|
||||
int_error_t n_err;
|
||||
|
||||
if (write)
|
||||
n_err = PagerWriteAt(file->stream.cache, (u8 *) buf, nbyte, AtomicLoad(&file->offset));
|
||||
else
|
||||
n_err = PagerReadAt(file->stream.cache, (u8 *) buf, nbyte, AtomicLoad(&file->offset));
|
||||
|
||||
if ((n_err.err != nil) && strcmp(n_err.err, "io.EOF")) {
|
||||
FILE_ERROR(ERRNO, -3);
|
||||
}
|
||||
|
||||
file_error:;
|
||||
#ifdef DEBUG
|
||||
if (errno == ENOSPC)
|
||||
logf("No space left on device\n");
|
||||
#endif
|
||||
|
||||
size_t done = n_err.n;
|
||||
if (done)
|
||||
{
|
||||
/* error or not, update the file offset and size if anything was
|
||||
transferred */
|
||||
AtomicAdd(&file->offset, done);
|
||||
#ifndef LOGF_ENABLE /* wipes out log before you can save it */
|
||||
/* DEBUGF("file offset: %lld\n", file->offset); */
|
||||
#endif
|
||||
/* adjust file size to length written */
|
||||
if (write && AtomicLoad(&file->offset) > size)
|
||||
*file->sizep = AtomicLoad(&file->offset);
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* initialize the base descriptor */
|
||||
static void filestr_base_init(struct filestr_base *stream)
|
||||
{
|
||||
stream->cache = nil;
|
||||
stream->handle = 0;
|
||||
stream->size = 0;
|
||||
LightLock_Init(&stream->mtx);
|
||||
}
|
||||
|
||||
int open_internal_inner2(Handle *handle, const char *path, u32 openFlags, u32 attributes)
|
||||
{
|
||||
int rc;
|
||||
Result res = FSUSER_OpenFile(handle,
|
||||
sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, path),
|
||||
openFlags,
|
||||
attributes);
|
||||
if (R_FAILED(res)) {
|
||||
FILE_ERROR(ERRNO, -1);
|
||||
}
|
||||
|
||||
rc = 1;
|
||||
file_error:
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int open_internal_inner1(const char *path, int oflag)
|
||||
{
|
||||
int rc;
|
||||
struct filestr_desc *file;
|
||||
int fildes = alloc_filestr(&file);
|
||||
if (fildes < 0)
|
||||
FILE_ERROR_RETURN(EMFILE, -1);
|
||||
|
||||
u32 openFlags = 0, attributes = 0;
|
||||
|
||||
/* open for reading by default */
|
||||
openFlags = FS_OPEN_READ;
|
||||
|
||||
if (oflag & O_ACCMODE)
|
||||
{
|
||||
if ((oflag & O_ACCMODE) == O_RDONLY) {
|
||||
attributes |= FS_ATTRIBUTE_READ_ONLY;
|
||||
}
|
||||
if ((oflag & O_ACCMODE) == O_WRONLY) {
|
||||
openFlags |= FS_OPEN_WRITE;
|
||||
}
|
||||
if ((oflag & O_ACCMODE) == O_RDWR) {
|
||||
openFlags |= FS_OPEN_WRITE;
|
||||
}
|
||||
}
|
||||
else if (oflag & O_TRUNC)
|
||||
{
|
||||
/* O_TRUNC requires write mode */
|
||||
logf("No write mode but have O_TRUNC\n");
|
||||
FILE_ERROR(EINVAL, -2);
|
||||
}
|
||||
|
||||
/* O_CREAT and O_APPEND are fine without write mode
|
||||
* for the former, an empty file is created but no data may be written
|
||||
* for the latter, no append will be allowed anyway */
|
||||
if (!(oflag & O_CREAT))
|
||||
oflag &= ~O_EXCL; /* result is undefined: we choose "ignore" */
|
||||
|
||||
filestr_base_init(&file->stream);
|
||||
rc = open_internal_inner2(&file->stream.handle, path, openFlags, attributes);
|
||||
|
||||
if (rc > 0) {
|
||||
if (oflag & O_EXCL)
|
||||
{
|
||||
logf("File exists\n");
|
||||
FILE_ERROR(EEXIST, -4);
|
||||
}
|
||||
}
|
||||
else if (oflag & O_CREAT)
|
||||
{
|
||||
/* not found; try to create it */
|
||||
openFlags |= FS_OPEN_CREATE;
|
||||
rc = open_internal_inner2(&file->stream.handle, path, openFlags, attributes);
|
||||
if (rc < 0)
|
||||
FILE_ERROR(ERRNO, rc * 10 - 6);
|
||||
}
|
||||
else
|
||||
{
|
||||
logf("File not found\n");
|
||||
FILE_ERROR(ENOENT, -5);
|
||||
}
|
||||
|
||||
/* truncate file if requested */
|
||||
if (oflag & O_TRUNC) {
|
||||
Result res = FSFILE_SetSize(file->stream.handle, 0);
|
||||
if (R_FAILED(res)) {
|
||||
FILE_ERROR(ERRNO, -6);
|
||||
}
|
||||
}
|
||||
|
||||
/* we need to set file size here, or else lseek
|
||||
will fail if no read or write has been done */
|
||||
u64 size = 0;
|
||||
Result res = FSFILE_GetSize(file->stream.handle, &size);
|
||||
if (R_FAILED(res)) {
|
||||
FILE_ERROR(ERRNO, -8);
|
||||
}
|
||||
|
||||
int pageSize = 4096; /* 4096 bytes */
|
||||
int bufferSize = 512 * 1024; /* 512 kB */
|
||||
|
||||
/* streamed file formats like flac and mp3 need very large page
|
||||
sizes to avoid stuttering */
|
||||
if (((oflag & O_ACCMODE) == O_RDONLY) && (size > 0x200000)) {
|
||||
/* printf("open(%s)_BIG_pageSize\n", path); */
|
||||
pageSize = 32 * 1024;
|
||||
bufferSize = MIN(size, defaultBufferSize);
|
||||
}
|
||||
|
||||
file->stream.cache = NewPagerSize(file->stream.handle,
|
||||
pageSize,
|
||||
bufferSize);
|
||||
if (file->stream.cache == nil) {
|
||||
FILE_ERROR(ERRNO, -7);
|
||||
}
|
||||
|
||||
file->stream.flags = oflag;
|
||||
file->stream.size = size;
|
||||
file->sizep = &file->stream.size;
|
||||
AtomicSwap(&file->offset, 0);
|
||||
|
||||
/* we will use file path to implement ctru_fsamefile function */
|
||||
strcpy(file->stream.path, path);
|
||||
|
||||
return fildes;
|
||||
|
||||
file_error:
|
||||
if (fildes >= 0) {
|
||||
if (file->stream.cache != nil) {
|
||||
PagerFlush(file->stream.cache);
|
||||
PagerClear(file->stream.cache);
|
||||
file->stream.cache = nil;
|
||||
}
|
||||
|
||||
FSFILE_Close(file->stream.handle);
|
||||
file->stream.handle = 0;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int open_internal_locked(const char *path, int oflag)
|
||||
{
|
||||
file_internal_lock_WRITER();
|
||||
int rc = open_internal_inner1(path, oflag);
|
||||
file_internal_unlock_WRITER();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int ctru_open(const char *path, int oflag, ...)
|
||||
{
|
||||
logf("open(path=\"%s\",oflag=%X)\n", path, (unsigned)oflag);
|
||||
return open_internal_locked(path, oflag);
|
||||
}
|
||||
|
||||
int ctru_creat(const char *path, mode_t mode)
|
||||
{
|
||||
logf("creat(path=\"%s\")\n", path);
|
||||
return ctru_open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
|
||||
}
|
||||
|
||||
int ctru_close(int fildes)
|
||||
{
|
||||
logf("close(fd=%d)\n", fildes);
|
||||
|
||||
int rc;
|
||||
|
||||
file_internal_lock_WRITER();
|
||||
|
||||
/* needs to work even if marked "nonexistant" */
|
||||
struct filestr_desc *file = &open_streams[fildes];
|
||||
if ((unsigned int)fildes >= MAX_OPEN_FILES || (file->stream.cache == nil))
|
||||
{
|
||||
logf("filedes %d not open\n", fildes);
|
||||
FILE_ERROR(EBADF, -2);
|
||||
}
|
||||
|
||||
if (file->stream.cache != nil) {
|
||||
PagerFlush(file->stream.cache);
|
||||
PagerClear(file->stream.cache);
|
||||
file->stream.cache = nil;
|
||||
}
|
||||
|
||||
FSFILE_Close(file->stream.handle);
|
||||
file->stream.handle = 0;
|
||||
file->stream.path[0] = '\0';
|
||||
|
||||
rc = 0;
|
||||
file_error:
|
||||
file_internal_unlock_WRITER();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* truncate a file to a specified length */
|
||||
int ctru_ftruncate(int fildes, off_t length)
|
||||
{
|
||||
logf("ftruncate(fd=%d,len=%ld)\n", fildes, (long)length);
|
||||
|
||||
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
|
||||
if (!file)
|
||||
FILE_ERROR_RETURN(ERRNO, -1);
|
||||
|
||||
int rc;
|
||||
|
||||
if (file->stream.flags & O_RDONLY)
|
||||
{
|
||||
logf("Descriptor is read-only mode\n");
|
||||
FILE_ERROR(EBADF, -2);
|
||||
}
|
||||
|
||||
if (length < 0)
|
||||
{
|
||||
logf("Length %ld is invalid\n", (long)length);
|
||||
FILE_ERROR(EINVAL, -3);
|
||||
}
|
||||
|
||||
file_error_t err = PagerTruncate(file->stream.cache, length);
|
||||
if (err) {
|
||||
FILE_ERROR(ERRNO, -11);
|
||||
}
|
||||
|
||||
*file->sizep = length;
|
||||
|
||||
rc = 0;
|
||||
file_error:
|
||||
RELEASE_FILESTR(READER, file);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* synchronize changes to a file */
|
||||
int ctru_fsync(int fildes)
|
||||
{
|
||||
logf("fsync(fd=%d)\n", fildes);
|
||||
|
||||
struct filestr_desc * const file = GET_FILESTR(WRITER, fildes);
|
||||
if (!file)
|
||||
FILE_ERROR_RETURN(ERRNO, -1);
|
||||
|
||||
int rc;
|
||||
|
||||
if (file->stream.flags & O_RDONLY)
|
||||
{
|
||||
logf("Descriptor is read-only mode\n");
|
||||
FILE_ERROR(EINVAL, -2);
|
||||
}
|
||||
|
||||
/* flush all pending changes to disk */
|
||||
file_error_t err = PagerFlush(file->stream.cache);
|
||||
if (err != nil) {
|
||||
FILE_ERROR(ERRNO, -3);
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
file_error:
|
||||
RELEASE_FILESTR(WRITER, file);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* move the read/write file offset */
|
||||
off_t ctru_lseek(int fildes, off_t offset, int whence)
|
||||
{
|
||||
#ifndef LOGF_ENABLE /* wipes out log before you can save it */
|
||||
/* DEBUGF("lseek(fd=%d,ofs=%ld,wh=%d)\n", fildes, (long)offset, whence); */
|
||||
#endif
|
||||
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
|
||||
if (!file)
|
||||
FILE_ERROR_RETURN(ERRNO, -1);
|
||||
|
||||
off_t rc = lseek_internal(file, offset, whence);
|
||||
if (rc < 0)
|
||||
FILE_ERROR(ERRNO, rc * 10 - 2);
|
||||
|
||||
file_error:
|
||||
RELEASE_FILESTR(READER, file);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* read from a file */
|
||||
ssize_t ctru_read(int fildes, void *buf, size_t nbyte)
|
||||
{
|
||||
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
|
||||
if (!file)
|
||||
FILE_ERROR_RETURN(ERRNO, -1);
|
||||
|
||||
ssize_t rc;
|
||||
|
||||
if (file->stream.flags & O_WRONLY)
|
||||
{
|
||||
logf("read(fd=%d,buf=%p,nb=%lu) - "
|
||||
"descriptor is write-only mode\n",
|
||||
fildes, buf, (unsigned long)nbyte);
|
||||
FILE_ERROR(EBADF, -2);
|
||||
}
|
||||
|
||||
rc = readwrite(file, buf, nbyte, false);
|
||||
if (rc < 0)
|
||||
FILE_ERROR(ERRNO, rc * 10 - 3);
|
||||
|
||||
file_error:
|
||||
RELEASE_FILESTR(READER, file);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* write on a file */
|
||||
ssize_t ctru_write(int fildes, const void *buf, size_t nbyte)
|
||||
{
|
||||
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
|
||||
if (!file)
|
||||
FILE_ERROR_RETURN(ERRNO, -1);
|
||||
|
||||
ssize_t rc;
|
||||
|
||||
if (file->stream.flags & O_RDONLY)
|
||||
{
|
||||
logf("write(fd=%d,buf=%p,nb=%lu) - "
|
||||
"descriptor is read-only mode\n",
|
||||
fildes, buf, (unsigned long)nbyte);
|
||||
FILE_ERROR(EBADF, -2);
|
||||
}
|
||||
|
||||
rc = readwrite(file, (void *)buf, nbyte, true);
|
||||
if (rc < 0)
|
||||
FILE_ERROR(ERRNO, rc * 10 - 3);
|
||||
|
||||
file_error:
|
||||
RELEASE_FILESTR(READER, file);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* remove a file */
|
||||
int ctru_remove(const char *path)
|
||||
{
|
||||
logf("remove(path=\"%s\")\n", path);
|
||||
|
||||
int rc;
|
||||
|
||||
file_internal_lock_WRITER();
|
||||
Result res = FSUSER_DeleteFile(sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, path));
|
||||
if (R_FAILED(res))
|
||||
FILE_ERROR(ERRNO, -1);
|
||||
|
||||
rc = 0;
|
||||
file_error:
|
||||
file_internal_unlock_WRITER();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* rename a file */
|
||||
int ctru_rename(const char *old, const char *new)
|
||||
{
|
||||
/* note: move by rename does not work in devkitARM toolchain */
|
||||
logf("rename(old=\"%s\",new=\"%s\")\n", old, new);
|
||||
|
||||
int rc;
|
||||
|
||||
/* if 'old' is a directory then 'new' is also required to be one if 'new'
|
||||
is to be overwritten */
|
||||
bool are_dirs = false;
|
||||
|
||||
file_internal_lock_WRITER();
|
||||
|
||||
if (!strcmp(new, old)) /* case-only is ok */
|
||||
{
|
||||
logf("No name change (success)\n");
|
||||
rc = 0;
|
||||
FILE_ERROR(ERRNO, _RC);
|
||||
}
|
||||
|
||||
/* open 'old'; it must exist */
|
||||
Handle open1rc;
|
||||
Result res = FSUSER_OpenFile(&open1rc,
|
||||
sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, old),
|
||||
FS_OPEN_READ,
|
||||
0);
|
||||
if (R_FAILED(res)) {
|
||||
/* not a file, try to open a directory */
|
||||
res = FSUSER_OpenDirectory(&open1rc,
|
||||
sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, old));
|
||||
if (R_FAILED(res)) {
|
||||
logf("Failed opening old\n");
|
||||
FILE_ERROR(ERRNO, -1);
|
||||
}
|
||||
|
||||
are_dirs = true;
|
||||
}
|
||||
|
||||
if (are_dirs) {
|
||||
/* rename directory */
|
||||
FSUSER_RenameDirectory(sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, old),
|
||||
sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, new));
|
||||
}
|
||||
else {
|
||||
/* rename file */
|
||||
FSUSER_RenameFile(sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, old),
|
||||
sdmcArchive,
|
||||
fsMakePath(PATH_ASCII, new));
|
||||
}
|
||||
|
||||
if (R_FAILED(res)) {
|
||||
logf("Rename failed\n");
|
||||
FILE_ERROR(ERRNO, -2);
|
||||
}
|
||||
|
||||
rc = 0;
|
||||
file_error:
|
||||
/* for now, there is nothing to fail upon closing the old stream */
|
||||
if (open1rc > 0) {
|
||||
if (are_dirs)
|
||||
FSDIR_Close(open1rc);
|
||||
else
|
||||
FSFILE_Close(open1rc);
|
||||
}
|
||||
|
||||
file_internal_unlock_WRITER();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/** Extensions **/
|
||||
|
||||
/* todo: utime does not work in devkitARM toolchain */
|
||||
int ctru_modtime(const char *path, time_t modtime)
|
||||
{
|
||||
struct utimbuf times =
|
||||
{
|
||||
.actime = modtime,
|
||||
.modtime = modtime,
|
||||
};
|
||||
|
||||
return utime(path, ×);
|
||||
}
|
||||
|
||||
/* get the binary size of a file (in bytes) */
|
||||
off_t ctru_filesize(int fildes)
|
||||
{
|
||||
struct filestr_desc * const file = GET_FILESTR(READER, fildes);
|
||||
if (!file)
|
||||
FILE_ERROR_RETURN(ERRNO, -1);
|
||||
|
||||
off_t rc;
|
||||
file_size_t size = *file->sizep;
|
||||
|
||||
if (size > FILE_SIZE_MAX)
|
||||
FILE_ERROR(EOVERFLOW, -2);
|
||||
|
||||
rc = (off_t)size;
|
||||
file_error:
|
||||
RELEASE_FILESTR(READER, file);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* test if two file descriptors refer to the same file */
|
||||
int ctru_fsamefile(int fildes1, int fildes2)
|
||||
{
|
||||
struct filestr_desc * const file1 = GET_FILESTR(WRITER, fildes1);
|
||||
if (!file1)
|
||||
FILE_ERROR_RETURN(ERRNO, -1);
|
||||
|
||||
int rc = -2;
|
||||
|
||||
struct filestr_desc * const file2 = get_filestr(fildes2);
|
||||
if (file2)
|
||||
rc = strcmp(file1->stream.path, file2->stream.path) == 0 ? 1 : 0;
|
||||
|
||||
RELEASE_FILESTR(WRITER, file1);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* tell the relationship of path1 to path2 */
|
||||
int ctru_relate(const char *path1, const char *path2)
|
||||
{
|
||||
/* FAT32 file system does not support symbolic links,
|
||||
therefore, comparing the two full paths should be enough
|
||||
to tell relationship */
|
||||
logf("relate(path1=\"%s\",path2=\"%s\")\n", path1, path2);
|
||||
int rc = RELATE_DIFFERENT;
|
||||
if (strcmp(path1, path2) == 0)
|
||||
rc = RELATE_SAME;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* test file or directory existence */
|
||||
bool ctru_file_exists(const char *path)
|
||||
{
|
||||
file_internal_lock_WRITER();
|
||||
bool rc = test_stream_exists_internal(path) > 0;
|
||||
file_internal_unlock_WRITER();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* note: no symbolic links support in devkitARM */
|
||||
ssize_t ctru_readlink(const char *path, char *buf, size_t bufsiz)
|
||||
{
|
||||
return readlink(path, buf, bufsiz);
|
||||
}
|
||||
|
||||
129
firmware/target/hosted/ctru/lib/sys_file.h
Normal file
129
firmware/target/hosted/ctru/lib/sys_file.h
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2025 by Mauricio G.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
#ifndef _SYS_FILE_H
|
||||
#define _SYS_FILE_H
|
||||
|
||||
#include "bfile.h"
|
||||
|
||||
/* Include for file.h and dir.h because mkdir and friends may be here */
|
||||
#include <sys/stat.h>
|
||||
|
||||
#define strlcpy_from_os strlcpy
|
||||
|
||||
#define AtomicLoad(ptr) __atomic_load_n((u32*)(ptr), __ATOMIC_SEQ_CST)
|
||||
#define AtomicAdd(ptr, value) __atomic_add_fetch((u32*)(ptr), value, __ATOMIC_SEQ_CST)
|
||||
|
||||
struct filestr_base {
|
||||
Pager* cache; /* buffer IO implementation (cache) */
|
||||
Handle handle; /* file handle */
|
||||
u64 size; /* file size */
|
||||
int flags; /* stream flags */
|
||||
char path[MAX_PATH+1];
|
||||
|
||||
LightLock mtx; /* serialization for this stream */
|
||||
};
|
||||
|
||||
static inline void filestr_lock(struct filestr_base *stream)
|
||||
{
|
||||
LightLock_Lock(&stream->mtx);
|
||||
}
|
||||
|
||||
static inline void filestr_unlock(struct filestr_base *stream)
|
||||
{
|
||||
LightLock_Unlock(&stream->mtx);
|
||||
}
|
||||
|
||||
/* stream lock doesn't have to be used if getting RW lock writer access */
|
||||
#define FILESTR_WRITER 0
|
||||
#define FILESTR_READER 1
|
||||
|
||||
#define FILESTR_LOCK(type, stream) \
|
||||
({ if (FILESTR_##type) filestr_lock(stream); })
|
||||
|
||||
#define FILESTR_UNLOCK(type, stream) \
|
||||
({ if (FILESTR_##type) filestr_unlock(stream); })
|
||||
|
||||
/** Synchronization used throughout **/
|
||||
|
||||
/* acquire the filesystem lock as READER */
|
||||
static inline void file_internal_lock_READER(void)
|
||||
{
|
||||
extern sync_RWMutex file_internal_mrsw;
|
||||
sync_RWMutexRLock(&file_internal_mrsw);
|
||||
}
|
||||
|
||||
/* release the filesystem lock as READER */
|
||||
static inline void file_internal_unlock_READER(void)
|
||||
{
|
||||
extern sync_RWMutex file_internal_mrsw;
|
||||
sync_RWMutexRUnlock(&file_internal_mrsw);
|
||||
}
|
||||
|
||||
/* acquire the filesystem lock as WRITER */
|
||||
static inline void file_internal_lock_WRITER(void)
|
||||
{
|
||||
extern sync_RWMutex file_internal_mrsw;
|
||||
sync_RWMutexLock(&file_internal_mrsw);
|
||||
}
|
||||
|
||||
/* release the filesystem lock as WRITER */
|
||||
static inline void file_internal_unlock_WRITER(void)
|
||||
{
|
||||
extern sync_RWMutex file_internal_mrsw;
|
||||
sync_RWMutexUnlock(&file_internal_mrsw);
|
||||
}
|
||||
|
||||
#define ERRNO 0 /* maintain errno value */
|
||||
#define _RC 0 /* maintain rc value */
|
||||
|
||||
/* NOTES: if _errno is a non-constant expression, it must set an error
|
||||
* number and not return the ERRNO constant which will merely set
|
||||
* errno to zero, not preserve the current value; if you must set
|
||||
* errno to zero, set it explicitly, not in the macro
|
||||
*
|
||||
* if _rc is constant-expression evaluation to 'RC', then rc will
|
||||
* NOT be altered; i.e. if you must set rc to zero, set it explicitly,
|
||||
* not in the macro
|
||||
*/
|
||||
|
||||
#define FILE_SET_CODE(_name, _keepcode, _value) \
|
||||
({ __builtin_constant_p(_value) ? \
|
||||
({ if ((_value) != (_keepcode)) _name = (_value); }) : \
|
||||
({ _name = (_value); }); })
|
||||
|
||||
/* set errno and rc and proceed to the "file_error:" label */
|
||||
#define FILE_ERROR(_errno, _rc) \
|
||||
({ FILE_SET_CODE(errno, ERRNO, (_errno)); \
|
||||
FILE_SET_CODE(rc, _RC, (_rc)); \
|
||||
goto file_error; })
|
||||
|
||||
/* set errno and return a value at the point of invocation */
|
||||
#define FILE_ERROR_RETURN(_errno, _rc...) \
|
||||
({ FILE_SET_CODE(errno, ERRNO, _errno); \
|
||||
return _rc; })
|
||||
|
||||
/* set errno and return code, no branching */
|
||||
#define FILE_ERROR_SET(_errno, _rc) \
|
||||
({ FILE_SET_CODE(errno, ERRNO, (_errno)); \
|
||||
FILE_SET_CODE(rc, _RC, (_rc)); })
|
||||
|
||||
#endif /* _SYS_FILE_H */
|
||||
|
||||
403
firmware/target/hosted/ctru/lib/sys_thread.c
Normal file
403
firmware/target/hosted/ctru/lib/sys_thread.c
Normal file
|
|
@ -0,0 +1,403 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2025 Mauricio Ga.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "sys_thread.h"
|
||||
#include "sys_timer.h"
|
||||
#include "debug.h"
|
||||
#include "logf.h"
|
||||
|
||||
bool _AtomicCAS(u32 *ptr, int oldval, int newval)
|
||||
{
|
||||
int expected = oldval;
|
||||
int desired = newval;
|
||||
return __atomic_compare_exchange(ptr, &expected, &desired, false,
|
||||
__ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
|
||||
}
|
||||
|
||||
bool _AtomicTryLock(int *lock)
|
||||
{
|
||||
int result;
|
||||
asm volatile(
|
||||
"ldrex %0, [%2] \n"
|
||||
"teq %0, #0 \n"
|
||||
"strexeq %0, %1, [%2] \n"
|
||||
: "=&r"(result)
|
||||
: "r"(1), "r"(lock)
|
||||
: "cc", "memory"
|
||||
);
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
#define CPUPauseInstruction() asm volatile("yield" ::: "memory")
|
||||
void AtomicLock(int *lock)
|
||||
{
|
||||
int iterations = 0;
|
||||
while (!_AtomicTryLock(lock)) {
|
||||
if (iterations < 32) {
|
||||
iterations++;
|
||||
CPUPauseInstruction();
|
||||
} else {
|
||||
sys_delay(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AtomicUnlock(int *lock)
|
||||
{
|
||||
*lock = 0;
|
||||
}
|
||||
|
||||
/* Convert rockbox priority value to libctru value */
|
||||
int get_ctru_thread_priority(int priority)
|
||||
{
|
||||
if ((priority == PRIORITY_REALTIME_1) || (priority == PRIORITY_REALTIME_2) ||
|
||||
(priority == PRIORITY_REALTIME_3) || (priority == PRIORITY_REALTIME_4) ||
|
||||
(priority == PRIORITY_REALTIME))
|
||||
return 0x18;
|
||||
else if (priority == PRIORITY_BUFFERING)
|
||||
return 0x18; /* Highest */
|
||||
else if ((priority == PRIORITY_USER_INTERFACE) || (priority == PRIORITY_RECORDING) ||
|
||||
(priority == PRIORITY_PLAYBACK))
|
||||
return 0x30;
|
||||
else if (priority == PRIORITY_PLAYBACK_MAX)
|
||||
return 0x2F;
|
||||
else if (priority == PRIORITY_SYSTEM)
|
||||
return 0x30;
|
||||
else if (priority == PRIORITY_BACKGROUND)
|
||||
return 0x3F; /* Lowest */
|
||||
else
|
||||
return 0x30;
|
||||
}
|
||||
|
||||
static size_t get_thread_stack_size(size_t requested_size)
|
||||
{
|
||||
if (requested_size == 0) {
|
||||
return (80 * 1024); /* 80 kB */
|
||||
}
|
||||
|
||||
return requested_size;
|
||||
}
|
||||
|
||||
static void thread_entry(void *arg)
|
||||
{
|
||||
sys_run_thread((sysThread *)arg);
|
||||
threadExit(0);
|
||||
}
|
||||
|
||||
int wait_on_semaphore_for(LightSemaphore *sem, u32 timeout)
|
||||
{
|
||||
u64 stop_time = sys_get_ticks64() + timeout;
|
||||
u64 current_time = sys_get_ticks64();
|
||||
while (current_time < stop_time) {
|
||||
if (LightSemaphore_TryAcquire(sem, 1) == 0) {
|
||||
return 0;
|
||||
}
|
||||
/* 100 microseconds seems to be the sweet spot */
|
||||
svcSleepThread(100000LL);
|
||||
current_time = sys_get_ticks64();
|
||||
}
|
||||
|
||||
/* If we failed, yield to avoid starvation on busy waits */
|
||||
svcSleepThread(1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sys_sem_try_wait(LightSemaphore *sem)
|
||||
{
|
||||
if (LightSemaphore_TryAcquire(sem, 1) != 0) {
|
||||
/* If we failed, yield to avoid starvation on busy waits */
|
||||
svcSleepThread(1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sys_sem_wait_timeout(LightSemaphore *sem, u32 timeout)
|
||||
{
|
||||
if (timeout == (~(u32)0)) {
|
||||
LightSemaphore_Acquire(sem, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (LightSemaphore_TryAcquire(sem, 1) != 0) {
|
||||
return wait_on_semaphore_for(sem, timeout);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sys_sem_wait(LightSemaphore *sem)
|
||||
{
|
||||
return sys_sem_wait_timeout(sem, (~(u32)0));
|
||||
}
|
||||
|
||||
u32 sys_sem_value(LightSemaphore *sem)
|
||||
{
|
||||
return sem->current_count;
|
||||
}
|
||||
|
||||
int sys_thread_id(void)
|
||||
{
|
||||
u32 thread_ID = 0;
|
||||
svcGetThreadId(&thread_ID, CUR_THREAD_HANDLE);
|
||||
return (int)thread_ID;
|
||||
}
|
||||
|
||||
void sys_run_thread(sysThread *thread)
|
||||
{
|
||||
void *userdata = thread->userdata;
|
||||
int(* userfunc)(void *) = thread->userfunc;
|
||||
|
||||
int *statusloc = &thread->status;
|
||||
|
||||
/* Get the thread id */
|
||||
thread->threadid = sys_thread_id();
|
||||
|
||||
/* Run the function */
|
||||
*statusloc = userfunc(userdata);
|
||||
|
||||
/* Mark us as ready to be joined (or detached) */
|
||||
if (!AtomicCAS(&thread->state, THREAD_STATE_ALIVE, THREAD_STATE_ZOMBIE)) {
|
||||
/* Clean up if something already detached us. */
|
||||
if (AtomicCAS(&thread->state, THREAD_STATE_DETACHED, THREAD_STATE_CLEANED)) {
|
||||
free(thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sysThread *sys_create_thread(int(*fn)(void *), const char *name, const size_t stacksize,
|
||||
void *data IF_PRIO(, int priority) IF_COP(, unsigned int core))
|
||||
{
|
||||
sys_ticks_init();
|
||||
|
||||
/* Allocate memory for the thread info structure */
|
||||
sysThread *thread = (sysThread *) calloc(1, sizeof(sysThread));
|
||||
if (thread == NULL) {
|
||||
DEBUGF("sys_create_thread: could not allocate memory\n");
|
||||
return NULL;
|
||||
}
|
||||
thread->status = -1;
|
||||
AtomicSet(&thread->state, THREAD_STATE_ALIVE);
|
||||
|
||||
/* Set up the arguments for the thread */
|
||||
thread->userfunc = fn;
|
||||
thread->userdata = data;
|
||||
thread->stacksize = stacksize;
|
||||
|
||||
int cpu = -1;
|
||||
if (name && (strncmp(name, "tagcache", 8) == 0) && R_SUCCEEDED(APT_SetAppCpuTimeLimit(30))) {
|
||||
cpu = 1;
|
||||
printf("thread: %s, running in cpu 1\n", name);
|
||||
}
|
||||
|
||||
thread->handle = threadCreate(thread_entry,
|
||||
thread,
|
||||
get_thread_stack_size(stacksize),
|
||||
get_ctru_thread_priority(priority),
|
||||
cpu,
|
||||
false);
|
||||
|
||||
if (!thread->handle) {
|
||||
DEBUGF("sys_create_thread: threadCreate failed\n");
|
||||
free(thread);
|
||||
thread = NULL;
|
||||
}
|
||||
|
||||
/* Everything is running now */
|
||||
return thread;
|
||||
}
|
||||
|
||||
void sys_wait_thread(sysThread *thread, int *status)
|
||||
{
|
||||
if (thread) {
|
||||
Result res = threadJoin(thread->handle, U64_MAX);
|
||||
|
||||
/*
|
||||
Detached threads can be waited on, but should NOT be cleaned manually
|
||||
as it would result in a fatal error.
|
||||
*/
|
||||
if (R_SUCCEEDED(res) && AtomicGet(&thread->state) != THREAD_STATE_DETACHED) {
|
||||
threadFree(thread->handle);
|
||||
}
|
||||
if (status) {
|
||||
*status = thread->status;
|
||||
}
|
||||
free(thread);
|
||||
}
|
||||
}
|
||||
|
||||
void sys_detach_thread(sysThread *thread)
|
||||
{
|
||||
if (!thread) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Grab dibs if the state is alive+joinable. */
|
||||
if (AtomicCAS(&thread->state, THREAD_STATE_ALIVE, THREAD_STATE_DETACHED)) {
|
||||
threadDetach(thread->handle);
|
||||
} else {
|
||||
/* all other states are pretty final, see where we landed. */
|
||||
const int thread_state = AtomicGet(&thread->state);
|
||||
if ((thread_state == THREAD_STATE_DETACHED) || (thread_state == THREAD_STATE_CLEANED)) {
|
||||
return; /* already detached (you shouldn't call this twice!) */
|
||||
} else if (thread_state == THREAD_STATE_ZOMBIE) {
|
||||
sys_wait_thread(thread, NULL); /* already done, clean it up. */
|
||||
} else {
|
||||
assert(0 && "Unexpected thread state");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int sys_set_thread_priority(sysThread *thread, int priority)
|
||||
{
|
||||
Handle h = threadGetHandle(thread->handle);
|
||||
int old_priority = priority;
|
||||
Result res = svcSetThreadPriority(h, get_ctru_thread_priority(priority));
|
||||
if (R_SUCCEEDED(res)) {
|
||||
return priority;
|
||||
}
|
||||
|
||||
return old_priority;
|
||||
}
|
||||
|
||||
/* sysCond */
|
||||
sysCond *sys_cond_create(void)
|
||||
{
|
||||
sysCond *cond;
|
||||
|
||||
cond = (sysCond *)malloc(sizeof(sysCond));
|
||||
if (cond) {
|
||||
RecursiveLock_Init(&cond->lock);
|
||||
LightSemaphore_Init(&cond->wait_sem, 0, ((s16)0x7FFF));
|
||||
LightSemaphore_Init(&cond->wait_done, 0, ((s16)0x7FFF));
|
||||
cond->waiting = cond->signals = 0;
|
||||
} else {
|
||||
DEBUGF("sys_cond_create: out of memory.\n");;
|
||||
}
|
||||
return cond;
|
||||
}
|
||||
|
||||
/* Destroy a condition variable */
|
||||
void sys_cond_destroy(sysCond *cond)
|
||||
{
|
||||
if (cond) {
|
||||
free(cond);
|
||||
}
|
||||
}
|
||||
|
||||
/* Restart one of the threads that are waiting on the condition variable */
|
||||
int sys_cond_signal(sysCond *cond)
|
||||
{
|
||||
if (!cond) {
|
||||
DEBUGF("sys_cond_signal: Invalid param 'cond'\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If there are waiting threads not already signalled, then
|
||||
signal the condition and wait for the thread to respond.
|
||||
*/
|
||||
RecursiveLock_Lock(&cond->lock);
|
||||
if (cond->waiting > cond->signals) {
|
||||
++cond->signals;
|
||||
LightSemaphore_Release(&cond->wait_sem, 1);
|
||||
RecursiveLock_Unlock(&cond->lock);
|
||||
LightSemaphore_Acquire(&cond->wait_done, 1);
|
||||
} else {
|
||||
RecursiveLock_Unlock(&cond->lock);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Restart all threads that are waiting on the condition variable */
|
||||
int sys_cond_broadcast(sysCond *cond)
|
||||
{
|
||||
if (!cond) {
|
||||
DEBUGF("sys_cond_signal: Invalid param 'cond'\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* If there are waiting threads not already signalled, then
|
||||
signal the condition and wait for the thread to respond.
|
||||
*/
|
||||
RecursiveLock_Lock(&cond->lock);
|
||||
if (cond->waiting > cond->signals) {
|
||||
int i, num_waiting;
|
||||
|
||||
num_waiting = (cond->waiting - cond->signals);
|
||||
cond->signals = cond->waiting;
|
||||
for (i = 0; i < num_waiting; ++i) {
|
||||
LightSemaphore_Release(&cond->wait_sem, 1);
|
||||
}
|
||||
/* Now all released threads are blocked here, waiting for us.
|
||||
Collect them all (and win fabulous prizes!) :-)
|
||||
*/
|
||||
RecursiveLock_Unlock(&cond->lock);
|
||||
for (i = 0; i < num_waiting; ++i) {
|
||||
LightSemaphore_Acquire(&cond->wait_done, 1);
|
||||
}
|
||||
} else {
|
||||
RecursiveLock_Unlock(&cond->lock);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sys_cond_wait(sysCond *cond, RecursiveLock *mutex)
|
||||
{
|
||||
if (!cond) {
|
||||
DEBUGF("sys_cond_signal: Invalid param 'cond'\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
RecursiveLock_Lock(&cond->lock);
|
||||
++cond->waiting;
|
||||
RecursiveLock_Unlock(&cond->lock);
|
||||
|
||||
/* Unlock the mutex, as is required by condition variable semantics */
|
||||
RecursiveLock_Unlock(mutex);
|
||||
|
||||
/* Wait for a signal */
|
||||
LightSemaphore_Acquire(&cond->wait_sem, 1);
|
||||
|
||||
RecursiveLock_Lock(&cond->lock);
|
||||
if (cond->signals > 0) {
|
||||
/* We always notify the signal thread that we are done */
|
||||
LightSemaphore_Release(&cond->wait_done, 1);
|
||||
|
||||
/* Signal handshake complete */
|
||||
--cond->signals;
|
||||
}
|
||||
--cond->waiting;
|
||||
RecursiveLock_Unlock(&cond->lock);
|
||||
|
||||
/* Lock the mutex, as is required by condition variable semantics */
|
||||
RecursiveLock_Lock(mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
88
firmware/target/hosted/ctru/lib/sys_thread.h
Normal file
88
firmware/target/hosted/ctru/lib/sys_thread.h
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2025 Mauricio G.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef __SYSTHREAD_H__
|
||||
#define __SYSTHREAD_H__
|
||||
|
||||
#include "thread.h"
|
||||
|
||||
#include <3ds/synchronization.h>
|
||||
#include <3ds/thread.h>
|
||||
#include <3ds/services/apt.h>
|
||||
|
||||
/* Complementary atomic operations */
|
||||
bool _AtomicCAS(u32 *ptr, int oldval, int newval);
|
||||
#define AtomicGet(ptr) __atomic_load_n((u32*)(ptr), __ATOMIC_SEQ_CST)
|
||||
#define AtomicSet(ptr, value) AtomicSwap(ptr, value)
|
||||
#define AtomicCAS(ptr, oldvalue, newvalue) _AtomicCAS((u32 *)(ptr), oldvalue, newvalue)
|
||||
void AtomicLock(int *lock);
|
||||
void AtomicUnlock(int *lock);
|
||||
|
||||
/* This code was taken from SDL2 thread implementation */
|
||||
|
||||
enum thread_state_t
|
||||
{
|
||||
THREAD_STATE_ALIVE,
|
||||
THREAD_STATE_DETACHED,
|
||||
THREAD_STATE_ZOMBIE,
|
||||
THREAD_STATE_CLEANED,
|
||||
};
|
||||
|
||||
typedef struct _thread
|
||||
{
|
||||
int threadid;
|
||||
Thread handle;
|
||||
int status;
|
||||
int state;
|
||||
size_t stacksize;
|
||||
int(* userfunc)(void *);
|
||||
void *userdata;
|
||||
void *data;
|
||||
} sysThread;
|
||||
|
||||
typedef struct _cond
|
||||
{
|
||||
RecursiveLock lock;
|
||||
int waiting;
|
||||
int signals;
|
||||
LightSemaphore wait_sem;
|
||||
LightSemaphore wait_done;
|
||||
} sysCond;
|
||||
|
||||
int sys_sem_wait(LightSemaphore *sem);
|
||||
int sys_sem_wait_timeout(LightSemaphore *sem, u32 timeout);
|
||||
int sys_sem_try_wait(LightSemaphore *sem);
|
||||
u32 sys_sem_value(LightSemaphore *sem);
|
||||
|
||||
sysThread *sys_create_thread(int(*fn)(void *), const char *name, const size_t stacksize,
|
||||
void *data IF_PRIO(, int priority) IF_COP(, unsigned int core));
|
||||
void sys_run_thread(sysThread *thread);
|
||||
void sys_wait_thread(sysThread *thread, int *status);
|
||||
int sys_thread_id(void);
|
||||
int sys_set_thread_priority(sysThread *thread, int priority);
|
||||
|
||||
sysCond *sys_cond_create(void);
|
||||
void sys_cond_destroy(sysCond *cond);
|
||||
int sys_cond_signal(sysCond *cond);
|
||||
int sys_cond_broadcast(sysCond *cond);
|
||||
int sys_cond_wait(sysCond *cond, RecursiveLock *mutex);
|
||||
#endif /* #ifndef __SYSTHREAD_H__ */
|
||||
|
||||
402
firmware/target/hosted/ctru/lib/sys_timer.c
Normal file
402
firmware/target/hosted/ctru/lib/sys_timer.c
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2006 Dan Everton
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "debug.h"
|
||||
#include "logf.h"
|
||||
|
||||
#include <3ds/os.h>
|
||||
#include "sys_thread.h"
|
||||
#include "sys_timer.h"
|
||||
|
||||
#define CACHELINE_SIZE 128
|
||||
|
||||
static bool ticks_started = false;
|
||||
static u64 start_tick;
|
||||
|
||||
#define NSEC_PER_MSEC 1000000ULL
|
||||
|
||||
void sys_ticks_init(void)
|
||||
{
|
||||
if (ticks_started) {
|
||||
return;
|
||||
}
|
||||
ticks_started = true;
|
||||
|
||||
start_tick = svcGetSystemTick();
|
||||
}
|
||||
|
||||
void sys_ticks_quit(void)
|
||||
{
|
||||
ticks_started = false;
|
||||
}
|
||||
|
||||
u64 sys_get_ticks64(void)
|
||||
{
|
||||
u64 elapsed;
|
||||
if (!ticks_started) {
|
||||
sys_ticks_init();
|
||||
}
|
||||
|
||||
elapsed = svcGetSystemTick() - start_tick;
|
||||
return elapsed / CPU_TICKS_PER_MSEC;
|
||||
}
|
||||
|
||||
u32 sys_get_ticks(void)
|
||||
{
|
||||
return (u32)(sys_get_ticks64() & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
void sys_delay(u32 ms)
|
||||
{
|
||||
svcSleepThread(ms * NSEC_PER_MSEC);
|
||||
}
|
||||
|
||||
typedef struct _timer
|
||||
{
|
||||
int timerID;
|
||||
timer_callback_ptr callback;
|
||||
void *param;
|
||||
u32 interval;
|
||||
u32 scheduled;
|
||||
int canceled;
|
||||
struct _timer *next;
|
||||
} sysTimer;
|
||||
|
||||
typedef struct _timer_map
|
||||
{
|
||||
int timerID;
|
||||
sysTimer *timer;
|
||||
struct _timer_map *next;
|
||||
} timerMap;
|
||||
|
||||
/* The timers are kept in a sorted list */
|
||||
typedef struct
|
||||
{
|
||||
/* Data used by the main thread */
|
||||
Thread thread;
|
||||
int nextID;
|
||||
timerMap *timermap;
|
||||
RecursiveLock timermap_lock;
|
||||
|
||||
/* Padding to separate cache lines between threads */
|
||||
char cache_pad[CACHELINE_SIZE];
|
||||
|
||||
/* Data used to communicate with the timer thread */
|
||||
int lock;
|
||||
LightSemaphore sem;
|
||||
sysTimer *pending;
|
||||
sysTimer *freelist;
|
||||
int active;
|
||||
|
||||
/* List of timers - this is only touched by the timer thread */
|
||||
sysTimer *timers;
|
||||
} timerData;
|
||||
|
||||
static timerData timer_data = { .active = 0 };
|
||||
|
||||
/* The idea here is that any thread might add a timer, but a single
|
||||
* thread manages the active timer queue, sorted by scheduling time.
|
||||
*
|
||||
* Timers are removed by simply setting a canceled flag
|
||||
*/
|
||||
|
||||
static void add_timer_interval(timerData *data, sysTimer *timer)
|
||||
{
|
||||
sysTimer *prev, *curr;
|
||||
|
||||
prev = NULL;
|
||||
for (curr = data->timers; curr; prev = curr, curr = curr->next) {
|
||||
if ((s32)(timer->scheduled - curr->scheduled) < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Insert the timer here! */
|
||||
if (prev) {
|
||||
prev->next = timer;
|
||||
} else {
|
||||
data->timers = timer;
|
||||
}
|
||||
timer->next = curr;
|
||||
}
|
||||
|
||||
static void timer_thread(void *_data)
|
||||
{
|
||||
timerData *data = (timerData *)_data;
|
||||
sysTimer *pending;
|
||||
sysTimer *current;
|
||||
sysTimer *freelist_head = NULL;
|
||||
sysTimer *freelist_tail = NULL;
|
||||
u32 tick, now, interval, delay;
|
||||
|
||||
/* Threaded timer loop:
|
||||
* 1. Queue timers added by other threads
|
||||
* 2. Handle any timers that should dispatch this cycle
|
||||
* 3. Wait until next dispatch time or new timer arrives
|
||||
*/
|
||||
for (;;) {
|
||||
/* Pending and freelist maintenance */
|
||||
AtomicLock(&data->lock);
|
||||
{
|
||||
/* Get any timers ready to be queued */
|
||||
pending = data->pending;
|
||||
data->pending = NULL;
|
||||
|
||||
/* Make any unused timer structures available */
|
||||
if (freelist_head) {
|
||||
freelist_tail->next = data->freelist;
|
||||
data->freelist = freelist_head;
|
||||
}
|
||||
}
|
||||
AtomicUnlock(&data->lock);
|
||||
|
||||
/* Sort the pending timers into our list */
|
||||
while (pending) {
|
||||
current = pending;
|
||||
pending = pending->next;
|
||||
add_timer_interval(data, current);
|
||||
}
|
||||
freelist_head = NULL;
|
||||
freelist_tail = NULL;
|
||||
|
||||
/* Check to see if we're still running, after maintenance */
|
||||
if (!AtomicGet(&data->active)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Initial delay if there are no timers */
|
||||
delay = (~(u32)0);
|
||||
|
||||
tick = sys_get_ticks();
|
||||
|
||||
/* Process all the pending timers for this tick */
|
||||
while (data->timers) {
|
||||
current = data->timers;
|
||||
|
||||
if ((s32)(tick - current->scheduled) < 0) {
|
||||
/* Scheduled for the future, wait a bit */
|
||||
delay = (current->scheduled - tick);
|
||||
break;
|
||||
}
|
||||
|
||||
/* We're going to do something with this timer */
|
||||
data->timers = current->next;
|
||||
|
||||
if (AtomicGet(¤t->canceled)) {
|
||||
interval = 0;
|
||||
} else {
|
||||
interval = current->callback(current->interval, current->param);
|
||||
}
|
||||
|
||||
if (interval > 0) {
|
||||
/* Reschedule this timer */
|
||||
current->interval = interval;
|
||||
current->scheduled = tick + interval;
|
||||
add_timer_interval(data, current);
|
||||
} else {
|
||||
if (!freelist_head) {
|
||||
freelist_head = current;
|
||||
}
|
||||
if (freelist_tail) {
|
||||
freelist_tail->next = current;
|
||||
}
|
||||
freelist_tail = current;
|
||||
|
||||
AtomicSet(¤t->canceled, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Adjust the delay based on processing time */
|
||||
now = sys_get_ticks();
|
||||
interval = (now - tick);
|
||||
if (interval > delay) {
|
||||
delay = 0;
|
||||
} else {
|
||||
delay -= interval;
|
||||
}
|
||||
|
||||
/* Note that each time a timer is added, this will return
|
||||
immediately, but we process the timers added all at once.
|
||||
That's okay, it just means we run through the loop a few
|
||||
extra times.
|
||||
*/
|
||||
sys_sem_wait_timeout(&data->sem, delay);
|
||||
}
|
||||
}
|
||||
|
||||
int sys_timer_init(void)
|
||||
{
|
||||
timerData *data = &timer_data;
|
||||
|
||||
if (!AtomicGet(&data->active)) {
|
||||
RecursiveLock_Init(&data->timermap_lock);
|
||||
LightSemaphore_Init(&data->sem, 0, ((s16)0x7FFF));
|
||||
AtomicSet(&data->active, 1);
|
||||
|
||||
/* Timer threads use a callback into the app, so we can't set a limited stack size here. */
|
||||
data->thread = threadCreate(timer_thread,
|
||||
data,
|
||||
32 * 1024,
|
||||
0x28,
|
||||
-1,
|
||||
false);
|
||||
if (!data->thread) {
|
||||
sys_timer_quit();
|
||||
return -1;
|
||||
}
|
||||
|
||||
AtomicSet(&data->nextID, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sys_timer_quit(void)
|
||||
{
|
||||
timerData *data = &timer_data;
|
||||
sysTimer *timer;
|
||||
timerMap *entry;
|
||||
|
||||
if (AtomicCAS(&data->active, 1, 0)) { /* active? Move to inactive. */
|
||||
/* Shutdown the timer thread */
|
||||
if (data->thread) {
|
||||
LightSemaphore_Release(&data->sem, 1);
|
||||
Result res = threadJoin(data->thread, U64_MAX);
|
||||
threadFree(data->thread);
|
||||
data->thread = NULL;
|
||||
}
|
||||
|
||||
/* Clean up the timer entries */
|
||||
while (data->timers) {
|
||||
timer = data->timers;
|
||||
data->timers = timer->next;
|
||||
free(timer);
|
||||
}
|
||||
while (data->freelist) {
|
||||
timer = data->freelist;
|
||||
data->freelist = timer->next;
|
||||
free(timer);
|
||||
}
|
||||
while (data->timermap) {
|
||||
entry = data->timermap;
|
||||
data->timermap = entry->next;
|
||||
free(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int sys_add_timer(u32 interval, timer_callback_ptr callback, void *param)
|
||||
{
|
||||
timerData *data = &timer_data;
|
||||
sysTimer *timer;
|
||||
timerMap *entry;
|
||||
|
||||
AtomicLock(&data->lock);
|
||||
if (!AtomicGet(&data->active)) {
|
||||
if (sys_timer_init() < 0) {
|
||||
AtomicUnlock(&data->lock);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
timer = data->freelist;
|
||||
if (timer) {
|
||||
data->freelist = timer->next;
|
||||
}
|
||||
AtomicUnlock(&data->lock);
|
||||
|
||||
if (timer) {
|
||||
sys_remove_timer(timer->timerID);
|
||||
} else {
|
||||
timer = (sysTimer *) malloc(sizeof(*timer));
|
||||
if (!timer) {
|
||||
DEBUGF("sys_add_timer: out of memory\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
timer->timerID = AtomicIncrement(&data->nextID);
|
||||
timer->callback = callback;
|
||||
timer->param = param;
|
||||
timer->interval = interval;
|
||||
timer->scheduled = sys_get_ticks() + interval;
|
||||
AtomicSet(&timer->canceled, 0);
|
||||
|
||||
entry = (timerMap *) malloc(sizeof(*entry));
|
||||
if (!entry) {
|
||||
free(timer);
|
||||
DEBUGF("sys_add_timer: out of memory\n");
|
||||
return 0;
|
||||
}
|
||||
entry->timer = timer;
|
||||
entry->timerID = timer->timerID;
|
||||
|
||||
RecursiveLock_Lock(&data->timermap_lock);
|
||||
entry->next = data->timermap;
|
||||
data->timermap = entry;
|
||||
RecursiveLock_Unlock(&data->timermap_lock);
|
||||
|
||||
/* Add the timer to the pending list for the timer thread */
|
||||
AtomicLock(&data->lock);
|
||||
timer->next = data->pending;
|
||||
data->pending = timer;
|
||||
AtomicUnlock(&data->lock);
|
||||
|
||||
/* Wake up the timer thread if necessary */
|
||||
LightSemaphore_Release(&data->sem, 1);
|
||||
|
||||
return entry->timerID;
|
||||
}
|
||||
|
||||
bool sys_remove_timer(int id)
|
||||
{
|
||||
timerData *data = &timer_data;
|
||||
timerMap *prev, *entry;
|
||||
bool canceled = false;
|
||||
|
||||
/* Find the timer */
|
||||
RecursiveLock_Lock(&data->timermap_lock);
|
||||
prev = NULL;
|
||||
for (entry = data->timermap; entry; prev = entry, entry = entry->next) {
|
||||
if (entry->timerID == id) {
|
||||
if (prev) {
|
||||
prev->next = entry->next;
|
||||
} else {
|
||||
data->timermap = entry->next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
RecursiveLock_Unlock(&data->timermap_lock);
|
||||
|
||||
if (entry) {
|
||||
if (!AtomicGet(&entry->timer->canceled)) {
|
||||
AtomicSet(&entry->timer->canceled, 1);
|
||||
canceled = true;
|
||||
}
|
||||
free(entry);
|
||||
}
|
||||
return canceled;
|
||||
}
|
||||
|
||||
42
firmware/target/hosted/ctru/lib/sys_timer.h
Normal file
42
firmware/target/hosted/ctru/lib/sys_timer.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||
* \/ \/ \/ \/ \/
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2025 Mauricio G.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef __SYSTIMER_H__
|
||||
#define __SYSTIMER_H__
|
||||
|
||||
#include <3ds/svc.h>
|
||||
#include "sys_thread.h"
|
||||
|
||||
typedef u32 (* timer_callback_ptr) (u32 interval, void *param);
|
||||
|
||||
void sys_ticks_init(void);
|
||||
void sys_ticks_quit(void);
|
||||
u32 sys_get_ticks(void);
|
||||
u64 sys_get_ticks64(void);
|
||||
void sys_delay(u32 ms);
|
||||
|
||||
int sys_timer_init(void);
|
||||
void sys_timer_quit(void);
|
||||
int sys_add_timer(u32 interval, timer_callback_ptr callback, void *param);
|
||||
bool sys_remove_timer(int id);
|
||||
|
||||
#endif /* #ifndef __SYSTIMER_H__ */
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue