#include "neonucleus.h" #include "ncomplib.h" #include #include static bool ncl_defaultHandler(ncl_VFSRequest *request); #ifdef NN_BAREMETAL bool ncl_defaultHandler(ncl_VFSRequest *request) { return false; // all fails } #else #include #ifdef NN_POSIX #include #include // Read me all my rights #elif defined(NN_WINDOWS) #include #include #include typedef struct ncl_WinDir { HANDLE handle; WIN32_FIND_DATAA findData; bool isFirst; } ncl_WinDir; #endif bool ncl_defaultHandler(ncl_VFSRequest *request) { if(request->action == NCL_VFS_OPEN) { char mode[3] = "rb"; mode[0] = request->open.mode[0]; FILE *f = fopen(request->open.path, mode); request->open.file = f; return f != NULL; } if(request->action == NCL_VFS_CLOSE) { fclose(request->close); return true; } if(request->action == NCL_VFS_READ) { FILE *f = request->read.file; if(feof(f)) { request->read.buf = NULL; return true; } request->read.len = fread(request->read.buf, sizeof(char), request->read.len, f); return true; } if(request->action == NCL_VFS_WRITE) { FILE *f = request->write.file; size_t written = fwrite(request->write.buf, sizeof(char), request->write.len, f); return written == request->write.len; } if(request->action == NCL_VFS_SEEK) { FILE *f = request->seek.file; nn_FSWhence wanted = request->seek.whence; int whence = SEEK_SET; if(wanted == NN_SEEK_SET) whence = SEEK_SET; if(wanted == NN_SEEK_CUR) whence = SEEK_CUR; if(wanted == NN_SEEK_END) whence = SEEK_END; // fseek takes (file, offset, whence), not (file, whence, offset). // The original code had these two arguments swapped, which caused // every seek to go to the wrong position or fail entirely. /* * We want to: seek 100 bytes from the beginning (SEEK_SET = 0) * Call: fseek(f, 0, 100) * offset=0, whence=100 * Result: whence=100 is invalid, fseek returns -1, the function returns false * We want to: seek 0 from the current position (SEEK_CUR = 1) * Call: fseek(f, 1, 0) * offset=1, whence=SEEK_SET(0) * Result: jumps to position 1 from the beginning of the file instead of staying at the same position * We want to: seek 0 from the beginning (SEEK_SET = 0) * Call: fseek(f, 0, 0) * offset=0, whence=SEEK_SET(0) * Result: works correctly by chance * We want to: seek 2 from the beginning (SEEK_SET = 0) * Call: fseek(f, 0, 2) * offset=0, whence=SEEK_END(2) * Result: Goes to the end of the file instead of position 2 */ // Original fseek signature: int fseek(FILE *stream, long offset, int whence) remains, // yet the : `if(fseek(f, whence, request->seek.off) < 0) return false;` // ...variant was wrong if(fseek(f, request->seek.off, whence) < 0) return false; request->seek.off = ftell(f); return true; } if(request->action == NCL_VFS_REMOVE) { // On Windows, remove() cannot delete directories. // We need to check if the path is a directory and use _rmdir instead. #ifdef NN_WINDOWS DWORD attrs = GetFileAttributesA(request->remove); if(attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { return _rmdir(request->remove) == 0; } #endif return remove(request->remove) == 0; } #ifdef NN_POSIX if(request->action == NCL_VFS_OPENDIR) { DIR *d = opendir(request->opendir.path); request->opendir.dir = d; return d != NULL; } if(request->action == NCL_VFS_CLOSEDIR) { DIR *d = request->closedir; closedir(d); return true; } if(request->action == NCL_VFS_READDIR) { while(1) { DIR *d = request->readdir.dir; struct dirent *ent = readdir(d); if(ent == NULL) { request->readdir.name = NULL; } else if(strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) { continue; } else { strncpy(request->readdir.name, ent->d_name, NN_MAX_PATH-1); } return true; } } if(request->action == NCL_VFS_STAT) { struct stat s; if(stat(request->stat.path, &s) != 0) { request->stat.path = NULL; return false; } ncl_Stat *stat = request->stat.stat; stat->isDirectory = S_ISDIR(s.st_mode); stat->diskSize = s.st_blocks * 512; stat->size = stat->isDirectory ? 0 : s.st_size; stat->lastModified = s.st_mtime; return true; } if(request->action == NCL_VFS_MKDIR) { return mkdir(request->mkdir, 0777) == 0; } #elif defined(NN_WINDOWS) if(request->action == NCL_VFS_OPENDIR) { // FindFirstFileA needs a glob pattern, so we append "\\*" to the path. // We allocate the wrapper struct on the heap because FindFirstFileA // fills in the first result immediately and we need to remember that. ncl_WinDir *dir = malloc(sizeof(ncl_WinDir)); if(dir == NULL) return false; char searchPath[NN_MAX_PATH + 4]; snprintf(searchPath, sizeof(searchPath), "%s\\*", request->opendir.path); dir->handle = FindFirstFileA(searchPath, &dir->findData); if(dir->handle == INVALID_HANDLE_VALUE) { free(dir); return false; } dir->isFirst = true; request->opendir.dir = dir; return true; } if(request->action == NCL_VFS_CLOSEDIR) { ncl_WinDir *dir = request->closedir; FindClose(dir->handle); free(dir); return true; } if(request->action == NCL_VFS_READDIR) { // Mirrors the POSIX readdir loop: skip "." and "..", // copy name into the caller buffer, signal end with NULL. ncl_WinDir *dir = request->readdir.dir; while(1) { if(!dir->isFirst) { if(!FindNextFileA(dir->handle, &dir->findData)) { request->readdir.name = NULL; return true; } } dir->isFirst = false; if(strcmp(dir->findData.cFileName, ".") == 0 || strcmp(dir->findData.cFileName, "..") == 0) { continue; } strncpy(request->readdir.name, dir->findData.cFileName, NN_MAX_PATH - 1); request->readdir.name[NN_MAX_PATH - 1] = '\0'; return true; } } if(request->action == NCL_VFS_STAT) { // Windows does not have st_blocks, so we approximate disk size // as the file size itself. This is less accurate than the POSIX // version but avoids needing the full Win32 file information API. struct _stat s; if(_stat(request->stat.path, &s) != 0) { request->stat.path = NULL; return false; } ncl_Stat *st = request->stat.stat; st->isDirectory = (s.st_mode & _S_IFDIR) != 0; st->diskSize = s.st_size; st->size = st->isDirectory ? 0 : s.st_size; st->lastModified = s.st_mtime; return true; } if(request->action == NCL_VFS_MKDIR) { // _mkdir on Windows does not take a permissions argument. return _mkdir(request->mkdir) == 0; } #endif return false; } #endif ncl_VFS ncl_defaultFS = (ncl_VFS) { .state = NULL, .handler = ncl_defaultHandler, #ifdef NN_WINDOWS .pathsep = '\\', #else .pathsep = '/', #endif .fileCost = NCL_FILECOST_DEFAULT, }; ncl_VFS ncl_installerFS = (ncl_VFS) { .state = NULL, .handler = ncl_defaultHandler, #ifdef NN_WINDOWS .pathsep = '\\', #else .pathsep = '/', #endif .fileCost = NCL_FILECOST_INSTALL, }; void *ncl_openfile(ncl_VFS vfs, const char *path, const char *mode) { ncl_VFSRequest req; req.state = vfs.state; req.action = NCL_VFS_OPEN; req.open.path = path; req.open.mode = mode; if(!vfs.handler(&req)) return NULL; return req.open.file; } void ncl_closefile(ncl_VFS vfs, void *file) { ncl_VFSRequest req; req.state = vfs.state; req.action = NCL_VFS_CLOSE; req.close = file; vfs.handler(&req); } bool ncl_readfile(ncl_VFS vfs, void *file, char *buf, size_t *len) { ncl_VFSRequest req; req.state = vfs.state; req.action = NCL_VFS_READ; req.read.file = file; req.read.buf = buf; req.read.len = *len; if(!vfs.handler(&req)) return false; if(req.read.buf == NULL) return false; *len = req.read.len; return true; } bool ncl_writefile(ncl_VFS vfs, void *file, const char *data, size_t len) { ncl_VFSRequest req; req.state = vfs.state; req.action = NCL_VFS_WRITE; req.write.file = file; req.write.buf = data; req.write.len = len; return vfs.handler(&req); } bool ncl_seekfile(ncl_VFS vfs, void *file, nn_FSWhence whence, int *off) { ncl_VFSRequest req; req.state = vfs.state; req.action = NCL_VFS_SEEK; req.seek.file = file; req.seek.whence = whence; req.seek.off = *off; if(!vfs.handler(&req)) return false; *off = req.seek.off; return true; } bool ncl_stat(ncl_VFS vfs, const char *path, ncl_Stat *stat) { ncl_VFSRequest req; req.state = vfs.state; req.action = NCL_VFS_STAT; req.stat.path = path; req.stat.stat = stat; if(!vfs.handler(&req)) return false; if(req.stat.path == NULL) return false; return true; } void *ncl_opendir(ncl_VFS vfs, const char *path) { ncl_VFSRequest req; req.state = vfs.state; req.action = NCL_VFS_OPENDIR; req.opendir.path = path; if(!vfs.handler(&req)) return NULL; return req.opendir.dir; } void ncl_closedir(ncl_VFS vfs, void *dir) { ncl_VFSRequest req; req.state = vfs.state; req.action = NCL_VFS_CLOSEDIR; req.closedir = dir; vfs.handler(&req); } bool ncl_readdir(ncl_VFS vfs, void *dir, char name[NN_MAX_PATH]) { ncl_VFSRequest req; req.state = vfs.state; req.action = NCL_VFS_READDIR; req.readdir.dir = dir; req.readdir.name = name; if(!vfs.handler(&req)) return false; if(req.readdir.name == NULL) return false; return true; } size_t ncl_spaceUsedIn(ncl_VFS vfs, const char *path) { ncl_Stat s; if(!ncl_stat(vfs, path, &s)) return 0; size_t spaceUsed = vfs.fileCost + s.size; if(!s.isDirectory) return spaceUsed; void *dir = ncl_opendir(vfs, path); if(dir == NULL) return spaceUsed; char name[NN_MAX_PATH]; while(ncl_readdir(vfs, dir, name)) { char subpath[NN_MAX_PATH]; snprintf(subpath, sizeof(subpath), "%s%c%s", path, vfs.pathsep, name); spaceUsed += ncl_spaceUsedIn(vfs, subpath); } ncl_closedir(vfs, dir); return spaceUsed; } size_t ncl_spaceUsedBy(ncl_VFS vfs, const char *path) { ncl_Stat s; if(!ncl_stat(vfs, path, &s)) return 0; size_t spaceUsed = s.diskSize; if(!s.isDirectory) return s.diskSize; void *dir = ncl_opendir(vfs, path); if(dir == NULL) return spaceUsed; char name[NN_MAX_PATH]; while(ncl_readdir(vfs, dir, name)) { char subpath[NN_MAX_PATH]; snprintf(subpath, sizeof(subpath), "%s%c%s", path, vfs.pathsep, name); spaceUsed += ncl_spaceUsedBy(vfs, subpath); } ncl_closedir(vfs, dir); return spaceUsed; } bool ncl_exists(ncl_VFS vfs, const char *path) { ncl_Stat s; return ncl_stat(vfs, path, &s); } bool ncl_remove(ncl_VFS vfs, const char *path) { ncl_VFSRequest req; req.state = vfs.state; req.action = NCL_VFS_REMOVE; req.remove = path; return vfs.handler(&req); } bool ncl_removeRecursive(ncl_VFS vfs, const char *path) { ncl_Stat s; if(!ncl_stat(vfs, path, &s)) return false; if(s.isDirectory) { void *dir = ncl_opendir(vfs, path); char name[NN_MAX_PATH]; while(ncl_readdir(vfs, dir, name)) { char subpath[NN_MAX_PATH]; snprintf(subpath, sizeof(subpath), "%s%c%s", path, vfs.pathsep, name); ncl_removeRecursive(vfs, subpath); } ncl_closedir(vfs, dir); } return ncl_remove(vfs, path); } bool ncl_mkdir(ncl_VFS vfs, const char *path) { ncl_VFSRequest req; req.state = vfs.state; req.action = NCL_VFS_MKDIR; req.mkdir = path; return vfs.handler(&req); } static void ncl_splitParentName(const char *path, char parent[NN_MAX_PATH], char name[NN_MAX_PATH]) { char buf[NN_MAX_PATH]; // use snprintf instead of strncpy cuz NULL terminator snprintf(buf, NN_MAX_PATH, "%s", path); char *sep = strrchr(buf, '/'); if(sep == NULL) { parent[0] = '\0'; snprintf(name, NN_MAX_PATH, "%s", path); return; } *sep = '\0'; snprintf(parent, NN_MAX_PATH, "%s", buf); snprintf(name, NN_MAX_PATH, "%s", sep + 1); } bool ncl_mkdirRecursive(ncl_VFS vfs, const char *path) { ncl_Stat s; if(ncl_stat(vfs, path, &s)) { return s.isDirectory; } char buf[NN_MAX_PATH]; // use snprintf instead of strncpy cuz NULL terminator snprintf(buf, NN_MAX_PATH, "%s", path); char *sep = strrchr(buf, vfs.pathsep); if(sep == NULL) { return ncl_mkdir(vfs, path); } *sep = '\0'; if(!ncl_mkdirRecursive(vfs, buf)) return false; return ncl_mkdir(vfs, path); } static bool ncl_copydir(ncl_VFS vfs, const char *from, const char *to) { bool created = ncl_mkdir(vfs, to); if(!created) return false; void *dir = ncl_opendir(vfs, from); if(dir == NULL) goto fail; char name[NN_MAX_PATH]; while(ncl_readdir(vfs, dir, name)) { char subpath[NN_MAX_PATH]; snprintf(subpath, NN_MAX_PATH, "%s%c%s", from, vfs.pathsep, name); char subdest[NN_MAX_PATH]; snprintf(subdest, NN_MAX_PATH, "%s%c%s", to, vfs.pathsep, name); printf("%s -> %s\n", subpath, subdest); if(!ncl_copyto(vfs, subpath, subdest)) goto fail; } ncl_closedir(vfs, dir); return true; fail: if(dir != NULL) ncl_closedir(vfs, dir); // erase all evidence //ncl_removeRecursive(vfs, to); return false; } static bool ncl_copyfile(ncl_VFS vfs, const char *from, const char *to) { // copy some files! void *src = NULL; void *dest = NULL; src = ncl_openfile(vfs, from, "r"); if(src == NULL) goto fail; dest = ncl_openfile(vfs, to, "w"); if(dest == NULL) goto fail; char buf[16384]; size_t len = 16384; while(ncl_readfile(vfs, src, buf, &len)) { if(!ncl_writefile(vfs, dest, buf, len)) goto fail; len = 16384; } ncl_closefile(vfs, src); ncl_closefile(vfs, dest); return true; fail: if(src != NULL) ncl_closefile(vfs, src); if(dest != NULL) ncl_closefile(vfs, dest); //ncl_remove(vfs, to); return false; } static bool ncl_isIllegalCopy(const char *from, const char *to) { // check if to starts with from, or from starts with to if(strncmp(from, to, strlen(from)) == 0) return true; if(strncmp(to, from, strlen(to)) == 0) return true; return false; } bool ncl_copyto(ncl_VFS vfs, const char *from, const char *to) { if(strcmp(from, to) == 0) return true; if(ncl_isIllegalCopy(from, to)) return false; // already exists if(ncl_exists(vfs, to)) return false; ncl_Stat s; // missing if(!ncl_stat(vfs, from, &s)) return false; if(s.isDirectory) return ncl_copydir(vfs, from, to); return ncl_copyfile(vfs, from, to); } typedef struct ncl_ScreenPixel { nn_codepoint codepoint; int storedFg; int storedBg; // if negative, its in palette int realFg; // if negative, its in palette int realBg; } ncl_ScreenPixel; typedef struct ncl_ScreenState { nn_Context *ctx; nn_Lock *lock; nn_ScreenConfig conf; int width; int height; int viewportWidth; int viewportHeight; char depth; int *palette; int *resolvedPalette; ncl_ScreenPixel *pixels; ncl_ScreenFlags flags; size_t keyboardCount; double brightness; char *keyboards[NCL_MAX_KEYBOARD]; } ncl_ScreenState; typedef struct nn_VRAMBuf { int width; int height; ncl_ScreenPixel pixels[]; } ncl_VRAMBuf; typedef struct ncl_GPUState { nn_Context *ctx; nn_Lock *lock; nn_GPU conf; size_t vramFree; ncl_VRAMBuf *vram[NCL_MAX_VRAMBUF]; char *screenAddress; int currentFg; int currentBg; int activeBuffer; bool isFgPalette; bool isBgPalette; } ncl_GPUState; static void ncl_freeVRAM(nn_Context *ctx, ncl_VRAMBuf *buf) { nn_free(ctx, buf, sizeof(ncl_VRAMBuf) + sizeof(ncl_ScreenPixel) * buf->width * buf->height); } static ncl_VRAMBuf *ncl_allocVRAM(nn_Context *ctx, int width, int height) { ncl_VRAMBuf *buf = nn_alloc(ctx, sizeof(ncl_VRAMBuf) + sizeof(ncl_ScreenPixel) * width * height); if(buf == NULL) return NULL; buf->width = width; buf->height = height; for(int i = 0; i < width*height; i++) { buf->pixels[i] = (ncl_ScreenPixel) { .codepoint = ' ', .storedFg = 0xFFFFFF, .storedBg = 0x000000, .realFg = 0xFFFFFF, .realBg = 0x000000, }; } return buf; } static ncl_ScreenPixel *ncl_vramPtr(ncl_VRAMBuf *buf, int x, int y) { x--; y--; if(x < 0 || y < 0 || x >= buf->width || y >= buf->height) return NULL; return &buf->pixels[x + y * buf->width]; } static ncl_ScreenPixel ncl_vramGet(ncl_VRAMBuf *buf, int x, int y) { ncl_ScreenPixel *ptr = ncl_vramPtr(buf, x, y); if(ptr != NULL) return *ptr; return (ncl_ScreenPixel) { .codepoint = ' ', .storedFg = 0xFFFFFF, .storedBg = 0x000000, .realFg = 0xFFFFFF, .realBg = 0x000000, }; } static void ncl_vramSet(ncl_VRAMBuf *buf, int x, int y, ncl_ScreenPixel pixel) { ncl_ScreenPixel *ptr = ncl_vramPtr(buf, x, y); if(ptr != NULL) *ptr = pixel; } typedef struct ncl_FSState { nn_Context *ctx; nn_Lock *lock; nn_Filesystem conf; char *path; ncl_VFS vfs; // if 0, needs to be recomputed size_t spaceUsed; // if 0, needs to be recomputed size_t realSpaceUsed; size_t usage; bool isReadonly; // all the arrays void *fds[NN_MAX_OPENFILES]; void *dirs[NN_MAX_OPENFILES]; char label[NN_MAX_LABEL]; size_t labellen; } ncl_FSState; typedef struct ncl_DriveState { nn_Context *ctx; nn_Lock *lock; nn_Drive conf; bool isReadonly; size_t usage; size_t lastSector; char *data; char label[NN_MAX_LABEL]; size_t labellen; } ncl_DriveState; typedef struct ncl_FlashState { nn_Context *ctx; nn_Lock *lock; nn_NandFlash conf; bool isReadonly; size_t usage; size_t writeCount; char *data; char label[NN_MAX_LABEL]; size_t labellen; } ncl_FlashState; typedef struct ncl_EEState { nn_Context *ctx; nn_Lock *lock; nn_EEPROM conf; bool isReadonly; size_t usage; char *code; size_t codelen; char *data; size_t datalen; char label[NN_MAX_LABEL]; size_t labellen; char archname[NN_MAX_ARCHNAME]; size_t archlen; } ncl_EEState; static void ncl_fixPath(ncl_FSState *fs, const char *path, char buf[NN_MAX_PATH]) { snprintf(buf, NN_MAX_PATH, "%s%c%s", fs->path, fs->vfs.pathsep, path); for(size_t i = 0; buf[i]; i++) { if(buf[i] == '/') buf[i] = fs->vfs.pathsep; } } // assumes locked static size_t ncl_fsGetUsage(ncl_FSState *fs) { if(fs->spaceUsed == 0) { fs->spaceUsed = ncl_spaceUsedIn(fs->vfs, fs->path); } if(fs->spaceUsed > fs->conf.spaceTotal) fs->spaceUsed = fs->conf.spaceTotal; return fs->spaceUsed; } // assumes locked static size_t ncl_fsGetRealUsage(ncl_FSState *fs) { if(fs->realSpaceUsed == 0) { fs->realSpaceUsed = ncl_spaceUsedBy(fs->vfs, fs->path); } return fs->realSpaceUsed; } // -1 on too many static int ncl_findFileDesc(void *fds[NN_MAX_OPENFILES]) { for(int i = 0; i < NN_MAX_OPENFILES; i++) { if(fds[i] == NULL) return i; } return -1; } static void *ncl_getFile(void *fds[NN_MAX_OPENFILES], int fd) { if(fd < 0 || fd > NN_MAX_OPENFILES) return NULL; return fds[fd]; } static nn_Exit ncl_fsHandler(nn_FSRequest *req) { ncl_FSState *state = req->state; nn_Context *ctx = req->ctx; nn_Computer *C = req->computer; const nn_Filesystem *fs = req->fs; if(req->action == NN_FS_DROP) { for(size_t i = 0; i < NN_MAX_OPENFILES; i++) { if(state->fds[i] != NULL) ncl_closefile(state->vfs, state->fds[i]); if(state->dirs[i] != NULL) ncl_closedir(state->vfs, state->dirs[i]); } nn_destroyLock(ctx, state->lock); nn_strfree(ctx, state->path); nn_free(ctx, state, sizeof(*state)); return NN_OK; } if(req->action == NN_FS_SPACEUSED) { nn_lock(ctx, state->lock); state->usage++; req->spaceUsed = ncl_fsGetUsage(state); nn_unlock(ctx, state->lock); return NN_OK; } if(req->action == NN_FS_GETLABEL) { nn_lock(ctx, state->lock); state->usage++; size_t len = state->labellen; if(len > req->getlabel.len) len = req->getlabel.len; memcpy(req->getlabel.buf, state->label, len); req->getlabel.len = len; nn_unlock(ctx, state->lock); return NN_OK; } if(req->action == NN_FS_SETLABEL) { nn_lock(ctx, state->lock); if(state->isReadonly) { nn_unlock(ctx, state->lock); nn_setError(C, "is readonly"); return NN_EBADCALL; } state->usage++; size_t len = req->setlabel.len; if(len > NN_MAX_LABEL) len = NN_MAX_LABEL; memcpy(state->label, req->setlabel.buf, len); state->labellen = len; nn_unlock(ctx, state->lock); return NN_OK; } if(req->action == NN_FS_ISRO) { req->isReadonly = state->isReadonly; return NN_OK; } if(req->action == NN_FS_OPEN) { nn_lock(ctx, state->lock); state->usage++; int fd = ncl_findFileDesc(state->fds); if(fd < 0) { nn_unlock(ctx, state->lock); nn_setError(C, "too many files"); return NN_EBADCALL; } const char *mode = req->open.mode; if(mode[0] != 'r' && state->isReadonly) { nn_unlock(ctx, state->lock); nn_setError(C, "is readonly"); return NN_EBADCALL; } char path[NN_MAX_PATH]; ncl_fixPath(state, req->open.path, path); size_t spaceRemaining = state->conf.spaceTotal - ncl_fsGetUsage(state); if(mode[0] == 'w' && !ncl_exists(state->vfs, path) && spaceRemaining < state->vfs.fileCost) { nn_unlock(ctx, state->lock); nn_setError(C, "out of space"); return NN_EBADCALL; } void *file = ncl_openfile(state->vfs, path, mode); if(file == NULL) { nn_unlock(ctx, state->lock); nn_setError(C, req->open.path); return NN_EBADCALL; } state->fds[fd] = file; req->fd = fd; if(mode[0] == 'w') { // file cleared state->spaceUsed = 0; state->realSpaceUsed = 0; } nn_unlock(ctx, state->lock); return NN_OK; } if(req->action == NN_FS_CLOSE) { nn_lock(ctx, state->lock); int fd = req->fd; void *file = ncl_getFile(state->fds, fd); if(file == NULL) { nn_unlock(ctx, state->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } state->fds[fd] = NULL; state->spaceUsed = 0; state->realSpaceUsed = 0; volatile ncl_VFS vfs = state->vfs; nn_unlock(ctx, state->lock); // out of lock for the most minimal of performance ncl_closefile(vfs, file); return NN_OK; } if(req->action == NN_FS_READ) { nn_lock(ctx, state->lock); state->usage++; void *file = ncl_getFile(state->fds, req->fd); if(file == NULL) { nn_unlock(ctx, state->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } if(!ncl_readfile(state->vfs, file, req->read.buf, &req->read.len)) { req->read.buf = NULL; } nn_unlock(ctx, state->lock); return NN_OK; } if(req->action == NN_FS_WRITE) { nn_lock(ctx, state->lock); state->usage++; void *file = ncl_getFile(state->fds, req->fd); if(file == NULL) { nn_unlock(ctx, state->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } size_t spaceRemaining = state->conf.spaceTotal - ncl_fsGetUsage(state); // inaccurate... if(spaceRemaining < req->write.len) { nn_unlock(ctx, state->lock); nn_setError(C, "out of space"); return NN_EBADCALL; } bool ok = ncl_writefile(state->vfs, file, req->write.buf, req->write.len); state->spaceUsed = 0; state->realSpaceUsed = 0; nn_unlock(ctx, state->lock); if(ok) return NN_OK; nn_setError(C, "write failed"); return NN_EBADCALL; } if(req->action == NN_FS_SEEK) { nn_lock(ctx, state->lock); void *file = ncl_getFile(state->fds, req->fd); if(file == NULL) { nn_unlock(ctx, state->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } bool ok = ncl_seekfile(state->vfs, file, req->seek.whence, &req->seek.off); nn_unlock(ctx, state->lock); if(ok) return NN_OK; nn_setError(C, "seek failed"); return NN_EBADCALL; } if(req->action == NN_FS_OPENDIR) { nn_lock(ctx, state->lock); state->usage++; int fd = ncl_findFileDesc(state->dirs); if(fd < 0) { nn_unlock(ctx, state->lock); nn_setError(C, "too many directories listed simultaneously"); return NN_EBADCALL; } char path[NN_MAX_PATH]; ncl_fixPath(state, req->open.path, path); void *dir = ncl_opendir(state->vfs, path); if(dir == NULL) { nn_unlock(ctx, state->lock); nn_setError(C, req->opendir); return NN_EBADCALL; } state->dirs[fd] = dir; req->fd = fd; nn_unlock(ctx, state->lock); return NN_OK; } if(req->action == NN_FS_CLOSEDIR) { nn_lock(ctx, state->lock); int fd = req->fd; void *file = ncl_getFile(state->dirs, fd); if(file == NULL) { nn_unlock(ctx, state->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } state->dirs[fd] = NULL; volatile ncl_VFS vfs = state->vfs; nn_unlock(ctx, state->lock); // out of lock for the most minimal of performance ncl_closedir(vfs, file); return NN_OK; } if(req->action == NN_FS_READDIR) { nn_lock(ctx, state->lock); int fd = req->fd; void *dir = ncl_getFile(state->dirs, fd); if(dir == NULL) { nn_unlock(ctx, state->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } char name[NN_MAX_PATH]; if(!ncl_readdir(state->vfs, dir, name)) { nn_unlock(ctx, state->lock); req->readdir.buf = NULL; return NN_OK; } char path[NN_MAX_PATH]; snprintf(path, NN_MAX_PATH, "%s%c%s%c%s", state->path, state->vfs.pathsep, req->readdir.dirpath, state->vfs.pathsep, name); ncl_Stat s; if(!ncl_stat(state->vfs, path, &s)) s.isDirectory = false; if(s.isDirectory) snprintf(req->readdir.buf, req->readdir.len, "%s/", name); else snprintf(req->readdir.buf, req->readdir.len, "%s", name); req->readdir.len = strlen(req->readdir.buf); nn_unlock(ctx, state->lock); return NN_OK; } if(req->action == NN_FS_STAT) { nn_lock(ctx, state->lock); state->usage++; char path[NN_MAX_PATH]; ncl_fixPath(state, req->stat.path, path); ncl_Stat s; if(!ncl_stat(state->vfs, path, &s)) { nn_unlock(ctx, state->lock); req->stat.path = NULL; return NN_OK; } req->stat.isDirectory = s.isDirectory; req->stat.size = s.size; req->stat.lastModified = s.lastModified; nn_unlock(ctx, state->lock); return NN_OK; } if(req->action == NN_FS_MKDIR) { nn_lock(ctx, state->lock); state->usage++; if(state->isReadonly) { nn_unlock(ctx, state->lock); nn_setError(C, "is readonly"); return NN_EBADCALL; } char path[NN_MAX_PATH]; ncl_fixPath(state, req->mkdir, path); ncl_Stat s; if(ncl_stat(state->vfs, path, &s)) { nn_unlock(ctx, state->lock); if(s.isDirectory) { return NN_OK; } nn_setError(C, "not a directory"); return NN_EBADCALL; } if(!ncl_mkdirRecursive(state->vfs, path)) { nn_unlock(ctx, state->lock); nn_setError(C, "operation failed"); return NN_EBADCALL; } size_t newSpaceUsed = ncl_spaceUsedIn(state->vfs, state->path); if(newSpaceUsed > state->conf.spaceTotal) { ncl_removeRecursive(state->vfs, path); state->spaceUsed = 0; state->realSpaceUsed = 0; nn_unlock(ctx, state->lock); nn_setError(C, "out of space"); return NN_EBADCALL; } state->spaceUsed = newSpaceUsed; state->realSpaceUsed = 0; nn_unlock(ctx, state->lock); return NN_OK; } if(req->action == NN_FS_RENAME) { if(req->rename.from[0] == '\0') { nn_setError(C, "root is forbidden"); return NN_EBADCALL; } if(req->rename.to != NULL && req->rename.to[0] == '\0') { nn_setError(C, "root is forbidden"); return NN_EBADCALL; } nn_lock(ctx, state->lock); state->usage++; if(state->isReadonly) { nn_unlock(ctx, state->lock); nn_setError(C, "is readonly"); return NN_EBADCALL; } char from[NN_MAX_PATH]; ncl_fixPath(state, req->rename.from, from); if(req->rename.to == NULL) { bool ok = ncl_removeRecursive(state->vfs, from); nn_unlock(ctx, state->lock); if(!ok) { nn_setError(C, "operation failed"); return NN_EBADCALL; } return NN_OK; } char to[NN_MAX_PATH]; ncl_fixPath(state, req->rename.to, to); // copy a to a is illegal btw if(ncl_isIllegalCopy(from, to)) { nn_unlock(ctx, state->lock); nn_setError(C, "illegal copy operation"); return NN_EBADCALL; } // matches tmpfs behavior if(ncl_exists(state->vfs, to)) { ncl_removeRecursive(state->vfs, to); } bool ok = ncl_copyto(state->vfs, from, to); if(ok) { ncl_removeRecursive(state->vfs, from); } state->spaceUsed = 0; state->realSpaceUsed = 0; nn_unlock(ctx, state->lock); if(!ok) { nn_setError(C, "operation failed"); return NN_EBADCALL; } return NN_OK; } if(C) nn_setError(C, "not implemented yet"); return NN_EBADCALL; } nn_Component *ncl_createFilesystem(nn_Universe *universe, const char *address, const char *path, const nn_Filesystem *fs, bool isReadonly) { nn_Context *ctx = nn_getUniverseContext(universe); ncl_FSState *state = nn_alloc(ctx, sizeof(*state)); if(state == NULL) return NULL; state->ctx = ctx; state->lock = nn_createLock(ctx); if(state->lock == NULL) { nn_free(ctx, state, sizeof(*state)); return NULL; } state->path = nn_strdup(ctx, path); if(state->path == NULL) { nn_destroyLock(ctx, state->lock); nn_free(ctx, state, sizeof(*state)); return NULL; } state->vfs = ncl_defaultFS; state->usage = 0; state->isReadonly = isReadonly; state->conf = *fs; state->labellen = 0; state->realSpaceUsed = 0; state->spaceUsed = 0; for(size_t i = 0; i < NN_MAX_OPENFILES; i++) { state->fds[i] = NULL; state->dirs[i] = NULL; } nn_Component *c = nn_createFilesystem(universe, address, fs, state, ncl_fsHandler); if(c == NULL) { nn_strfree(ctx, state->path); nn_destroyLock(ctx, state->lock); nn_free(ctx, state, sizeof(*state)); return NULL; } if(nn_setComponentTypeID(c, NCL_FS)) { nn_dropComponent(c); return NULL; } return c; } #define NCL_INITFILECAP (8 * NN_KiB) #define NCL_INITDIRCAP 8 #define NCL_MAXDIRSIZE 1024 #define NCL_MAXFILESIZE (1 * NN_GiB) typedef struct ncl_TmpFile { nn_Context *ctx; struct ncl_TmpFile *parent; bool isFile; size_t size; size_t cap; size_t openFD; union { char *code; struct ncl_TmpFile **files; }; char name[NN_MAX_PATH]; } ncl_TmpFile; typedef struct ncl_TmpFileDesc { ncl_TmpFile *f; size_t off; char mode; } ncl_TmpFileDesc; static ncl_TmpFile *ncl_allocTmpFile(nn_Context *ctx, const char *name, bool isFile) { size_t cap = isFile ? NCL_INITFILECAP : NCL_INITDIRCAP; ncl_TmpFile *f = nn_alloc(ctx, sizeof(*f)); if(f == NULL) return NULL; snprintf(f->name, NN_MAX_PATH, "%s", name); f->ctx = ctx; f->parent = NULL; f->isFile = isFile; f->openFD = 0; f->size = 0; f->cap = cap; if(isFile) { char *code = nn_alloc(ctx, cap); if(code == NULL) { nn_free(ctx, f, sizeof(*f)); return NULL; } f->code = code; } else { ncl_TmpFile **files = nn_alloc(ctx, sizeof(ncl_TmpFile *) * cap); if(files == NULL) { nn_free(ctx, f, sizeof(*f)); return NULL; } f->files = files; } return f; } static void ncl_freeTmpFile(ncl_TmpFile *f) { nn_Context *ctx = f->ctx; if(f->isFile) { nn_free(ctx, f->code, f->cap); } else { for(size_t i = 0; i < f->size; i++) { ncl_freeTmpFile(f->files[i]); } nn_free(ctx, f->files, f->cap * sizeof(ncl_TmpFile *)); } nn_free(ctx, f, sizeof(*f)); } static bool ncl_removeTmpFile(ncl_TmpFile *dir, ncl_TmpFile *ent) { size_t j = 0; for(size_t i = 0; i < dir->size; i++) { if(dir->files[i] != ent) { dir->files[j] = dir->files[i]; j++; } } bool removed = j < dir->size; dir->size = j; return removed; } static bool ncl_addTmpFile(ncl_TmpFile *dir, ncl_TmpFile *ent) { nn_Context *ctx = dir->ctx; if(dir->size == dir->cap) { size_t newCap = dir->cap * 2; if(newCap > NCL_MAXDIRSIZE) return false; ncl_TmpFile **files = nn_realloc(ctx, dir->files, sizeof(ncl_TmpFile *) * dir->cap, sizeof(ncl_TmpFile *) * newCap); if(files == NULL) return false; dir->cap = newCap; dir->files = files; } dir->files[dir->size] = ent; dir->size++; ent->parent = dir; return true; } // ensures minimum file capacity static bool ncl_tmpEnsureCap(ncl_TmpFile *file, size_t minCap) { if(!file->isFile) return false; if(minCap > NCL_MAXFILESIZE) return false; if(file->cap < minCap) { size_t newCap = file->cap; while(newCap < minCap) newCap *= 2; if(newCap > NCL_MAXFILESIZE) return false; char *code = nn_realloc(file->ctx, file->code, file->cap, newCap); if(code == NULL) return false; file->code = code; } return true; } static ncl_TmpFile *ncl_tmpGet(ncl_TmpFile *root, const char *path) { if(root->isFile) return NULL; char pathbuf[NN_MAX_PATH]; strncpy(pathbuf, path, NN_MAX_PATH-1); if(pathbuf[0] == '\0') { return root; } char *endOfParent = strchr(pathbuf, '/'); if(endOfParent == NULL) { for(size_t i = 0; i < root->size; i++) { if(strcmp(root->files[i]->name, pathbuf) == 0) return root->files[i]; } return NULL; } *endOfParent = '\0'; const char *subpath = endOfParent + 1; ncl_TmpFile *parent = ncl_tmpGet(root, pathbuf); if(parent == NULL) return NULL; return ncl_tmpGet(parent, subpath); } static size_t ncl_tmpSpaceUsedIn(ncl_TmpFile *f, size_t fileCost) { if(f->isFile) return fileCost + f->size; size_t spaceUsed = fileCost; for(size_t i = 0; i < f->size; f++) { spaceUsed += ncl_tmpSpaceUsedIn(f->files[i], fileCost); } return spaceUsed; } typedef struct ncl_TmpFS { nn_Context *ctx; nn_Lock *lock; nn_Filesystem conf; size_t usage; size_t fileCost; size_t spaceUsed; bool isReadonly; ncl_TmpFileDesc *fds[NN_MAX_OPENFILES]; ncl_TmpFile *root; char label[NN_MAX_LABEL]; size_t labellen; } ncl_TmpFS; static size_t ncl_tmpSpaceUsedCached(ncl_TmpFS *tmpfs) { if(tmpfs->spaceUsed == 0) { tmpfs->spaceUsed = ncl_tmpSpaceUsedIn(tmpfs->root, tmpfs->fileCost); if(tmpfs->spaceUsed > tmpfs->conf.spaceTotal) tmpfs->spaceUsed = tmpfs->conf.spaceTotal; } return tmpfs->spaceUsed; } static size_t ncl_tmpSpaceFree(ncl_TmpFS *tmpfs) { return tmpfs->conf.spaceTotal - ncl_tmpSpaceUsedCached(tmpfs); } static bool ncl_tmpMkdir(ncl_TmpFile *root, const char *path) { if(root->isFile) return false; printf("tmpfs mkdir: %s\n", path); char parent[NN_MAX_PATH]; char name[NN_MAX_PATH]; ncl_splitParentName(path, parent, name); // non-recursive for now ncl_TmpFile *dir = ncl_tmpGet(root, path); if(dir == NULL) return false; // TODO: mkdir if(dir->isFile) return false; ncl_TmpFile *f = ncl_allocTmpFile(root->ctx, name, false); if(!ncl_addTmpFile(dir, f)) { return false; } return true; } static nn_Exit ncl_tmpfsHandler(nn_FSRequest *req) { nn_Context *ctx = req->ctx; nn_Computer *C = req->computer; ncl_TmpFS *tmpfs = req->state; if(req->action == NN_FS_DROP) { ncl_freeTmpFile(tmpfs->root); for(size_t i = 0; i < NN_MAX_OPENFILES; i++) { ncl_TmpFileDesc *desc = tmpfs->fds[i]; if(desc != NULL) { nn_free(ctx, desc, sizeof(*desc)); } } nn_destroyLock(ctx, tmpfs->lock); nn_free(ctx, tmpfs, sizeof(*tmpfs)); return NN_OK; } if(req->action == NN_FS_SPACEUSED) { nn_lock(ctx, tmpfs->lock); req->spaceUsed = ncl_tmpSpaceUsedCached(tmpfs); nn_unlock(ctx, tmpfs->lock); return NN_OK; } if(req->action == NN_FS_GETLABEL) { nn_lock(ctx, tmpfs->lock); memcpy(req->getlabel.buf, tmpfs->label, tmpfs->labellen); req->getlabel.len = tmpfs->labellen; nn_unlock(ctx, tmpfs->lock); return NN_OK; } if(req->action == NN_FS_SETLABEL) { nn_lock(ctx, tmpfs->lock); if(req->setlabel.len > NN_MAX_LABEL) req->setlabel.len = NN_MAX_LABEL; memcpy(tmpfs->label, req->setlabel.buf, req->setlabel.len); tmpfs->labellen = req->setlabel.len; nn_unlock(ctx, tmpfs->lock); return NN_OK; } if(req->action == NN_FS_ISRO) { nn_lock(ctx, tmpfs->lock); req->isReadonly = tmpfs->isReadonly; nn_unlock(ctx, tmpfs->lock); return NN_OK; } if(req->action == NN_FS_OPEN) { nn_lock(ctx, tmpfs->lock); char mode = req->open.mode[0]; if(mode != 'w' && mode != 'a') mode = 'r'; int fd = ncl_findFileDesc((void **)tmpfs->fds); if(fd < 0) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "too many files open at once"); return NN_EBADCALL; } if(mode != 'r' && tmpfs->isReadonly) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "is readonly"); return NN_EBADCALL; } ncl_TmpFile *f = ncl_tmpGet(tmpfs->root, req->open.path); if(f == NULL) { if(mode == 'r') { nn_unlock(ctx, tmpfs->lock); nn_setError(C, req->open.path); return NN_EBADCALL; } if(ncl_tmpSpaceFree(tmpfs) < tmpfs->fileCost) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "out of space"); return NN_EBADCALL; } char parent[NN_MAX_PATH]; char name[NN_MAX_PATH]; ncl_splitParentName(req->open.path, parent, name); ncl_TmpFile *dir = ncl_tmpGet(tmpfs->root, parent); if(dir == NULL) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, req->open.path); return NN_EBADCALL; } if(dir->isFile) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "is a directory"); return NN_EBADCALL; } f = ncl_allocTmpFile(ctx, name, true); if(f == NULL) { nn_unlock(ctx, tmpfs->lock); return NN_ENOMEM; } if(!ncl_addTmpFile(dir, f)) { nn_unlock(ctx, tmpfs->lock); ncl_freeTmpFile(f); nn_setError(C, "too many directory entries"); return NN_EBADCALL; } tmpfs->spaceUsed += tmpfs->fileCost; } if(!f->isFile) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "is a directory"); return NN_EBADCALL; } if(mode == 'w') f->size = 0; ncl_TmpFileDesc *desc = nn_alloc(tmpfs->ctx, sizeof(*desc)); if(desc == NULL) { nn_unlock(ctx, tmpfs->lock); return NN_ENOMEM; } desc->f = f; desc->mode = mode; desc->off = mode == 'a' ? f->size : 0; tmpfs->fds[fd] = desc; f->openFD++; req->fd = fd; nn_unlock(ctx, tmpfs->lock); return NN_OK; } if(req->action == NN_FS_CLOSE || req->action == NN_FS_CLOSEDIR) { nn_lock(ctx, tmpfs->lock); ncl_TmpFileDesc *desc = ncl_getFile((void **)tmpfs->fds, req->fd); if(desc == NULL) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } desc->f->openFD--; nn_free(ctx, desc, sizeof(*desc)); tmpfs->fds[req->fd] = NULL; nn_unlock(ctx, tmpfs->lock); return NN_OK; } if(req->action == NN_FS_READ) { nn_lock(ctx, tmpfs->lock); ncl_TmpFileDesc *desc = ncl_getFile((void **)tmpfs->fds, req->fd); if(desc == NULL) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } if(!desc->f->isFile) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } size_t remaining = desc->f->size - desc->off; if(remaining == 0) { nn_unlock(ctx, tmpfs->lock); req->read.buf = NULL; return NN_OK; } if(req->read.len > remaining) req->read.len = remaining; memcpy(req->read.buf, desc->f->code + desc->off, req->read.len); desc->off += req->read.len; nn_unlock(ctx, tmpfs->lock); return NN_OK; } if(req->action == NN_FS_WRITE) { nn_lock(ctx, tmpfs->lock); if(ncl_tmpSpaceFree(tmpfs) < req->write.len) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "out of space"); return NN_EBADCALL; } ncl_TmpFileDesc *desc = ncl_getFile((void **)tmpfs->fds, req->fd); if(desc == NULL) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } if(!desc->f->isFile) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } size_t minSizeNeeded = desc->off + req->write.len; if(!ncl_tmpEnsureCap(desc->f, minSizeNeeded)) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "file too big"); return NN_EBADCALL; } memcpy(desc->f->code + desc->off, req->write.buf, req->write.len); if(desc->f->size < minSizeNeeded) desc->f->size = minSizeNeeded; desc->off = minSizeNeeded; tmpfs->spaceUsed = 0; nn_unlock(ctx, tmpfs->lock); return NN_OK; } if(req->action == NN_FS_SEEK) { nn_lock(ctx, tmpfs->lock); ncl_TmpFileDesc *desc = ncl_getFile((void **)tmpfs->fds, req->fd); if(desc == NULL) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } if(!desc->f->isFile) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } intptr_t newOff = desc->off; size_t size = desc->f->size; switch(req->seek.whence) { case NN_SEEK_SET: newOff = req->seek.off; break; case NN_SEEK_CUR: newOff += req->seek.off; break; case NN_SEEK_END: newOff = size - req->seek.off; break; } if(newOff < 0) newOff = 0; if(newOff > size) newOff = size; desc->off = newOff; nn_unlock(ctx, tmpfs->lock); return NN_OK; } if(req->action == NN_FS_OPENDIR) { nn_lock(ctx, tmpfs->lock); int fd = ncl_findFileDesc((void **)tmpfs->fds); if(fd < 0) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "too many files open at once"); return NN_EBADCALL; } ncl_TmpFile *f = ncl_tmpGet(tmpfs->root, req->opendir); if(f == NULL) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, req->open.path); return NN_EBADCALL; } if(f->isFile) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "not a directory"); return NN_EBADCALL; } ncl_TmpFileDesc *desc = nn_alloc(tmpfs->ctx, sizeof(*desc)); if(desc == NULL) { nn_unlock(ctx, tmpfs->lock); return NN_ENOMEM; } desc->f = f; desc->mode = 'd'; desc->off = 0; tmpfs->fds[fd] = desc; f->openFD++; req->fd = fd; nn_unlock(ctx, tmpfs->lock); return NN_OK; } if(req->action == NN_FS_READDIR) { nn_lock(ctx, tmpfs->lock); ncl_TmpFileDesc *desc = ncl_getFile((void **)tmpfs->fds, req->fd); if(desc == NULL) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } if(desc->f->isFile) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "bad file descriptor"); return NN_EBADCALL; } if(desc->off == desc->f->size) { nn_unlock(ctx, tmpfs->lock); req->readdir.buf = NULL; return NN_OK; } ncl_TmpFile *ent = desc->f->files[desc->off]; if(ent->isFile) { snprintf(req->readdir.buf, req->readdir.len, "%s", ent->name); } else { snprintf(req->readdir.buf, req->readdir.len, "%s/", ent->name); } req->readdir.len = strlen(req->readdir.buf); desc->off++; nn_unlock(ctx, tmpfs->lock); return NN_OK; } if(req->action == NN_FS_STAT) { nn_lock(ctx, tmpfs->lock); ncl_TmpFile *f = ncl_tmpGet(tmpfs->root, req->stat.path); if(f == NULL) { nn_unlock(ctx, tmpfs->lock); req->stat.path = NULL; return NN_OK; } req->stat.isDirectory = !f->isFile; req->stat.size = f->isFile ? f->size : 0; // TODO: give nn_Context a way to get UNIX timestamp req->stat.lastModified = 0; nn_unlock(ctx, tmpfs->lock); return NN_OK; } if(req->action == NN_FS_MKDIR) { nn_lock(ctx, tmpfs->lock); ncl_tmpMkdir(tmpfs->root, req->mkdir); nn_unlock(ctx, tmpfs->lock); return NN_OK; } if(req->action == NN_FS_RENAME) { nn_lock(ctx, tmpfs->lock); const char *fromPath = req->rename.from; const char *toPath = req->rename.to; if(toPath == NULL) { ncl_TmpFile *f = ncl_tmpGet(tmpfs->root, fromPath); if(f == NULL) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "no such file or directory"); return NN_EBADCALL; } if(f->parent == NULL) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "root is forbidden"); return NN_EBADCALL; } if(f->openFD > 0) { nn_unlock(ctx, tmpfs->lock); nn_setError(C, "entry is in use"); return NN_EBADCALL; } ncl_removeTmpFile(f->parent, f); nn_unlock(ctx, tmpfs->lock); ncl_freeTmpFile(f); return NN_OK; } nn_unlock(ctx, tmpfs->lock); nn_setError(C, "rename is not implemented yet"); return NN_EBADCALL; } if(C) nn_setError(C, "tmpfs: not implemented yet"); return NN_EBADCALL; } nn_Component *ncl_createTmpFS(nn_Universe *universe, const char *address, const nn_Filesystem *fs, size_t fileCost, bool isReadonly) { nn_Context *ctx = nn_getUniverseContext(universe); ncl_TmpFS *state = nn_alloc(ctx, sizeof(*state)); if(state == NULL) return NULL; state->ctx = ctx; state->lock = nn_createLock(ctx); if(state->lock == NULL) { nn_free(ctx, state, sizeof(*state)); return NULL; } state->root = ncl_allocTmpFile(ctx, "", false); if(state->root == NULL) { nn_free(ctx, state, sizeof(*state)); return NULL; } state->usage = 0; state->isReadonly = isReadonly; state->fileCost = fileCost; state->conf = *fs; state->labellen = 0; state->spaceUsed = 0; for(size_t i = 0; i < NN_MAX_OPENFILES; i++) { state->fds[i] = NULL; } nn_Component *c = nn_createFilesystem(universe, address, fs, state, ncl_tmpfsHandler); if(c == NULL) { ncl_freeTmpFile(state->root); nn_destroyLock(ctx, state->lock); nn_free(ctx, state, sizeof(*state)); return NULL; } if(nn_setComponentTypeID(c, NCL_TMPFS)) { nn_dropComponent(c); return NULL; } return c; } static nn_Exit ncl_drvHandler(nn_DriveRequest *request) { nn_Context *ctx = request->ctx; nn_Computer *C = request->computer; ncl_DriveState *drv = request->state; size_t ss = drv->conf.sectorSize; if(request->action == NN_DRIVE_DROP) { nn_destroyLock(ctx, drv->lock); nn_free(ctx, drv->data, drv->conf.capacity); nn_free(ctx, drv, sizeof(*drv)); return NN_OK; } if(request->action == NN_DRIVE_CURPOS) { nn_lock(ctx, drv->lock); request->curpos = drv->lastSector; nn_unlock(ctx, drv->lock); return NN_OK; } if(request->action == NN_DRIVE_ISRO) { nn_lock(ctx, drv->lock); request->readonly = drv->isReadonly; nn_unlock(ctx, drv->lock); return NN_OK; } if(request->action == NN_DRIVE_GETLABEL) { nn_lock(ctx, drv->lock); drv->usage++; memcpy(request->getlabel.buf, drv->label, drv->labellen); request->getlabel.len = drv->labellen; nn_unlock(ctx, drv->lock); return NN_OK; } if(request->action == NN_DRIVE_READSECTOR) { nn_lock(ctx, drv->lock); drv->usage++; size_t off = (request->readSector.sector - 1) * ss; memcpy(request->readSector.buf, drv->data + off, ss); drv->lastSector = request->readSector.sector; nn_unlock(ctx, drv->lock); return NN_OK; } if(C) nn_setError(C, "ncl-drive: not implemented yet"); return NN_EBADCALL; } nn_Component *ncl_createDrive(nn_Universe *universe, const char *address, const nn_Drive *drive, const char *data, size_t len, bool isReadonly) { nn_Context *ctx = nn_getUniverseContext(universe); nn_Component *c = NULL; nn_Lock *lock = NULL; char *databuf = NULL; ncl_DriveState *state = NULL; state = nn_alloc(ctx, sizeof(*state)); if(state == NULL) goto fail; lock = nn_createLock(ctx); if(lock == NULL) goto fail; databuf = nn_alloc(ctx, drive->capacity); if(databuf == NULL) goto fail; if(len > drive->capacity) len = drive->capacity; memcpy(databuf, data, len); memset(databuf + len, 0, drive->capacity - len); state->ctx = ctx; state->lock = lock; state->conf = *drive; state->usage = 0; state->labellen = 0; state->lastSector = 1; state->data = databuf; state->isReadonly = isReadonly; c = nn_createDrive(universe, address, drive, state, ncl_drvHandler); if(c == NULL) goto fail; if(nn_setComponentTypeID(c, NCL_DRIVE)) goto fail; return c; fail: if(c != NULL) { nn_dropComponent(c); return NULL; } if(lock != NULL) nn_destroyLock(ctx, lock); nn_free(ctx, databuf, drive->capacity); nn_free(ctx, state, sizeof(*state)); return NULL; } static nn_Exit ncl_flashHandler(nn_FlashRequest *request) { nn_Context *ctx = request->ctx; nn_Computer *C = request->computer; ncl_FlashState *drv = request->state; size_t ss = drv->conf.sectorSize; if(request->action == NN_FLASH_DROP) { nn_destroyLock(ctx, drv->lock); nn_free(ctx, drv->data, drv->conf.capacity); nn_free(ctx, drv, sizeof(*drv)); return NN_OK; } if(request->action == NN_FLASH_GETWRITES) { nn_lock(ctx, drv->lock); request->writeCount = drv->writeCount; nn_unlock(ctx, drv->lock); return NN_OK; } if(request->action == NN_FLASH_ISRO) { nn_lock(ctx, drv->lock); request->readonly = drv->isReadonly; nn_unlock(ctx, drv->lock); return NN_OK; } if(request->action == NN_FLASH_GETLABEL) { nn_lock(ctx, drv->lock); drv->usage++; memcpy(request->getlabel.buf, drv->label, drv->labellen); request->getlabel.len = drv->labellen; nn_unlock(ctx, drv->lock); return NN_OK; } if(request->action == NN_FLASH_READSECTOR) { nn_lock(ctx, drv->lock); drv->usage++; size_t off = (request->readsector.sec - 1) * ss; memcpy(request->readsector.buf, drv->data + off, ss); nn_unlock(ctx, drv->lock); return NN_OK; } if(request->action == NN_FLASH_WRITESECTOR) { nn_lock(ctx, drv->lock); drv->usage++; size_t off = (request->writesector.sec - 1) * ss; memcpy(drv->data + off, request->writesector.buf, ss); drv->writeCount += request->writesector.writesAdded; nn_unlock(ctx, drv->lock); return NN_OK; } if(C) nn_setError(C, "ncl-flash: not implemented yet"); return NN_EBADCALL; } nn_Component *ncl_createFlash(nn_Universe *universe, const char *address, const nn_NandFlash *flash, const char *data, size_t len, bool isReadonly) { nn_Context *ctx = nn_getUniverseContext(universe); nn_Component *c = NULL; nn_Lock *lock = NULL; char *databuf = NULL; ncl_FlashState *state = NULL; state = nn_alloc(ctx, sizeof(*state)); if(state == NULL) goto fail; lock = nn_createLock(ctx); if(lock == NULL) goto fail; databuf = nn_alloc(ctx, flash->capacity); if(databuf == NULL) goto fail; if(len > flash->capacity) len = flash->capacity; memcpy(databuf, data, len); memset(databuf + len, 0, flash->capacity - len); state->ctx = ctx; state->lock = lock; state->conf = *flash; state->usage = 0; state->labellen = 0; state->writeCount = 0; state->data = databuf; state->isReadonly = isReadonly; c = nn_createFlash(universe, address, flash, state, ncl_flashHandler); if(c == NULL) goto fail; if(nn_setComponentTypeID(c, NCL_FLASH)) goto fail; return c; fail: if(c != NULL) { nn_dropComponent(c); return NULL; } if(lock != NULL) nn_destroyLock(ctx, lock); nn_free(ctx, databuf, flash->capacity); nn_free(ctx, state, sizeof(*state)); return NULL; } static nn_Exit ncl_eepromHandler(nn_EEPROMRequest *req) { nn_Context *ctx = req->ctx; nn_Computer *C = req->computer; ncl_EEState *state = req->state; if(req->action == NN_EEPROM_DROP) { nn_destroyLock(ctx, state->lock); nn_free(ctx, state->code, req->eeprom->size); nn_free(ctx, state->data, req->eeprom->dataSize); nn_free(ctx, state, sizeof(*state)); return NN_OK; } if(req->action == NN_EEPROM_GET) { memcpy(req->buf, state->code, state->codelen); req->buflen = state->codelen; return NN_OK; } if(req->action == NN_EEPROM_GETDATA) { memcpy(req->buf, state->data, state->datalen); req->buflen = state->datalen; return NN_OK; } if(req->action == NN_EEPROM_GETLABEL) { memcpy(req->buf, state->label, state->labellen); req->buflen = state->labellen; return NN_OK; } if(req->action == NN_EEPROM_GETARCH) { memcpy(req->buf, state->archname, state->archlen); req->buflen = state->archlen; return NN_OK; } if(req->action == NN_EEPROM_SET) { memcpy(state->code, req->robuf, req->buflen); state->codelen = req->buflen; return NN_OK; } if(req->action == NN_EEPROM_SETDATA) { memcpy(state->data, req->robuf, req->buflen); state->datalen = req->buflen; return NN_OK; } if(req->action == NN_EEPROM_SETLABEL) { if(req->buflen > NN_MAX_LABEL) req->buflen = NN_MAX_LABEL; memcpy(state->label, req->robuf, req->buflen); state->labellen = req->buflen; return NN_OK; } if(req->action == NN_EEPROM_SETARCH) { if(req->buflen > NN_MAX_ARCHNAME) req->buflen = NN_MAX_ARCHNAME; memcpy(state->archname, req->robuf, req->buflen); state->archlen = req->buflen; return NN_OK; } if(C) nn_setError(C, "ncl-eeprom: not implemented yet"); return NN_EBADCALL; } nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const nn_EEPROM *eeprom, const char *code, size_t codelen, bool isReadonly) { nn_Context *ctx = nn_getUniverseContext(universe); nn_Component *c = NULL; nn_Lock *lock = NULL; char *codebuf = NULL; char *databuf = NULL; ncl_EEState *state = NULL; state = nn_alloc(ctx, sizeof(*state)); if(state == NULL) goto fail; lock = nn_createLock(ctx); if(lock == NULL) goto fail; codebuf = nn_alloc(ctx, eeprom->size); if(codebuf == NULL) goto fail; databuf = nn_alloc(ctx, eeprom->dataSize); if(databuf == NULL) goto fail; state->ctx = ctx; state->lock = lock; state->usage = 0; state->code = codebuf; state->codelen = codelen; memcpy(state->code, code, codelen); state->data = databuf; state->datalen = 0; state->labellen = 0; state->archlen = 0; c = nn_createEEPROM(universe, address, eeprom, state, ncl_eepromHandler); if(c == NULL) goto fail; if(nn_setComponentTypeID(c, NCL_EEPROM)) goto fail; return c; fail: if(c != NULL) { nn_dropComponent(c); return NULL; } if(lock != NULL) nn_destroyLock(ctx, lock); nn_free(ctx, codebuf, eeprom->size); nn_free(ctx, databuf, eeprom->dataSize); nn_free(ctx, state, sizeof(*state)); return NULL; } size_t ncl_getEEPROMData(nn_Component *component, char *buf) { const char *typeid = nn_getComponentTypeID(component); if(strcmp(typeid, NCL_EEPROM) == 0) { ncl_EEState *ee = nn_getComponentState(component); nn_lock(ee->ctx, ee->lock); memcpy(buf, ee->data, ee->datalen); size_t len = ee->datalen; nn_unlock(ee->ctx, ee->lock); return len; } return 0; } void ncl_setEEPROMData(nn_Component *component, const char *data, size_t len) { const char *typeid = nn_getComponentTypeID(component); if(strcmp(typeid, NCL_EEPROM) == 0) { ncl_EEState *ee = nn_getComponentState(component); nn_lock(ee->ctx, ee->lock); if(len > ee->conf.size) len = ee->conf.size; memcpy(ee->data, data, len); ee->datalen = len; nn_unlock(ee->ctx, ee->lock); return; } } size_t ncl_getEEPROMCode(nn_Component *component, char *buf) { const char *typeid = nn_getComponentTypeID(component); if(strcmp(typeid, NCL_EEPROM) == 0) { ncl_EEState *ee = nn_getComponentState(component); memcpy(buf, ee->code, ee->codelen); return ee->codelen; } return 0; } void ncl_setEEPROMCode(nn_Component *component, const char *data, size_t len) { const char *typeid = nn_getComponentTypeID(component); if(strcmp(typeid, NCL_EEPROM) == 0) { ncl_EEState *ee = nn_getComponentState(component); nn_lock(ee->ctx, ee->lock); memcpy(ee->code, data, len); ee->codelen = len; nn_unlock(ee->ctx, ee->lock); return; } } size_t ncl_getEEPROMArch(nn_Component *component, char buf[NN_MAX_ARCHNAME]); void ncl_setEEPROMArch(nn_Component *component, const char *arch, size_t len); size_t ncl_readDrive(nn_Component *component, size_t offset, char *buf, size_t len) { const char *typeid = nn_getComponentTypeID(component); if(strcmp(typeid, NCL_DRIVE) == 0) { ncl_DriveState *drv = nn_getComponentState(component); if(offset > drv->conf.capacity) return 0; size_t remaining = drv->conf.capacity - offset; if(remaining < len) len = remaining; nn_lock(drv->ctx, drv->lock); memcpy(buf, drv->data + offset, len); nn_unlock(drv->ctx, drv->lock); return len; } if(strcmp(typeid, NCL_FLASH) == 0) { ncl_FlashState *drv = nn_getComponentState(component); if(offset > drv->conf.capacity) return 0; size_t remaining = drv->conf.capacity - offset; if(remaining < len) len = remaining; nn_lock(drv->ctx, drv->lock); memcpy(buf, drv->data + offset, len); nn_unlock(drv->ctx, drv->lock); return len; } return 0; } void ncl_writeDrive(nn_Component *component, size_t offset, const char *buf, size_t len) { const char *typeid = nn_getComponentTypeID(component); if(strcmp(typeid, NCL_DRIVE) == 0) { ncl_DriveState *drv = nn_getComponentState(component); if(offset > drv->conf.capacity) return; size_t remaining = drv->conf.capacity - offset; if(remaining < len) len = remaining; nn_lock(drv->ctx, drv->lock); memcpy(drv->data + offset, buf, len); nn_unlock(drv->ctx, drv->lock); return; } if(strcmp(typeid, NCL_FLASH) == 0) { ncl_FlashState *drv = nn_getComponentState(component); if(offset > drv->conf.capacity) return; size_t remaining = drv->conf.capacity - offset; if(remaining < len) len = remaining; nn_lock(drv->ctx, drv->lock); memcpy(drv->data + offset, buf, len); nn_unlock(drv->ctx, drv->lock); return; } } char *ncl_getDriveBuffer(nn_Component *component, size_t *len) { const char *typeid = nn_getComponentTypeID(component); if(strcmp(typeid, NCL_DRIVE) == 0) { ncl_DriveState *drv = nn_getComponentState(component); *len = drv->conf.capacity; return drv->data; } if(strcmp(typeid, NCL_FLASH) == 0) { ncl_FlashState *drv = nn_getComponentState(component); *len = drv->conf.capacity; return drv->data; } if(len != NULL) *len = 0; return NULL; } ncl_VFS ncl_getVFS(nn_Component *component) { const char *typeid = nn_getComponentTypeID(component); if(strcmp(typeid, NCL_FS) == 0) { ncl_FSState *fs = nn_getComponentState(component); nn_lock(fs->ctx, fs->lock); ncl_VFS vfs = fs->vfs; nn_unlock(fs->ctx, fs->lock); return vfs; } return ncl_defaultFS; } ncl_VFS ncl_setVFS(nn_Component *component, ncl_VFS vfs) { ncl_VFS old = ncl_getVFS(component); const char *typeid = nn_getComponentTypeID(component); if(strcmp(typeid, NCL_FS) == 0) { ncl_FSState *fs = nn_getComponentState(component); nn_lock(fs->ctx, fs->lock); fs->vfs = vfs; nn_unlock(fs->ctx, fs->lock); return old; } return old; } static ncl_ScreenPixel ncl_getRealScreenPixel(const ncl_ScreenState *state, int x, int y) { if(x < 1 || y < 1 || x > state->width || y > state->height) { return (ncl_ScreenPixel) { .codepoint = ' ', .storedFg = 0xFFFFFF, .storedBg = 0x000000, .realFg = 0xFFFFFF, .realBg = 0x000000, }; } // make it 0-indexed x--; y--; return state->pixels[x + y * state->conf.maxWidth]; } static ncl_ScreenPixel *ncl_getRealScreenPixelPointer(const ncl_ScreenState *state, int x, int y) { if(x < 1 || y < 1 || x > state->width || y > state->height) { return NULL; } // make it 0-indexed x--; y--; return &state->pixels[x + y * state->conf.maxWidth]; } static void ncl_setRealScreenPixel(ncl_ScreenState *state, int x, int y, ncl_ScreenPixel pixel) { if(x < 1 || y < 1 || x > state->width || y > state->height) return; x--; y--; state->pixels[x + y * state->conf.maxWidth] = pixel; } static void ncl_recomputeScreen(const ncl_ScreenState *state) { for(int y = 1; y <= state->height; y++) { for(int x = 1; x <= state->width; x++) { ncl_ScreenPixel *pixel = ncl_getRealScreenPixelPointer(state, x, y); if(pixel == NULL) continue; if(pixel->realFg >= 0) { pixel->realFg = nn_mapDepth(pixel->storedFg, state->depth); } if(pixel->realBg >= 0) { pixel->realBg = nn_mapDepth(pixel->storedBg, state->depth); } } } for(int i = 0; i < state->conf.paletteColors; i++) { state->resolvedPalette[i] = nn_mapDepth(state->palette[i], state->depth); } } static nn_Exit ncl_screenHandler(nn_ScreenRequest *req) { nn_Context *ctx = req->ctx; nn_Computer *C = req->computer; ncl_ScreenState *st = req->state; if(req->action == NN_SCREEN_DROP) { for(size_t i = 0; i < st->keyboardCount; i++) nn_strfree(ctx, st->keyboards[i]); nn_destroyLock(ctx, st->lock); nn_free(ctx, st->pixels, sizeof(ncl_ScreenPixel) * st->conf.maxWidth * st->conf.maxHeight); nn_free(ctx, st->palette, sizeof(int) * st->conf.paletteColors); nn_free(ctx, st->resolvedPalette, sizeof(int) * st->conf.paletteColors); nn_free(ctx, st, sizeof(*st)); return NN_OK; } if(req->action == NN_SCREEN_ISON) { nn_lock(ctx, st->lock); req->power.isOn = (st->flags & NCL_SCREEN_ON) != 0; nn_unlock(ctx, st->lock); return NN_OK; } if(req->action == NN_SCREEN_TURNON) { nn_lock(ctx, st->lock); bool was = (st->flags & NCL_SCREEN_ON) != 0; st->flags |= NCL_SCREEN_ON; req->power.wasOn = !was; req->power.isOn = true; nn_unlock(ctx, st->lock); return NN_OK; } if(req->action == NN_SCREEN_TURNOFF) { nn_lock(ctx, st->lock); bool was = (st->flags & NCL_SCREEN_ON) != 0; st->flags &= ~NCL_SCREEN_ON; req->power.wasOn = was; req->power.isOn = false; nn_unlock(ctx, st->lock); return NN_OK; } if(req->action == NN_SCREEN_GETASPECTRATIO) { // single-block screen req->aspect.w = 1; req->aspect.h = 1; return NN_OK; } if(req->action == NN_SCREEN_GETKEYBOARDS) { nn_lock(ctx, st->lock); for(size_t i = 0; i < st->keyboardCount; i++) nn_pushstring(C, st->keyboards[i]); req->kbCount = st->keyboardCount; nn_unlock(ctx, st->lock); return NN_OK; } if(req->action == NN_SCREEN_SETPRECISE) { nn_lock(ctx, st->lock); if(req->flag) st->flags |= NCL_SCREEN_PRECISE; else st->flags &= ~NCL_SCREEN_PRECISE; req->flag = (st->flags & NCL_SCREEN_PRECISE) != 0; nn_unlock(ctx, st->lock); return NN_OK; } if(req->action == NN_SCREEN_ISPRECISE) { nn_lock(ctx, st->lock); req->flag = (st->flags & NCL_SCREEN_PRECISE) != 0; nn_unlock(ctx, st->lock); return NN_OK; } if(req->action == NN_SCREEN_SETTOUCHINVERTED) { nn_lock(ctx, st->lock); if(req->flag) st->flags |= NCL_SCREEN_TOUCHINVERTED; else st->flags &= ~NCL_SCREEN_TOUCHINVERTED; req->flag = (st->flags & NCL_SCREEN_TOUCHINVERTED) != 0; nn_unlock(ctx, st->lock); return NN_OK; } if(req->action == NN_SCREEN_ISTOUCHINVERTED) { nn_lock(ctx, st->lock); req->flag = (st->flags & NCL_SCREEN_TOUCHINVERTED) != 0; nn_unlock(ctx, st->lock); return NN_OK; } if(C) nn_setError(C, "ncl-screen: bad action"); return NN_EBADCALL; } nn_Component *ncl_createScreen(nn_Universe *universe, const char *address, const nn_ScreenConfig *config) { nn_Context *ctx = nn_getUniverseContext(universe); ncl_ScreenState *screen = NULL; ncl_ScreenPixel *pixels = NULL; int *palette = NULL; int *resolvedPalette = NULL; nn_Component *c = NULL; nn_Lock *lock = NULL; screen = nn_alloc(ctx, sizeof(ncl_ScreenState)); if(screen == NULL) goto fail; lock = nn_createLock(ctx); if(lock == NULL) goto fail; pixels = nn_alloc(ctx, sizeof(ncl_ScreenPixel) * config->maxWidth * config->maxHeight); if(pixels == NULL) goto fail; palette = nn_alloc(ctx, sizeof(int) * config->paletteColors); if(palette == NULL) goto fail; memcpy(palette, config->defaultPalette, sizeof(int) * config->paletteColors); resolvedPalette = nn_alloc(ctx, sizeof(int) * config->paletteColors); if(resolvedPalette == NULL) goto fail; screen->conf = *config; screen->ctx = ctx; screen->lock = lock; screen->width = config->maxWidth; screen->height = config->maxHeight; screen->palette = palette; screen->resolvedPalette = resolvedPalette; screen->pixels = pixels; screen->flags = NCL_SCREEN_ON; screen->depth = config->maxDepth; screen->viewportWidth = screen->width; screen->viewportHeight = screen->height; screen->keyboardCount = 0; screen->brightness = 1; ncl_resetScreen(screen); c = nn_createScreen(universe, address, config, screen, ncl_screenHandler); if(c == NULL) goto fail; if(nn_setComponentTypeID(c, NCL_SCREEN)) goto fail; return c; fail:; if(c != NULL) { nn_dropComponent(c); return NULL; } if(lock != NULL) nn_destroyLock(ctx, lock); nn_free(ctx, screen, sizeof(*screen)); nn_free(ctx, palette, sizeof(int) * config->paletteColors); nn_free(ctx, resolvedPalette, sizeof(int) * config->paletteColors); nn_free(ctx, pixels, sizeof(ncl_ScreenPixel) * config->maxWidth * config->maxHeight); return NULL; } static ncl_ScreenState *ncl_getBoundScreen(ncl_GPUState *gpu, nn_Computer *C) { if(gpu->screenAddress == NULL) return NULL; nn_Component *c = nn_getComponent(C, gpu->screenAddress); if(c == NULL) return NULL; return nn_getComponentState(c); } /* static void ncl_getGPULimits(ncl_GPUState *gpu, nn_Computer *C, int *maxWidth, int *maxHeight, char *maxDepth) { int w = gpu->conf.maxWidth, h = gpu->conf.maxHeight; char d = gpu->conf.maxDepth; ncl_ScreenState *screen = ncl_getBoundScreen(gpu, C); if(screen != NULL) { if(w > screen->conf.maxWidth) w = screen->conf.maxWidth; if(h > screen->conf.maxHeight) h = screen->conf.maxHeight; if(d > screen->conf.maxDepth) d = screen->conf.maxDepth; } *maxWidth = w; *maxHeight = h; *maxDepth = d; } */ static void ncl_getGPULimitsWithScreen( ncl_GPUState *gpu, ncl_ScreenState *screen, int *maxWidth, int *maxHeight, char *maxDepth) { int w = gpu->conf.maxWidth; int h = gpu->conf.maxHeight; char d = gpu->conf.maxDepth; if(screen != NULL) { if(w > screen->conf.maxWidth) w = screen->conf.maxWidth; if(h > screen->conf.maxHeight) h = screen->conf.maxHeight; if(d > screen->conf.maxDepth) d = screen->conf.maxDepth; } *maxWidth = w; *maxHeight = h; *maxDepth = d; } // helper: get the target buffer for the active index. // Returns NULL + sets error on failure. // If index==0 and screen is non-NULL, caller must use // the screen pixel functions instead. static ncl_VRAMBuf *ncl_getVRAMBuf( ncl_GPUState *st, int idx, nn_Computer *C) { if(idx <= 0 || idx >= NCL_MAX_VRAMBUF) { if(C) nn_setError(C, "invalid buffer index"); return NULL; } ncl_VRAMBuf *b = st->vram[idx]; if(b == NULL && C) nn_setError(C, "no such buffer"); return b; } static nn_Exit ncl_gpuHandler(nn_GPURequest *req) { nn_Context *ctx = req->ctx; nn_Computer *C = req->computer; ncl_GPUState *st = req->state; if(req->action == NN_GPU_DROP) { for(size_t i = 0; i < NCL_MAX_VRAMBUF; i++) { if(st->vram[i] != NULL) ncl_freeVRAM(ctx, st->vram[i]); } if(st->screenAddress != NULL) nn_strfree(ctx, st->screenAddress); nn_destroyLock(ctx, st->lock); nn_free(ctx, st, sizeof(*st)); return NN_OK; } // bind if(req->action == NN_GPU_BIND) { nn_Component *sc = nn_getComponent(C, req->bind.address); if(sc == NULL) { nn_setError(C, "no such component"); return NN_EBADCALL; } const char *tid = nn_getComponentTypeID(sc); if(strcmp(tid, NCL_SCREEN) != 0) { nn_setError(C, "not a screen"); return NN_EBADCALL; } nn_lock(ctx, st->lock); if(st->screenAddress != NULL) nn_strfree(ctx, st->screenAddress); st->screenAddress = nn_strdup(ctx, req->bind.address); nn_unlock(ctx, st->lock); if(req->bind.reset) { ncl_ScreenState *scr = nn_getComponentState(sc); ncl_lockScreen(scr); ncl_resetScreen(scr); ncl_unlockScreen(scr); } return NN_OK; } // getScreen if(req->action == NN_GPU_GETSCREEN) { nn_lock(ctx, st->lock); if(st->screenAddress != NULL) { size_t len = strlen(st->screenAddress); if(len >= NN_MAX_ADDRESS) len = NN_MAX_ADDRESS - 1; memcpy(req->screenAddr, st->screenAddress, len); req->screenAddr[len] = '\0'; } nn_unlock(ctx, st->lock); return NN_OK; } // getBackground if(req->action == NN_GPU_GETBG) { nn_lock(ctx, st->lock); req->color.color = st->currentBg; req->color.isPalette = st->isBgPalette; nn_unlock(ctx, st->lock); return NN_OK; } // setBackground if(req->action == NN_GPU_SETBG) { nn_lock(ctx, st->lock); ncl_ScreenState *screen = ncl_getBoundScreen(st, C); if(req->color.isPalette && screen != NULL) { if(req->color.color < 0 || req->color.color >= screen->conf.paletteColors) { nn_unlock(ctx, st->lock); nn_setError(C, "palette out of bounds"); return NN_EBADCALL; } } req->color.oldColor = st->currentBg; req->color.wasPalette = st->isBgPalette; req->color.oldPaletteIdx = st->isBgPalette ? st->currentBg : -1; st->currentBg = req->color.color; st->isBgPalette = req->color.isPalette; nn_unlock(ctx, st->lock); return NN_OK; } // getForeground if(req->action == NN_GPU_GETFG) { nn_lock(ctx, st->lock); req->color.color = st->currentFg; req->color.isPalette = st->isFgPalette; nn_unlock(ctx, st->lock); return NN_OK; } // setForeground if(req->action == NN_GPU_SETFG) { nn_lock(ctx, st->lock); ncl_ScreenState *screen = ncl_getBoundScreen(st, C); if(req->color.isPalette && screen != NULL) { if(req->color.color < 0 || req->color.color >= screen->conf.paletteColors) { nn_unlock(ctx, st->lock); nn_setError(C, "palette out of bounds"); return NN_EBADCALL; } } req->color.oldColor = st->currentFg; req->color.wasPalette = st->isFgPalette; req->color.oldPaletteIdx = st->isFgPalette ? st->currentFg : -1; st->currentFg = req->color.color; st->isFgPalette = req->color.isPalette; nn_unlock(ctx, st->lock); return NN_OK; } // getPaletteColor if(req->action == NN_GPU_GETPALETTE) { nn_lock(ctx, st->lock); ncl_ScreenState *scr = ncl_getBoundScreen(st, C); nn_unlock(ctx, st->lock); if(scr == NULL) { nn_setError(C, "no screen"); return NN_EBADCALL; } ncl_lockScreen(scr); int idx = req->palette.index; if(idx < 0 || idx >= scr->conf.paletteColors) { ncl_unlockScreen(scr); nn_setError(C, "invalid palette index"); return NN_EBADCALL; } req->palette.color = scr->palette[idx]; ncl_unlockScreen(scr); return NN_OK; } // setPaletteColor if(req->action == NN_GPU_SETPALETTE) { nn_lock(ctx, st->lock); ncl_ScreenState *scr = ncl_getBoundScreen(st, C); nn_unlock(ctx, st->lock); if(scr == NULL) { nn_setError(C, "no screen"); return NN_EBADCALL; } ncl_lockScreen(scr); int idx = req->palette.index; if(idx < 0 || idx >= scr->conf.editableColors) { ncl_unlockScreen(scr); nn_setError(C, "invalid palette index"); return NN_EBADCALL; } req->palette.oldColor = scr->palette[idx]; scr->palette[idx] = req->palette.color; scr->resolvedPalette[idx] = nn_mapDepth(req->palette.color, scr->depth); ncl_unlockScreen(scr); return NN_OK; } // maxDepth if(req->action == NN_GPU_MAXDEPTH) { nn_lock(ctx, st->lock); char d = st->conf.maxDepth; ncl_ScreenState *scr = ncl_getBoundScreen(st, C); nn_unlock(ctx, st->lock); if(scr != NULL) { ncl_lockScreen(scr); if(scr->conf.maxDepth < d) d = scr->conf.maxDepth; ncl_unlockScreen(scr); } req->depth.depth = d; return NN_OK; } // getDepth if(req->action == NN_GPU_GETDEPTH) { nn_lock(ctx, st->lock); ncl_ScreenState *scr = ncl_getBoundScreen(st, C); nn_unlock(ctx, st->lock); if(scr == NULL) { req->depth.depth = st->conf.maxDepth; return NN_OK; } ncl_lockScreen(scr); req->depth.depth = scr->depth; ncl_unlockScreen(scr); return NN_OK; } // setDepth if(req->action == NN_GPU_SETDEPTH) { char want = req->depth.depth; if(nn_depthName(want) == NULL) { nn_setError(C, "unsupported depth"); return NN_EBADCALL; } nn_lock(ctx, st->lock); int maxD = st->conf.maxDepth; ncl_ScreenState *scr = ncl_getBoundScreen(st, C); nn_unlock(ctx, st->lock); if(scr == NULL) { nn_setError(C, "no screen"); return NN_EBADCALL; } ncl_lockScreen(scr); if(scr->conf.maxDepth < maxD) maxD = scr->conf.maxDepth; if(want > maxD) { ncl_unlockScreen(scr); nn_setError(C, "unsupported depth"); return NN_EBADCALL; } req->depth.oldDepth = scr->depth; scr->depth = want; ncl_recomputeScreen(scr); ncl_unlockScreen(scr); return NN_OK; } // maxResolution if(req->action == NN_GPU_MAXRES) { nn_lock(ctx, st->lock); ncl_ScreenState *scr = ncl_getBoundScreen(st, C); nn_unlock(ctx, st->lock); int w, h; char d; if(scr != NULL) ncl_lockScreen(scr); ncl_getGPULimitsWithScreen( st, scr, &w, &h, &d); if(scr != NULL) ncl_unlockScreen(scr); req->resolution.width = w; req->resolution.height = h; return NN_OK; } // getResolution if(req->action == NN_GPU_GETRES) { nn_lock(ctx, st->lock); ncl_ScreenState *scr = ncl_getBoundScreen(st, C); nn_unlock(ctx, st->lock); if(scr == NULL) { nn_setError(C, "no screen"); return NN_EBADCALL; } ncl_lockScreen(scr); req->resolution.width = scr->width; req->resolution.height = scr->height; ncl_unlockScreen(scr); return NN_OK; } // setResolution if(req->action == NN_GPU_SETRES) { int w = req->resolution.width; int h = req->resolution.height; nn_lock(ctx, st->lock); ncl_ScreenState *scr = ncl_getBoundScreen(st, C); nn_unlock(ctx, st->lock); int maxW, maxH; char maxD; if(scr != NULL) ncl_lockScreen(scr); ncl_getGPULimitsWithScreen( st, scr, &maxW, &maxH, &maxD); if(w < 1 || h < 1 || w > maxW || h > maxH) { if(scr != NULL) ncl_unlockScreen(scr); nn_setError(C, "unsupported resolution"); return NN_EBADCALL; } if(scr == NULL) { nn_setError(C, "no screen"); return NN_EBADCALL; } scr->width = w; scr->height = h; scr->viewportWidth = w; scr->viewportHeight = h; ncl_unlockScreen(scr); return NN_OK; } // getViewport if(req->action == NN_GPU_GETVIEWPORT) { nn_lock(ctx, st->lock); ncl_ScreenState *scr = ncl_getBoundScreen(st, C); nn_unlock(ctx, st->lock); if(scr == NULL) { nn_setError(C, "no screen"); return NN_EBADCALL; } ncl_lockScreen(scr); req->resolution.width = scr->viewportWidth; req->resolution.height = scr->viewportHeight; ncl_unlockScreen(scr); return NN_OK; } // setViewport if(req->action == NN_GPU_SETVIEWPORT) { int w = req->resolution.width; int h = req->resolution.height; nn_lock(ctx, st->lock); ncl_ScreenState *scr = ncl_getBoundScreen(st, C); nn_unlock(ctx, st->lock); if(scr == NULL) { nn_setError(C, "no screen"); return NN_EBADCALL; } ncl_lockScreen(scr); if(w < 1 || h < 1 || w > scr->width || h > scr->height) { ncl_unlockScreen(scr); nn_setError(C, "viewport exceeds resolution"); return NN_EBADCALL; } scr->viewportWidth = w; scr->viewportHeight = h; ncl_unlockScreen(scr); return NN_OK; } // get if(req->action == NN_GPU_GET) { nn_lock(ctx, st->lock); int active = st->activeBuffer; ncl_ScreenState *scr = (active == 0) ? ncl_getBoundScreen(st, C) : NULL; nn_unlock(ctx, st->lock); ncl_ScreenPixel px; if(active == 0) { if(scr == NULL) { nn_setError(C, "no screen"); return NN_EBADCALL; } ncl_lockScreen(scr); px = ncl_getRealScreenPixel( scr, req->get.x, req->get.y); ncl_unlockScreen(scr); } else { nn_lock(ctx, st->lock); ncl_VRAMBuf *b = ncl_getVRAMBuf(st, active, C); if(b == NULL) { nn_unlock(ctx, st->lock); return NN_EBADCALL; } px = ncl_vramGet( b, req->get.x, req->get.y); nn_unlock(ctx, st->lock); } req->get.codepoint = px.codepoint; req->get.fg = px.storedFg; req->get.bg = px.storedBg; req->get.fgIdx = (px.realFg < 0) ? px.storedFg : -1; req->get.bgIdx = (px.realBg < 0) ? px.storedBg : -1; return NN_OK; } // set if(req->action == NN_GPU_SET) { nn_lock(ctx, st->lock); int fg = st->currentFg; int bg = st->currentBg; bool fgP = st->isFgPalette; bool bgP = st->isBgPalette; int active = st->activeBuffer; ncl_ScreenState *scr = (active == 0) ? ncl_getBoundScreen(st, C) : NULL; nn_unlock(ctx, st->lock); int x = req->set.x, y = req->set.y; const char *s = req->set.value; size_t len = req->set.len; bool vert = req->set.vertical; ncl_ScreenPixel px; px.storedFg = fg; px.storedBg = bg; px.realFg = fgP ? -1 : fg; px.realBg = bgP ? -1 : bg; if(active == 0) { if(scr == NULL) { nn_setError(C, "no screen"); return NN_EBADCALL; } ncl_lockScreen(scr); // depth-map direct colors if(!fgP) px.realFg = nn_mapDepth(fg, scr->depth); if(!bgP) px.realBg = nn_mapDepth(bg, scr->depth); size_t i = 0; while(i < len) { size_t cw = nn_unicode_validateFirstChar( s + i, len - i); if(cw == 0) { cw = 1; px.codepoint = (unsigned char)s[i]; } else px.codepoint = nn_unicode_firstCodepoint(s + i); ncl_setRealScreenPixel( scr, x, y, px); i += cw; if(vert) y++; else x++; } ncl_unlockScreen(scr); } else { nn_lock(ctx, st->lock); ncl_VRAMBuf *b = ncl_getVRAMBuf(st, active, C); if(b == NULL) { nn_unlock(ctx, st->lock); return NN_EBADCALL; } size_t i = 0; while(i < len) { size_t cw = nn_unicode_validateFirstChar( s + i, len - i); if(cw == 0) { cw = 1; px.codepoint = (unsigned char)s[i]; } else px.codepoint = nn_unicode_firstCodepoint(s + i); ncl_vramSet(b, x, y, px); i += cw; if(vert) y++; else x++; } nn_unlock(ctx, st->lock); } return NN_OK; } // copy if(req->action == NN_GPU_COPY) { int sx = req->copy.x, sy = req->copy.y; int w = req->copy.w, h = req->copy.h; int tx = req->copy.tx, ty = req->copy.ty; nn_lock(ctx, st->lock); int active = st->activeBuffer; ncl_ScreenState *scr = (active == 0) ? ncl_getBoundScreen(st, C) : NULL; nn_unlock(ctx, st->lock); // overlap-safe iteration order int y0 = (ty > 0) ? sy+h-1 : sy; int y1 = (ty > 0) ? sy-1 : sy+h; int dy = (ty > 0) ? -1 : 1; int x0 = (tx > 0) ? sx+w-1 : sx; int x1 = (tx > 0) ? sx-1 : sx+w; int dx = (tx > 0) ? -1 : 1; if(active == 0) { if(scr == NULL) { nn_setError(C, "no screen"); return NN_EBADCALL; } ncl_lockScreen(scr); for(int y = y0; y != y1; y += dy) for(int x = x0; x != x1; x += dx) { ncl_ScreenPixel p = ncl_getRealScreenPixel( scr, x, y); ncl_setRealScreenPixel( scr, x+tx, y+ty, p); } ncl_unlockScreen(scr); } else { nn_lock(ctx, st->lock); ncl_VRAMBuf *b = ncl_getVRAMBuf(st, active, C); if(b == NULL) { nn_unlock(ctx, st->lock); return NN_EBADCALL; } for(int y = y0; y != y1; y += dy) for(int x = x0; x != x1; x += dx) { ncl_ScreenPixel p = ncl_vramGet(b, x, y); ncl_vramSet(b, x+tx, y+ty, p); } nn_unlock(ctx, st->lock); } return NN_OK; } // fill if(req->action == NN_GPU_FILL) { nn_lock(ctx, st->lock); int fg = st->currentFg; int bg = st->currentBg; bool fgP = st->isFgPalette; bool bgP = st->isBgPalette; int active = st->activeBuffer; ncl_ScreenState *scr = (active == 0) ? ncl_getBoundScreen(st, C) : NULL; nn_unlock(ctx, st->lock); ncl_ScreenPixel px; px.codepoint = req->fill.codepoint; px.storedFg = fg; px.storedBg = bg; px.realFg = fgP ? -1 : fg; px.realBg = bgP ? -1 : bg; int x0 = req->fill.x, y0 = req->fill.y; int w = req->fill.w, h = req->fill.h; if(active == 0) { if(scr == NULL) { nn_setError(C, "no screen"); return NN_EBADCALL; } ncl_lockScreen(scr); if(!fgP) px.realFg = nn_mapDepth(fg, scr->depth); if(!bgP) px.realBg = nn_mapDepth(bg, scr->depth); for(int y = y0; y < y0+h; y++) for(int x = x0; x < x0+w; x++) ncl_setRealScreenPixel( scr, x, y, px); ncl_unlockScreen(scr); } else { nn_lock(ctx, st->lock); ncl_VRAMBuf *b = ncl_getVRAMBuf(st, active, C); if(b == NULL) { nn_unlock(ctx, st->lock); return NN_EBADCALL; } for(int y = y0; y < y0+h; y++) for(int x = x0; x < x0+w; x++) ncl_vramSet(b, x, y, px); nn_unlock(ctx, st->lock); } return NN_OK; } // VRAM: getActiveBuffer if(req->action == NN_GPU_GETACTIVEBUF) { nn_lock(ctx, st->lock); req->buffer.index = st->activeBuffer; nn_unlock(ctx, st->lock); return NN_OK; } // VRAM: setActiveBuffer if(req->action == NN_GPU_SETACTIVEBUF) { int idx = req->buffer.index; nn_lock(ctx, st->lock); if(idx != 0 && (idx < 0 || idx >= NCL_MAX_VRAMBUF || st->vram[idx] == NULL)) { nn_unlock(ctx, st->lock); nn_setError(C, "invalid buffer index"); return NN_EBADCALL; } st->activeBuffer = idx; nn_unlock(ctx, st->lock); return NN_OK; } // VRAM: buffers if(req->action == NN_GPU_BUFFERS) { nn_lock(ctx, st->lock); size_t count = 0; for(int i = 1; i < NCL_MAX_VRAMBUF; i++) { if(st->vram[i] != NULL) { nn_pushinteger(C, i); count++; } } nn_unlock(ctx, st->lock); req->bufCount = count; return NN_OK; } // VRAM: allocateBuffer if(req->action == NN_GPU_ALLOCBUF) { int w = req->allocBuf.w; int h = req->allocBuf.h; // default to current GPU max if 0 if(w <= 0) w = st->conf.maxWidth; if(h <= 0) h = st->conf.maxHeight; size_t cost = (size_t)w * h; nn_lock(ctx, st->lock); if(cost > st->vramFree) { nn_unlock(ctx, st->lock); nn_setError(C, "not enough video memory"); return NN_EBADCALL; } int slot = -1; for(int i = 1; i < NCL_MAX_VRAMBUF; i++) { if(st->vram[i] == NULL) { slot = i; break; } } if(slot < 0) { nn_unlock(ctx, st->lock); nn_setError(C, "too many buffers"); return NN_EBADCALL; } ncl_VRAMBuf *b = ncl_allocVRAM(ctx, w, h); if(b == NULL) { nn_unlock(ctx, st->lock); nn_setError(C, "allocation failed"); return NN_EBADCALL; } st->vram[slot] = b; st->vramFree -= cost; nn_unlock(ctx, st->lock); req->allocBuf.index = slot; return NN_OK; } // VRAM: freeBuffer if(req->action == NN_GPU_FREEBUF) { int idx = req->buffer.index; if(idx <= 0 || idx >= NCL_MAX_VRAMBUF) { nn_setError(C, "invalid buffer index"); return NN_EBADCALL; } nn_lock(ctx, st->lock); ncl_VRAMBuf *b = st->vram[idx]; if(b == NULL) { nn_unlock(ctx, st->lock); nn_setError(C, "no such buffer"); return NN_EBADCALL; } st->vramFree += (size_t)b->width * b->height; st->vram[idx] = NULL; if(st->activeBuffer == idx) st->activeBuffer = 0; nn_unlock(ctx, st->lock); ncl_freeVRAM(ctx, b); return NN_OK; } // VRAM: freeAllBuffers if(req->action == NN_GPU_FREEALLBUFS) { nn_lock(ctx, st->lock); for(int i = 1; i < NCL_MAX_VRAMBUF; i++) { if(st->vram[i] != NULL) { st->vramFree += (size_t)st->vram[i]->width * st->vram[i]->height; ncl_freeVRAM(ctx, st->vram[i]); st->vram[i] = NULL; } } st->activeBuffer = 0; nn_unlock(ctx, st->lock); return NN_OK; } // VRAM: freeMemory if(req->action == NN_GPU_FREEMEM) { nn_lock(ctx, st->lock); req->memory = st->vramFree; nn_unlock(ctx, st->lock); return NN_OK; } // VRAM: getBufferSize if(req->action == NN_GPU_GETBUFSIZE) { int idx = req->bufSize.index; if(idx == 0) { // return screen resolution nn_lock(ctx, st->lock); ncl_ScreenState *scr = ncl_getBoundScreen(st, C); nn_unlock(ctx, st->lock); if(scr == NULL) { req->bufSize.w = 0; req->bufSize.h = 0; return NN_OK; } ncl_lockScreen(scr); req->bufSize.w = scr->width; req->bufSize.h = scr->height; ncl_unlockScreen(scr); return NN_OK; } nn_lock(ctx, st->lock); ncl_VRAMBuf *b = ncl_getVRAMBuf(st, idx, C); if(b == NULL) { nn_unlock(ctx, st->lock); return NN_EBADCALL; } req->bufSize.w = b->width; req->bufSize.h = b->height; nn_unlock(ctx, st->lock); return NN_OK; } // VRAM: bitblt if(req->action == NN_GPU_BITBLT) { int dstI = req->bitblt.dst; int srcI = req->bitblt.src; int col = req->bitblt.col; int row = req->bitblt.row; int w = req->bitblt.w; int h = req->bitblt.h; int fc = req->bitblt.fromCol; int fr = req->bitblt.fromRow; nn_lock(ctx, st->lock); if(srcI == 0 && st->activeBuffer != 0) srcI = st->activeBuffer; ncl_ScreenState *scr = ncl_getBoundScreen(st, C); ncl_VRAMBuf *sb = NULL, *db = NULL; if(srcI != 0) { sb = ncl_getVRAMBuf(st, srcI, C); if(sb == NULL) { nn_unlock(ctx, st->lock); return NN_EBADCALL; } if(w == 0) w = sb->width; if(h == 0) h = sb->height; } if(dstI != 0) { db = ncl_getVRAMBuf(st, dstI, C); if(db == NULL) { nn_unlock(ctx, st->lock); return NN_EBADCALL; } } nn_unlock(ctx, st->lock); // Lock both resources once for the // entire blit, not per-pixel. bool needScreen = (srcI == 0 || dstI == 0); if(needScreen && scr != NULL) ncl_lockScreen(scr); if(sb != NULL || db != NULL) nn_lock(ctx, st->lock); for(int y = 0; y < h; y++) { for(int x = 0; x < w; x++) { ncl_ScreenPixel p; int rx = fc + x, ry = fr + y; if(srcI == 0) { if(scr == NULL) continue; p = ncl_getRealScreenPixel( scr, rx, ry); } else { p = ncl_vramGet(sb, rx, ry); } int wx = col + x, wy = row + y; if(dstI == 0) { if(scr == NULL) continue; p.realFg = nn_mapDepth( p.storedFg, scr->depth); p.realBg = nn_mapDepth( p.storedBg, scr->depth); ncl_setRealScreenPixel( scr, wx, wy, p); } else { ncl_vramSet(db, wx, wy, p); } } } if(sb != NULL || db != NULL) nn_unlock(ctx, st->lock); if(needScreen && scr != NULL) ncl_unlockScreen(scr); return NN_OK; } if(C) nn_setError(C, "ncl-gpu: not implemented"); return NN_EBADCALL; } nn_Component *ncl_createGPU( nn_Universe *universe, const char *address, const nn_GPU *gpu) { nn_Context *ctx = nn_getUniverseContext(universe); nn_Lock *lock = NULL; ncl_GPUState *state = NULL; nn_Component *c = NULL; lock = nn_createLock(ctx); if(lock == NULL) goto fail; state = nn_alloc(ctx, sizeof(*state)); if(state == NULL) goto fail; state->ctx = ctx; state->lock = lock; state->conf = *gpu; state->vramFree = gpu->totalVRAM; state->screenAddress = NULL; state->currentFg = 0xFFFFFF; state->currentBg = 0x000000; state->activeBuffer = 0; state->isFgPalette = false; state->isBgPalette = false; for(size_t i = 0; i < NCL_MAX_VRAMBUF; i++) state->vram[i] = NULL; c = nn_createGPU(universe, address, gpu, state, ncl_gpuHandler); if(c == NULL) goto fail; if(nn_setComponentTypeID(c, NCL_GPU)) goto fail; return c; fail: if(c != NULL) { nn_dropComponent(c); return NULL; } if(lock != NULL) nn_destroyLock(ctx, lock); nn_free(ctx, state, sizeof(*state)); return NULL; } void ncl_lockScreen(ncl_ScreenState *state) { nn_lock(state->ctx, state->lock); } void ncl_unlockScreen(ncl_ScreenState *state) { nn_unlock(state->ctx, state->lock); } void ncl_resetScreen(ncl_ScreenState *state) { state->width = state->conf.maxWidth; state->height = state->conf.maxHeight; state->viewportWidth = state->conf.maxWidth; state->viewportHeight = state->conf.maxHeight; for(int y = 1; y <= state->height; y++) { for(int x = 1; x <= state->width; x++) { ncl_setRealScreenPixel(state, x, y, (ncl_ScreenPixel) { .codepoint = ' ', .storedFg = 0xFFFFFF, .storedBg = 0x000000, .realFg = 0xFFFFFF, .realBg = 0x000000, }); } } memcpy(state->palette, state->conf.defaultPalette, sizeof(int) * state->conf.paletteColors); ncl_recomputeScreen(state); } void ncl_getScreenResolution(const ncl_ScreenState *state, size_t *width, size_t *height) { *width = state->width; *height = state->height; } void ncl_getScreenViewport(const ncl_ScreenState *state, size_t *width, size_t *height) { *width = state->viewportWidth; *height = state->viewportHeight; } ncl_Pixel ncl_getScreenPixel(const ncl_ScreenState *state, int x, int y) { ncl_ScreenPixel p = ncl_getRealScreenPixel(state, x, y); return (ncl_Pixel) { .codepoint = p.codepoint, .fgColor = p.realFg < 0 ? state->resolvedPalette[p.storedFg] : p.realFg, .bgColor = p.realBg < 0 ? state->resolvedPalette[p.storedBg] : p.realBg, }; } void ncl_setScreenPixel(ncl_ScreenState *state, int x, int y, nn_codepoint codepoint, int fg, int bg, bool isFgPalette, bool isBgPalette) { ncl_ScreenPixel p = { .codepoint = codepoint, .storedFg = fg, .storedBg = bg, .realFg = isFgPalette ? -1 : fg, .realBg = isBgPalette ? -1 : bg, }; ncl_setRealScreenPixel(state, x, y, p); ncl_recomputeScreen(state); } ncl_ScreenFlags ncl_getScreenFlags(const ncl_ScreenState *state) { return state->flags; } void ncl_setScreenFlags(ncl_ScreenState *state, ncl_ScreenFlags flags) { state->flags = flags; } char ncl_getScreenDepth(ncl_ScreenState *state) { return state->depth; } void ncl_setScreenDepth(ncl_ScreenState *state, char depth) { state->depth = depth; } nn_Exit ncl_mountKeyboard(ncl_ScreenState *state, const char *keyboardAddress) { nn_lock(state->ctx, state->lock); if(state->keyboardCount >= NCL_MAX_KEYBOARD) { nn_unlock(state->ctx, state->lock); return NN_ELIMIT; } char *addr = nn_strdup( state->ctx, keyboardAddress); if(addr == NULL) { nn_unlock(state->ctx, state->lock); return NN_ENOMEM; } state->keyboards[state->keyboardCount++] = addr; nn_unlock(state->ctx, state->lock); return NN_OK; } void ncl_unmountKeyboard(ncl_ScreenState *state, const char *keyboardAddress) { nn_lock(state->ctx, state->lock); size_t j = 0; for(size_t i = 0; i < state->keyboardCount; i++) { if(strcmp(state->keyboards[i], keyboardAddress) == 0) { nn_strfree(state->ctx, state->keyboards[i]); } else { state->keyboards[j++] = state->keyboards[i]; } } state->keyboardCount = j; nn_unlock(state->ctx, state->lock); } bool ncl_hasKeyboard(ncl_ScreenState *state, const char *keyboardAddress) { nn_lock(state->ctx, state->lock); for(size_t i = 0; i < state->keyboardCount; i++) { if(strcmp(state->keyboards[i], keyboardAddress) == 0) { nn_unlock(state->ctx, state->lock); return true; } } nn_unlock(state->ctx, state->lock); return false; } const char *ncl_getKeyboard(ncl_ScreenState *state, size_t idx) { if(idx >= state->keyboardCount) return NULL; return state->keyboards[idx]; } double ncl_getScreenEnergyUsage(ncl_ScreenState *state) { if((state->flags & NCL_SCREEN_ON) == 0) return 0; double sum = 0; for(int y = 1; y <= state->viewportHeight; y++) { for(int x = 1; x <= state->viewportWidth; x++) { ncl_Pixel p = ncl_getScreenPixel(state, x, y); sum += state->conf.energyPerPixel * nn_colorLuminance(p.bgColor); if(p.codepoint != 0 && p.codepoint != ' ') { sum += state->conf.energyPerPixel * nn_colorLuminance(p.fgColor); } } } return sum; } double ncl_getScreenBrightness(ncl_ScreenState *state) { return state->brightness; } void ncl_setScreenBrightness(ncl_ScreenState *state, double brightness) { state->brightness = brightness; } // general stuff bool ncl_isNCLID(const char *type) { return strncmp(NCL_PREFIX, type, strlen(NCL_PREFIX)); } bool ncl_isNCLComponent(nn_Component *component) { return ncl_isNCLID(nn_getComponentTypeID(component)); } void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat) { stat->labellen = 0; stat->isReadonly = false; const char *ty = nn_getComponentTypeID(component); void *state = nn_getComponentState(component); if(strcmp(ty, NCL_FS) == 0) { ncl_FSState *fs = state; nn_lock(fs->ctx, fs->lock); stat->isReadonly = fs->isReadonly; stat->usageCounter = fs->usage; stat->labellen = fs->labellen; memcpy(stat->label, fs->label, stat->labellen); stat->fs.spaceUsed = ncl_fsGetUsage(fs); stat->fs.realDiskUsage = ncl_fsGetRealUsage(fs); stat->fs.conf = &fs->conf; stat->fs.path = fs->path; stat->fs.filesOpen = 0; for(size_t i = 0; i < NN_MAX_OPENFILES; i++) { if(fs->fds[i] != NULL) stat->fs.filesOpen++; } nn_unlock(fs->ctx, fs->lock); return; } if(strcmp(ty, NCL_DRIVE) == 0) { ncl_DriveState *drv = state; nn_lock(drv->ctx, drv->lock); stat->isReadonly = drv->isReadonly; stat->usageCounter = drv->usage; stat->labellen = drv->labellen; memcpy(stat->label, drv->label, stat->labellen); stat->drive.lastSector = drv->lastSector; stat->drive.conf = &drv->conf; nn_unlock(drv->ctx, drv->lock); return; } if(strcmp(ty, NCL_FLASH) == 0) { ncl_FlashState *drv = state; nn_lock(drv->ctx, drv->lock); stat->isReadonly = drv->isReadonly; stat->usageCounter = drv->usage; stat->labellen = drv->labellen; memcpy(stat->label, drv->label, stat->labellen); stat->flash.currentWriteCount = drv->writeCount; // TODO: compute wear level stat->flash.wearlevel = 0; stat->flash.conf = &drv->conf; nn_unlock(drv->ctx, drv->lock); return; } if(strcmp(ty, NCL_EEPROM) == 0) { ncl_EEState *ee = state; nn_lock(ee->ctx, ee->lock); stat->isReadonly = ee->isReadonly; stat->usageCounter = ee->usage; stat->labellen = ee->labellen; memcpy(stat->label, ee->label, stat->labellen); stat->eeprom.conf = &ee->conf; stat->eeprom.codeUsed = ee->codelen; stat->eeprom.dataUsed = ee->datalen; nn_unlock(ee->ctx, ee->lock); return; } if(strcmp(ty, NCL_SCREEN) == 0) { ncl_ScreenState *screen = state; nn_lock(screen->ctx, screen->lock); stat->screen.conf = &screen->conf; stat->screen.depth = screen->depth; stat->screen.flags = screen->flags; stat->screen.keyboardCount = screen->keyboardCount; stat->screen.viewportWidth = screen->viewportWidth; stat->screen.viewportHeight = screen->viewportHeight; stat->screen.state = screen; nn_unlock(screen->ctx, screen->lock); return; } } // For EEPROMs, filesystems, drives // Returns whether it was successful or not. bool ncl_makeReadonly(nn_Component *component) { const char *ty = nn_getComponentTypeID(component); void *state = nn_getComponentState(component); if(strcmp(ty, NCL_FS) == 0) { ncl_FSState *fs = state; nn_lock(fs->ctx, fs->lock); fs->isReadonly = true; fs->usage++; nn_unlock(fs->ctx, fs->lock); return true; } if(strcmp(ty, NCL_DRIVE) == 0) { ncl_DriveState *drv = state; nn_lock(drv->ctx, drv->lock); drv->isReadonly = true; drv->usage++; nn_unlock(drv->ctx, drv->lock); return true; } if(strcmp(ty, NCL_FLASH) == 0) { ncl_FlashState *drv = state; nn_lock(drv->ctx, drv->lock); drv->isReadonly = true; drv->usage++; nn_unlock(drv->ctx, drv->lock); return true; } if(strcmp(ty, NCL_EEPROM) == 0) { ncl_EEState *ee = state; nn_lock(ee->ctx, ee->lock); ee->isReadonly = true; ee->usage++; nn_unlock(ee->ctx, ee->lock); return true; } return false; } // all of these are encoding states nn_Exit ncl_encodeComponentState(nn_Universe *universe, nn_Component *comp, ncl_EncodedState *state); void ncl_freeEncodedState(nn_Universe *universe, ncl_EncodedState *state); nn_Exit ncl_loadComponentState(nn_Component *comp, const ncl_EncodedState *state); size_t ncl_getLabel(nn_Component *c, char buf[NN_MAX_LABEL]) { const char *typeid = nn_getComponentTypeID(c); if(strcmp(typeid, NCL_EEPROM) == 0) { ncl_EEState *s = nn_getComponentState(c); nn_lock(s->ctx, s->lock); size_t len = s->labellen; memcpy(buf, s->label, len); nn_unlock(s->ctx, s->lock); return len; } if(strcmp(typeid, NCL_FS) == 0) { ncl_FSState *s = nn_getComponentState(c); nn_lock(s->ctx, s->lock); size_t len = s->labellen; memcpy(buf, s->label, len); nn_unlock(s->ctx, s->lock); return len; } if(strcmp(typeid, NCL_TMPFS) == 0) { ncl_TmpFS *s = nn_getComponentState(c); nn_lock(s->ctx, s->lock); size_t len = s->labellen; memcpy(buf, s->label, len); nn_unlock(s->ctx, s->lock); return len; } if(strcmp(typeid, NCL_DRIVE) == 0) { ncl_DriveState *s = nn_getComponentState(c); nn_lock(s->ctx, s->lock); size_t len = s->labellen; memcpy(buf, s->label, len); nn_unlock(s->ctx, s->lock); return len; } if(strcmp(typeid, NCL_FLASH) == 0) { ncl_FlashState *s = nn_getComponentState(c); nn_lock(s->ctx, s->lock); size_t len = s->labellen; memcpy(buf, s->label, len); nn_unlock(s->ctx, s->lock); return len; } return 0; } size_t ncl_setLabel(nn_Component *c, const char *label, size_t len) { if(len > NN_MAX_LABEL) len = NN_MAX_LABEL; const char *typeid = nn_getComponentTypeID(c); if(strcmp(typeid, NCL_EEPROM) == 0) { ncl_EEState *s = nn_getComponentState(c); nn_lock(s->ctx, s->lock); memcpy(s->label, label, len); s->labellen = len; nn_unlock(s->ctx, s->lock); return len; } if(strcmp(typeid, NCL_FS) == 0) { ncl_FSState *s = nn_getComponentState(c); nn_lock(s->ctx, s->lock); memcpy(s->label, label, len); s->labellen = len; nn_unlock(s->ctx, s->lock); return len; } if(strcmp(typeid, NCL_TMPFS) == 0) { ncl_TmpFS *s = nn_getComponentState(c); nn_lock(s->ctx, s->lock); memcpy(s->label, label, len); s->labellen = len; nn_unlock(s->ctx, s->lock); return len; } if(strcmp(typeid, NCL_DRIVE) == 0) { ncl_DriveState *s = nn_getComponentState(c); nn_lock(s->ctx, s->lock); memcpy(s->label, label, len); s->labellen = len; nn_unlock(s->ctx, s->lock); return len; } if(strcmp(typeid, NCL_FLASH) == 0) { ncl_FlashState *s = nn_getComponentState(c); nn_lock(s->ctx, s->lock); memcpy(s->label, label, len); s->labellen = len; nn_unlock(s->ctx, s->lock); return len; } return 0; } size_t ncl_setCLabel(nn_Component *c, const char *label) { return ncl_setLabel(c, label, strlen(label)); }