From 32f94cf02be9ec097ac7bd7857d93d42d6a81f36 Mon Sep 17 00:00:00 2001 From: IonutParau Date: Fri, 3 Apr 2026 23:58:23 +0200 Subject: [PATCH] drives, rebalancing, and a broken tmpfs impl that tmpfs impl needs to be reworked to heck and back! --- TODO.md | 11 +- build.zig | 1 + src/glyphcache.c | 2 +- src/machine.lua | 2 + src/main.c | 85 ++-- src/ncomplib.c | 1047 ++++++++++++++++++++++++++++++++++++++++++++-- src/ncomplib.h | 47 ++- src/neonucleus.c | 292 +++++++------ src/neonucleus.h | 40 +- 9 files changed, 1291 insertions(+), 236 deletions(-) diff --git a/TODO.md b/TODO.md index 44e3103..52d018f 100644 --- a/TODO.md +++ b/TODO.md @@ -1,10 +1,8 @@ # For MVP functionality -- `gpu`, `screen` class implementations - write a tester OS, basically a menu with tests to run -- move veeprom into NCL as tmpeeprom - tmpdrive -- tmpfs +- tmpfs (reork the whole thing) - device info - userdata support @@ -53,6 +51,7 @@ Not everything OC has (as a few of them are really MC-centered) but most of it. - `serial` component, for serial communications with other devices (USB?) - `iron_noteblock` component - `colorful_lamp` component +- OpenSolidState flash storage # To make it good @@ -67,3 +66,9 @@ NOTE: we're mostly bottlenecked by the architecture (typically a Lua VM) and the - make signals use a circular buffer instead of a simple array - use more arenas if possible + +# Drive costs + +The drive has platters just like in OC, as well as a cache line. +The read cost is only factored in if `cachelineOf(lastSector) != cachelineOf(newSector)`. +Seek cost is also in whole cache lines. diff --git a/build.zig b/build.zig index 19aab8f..f5622e9 100644 --- a/build.zig +++ b/build.zig @@ -195,6 +195,7 @@ pub fn build(b: *std.Build) !void { .files = &.{ "src/luaarch.c", "src/main.c", + "src/glyphcache.c", }, .flags = &.{ if (opts.baremetal) "-DNN_BAREMETAL" else "", diff --git a/src/glyphcache.c b/src/glyphcache.c index cba5a21..5ba1a60 100644 --- a/src/glyphcache.c +++ b/src/glyphcache.c @@ -209,4 +209,4 @@ int ncl_cellWidth(ncl_GlyphCache *gc) { int ncl_cellHeight(ncl_GlyphCache *gc) { return gc->fontSize; -} \ No newline at end of file +} diff --git a/src/machine.lua b/src/machine.lua index fad8294..395652b 100644 --- a/src/machine.lua +++ b/src/machine.lua @@ -3,6 +3,8 @@ -- Do not use in a serious context, you will be hacked. -- There is no sandboxing here. +os.exit = nil +os.execute = nil local sysyieldobj = {} local coroutine = coroutine diff --git a/src/main.c b/src/main.c index c490e8a..3444705 100644 --- a/src/main.c +++ b/src/main.c @@ -5,12 +5,12 @@ #include "neonucleus.h" #include "ncomplib.h" +#include "glyphcache.h" #include #include #include #include #include -#include #ifdef NN_WINDOWS #include @@ -364,31 +364,44 @@ int main(int argc, char **argv) { {"log", "log(msg: string) - Log to stdout", NN_DIRECT}, {NULL}, }; - nn_Component *ocelotCard = nn_createComponent(u, "ocelot", "ocelot"); + nn_Component *ocelotCard = nn_createComponent(u, NULL, "ocelot"); nn_setComponentMethods(ocelotCard, sandboxMethods); nn_setComponentHandler(ocelotCard, sandbox_handler); - const nn_VEEPROM veeprom = { - .code = minBIOS, - .codelen = strlen(minBIOS), - .data = NULL, - .datalen = 0, - .label = NULL, - .labellen = 0, - .arch = NULL, - .isReadonly = false, - }; + nn_Component *eepromCard = ncl_createEEPROM(u, NULL, &nn_defaultEEPROMs[3], minBIOS, strlen(minBIOS), false); - nn_Component *eepromCard = nn_createVEEPROM(u, "eeprom", &veeprom, &nn_defaultEEPROMs[3]); + char mainfspath[NN_MAX_PATH]; + snprintf(mainfspath, NN_MAX_PATH, "data/%s", mainDir); + nn_Component *managedfs = ncl_createFilesystem(u, NULL, mainfspath, &nn_defaultFilesystems[3], true); + nn_Component *tmpfs = ncl_createTmpFS(u, NULL, &nn_defaultTmpFS, NCL_FILECOST_DEFAULT, false); + nn_Component *testingfs = ncl_createTmpFS(u, NULL, &nn_defaultFilesystems[3], NCL_FILECOST_DEFAULT, false); - nn_Component *managedfs = ncl_createFilesystem(u, "mainFS", "data/openos", &nn_defaultFilesystems[3], true); + const char * const testDriveData = + "local g, s, d = component.list('gpu')(), component.list('screen')(), component.list('drive')()\n" + "component.invoke(g, 'bind', s, true)\n" + "component.invoke(g, 'set', 1, 1, 'starting...')\n" + "local start = computer.uptime()\n" + "local bc = component.invoke(d, 'getCapacity') / component.invoke(d, 'getSectorSize')\n" + "for i=1,bc do component.invoke(d, 'readSector', i) end\n" + "local now = computer.uptime()\n" + "component.invoke(g, 'set', 1, 2, 'took ' .. (now - start) .. 's')\n" + "while computer.uptime() < now + 3 do computer.pullSignal(0.05) end\n" + "computer.shutdown(true)\n" + ; + nn_Component *testDrive = ncl_createDrive(u, NULL, &nn_floppyDrive, testDriveData, strlen(testDriveData), false); + + ncl_setCLabel(managedfs, "Main Filesystem"); + ncl_setCLabel(testingfs, "Secondary Filesystem"); + ncl_setCLabel(testDrive, "Unmanaged Storage"); size_t ramTotal = 0; ramTotal += nn_ramSizes[5]; SetExitKey(KEY_NULL); - Font font = LoadFont("unscii-16-full.ttf"); + const char *fontPath = getenv("NN_FONT"); + if(fontPath == NULL) fontPath = "unscii-16-full.ttf"; + ncl_GlyphCache *gc = ncl_createGlyphCache(fontPath, 20); double tickDelay = 0.05; if(getenv("NN_TICKDELAY") != NULL) { @@ -404,7 +417,7 @@ int main(int argc, char **argv) { double wattage = 0; nn_Component *screen = ncl_createScreen(u, NULL, &nn_defaultScreens[3]); - nn_Component *gpuCard = ncl_createGPU(u, NULL, &nn_defaultGPUs[2]); + nn_Component *gpuCard = ncl_createGPU(u, NULL, &nn_defaultGPUs[3]); nn_Component *keyboard = nn_createComponent( u, "mainKB", "keyboard"); @@ -420,7 +433,6 @@ int main(int argc, char **argv) { } } - restart:; nn_Computer *c = nn_createComputer(u, NULL, "computer0", ramTotal, 256, 256); if(showStats) { @@ -434,12 +446,17 @@ restart:; nn_setArchitecture(c, &arch); nn_addSupportedArchitecture(c, &arch); + //nn_setTmpAddress(c, nn_getComponentAddress(tmpfs)); + nn_mountComponent(c, screen, -1); nn_mountComponent(c, ocelotCard, -1); + //nn_mountComponent(c, tmpfs, -1); + nn_mountComponent(c, keyboard, -1); nn_mountComponent(c, eepromCard, 0); nn_mountComponent(c, managedfs, 1); nn_mountComponent(c, gpuCard, 2); - nn_mountComponent(c, keyboard, -1); + nn_mountComponent(c, testingfs, 3); + nn_mountComponent(c, testDrive, 4); while(true) { if(WindowShouldClose()) break; @@ -448,15 +465,20 @@ restart:; // drawing the screen { - int offX = 0; - int offY = 0; - int cheight = 20; - int cwidth = MeasureText("A", cheight); - ncl_ScreenState *scrbuf = nn_getComponentState(screen); ncl_lockScreen(scrbuf); size_t scrw, scrh; - ncl_getScreenResolution(scrbuf, &scrw, &scrh); + ncl_getScreenViewport(scrbuf, &scrw, &scrh); + + int cheight = GetScreenHeight() / scrh; + if(cheight != ncl_cellHeight(gc)) { + ncl_destroyGlyphCache(gc); + gc = ncl_createGlyphCache(fontPath, cheight); + } + int cwidth = ncl_cellWidth(gc); + int offX = (GetScreenWidth() - cwidth * scrw) / 2; + int offY = (GetScreenHeight() - cheight * scrh) / 2; + for(int y = 1; y <= scrh; y++) { for(int x = 1; x <= scrw; x++) { ncl_Pixel p = ncl_getScreenPixel(scrbuf, x, y); @@ -464,11 +486,16 @@ restart:; offX + (x - 1) * cwidth, offY + (y - 1) * cheight, }; + ncl_needGlyph(gc, p.codepoint); DrawRectangle(pos.x, pos.y, cwidth, cheight, ne_processColor(p.bgColor)); - DrawTextCodepoint(font, p.codepoint, pos, cheight, ne_processColor(p.fgColor)); + if(p.codepoint != 0) { + ncl_drawGlyph(gc, p.codepoint, pos, cheight, ne_processColor(p.fgColor)); + } } } + DrawRectangleLines(offX, offY, cwidth * scrw, cheight * scrh, WHITE); ncl_unlockScreen(scrbuf); + ncl_flushGlyphs(gc); } int statY = 10; @@ -492,7 +519,8 @@ restart:; // 1: clipboard if(IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) { - nn_pushClipboard(c, "mainKB", GetClipboardText(), player); + const char *t = GetClipboardText(); + if(t != NULL) nn_pushClipboard(c, "mainKB", t, player); } while(1) { @@ -571,12 +599,15 @@ cleanup:; nn_dropComponent(ocelotCard); nn_dropComponent(eepromCard); nn_dropComponent(managedfs); + nn_dropComponent(tmpfs); + nn_dropComponent(testingfs); + nn_dropComponent(testDrive); nn_dropComponent(screen); nn_dropComponent(gpuCard); nn_dropComponent(keyboard); // rip the universe nn_destroyUniverse(u); - UnloadFont(font); + ncl_destroyGlyphCache(gc); CloseWindow(); free(sand.buf); return 0; diff --git a/src/ncomplib.c b/src/ncomplib.c index e19156e..7b2aa15 100644 --- a/src/ncomplib.c +++ b/src/ncomplib.c @@ -404,6 +404,21 @@ bool ncl_mkdir(ncl_VFS vfs, const char *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)) { @@ -455,11 +470,11 @@ static bool ncl_copyfile(ncl_VFS vfs, const char *from, const char *to) { dest = ncl_openfile(vfs, to, "w"); if(dest == NULL) goto fail; - char buf[NN_MAX_READ]; - size_t len = NN_MAX_READ; + char buf[16384]; + size_t len = 16384; while(ncl_readfile(vfs, src, buf, &len)) { if(!ncl_writefile(vfs, dest, buf, len)) goto fail; - len = NN_MAX_READ; + len = 16384; } ncl_closefile(vfs, src); ncl_closefile(vfs, dest); @@ -608,11 +623,10 @@ typedef struct ncl_DriveState { nn_Context *ctx; nn_Lock *lock; nn_Drive conf; - ncl_VFS vfs; bool isReadonly; size_t usage; size_t lastSector; - char *path; + char *data; char label[NN_MAX_LABEL]; size_t labellen; } ncl_DriveState; @@ -621,16 +635,16 @@ typedef struct ncl_EEState { nn_Context *ctx; nn_Lock *lock; nn_EEPROM conf; - ncl_VFS vfs; bool isReadonly; size_t usage; - char *codepath; - // stored data buffer + char *code; + size_t codelen; char *data; - // the data length 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]) { @@ -915,6 +929,11 @@ static nn_Exit ncl_fsHandler(nn_FSRequest *req) { 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; @@ -934,6 +953,8 @@ static nn_Exit ncl_fsHandler(nn_FSRequest *req) { 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; @@ -954,6 +975,11 @@ static nn_Exit ncl_fsHandler(nn_FSRequest *req) { } 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) { @@ -1030,27 +1056,896 @@ nn_Component *ncl_createFilesystem(nn_Universe *universe, const char *address, c nn_free(ctx, state, sizeof(*state)); return NULL; } - // TODO: handle OOM case - nn_setComponentTypeID(c, NCL_FS); + if(nn_setComponentTypeID(c, NCL_FS)) { + nn_dropComponent(c); + return NULL; + } return c; } -nn_Component *ncl_createTmpFS(nn_Universe *universe, const nn_Filesystem *fs); +#define NCL_INITFILECAP (8 * NN_KiB) +#define NCL_INITDIRCAP 8 -nn_Component *ncl_createDrive(nn_Universe *universe, const char *address, const char *path, const nn_Drive *drive, bool isReadonly); -nn_Component *ncl_createTmpDrive(nn_Universe *universe, const nn_EEPROM *eeprom, const char *data, size_t datalen); -nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const char *path, bool isReadonly); -nn_Component *ncl_createTmpEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, const char *code, size_t codelen); +#define NCL_MAXDIRSIZE 1024 +#define NCL_MAXFILESIZE (1 * NN_GiB) -size_t ncl_getEEPROMData(nn_Component *component, char *buf); -void ncl_setEEPROMData(nn_Component *component, const char *data, size_t len); +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; -ncl_VFS ncl_getVFS(nn_Component *component); +typedef struct ncl_TmpFileDesc { + ncl_TmpFile *f; + size_t off; + char mode; +} ncl_TmpFileDesc; -ncl_VFS ncl_setVFS(nn_Component *component, ncl_VFS vfs); +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); + 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); + if(ncl_tmpSpaceFree(tmpfs) < tmpfs->fileCost) { + nn_unlock(ctx, tmpfs->lock); + nn_setError(C, "out of space"); + return NN_EBADCALL; + } + if(!ncl_tmpMkdir(tmpfs->root, req->mkdir)) { + nn_unlock(ctx, tmpfs->lock); + nn_setError(C, "operation failed"); + return NN_EBADCALL; + } + 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_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_GETLABEL) { + nn_lock(ctx, drv->lock); + 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); + 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_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_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; + } + 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; + } +} + +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(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) { + if(x < 1 || y < 1 || x > state->width || y > state->height) { return (ncl_ScreenPixel) { .codepoint = ' ', .storedFg = 0xFFFFFF, @@ -1068,7 +1963,7 @@ static ncl_ScreenPixel ncl_getRealScreenPixel(const ncl_ScreenState *state, int } static ncl_ScreenPixel *ncl_getRealScreenPixelPointer(const ncl_ScreenState *state, int x, int y) { - if(x < 1 || y < 1 || x >= state->width || y >= state->height) { + if(x < 1 || y < 1 || x > state->width || y > state->height) { return NULL; } @@ -1080,7 +1975,7 @@ static ncl_ScreenPixel *ncl_getRealScreenPixelPointer(const ncl_ScreenState *sta } 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; + if(x < 1 || y < 1 || x > state->width || y > state->height) return; x--; y--; @@ -1400,6 +2295,14 @@ static nn_Exit ncl_gpuHandler(nn_GPURequest *req) { // 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 = @@ -1420,6 +2323,14 @@ static nn_Exit ncl_gpuHandler(nn_GPURequest *req) { // 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 = @@ -1591,10 +2502,8 @@ static nn_Exit ncl_gpuHandler(nn_GPURequest *req) { } scr->width = w; scr->height = h; - if(scr->viewportWidth > w) - scr->viewportWidth = w; - if(scr->viewportHeight > h) - scr->viewportHeight = h; + scr->viewportWidth = w; + scr->viewportHeight = h; ncl_unlockScreen(scr); return NN_OK; } @@ -2317,7 +3226,6 @@ void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat) { stat->usageCounter = drv->usage; stat->labellen = drv->labellen; memcpy(stat->label, drv->label, stat->labellen); - stat->drive.path = drv->path; stat->drive.lastSector = drv->lastSector; stat->drive.conf = &drv->conf; nn_unlock(drv->ctx, drv->lock); @@ -2331,7 +3239,8 @@ void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat) { stat->labellen = ee->labellen; memcpy(stat->label, ee->label, stat->labellen); stat->eeprom.conf = &ee->conf; - stat->eeprom.codepath = ee->codepath; + stat->eeprom.codeUsed = ee->codelen; + stat->eeprom.dataUsed = ee->datalen; nn_unlock(ee->ctx, ee->lock); return; } @@ -2388,5 +3297,81 @@ nn_Exit ncl_encodeComponentState(nn_Universe *universe, nn_Component *comp, ncl_ 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]); -size_t ncl_setLabel(nn_Component *c, const char *label, size_t len); +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; + } + 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; + } + return 0; +} + +size_t ncl_setCLabel(nn_Component *c, const char *label) { + return ncl_setLabel(c, label, strlen(label)); +} diff --git a/src/ncomplib.h b/src/ncomplib.h index 24dc65f..50995c6 100644 --- a/src/ncomplib.h +++ b/src/ncomplib.h @@ -11,9 +11,7 @@ #define NCL_GPU "ncl-gpu" #define NCL_SCREEN "ncl-screen" -#define NCL_TMPEEPROM "ncl-tmpeeprom" #define NCL_TMPFS "ncl-tmpfs" -#define NCL_TMPDRIVE "ncl-tmpdrive" // Default file cost. // This is for a normal HDD/floppy. @@ -194,34 +192,30 @@ nn_Exit ncl_loadComponentState(nn_Component *comp, const ncl_EncodedState *state size_t ncl_getLabel(nn_Component *c, char buf[NN_MAX_LABEL]); size_t ncl_setLabel(nn_Component *c, const char *label, size_t len); +size_t ncl_setCLabel(nn_Component *c, const char *label); nn_Component *ncl_createFilesystem(nn_Universe *universe, const char *address, const char *path, const nn_Filesystem *fs, bool isReadonly); -#define NCL_VFS_NAMEMAX 32 - // Creates a tmpfs. // This component is mostly treated like a normal filesystem, -// except you obviously cannot bind a tmpfs to it. +// except you obviously cannot bind a vfs to it. // Do note, it is illegal to mix encoded state between normal filesystem // and tmpfs. -nn_Component *ncl_createTmpFS(nn_Universe *universe, const nn_Filesystem *fs); +nn_Component *ncl_createTmpFS(nn_Universe *universe, const char *address, const nn_Filesystem *fs, size_t fileCost, bool isReadonly); -nn_Component *ncl_createDrive(nn_Universe *universe, const char *address, const char *path, const nn_Drive *drive, bool isReadonly); - -// creates a temporary drive, with some initial data -nn_Component *ncl_createTmpDrive(nn_Universe *universe, const nn_EEPROM *eeprom, const char *data, size_t datalen); +// this drive has its data in RAM. +// However, the data is not encoded in its state. +// Remember to read the entire drive and save it somewhere before dropping it. +nn_Component *ncl_createDrive(nn_Universe *universe, const char *address, const nn_Drive *drive, const char *data, size_t len, bool isReadonly); // data is stored interally -nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const char *path, bool isReadonly); -// creates a temporary EEPROM, with some initial code -// the data is stored internally -nn_Component *ncl_createTmpEEPROM(nn_Universe *universe, const nn_EEPROM *eeprom, const char *code, size_t codelen); +nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const nn_EEPROM *eeprom, const char *code, size_t codelen, bool isReadonly); -// Gets the VFS bound to a filesystem, drive or eeprom. +// Gets the VFS bound to a filesystem or drive. // Returns the default FS if the component is not recognized. ncl_VFS ncl_getVFS(nn_Component *component); -// Sets the VFS bound to a filesystem, drive or eeprom. +// Sets the VFS bound to a filesystem or drive. // This determines the filesystem the operations are run in. // Returns the old VFS. ncl_VFS ncl_setVFS(nn_Component *component, ncl_VFS vfs); @@ -263,7 +257,6 @@ typedef struct ncl_ComponentStat { const nn_EEPROM *conf; size_t codeUsed; size_t dataUsed; - const char *codepath; } eeprom; struct { const nn_Filesystem *conf; @@ -275,7 +268,6 @@ typedef struct ncl_ComponentStat { struct { const nn_Drive *conf; size_t lastSector; - const char *path; } drive; struct { const nn_GPU *conf; @@ -308,6 +300,25 @@ bool ncl_makeReadonly(nn_Component *component); size_t ncl_getEEPROMData(nn_Component *component, char *buf); void ncl_setEEPROMData(nn_Component *component, const char *data, size_t len); +// Returns the amount of data written. +// The capacity MUST be at least the size of the EEPROM. +size_t ncl_getEEPROMCode(nn_Component *component, char *buf); +void ncl_setEEPROMCode(nn_Component *component, const char *data, size_t len); + +size_t ncl_getEEPROMArch(nn_Component *component, char buf[NN_MAX_ARCHNAME]); +void ncl_setEEPROMArch(nn_Component *component, const char *arch, size_t len); + +// Reads part of a drive. +// Off is 0-indexed. +size_t ncl_readDrive(nn_Component *component, size_t offset, char *buf, size_t len); +// Writes to part of a drive. +// Off is 0-indexed. +void ncl_writeDrive(nn_Component *component, size_t offset, const char *buf, size_t len); +// Returns the internal memory buffer with the drive data, +// and if len is not NULL, will also write the capacity. +// This is in case you do not want to risk OOM while saving the state. +char *ncl_getDriveBuffer(nn_Component *component, size_t *len); + void ncl_lockScreen(ncl_ScreenState *state); void ncl_unlockScreen(ncl_ScreenState *state); void ncl_resetScreen(ncl_ScreenState *state); diff --git a/src/neonucleus.c b/src/neonucleus.c index 8d28d22..882aec9 100644 --- a/src/neonucleus.c +++ b/src/neonucleus.c @@ -1708,6 +1708,10 @@ const char *nn_getComponentTypeID(nn_Component *c) { return c->internalID; } +const char *nn_getComponentAddress(nn_Component *c) { + return c->address; +} + static nn_Exit nn_pushComponentAdded(nn_Computer *c, const char *address, const char *type) { nn_Exit e = nn_pushstring(c, "component_added"); if(e) return e; @@ -2381,24 +2385,28 @@ const nn_Filesystem nn_defaultFilesystems[4] = { .readsPerTick = 4, .writesPerTick = 2, .dataEnergyCost = 256.0 / NN_MiB, + .maxReadSize = 4096, }, NN_INIT(nn_Filesystem) { .spaceTotal = 2 * NN_MiB, .readsPerTick = 4, .writesPerTick = 2, .dataEnergyCost = 512.0 / NN_MiB, + .maxReadSize = 8192, }, NN_INIT(nn_Filesystem) { .spaceTotal = 4 * NN_MiB, .readsPerTick = 7, .writesPerTick = 3, .dataEnergyCost = 1024.0 / NN_MiB, + .maxReadSize = 16384, }, NN_INIT(nn_Filesystem) { .spaceTotal = 8 * NN_MiB, .readsPerTick = 13, .writesPerTick = 5, .dataEnergyCost = 2048.0 / NN_MiB, + .maxReadSize = 32768, }, }; @@ -2408,6 +2416,7 @@ const nn_Filesystem nn_defaultFloppy = NN_INIT(nn_Filesystem) { .readsPerTick = 1, .writesPerTick = 1, .dataEnergyCost = 8.0 / NN_MiB, + .maxReadSize = 2048, }; const nn_Filesystem nn_defaultTmpFS = NN_INIT(nn_Filesystem) { @@ -2422,9 +2431,10 @@ const nn_Drive nn_defaultDrives[4] = { .capacity = 1 * NN_MiB, .sectorSize = 512, .platterCount = 2, - .readsPerTick = 10, - .writesPerTick = 5, - .rpm = 1800, + .cacheLineSize = 2, + .readsPerTick = 20, + .writesPerTick = 10, + .rpm = 3600, .onlySpinForwards = false, .dataEnergyCost = 256.0 / NN_MiB, }, @@ -2432,9 +2442,10 @@ const nn_Drive nn_defaultDrives[4] = { .capacity = 2 * NN_MiB, .sectorSize = 512, .platterCount = 4, - .readsPerTick = 20, - .writesPerTick = 10, - .rpm = 1800, + .cacheLineSize = 4, + .readsPerTick = 30, + .writesPerTick = 15, + .rpm = 5400, .onlySpinForwards = false, .dataEnergyCost = 512.0 / NN_MiB, }, @@ -2442,9 +2453,10 @@ const nn_Drive nn_defaultDrives[4] = { .capacity = 4 * NN_MiB, .sectorSize = 512, .platterCount = 8, - .readsPerTick = 30, - .writesPerTick = 15, - .rpm = 1800, + .cacheLineSize = 4, + .readsPerTick = 40, + .writesPerTick = 20, + .rpm = 7200, .onlySpinForwards = false, .dataEnergyCost = 1024.0 / NN_MiB, }, @@ -2452,9 +2464,10 @@ const nn_Drive nn_defaultDrives[4] = { .capacity = 8 * NN_MiB, .sectorSize = 512, .platterCount = 16, + .cacheLineSize = 8, .readsPerTick = 40, .writesPerTick = 20, - .rpm = 1800, + .rpm = 7200, .onlySpinForwards = false, .dataEnergyCost = 2048.0 / NN_MiB, }, @@ -2464,8 +2477,9 @@ const nn_Drive nn_floppyDrive = { .capacity = 512 * NN_KiB, .sectorSize = 512, .platterCount = 1, - .readsPerTick = 5, - .writesPerTick = 2, + .cacheLineSize = 2, + .readsPerTick = 10, + .writesPerTick = 5, .rpm = 1800, .onlySpinForwards = true, .dataEnergyCost = 128.0 / NN_MiB, @@ -2477,7 +2491,7 @@ const nn_ScreenConfig nn_defaultScreens[4] = { .maxWidth = 50, .maxHeight = 16, .maxDepth = 1, - .defaultPalette = NULL, + .defaultPalette = nn_ocpalette4, .paletteColors = 0, .editableColors = 0, .features = NN_SCRF_NONE, @@ -3396,118 +3410,11 @@ nn_Component *nn_createEEPROM(nn_Universe *universe, const char *address, const return c; } -typedef struct nn_VEEState { - char *code; - size_t codelen; - char *data; - size_t datalen; - char label[NN_MAX_LABEL]; - size_t labellen; - char arch[NN_MAX_ARCHNAME]; - size_t archlen; -} nn_VEEState; - -static nn_Exit nn_veepromHandler(nn_EEPROMRequest *request) { - nn_VEEState *state = request->state; - nn_Computer *C = request->computer; - const nn_EEPROM *eeprom = request->eeprom; - nn_Context *ctx = request->ctx; - if(request->action == NN_EEPROM_DROP) { - nn_free(ctx, state->code, eeprom->size); - nn_free(ctx, state->data, eeprom->dataSize); - nn_free(ctx, state, sizeof(*state)); - return NN_OK; - } - if(request->action == NN_EEPROM_GET) { - nn_memcpy(request->buf, state->code, state->codelen); - request->buflen = state->codelen; - return NN_OK; - } - if(request->action == NN_EEPROM_GETDATA) { - nn_memcpy(request->buf, state->data, state->datalen); - request->buflen = state->datalen; - return NN_OK; - } - if(request->action == NN_EEPROM_GETLABEL) { - nn_memcpy(request->buf, state->label, state->labellen); - request->buflen = state->labellen; - return NN_OK; - } - if(request->action == NN_EEPROM_GETARCH) { - nn_memcpy(request->buf, state->arch, state->archlen); - request->buflen = state->archlen; - return NN_OK; - } - if(request->action == NN_EEPROM_SET) { - state->codelen = request->buflen; - nn_memcpy(state->code, request->robuf, state->codelen); - return NN_OK; - } - if(request->action == NN_EEPROM_SETDATA) { - state->datalen = request->buflen; - nn_memcpy(state->data, request->robuf, state->datalen); - return NN_OK; - } - if(request->action == NN_EEPROM_SETLABEL) { - if(request->buflen > NN_MAX_LABEL) request->buflen = NN_MAX_LABEL; - state->labellen = request->buflen; - nn_memcpy(state->label, request->robuf, state->labellen); - return NN_OK; - } - if(request->action == NN_EEPROM_SETARCH) { - if(request->buflen > NN_MAX_ARCHNAME) request->buflen = NN_MAX_ARCHNAME; - state->archlen = request->buflen; - nn_memcpy(state->arch, request->robuf, state->archlen); - return NN_OK; - } - nn_setError(C, "veeprom: not implemented yet"); - return NN_EBADCALL; -} - -nn_Component *nn_createVEEPROM(nn_Universe *universe, const char *address, const nn_VEEPROM *veeprom, const nn_EEPROM *eeprom) { - nn_Context *ctx = &universe->ctx; - char *code = NULL; - char *data = NULL; - nn_VEEState *state = NULL; - - code = nn_alloc(ctx, eeprom->size); - if(code == NULL) goto fail; - data = nn_alloc(ctx, eeprom->dataSize); - if(data == NULL) goto fail; - state = nn_alloc(ctx, sizeof(*state)); - if(state == NULL) goto fail; - - state->code = code; - nn_memcpy(code, veeprom->code, veeprom->codelen); - state->codelen = veeprom->codelen; - state->data = data; - nn_memcpy(data, veeprom->data, veeprom->datalen); - state->datalen = veeprom->datalen; - nn_memcpy(state->label, veeprom->label, veeprom->labellen); - state->labellen = veeprom->labellen; - if(veeprom->arch == NULL) { - state->archlen = 0; - } else { - state->archlen = nn_strlen(veeprom->arch); - } - nn_memcpy(state->arch, veeprom->arch, state->archlen); - - nn_Component *c = nn_createEEPROM(universe, address, eeprom, state, nn_veepromHandler); - if(c == NULL) goto fail; - - return c; - -fail: - nn_free(ctx, code, eeprom->size); - nn_free(ctx, data, eeprom->dataSize); - nn_free(ctx, state, sizeof(*state)); - return NULL; -} - typedef enum nn_FSNum { // drive stuff NN_FSNUM_SPACETOTAL, NN_FSNUM_SPACEUSED, + NN_FSNUM_GETMAXREAD, NN_FSNUM_GETLABEL, NN_FSNUM_SETLABEL, NN_FSNUM_ISRO, @@ -3553,6 +3460,7 @@ static nn_Exit nn_fsPathCheck(nn_Computer *C, char buf[NN_MAX_PATH], const char static nn_Exit nn_fsHandler(nn_ComponentRequest *req) { if(req->action == NN_COMP_SIGNAL) return NN_OK; if(req->action == NN_COMP_CHECKMETHOD) return NN_OK; + nn_Context *ctx = req->ctx; nn_FSState *state = req->classState; nn_FSRequest freq; freq.ctx = req->ctx; @@ -3562,7 +3470,7 @@ static nn_Exit nn_fsHandler(nn_ComponentRequest *req) { if(req->action == NN_COMP_DROP) { freq.action = NN_FS_DROP; state->handler(&freq); - nn_free(req->ctx, state, sizeof(*state)); + nn_free(ctx, state, sizeof(*state)); return NN_OK; } nn_Computer *C = req->computer; @@ -3580,6 +3488,10 @@ static nn_Exit nn_fsHandler(nn_ComponentRequest *req) { req->returnCount = 1; return nn_pushinteger(C, freq.spaceUsed); } + if(method == NN_FSNUM_GETMAXREAD) { + req->returnCount = 1; + return nn_pushinteger(C, state->fs.maxReadSize); + } if(method == NN_FSNUM_GETLABEL) { char buf[NN_MAX_LABEL]; freq.action = NN_FS_GETLABEL; @@ -3628,23 +3540,32 @@ static nn_Exit nn_fsHandler(nn_ComponentRequest *req) { } if(method == NN_FSNUM_READ) { if(nn_checkinteger(C, 0, "bad argument #1 (fd expected)")) return NN_EBADCALL; - e = nn_defaultinteger(C, 1, NN_MAX_READ); + e = nn_defaultinteger(C, 1, state->fs.maxReadSize); if(e) return e; if(nn_checknumber(C, 1, "bad argument #2 (number expected)")) return NN_EBADCALL; double requested = nn_tonumber(C, 1); - if(requested > NN_MAX_READ) requested = NN_MAX_READ; + if(requested > state->fs.maxReadSize) requested = state->fs.maxReadSize; freq.action = NN_FS_READ; freq.fd = nn_tointeger(C, 0); - char buf[NN_MAX_READ]; + char *buf = nn_alloc(ctx, state->fs.maxReadSize); + if(buf == NULL) return NN_ENOMEM; freq.read.buf = buf; freq.read.len = requested; e = state->handler(&freq); - if(e) return e; - if(freq.read.buf == NULL) return NN_OK; + if(e) { + nn_free(ctx, buf, state->fs.maxReadSize); + return e; + } + if(freq.read.buf == NULL) { + nn_free(ctx, buf, state->fs.maxReadSize); + return NN_OK; + } nn_costComponent(C, req->compAddress, state->fs.readsPerTick); nn_removeEnergy(C, state->fs.dataEnergyCost * freq.read.len); req->returnCount = 1; - return nn_pushlstring(C, buf, freq.read.len); + e = nn_pushlstring(C, buf, freq.read.len); + nn_free(ctx, buf, state->fs.maxReadSize); + return e; } if(method == NN_FSNUM_WRITE) { if(nn_checkinteger(C, 0, "bad argument #1 (fd expected)")) return NN_EBADCALL; @@ -3663,7 +3584,7 @@ static nn_Exit nn_fsHandler(nn_ComponentRequest *req) { if(nn_checkinteger(C, 0, "bad argument #1 (fd expected)")) return NN_EBADCALL; e = nn_defaultstring(C, 1, "cur"); if(e) return e; - if(nn_checkinteger(C, 1, "bad argument #2 (whence expected)")) return NN_EBADCALL; + if(nn_checkstring(C, 1, "bad argument #2 (whence expected)")) return NN_EBADCALL; e = nn_defaultinteger(C, 2, 0); if(e) return e; if(nn_checkinteger(C, 2, "bad argument #3 (integer expected)")) return NN_EBADCALL; @@ -3678,7 +3599,7 @@ static nn_Exit nn_fsHandler(nn_ComponentRequest *req) { if(nn_strcmp(whence, "end") == 0) { seek = NN_SEEK_END; } - freq.action = NN_FS_CLOSE; + freq.action = NN_FS_SEEK; freq.fd = nn_tointeger(C, 0); freq.seek.whence = seek; freq.seek.off = nn_tointeger(C, 2); @@ -3686,7 +3607,7 @@ static nn_Exit nn_fsHandler(nn_ComponentRequest *req) { if(e) return e; req->returnCount = 1; nn_costComponent(C, req->compAddress, state->fs.readsPerTick); - return nn_pushbool(C, true); + return nn_pushinteger(C, freq.seek.off); } if(method == NN_FSNUM_CLOSE) { if(nn_checkinteger(C, 0, "bad argument #1 (fd expected)")) return NN_EBADCALL; @@ -3843,6 +3764,7 @@ nn_Component *nn_createFilesystem(nn_Universe *universe, const char *address, co const nn_Method methods[NN_FSNUM_COUNT] = { [NN_FSNUM_SPACETOTAL] = {"spaceTotal", "function(): integer - Capacity of the drive", NN_DIRECT}, [NN_FSNUM_SPACEUSED] = {"spaceUsed", "function(): integer - Amount of space used", NN_DIRECT}, + [NN_FSNUM_GETMAXREAD] = {"getMaxRead", "function(): integer - Capacity of read buffer, the maximum amount of data which can be read", NN_DIRECT}, [NN_FSNUM_GETLABEL] = {"getLabel", "function(): string? - Gets the label of the drive, if any", NN_DIRECT}, [NN_FSNUM_SETLABEL] = {"setLabel", "function(label?: string): string - Sets the label of the drive. Returns the new label, which may be truncated", NN_INDIRECT}, [NN_FSNUM_ISRO] = {"isReadOnly", "function(): boolean - Returns whether the drive is read-only", NN_DIRECT}, @@ -3886,8 +3808,6 @@ static void nn_drive_seekPenalty(nn_Computer *C, size_t lastSector, size_t newSe size_t maxSectors = drive->capacity / drive->sectorSize; size_t sectorsPerPlatter = maxSectors / drive->platterCount; - // RPM over the number of sectors, over 60 seconds. - double latencyPerSector = 1.0 / ((double)drive->rpm / 60 * maxSectors); // magic lastSector %= sectorsPerPlatter; @@ -3901,14 +3821,30 @@ static void nn_drive_seekPenalty(nn_Computer *C, size_t lastSector, size_t newSe } else { sectorDelta = lastSector - newSector; } + if(sectorDelta < drive->cacheLineSize) { + sectorDelta = 0; // within cache + } else { + // align to cache line + if(sectorDelta % drive->cacheLineSize != 0) { + sectorDelta += drive->cacheLineSize - sectorDelta % drive->cacheLineSize; + } + } - nn_addIdleTime(C, sectorDelta * latencyPerSector); + // RPM over the number of sectors, over 60 seconds. + double latency = (double)sectorDelta * 60 / ((double)drive->rpm * maxSectors); + nn_addIdleTime(C, latency); +} + +// 1-indexed +static size_t nn_drive_cachelineOf(size_t sector, size_t perCache) { + return (sector - 1) / perCache; } typedef enum nn_DrvNum { NN_DRVNUM_GETCAPACITY, NN_DRVNUM_GETSECTORSIZE, NN_DRVNUM_GETPLATTERCOUNT, + NN_DRVNUM_GETCACHESIZE, NN_DRVNUM_ISRO, NN_DRVNUM_GETLABEL, NN_DRVNUM_SETLABEL, @@ -3936,6 +3872,11 @@ static nn_Exit nn_drvHandler(nn_ComponentRequest *request) { dreq.ctx = ctx; dreq.computer = C; dreq.state = request->state; + dreq.drv = &state->drive; + nn_Exit e; + + if(request->action == NN_COMP_SIGNAL) return NN_OK; + if(request->action == NN_COMP_CHECKMETHOD) return NN_OK; if(request->action == NN_COMP_DROP) { dreq.action = NN_DRIVE_DROP; @@ -3943,7 +3884,87 @@ static nn_Exit nn_drvHandler(nn_ComponentRequest *request) { nn_free(ctx, state, sizeof(*state)); return NN_OK; } - if(C) nn_setError(C, "bad call"); + size_t ss = state->drive.sectorSize; + size_t cacheline = state->drive.cacheLineSize; + size_t cacheByteSize = cacheline * ss; + size_t sectorCount = state->drive.capacity / ss; + unsigned int method = request->methodIdx; + if(method == NN_DRVNUM_GETCAPACITY) { + request->returnCount = 1; + return nn_pushinteger(C, state->drive.capacity); + } + if(method == NN_DRVNUM_GETSECTORSIZE) { + request->returnCount = 1; + return nn_pushinteger(C, ss); + } + if(method == NN_DRVNUM_GETCACHESIZE) { + request->returnCount = 1; + return nn_pushinteger(C, cacheline); + } + if(method == NN_DRVNUM_ISRO) { + dreq.action = NN_DRIVE_ISRO; + e = state->handler(&dreq); + if(e) return e; + request->returnCount = 1; + return nn_pushbool(C, dreq.readonly); + } + if(method == NN_DRVNUM_GETLABEL) { + dreq.action = NN_DRIVE_GETLABEL; + char buf[NN_MAX_LABEL]; + dreq.getlabel.buf = buf; + dreq.getlabel.len = NN_MAX_LABEL; + e = state->handler(&dreq); + if(e) return e; + request->returnCount = 1; + return nn_pushlstring(C, dreq.getlabel.buf, dreq.getlabel.len); + } + if(method == NN_DRVNUM_SETLABEL) { + e = nn_defaultstring(C, 0, ""); + if(e) return e; + if(nn_checkstring(C, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; + dreq.action = NN_DRIVE_SETLABEL; + dreq.setlabel.label = nn_tolstring(C, 0, &dreq.setlabel.len); + e = state->handler(&dreq); + if(e) return e; + request->returnCount = 1; + return nn_pushlstring(C, dreq.setlabel.label, dreq.setlabel.len); + } + if(method == NN_DRVNUM_READSECTOR) { + if(nn_checkinteger(C, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; + int sec = nn_tointeger(C, 0); + if(sec < 1 || sec > sectorCount) { + nn_setError(C, "sector out of bounds"); + return NN_EBADCALL; + } + int curPos = 0; + dreq.action = NN_DRIVE_CURPOS; + e = state->handler(&dreq); + if(e) return e; + curPos = dreq.curpos; + + if(nn_drive_cachelineOf(curPos, cacheline) != nn_drive_cachelineOf(sec, cacheline)) { + nn_drive_seekPenalty(C, curPos, sec, &state->drive); + nn_costComponent(C, request->compAddress, state->drive.readsPerTick); + } + + char *sector = nn_alloc(ctx, ss); + if(sector == NULL) return NN_ENOMEM; + + dreq.action = NN_DRIVE_READSECTOR; + dreq.readSector.sector = sec; + dreq.readSector.buf = sector; + e = state->handler(&dreq); + if(e) { + nn_free(ctx, sector, ss); + return e; + } + request->returnCount = 1; + e = nn_pushlstring(C, sector, ss); + nn_free(ctx, sector, ss); + return e; + } + + if(C) nn_setError(C, "drive: not implemented yet"); return NN_EBADCALL; } @@ -3954,6 +3975,7 @@ nn_Component *nn_createDrive(nn_Universe *universe, const char *address, const n [NN_DRVNUM_GETCAPACITY] = {"getCapacity", "function(): integer - Get drive capacity", NN_DIRECT}, [NN_DRVNUM_GETSECTORSIZE] = {"getSectorSize", "function(): integer - Get sector size", NN_DIRECT}, [NN_DRVNUM_GETPLATTERCOUNT] = {"getPlatterCount", "function(): integer - Get number of platters on this drive", NN_DIRECT}, + [NN_DRVNUM_GETCACHESIZE] = {"getCacheSize", "function(): integer - Get number of sectors cached in a single read", NN_DIRECT}, [NN_DRVNUM_ISRO] = {"isReadOnly", "function(): boolean - Get whether the drive is read-only", NN_DIRECT}, [NN_DRVNUM_GETLABEL] = {"getLabel", "function(): string? - Get drive label", NN_DIRECT}, [NN_DRVNUM_SETLABEL] = {"setLabel", "function(label: string?): string - Set drive label", NN_INDIRECT}, diff --git a/src/neonucleus.h b/src/neonucleus.h index 5aba875..377cd53 100644 --- a/src/neonucleus.h +++ b/src/neonucleus.h @@ -86,9 +86,6 @@ void *_alloca(size_t); #define NN_MAX_STACK 256 // the maximum size a path is allowed to have, including the NULL terminator! #define NN_MAX_PATH 256 -// the maximum amount of bytes which can be read from a file. -// You are given a buffer you are meant to fill at least partially, this is simply the limit of that buffer's size. -#define NN_MAX_READ 8192 // the maximum size of a label #define NN_MAX_LABEL 256 // maximum size of a wakeup message @@ -641,6 +638,7 @@ const char *nn_getComponentDoc(nn_Component *c, const char *method); nn_MethodFlags nn_getComponentMethodFlags(nn_Component *c, const char *method); const char *nn_getComponentType(nn_Component *c); const char *nn_getComponentTypeID(nn_Component *c); +const char *nn_getComponentAddress(nn_Component *c); // Adds a component to the computer on a given slot. // This will also queue a component_added signal if the computer is in a running state. @@ -937,19 +935,7 @@ typedef nn_Exit (nn_EEPROMHandler)(nn_EEPROMRequest *request); // Tier 4- The best EEPROM extern const nn_EEPROM nn_defaultEEPROMs[4]; -typedef struct nn_VEEPROM { - const char *code; - size_t codelen; - const char *data; - size_t datalen; - const char *label; - size_t labellen; - const char *arch; - bool isReadonly; -} nn_VEEPROM; - nn_Component *nn_createEEPROM(nn_Universe *universe, const char *address, const nn_EEPROM *eeprom, void *state, nn_EEPROMHandler *handler); -nn_Component *nn_createVEEPROM(nn_Universe *universe, const char *address, const nn_VEEPROM *veeprom, const nn_EEPROM *eeprom); // Filesystem class @@ -964,6 +950,11 @@ typedef struct nn_Filesystem { // The energy cost of an actual read/write. // It is per-byte, so if a read returns 4096 bytes, then this cost is multiplied by 4096. double dataEnergyCost; + // maximum size of a read. + // Do note that this entire buffer is allocated, and thus if you + // set it to a high number, you may get weird high allocations. + // This also determines read performance. + size_t maxReadSize; } nn_Filesystem; typedef enum nn_FSAction { @@ -1095,18 +1086,22 @@ typedef struct nn_Drive { // However, if it has 2 platters, it'd be seen as 1 to 4 being at the same angle as 5 to 8, which // would mean only 3 rotations. size_t platterCount; + // how many sectors are cached together. + // Each platter has "its own cache." + size_t cacheLineSize; // how many reads can be issued per tick. - // Reading either a sector or a byte counts as 1 read. + // Anything that kicks out the current cacheline counts as a read. size_t readsPerTick; // how many writes can be issued per tick. // Writing a sector counts as 1 write. - // Writing a byte counts as 1 read and 1 write, + // Writing a byte counts as 1 read (may be eaten by cache) and 1 write, // you can imagine it as reading the sector, editing the byte, // then writing the sector back. size_t writesPerTick; // Set to 0 for *infinite*, effectively an SSD. // This would mean there is 0 penalty for seeking (technically unreliastic even for an SSD). // This is simply used to compute idle time. It is in literal full rotations per minute. + // It is aligned to the cache lines. size_t rpm; // If false, it behaves like a normal OC drive, where the drive can spin backwards to seek. // However, this is unrealistic, as doing so may crack the sensitive platter and make the @@ -1130,8 +1125,8 @@ typedef enum nn_DriveAction { NN_DRIVE_GETLABEL, // set or remove current label NN_DRIVE_SETLABEL, - // get the last read position - NN_DRIVE_LASTREADPOS, + // get the current head position, as a sector + NN_DRIVE_CURPOS, // read a sector NN_DRIVE_READSECTOR, // write a sector @@ -1140,13 +1135,15 @@ typedef enum nn_DriveAction { NN_DRIVE_READBYTE, // write a byte NN_DRIVE_WRITEBYTE, + // is drive read-only + NN_DRIVE_ISRO, } nn_DriveAction; typedef struct nn_DriveRequest { nn_Context *ctx; nn_Computer *computer; void *state; - const nn_Filesystem *fs; + const nn_Drive *drv; nn_DriveAction action; union { struct { @@ -1157,7 +1154,7 @@ typedef struct nn_DriveRequest { const char *label; size_t len; } setlabel; - size_t lastReadPos; + size_t curpos; struct { // 1-indexed size_t sector; @@ -1178,6 +1175,7 @@ typedef struct nn_DriveRequest { size_t byte; unsigned char value; } writeByte; + bool readonly; }; } nn_DriveRequest;