From 79ae1e13f8fcbc5ed83bf3dc8ed5a05e634f6c95 Mon Sep 17 00:00:00 2001 From: IonutParau Date: Sun, 5 Apr 2026 19:48:13 +0200 Subject: [PATCH] bunch of progress --- TODO.md | 3 +- src/main.c | 23 +-- src/minBIOS.lua | 7 + src/ncomplib.c | 183 ++++++++++++++++++++++ src/ncomplib.h | 9 ++ src/neonucleus.c | 397 ++++++++++++++++++++++++++++++++++++++++++++--- src/neonucleus.h | 107 +++++++++++-- 7 files changed, 684 insertions(+), 45 deletions(-) diff --git a/TODO.md b/TODO.md index cf77070..eab6658 100644 --- a/TODO.md +++ b/TODO.md @@ -1,7 +1,6 @@ # For MVP functionality -- move SSDs to nandflash -- remove HDD cachelines (they're pointless) +- NCL computer states, as computers that can be turned on/off/beep/etc. - write a tester OS, basically a menu with tests to run - tmpfs (rework the whole thing) - device info diff --git a/src/main.c b/src/main.c index c0c8bda..3935882 100644 --- a/src/main.c +++ b/src/main.c @@ -385,7 +385,8 @@ int main(int argc, char **argv) { nn_Component *testingfs = ncl_createTmpFS(u, NULL, &nn_defaultFilesystems[3], NCL_FILECOST_DEFAULT, false); const char * const testDriveData = - "local g, s, d = component.list('gpu')(), component.list('screen')(), component.list('drive')()\n" + "local g, s = component.list('gpu')(), component.list('screen')()\n" + "local d = computer.getBootAddress()\n" "component.invoke(g, 'bind', s, true)\n" "component.invoke(g, 'set', 1, 1, 'starting sequential bench...')\n" "local start = computer.uptime()\n" @@ -408,16 +409,13 @@ int main(int argc, char **argv) { "while computer.uptime() < now + 3 do computer.pullSignal(0.05) end\n" "computer.shutdown(true)\n" ; - nn_Drive driveconf; - nn_Drive driveparts[] = { - nn_defaultSSDs[3], - }; - nn_mergeDrives(&driveconf, driveparts, sizeof(driveparts) / sizeof(driveparts[0])); - nn_Component *testDrive = ncl_createDrive(u, NULL, &driveconf, testDriveData, strlen(testDriveData), false); + nn_Component *testDrive = ncl_createDrive(u, NULL, &nn_defaultDrives[3], testDriveData, strlen(testDriveData), false); + nn_Component *testFlash = ncl_createFlash(u, NULL, &nn_defaultSSDs[3], testDriveData, strlen(testDriveData), false); ncl_setCLabel(managedfs, "Main Filesystem"); ncl_setCLabel(testingfs, "Secondary Filesystem"); ncl_setCLabel(testDrive, "Unmanaged Storage"); + ncl_setCLabel(testFlash, "Flash Storage"); size_t ramTotal = 0; ramTotal += nn_ramSizes[5]; @@ -472,8 +470,7 @@ restart:; nn_pushnumber(c, 5.3); nn_pushbool(c, false); nn_encodeNetworkContents(c, &contents, 4); - - nn_dropNetworkContents(&contents); + nn_popn(c, 4); printf("size: %zu\n", contents.buflen); for(size_t i = 0; i < contents.buflen; i++) { @@ -499,6 +496,7 @@ restart:; nn_mountComponent(c, gpuCard, 2); //nn_mountComponent(c, testingfs, 3); nn_mountComponent(c, testDrive, 4); + nn_mountComponent(c, testFlash, 5); while(true) { if(WindowShouldClose()) break; @@ -580,6 +578,10 @@ restart:; if(keycode == KEY_TAB) unicode = '\t'; } + if(keycode == KEY_F1) { + nn_pushModemMessage(c, "bullshit", "someone", 1, 5, &contents); + } + nn_pushKeyDown(c, "mainKB", unicode, keycode_to_oc(keycode), player); } @@ -632,6 +634,7 @@ restart:; } if(state == NN_RESTART) { printf("restart requested\n"); + nn_dropNetworkContents(&contents); nn_destroyComputer(c); goto restart; } @@ -646,9 +649,11 @@ cleanup:; nn_dropComponent(tmpfs); nn_dropComponent(testingfs); nn_dropComponent(testDrive); + nn_dropComponent(testFlash); nn_dropComponent(screen); nn_dropComponent(gpuCard); nn_dropComponent(keyboard); + nn_dropNetworkContents(&contents); // rip the universe nn_destroyUniverse(u); ncl_destroyGlyphCache(gc); diff --git a/src/minBIOS.lua b/src/minBIOS.lua index 077871b..b292a3b 100644 --- a/src/minBIOS.lua +++ b/src/minBIOS.lua @@ -95,6 +95,13 @@ for addr in component.list("drive", true) do end end +for addr in component.list("nandflash", true) do + local f = getBootCode(addr) + if f then + table.insert(bootables, {code = f, addr = addr}) + end +end + local function boot(bootable) local w, h = component.invoke(gpu, "maxResolution") component.invoke(gpu, "setResolution", w, h) diff --git a/src/ncomplib.c b/src/ncomplib.c index 2aa844f..c9e2631 100644 --- a/src/ncomplib.c +++ b/src/ncomplib.c @@ -631,6 +631,18 @@ typedef struct ncl_DriveState { 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; @@ -1264,6 +1276,7 @@ static nn_Exit ncl_tmpfsHandler(nn_FSRequest *req) { } } nn_destroyLock(ctx, tmpfs->lock); + nn_free(ctx, tmpfs, sizeof(*tmpfs)); return NN_OK; } if(req->action == NN_FS_SPACEUSED) { @@ -1651,6 +1664,7 @@ static nn_Exit ncl_drvHandler(nn_DriveRequest *request) { 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; @@ -1661,8 +1675,15 @@ static nn_Exit ncl_drvHandler(nn_DriveRequest *request) { 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); @@ -1670,6 +1691,7 @@ static nn_Exit ncl_drvHandler(nn_DriveRequest *request) { } 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; @@ -1724,12 +1746,110 @@ fail: 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)); @@ -1891,6 +2011,16 @@ size_t ncl_readDrive(nn_Component *component, size_t offset, char *buf, size_t l 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; } @@ -1906,6 +2036,16 @@ void ncl_writeDrive(nn_Component *component, size_t offset, const char *buf, siz 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) { @@ -1915,6 +2055,11 @@ char *ncl_getDriveBuffer(nn_Component *component, size_t *len) { *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; } @@ -3245,6 +3390,20 @@ void ncl_statComponent(nn_Component *component, ncl_ComponentStat *stat) { 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); @@ -3294,6 +3453,14 @@ bool ncl_makeReadonly(nn_Component *component) { 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); @@ -3345,6 +3512,14 @@ size_t ncl_getLabel(nn_Component *c, char buf[NN_MAX_LABEL]) { 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; } @@ -3383,6 +3558,14 @@ size_t ncl_setLabel(nn_Component *c, const char *label, size_t 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; } diff --git a/src/ncomplib.h b/src/ncomplib.h index 1b975ed..68718b9 100644 --- a/src/ncomplib.h +++ b/src/ncomplib.h @@ -8,6 +8,7 @@ #define NCL_EEPROM "ncl-eeprom" #define NCL_FS "ncl-filesystem" #define NCL_DRIVE "ncl-drive" +#define NCL_FLASH "ncl-nandflash" #define NCL_GPU "ncl-gpu" #define NCL_SCREEN "ncl-screen" @@ -208,6 +209,9 @@ nn_Component *ncl_createTmpFS(nn_Universe *universe, const char *address, const // 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); +// usable like a drive, but is a nandflash component +nn_Component *ncl_createFlash(nn_Universe *universe, const char *address, const nn_NandFlash *flash, const char *data, size_t len, bool isReadonly); + // data is stored interally nn_Component *ncl_createEEPROM(nn_Universe *universe, const char *address, const nn_EEPROM *eeprom, const char *code, size_t codelen, bool isReadonly); @@ -269,6 +273,11 @@ typedef struct ncl_ComponentStat { const nn_Drive *conf; size_t lastSector; } drive; + struct { + const nn_NandFlash *conf; + size_t currentWriteCount; + double wearlevel; + } flash; struct { const nn_GPU *conf; size_t vramFree; diff --git a/src/neonucleus.c b/src/neonucleus.c index 21ca59e..d3043ed 100644 --- a/src/neonucleus.c +++ b/src/neonucleus.c @@ -2537,57 +2537,62 @@ const nn_Drive nn_floppyDrive = { .dataEnergyCost = 1.0 / NN_MiB, }; -const nn_Drive nn_defaultSSDs[4] = { - NN_INIT(nn_Drive) { +const nn_NandFlash nn_defaultSSDs[4] = { + NN_INIT(nn_NandFlash) { .capacity = 512 * NN_KiB, .sectorSize = 512, - .platterCount = 2, .readsPerTick = 10, .writesPerTick = 5, - .rpm = 0, - .onlySpinForwards = false, + .cellLevel = 1, + .maxWriteCount = 1<<10, + .maxWriteAmplification = 4, + .writeAmplificationExponent = 2, .dataEnergyCost = 64.0 / NN_MiB, }, - NN_INIT(nn_Drive) { + NN_INIT(nn_NandFlash) { .capacity = 1 * NN_MiB, .sectorSize = 512, - .platterCount = 4, .readsPerTick = 15, .writesPerTick = 7, - .rpm = 0, - .onlySpinForwards = false, + .cellLevel = 2, + .maxWriteCount = 1<<10, + .maxWriteAmplification = 8, + .writeAmplificationExponent = 2, .dataEnergyCost = 128.0 / NN_MiB, }, - NN_INIT(nn_Drive) { + NN_INIT(nn_NandFlash) { .capacity = 2 * NN_MiB, .sectorSize = 512, - .platterCount = 8, .readsPerTick = 20, .writesPerTick = 10, - .rpm = 0, - .onlySpinForwards = false, + .cellLevel = 3, + .maxWriteCount = 1<<10, + .maxWriteAmplification = 12, + .writeAmplificationExponent = 2, .dataEnergyCost = 256.0 / NN_MiB, }, - NN_INIT(nn_Drive) { + NN_INIT(nn_NandFlash) { .capacity = 4 * NN_MiB, .sectorSize = 512, - .platterCount = 16, .readsPerTick = 30, .writesPerTick = 15, - .rpm = 0, - .onlySpinForwards = false, + .cellLevel = 4, + .maxWriteCount = 1<<10, + .maxWriteAmplification = 16, + .writeAmplificationExponent = 2, .dataEnergyCost = 512.0 / NN_MiB, }, }; -const nn_Drive nn_floppySSD = { +const nn_NandFlash nn_floppySSD = { .capacity = 256 * NN_KiB, .sectorSize = 512, - .platterCount = 1, .readsPerTick = 5, .writesPerTick = 2, - .rpm = 0, - .onlySpinForwards = true, + .cellLevel = 1, + .maxWriteCount = 1<<10, + .maxWriteAmplification = 4, + .writeAmplificationExponent = 2, .dataEnergyCost = 16.0 / NN_MiB, }; @@ -3432,15 +3437,116 @@ nn_Exit nn_encodeNetworkContents(nn_Computer *computer, nn_EncodedNetworkContent return NN_OK; } -nn_Exit nn_copyNetworkContents(nn_Context *ctx, nn_EncodedNetworkContents *contents, const char *buf, size_t buflen, size_t valueCount); +nn_Exit nn_copyNetworkContents(nn_Context *ctx, nn_EncodedNetworkContents *contents, const char *buf, size_t buflen, size_t valueCount) { + contents->ctx = ctx; + contents->valueCount = valueCount; + contents->buflen = buflen; + contents->buf = nn_alloc(ctx, buflen); + if(contents->buf == NULL) return NN_ENOMEM; + nn_memcpy(contents->buf, buf, buflen); + return NN_OK; +} void nn_dropNetworkContents(nn_EncodedNetworkContents *contents) { nn_free(contents->ctx, contents->buf, contents->buflen); } -nn_Exit nn_pushNetworkContents(nn_Computer *computer, const nn_EncodedNetworkContents *contents); +static nn_Exit nn_decodeNetworkValue(nn_Value *val, nn_Context *ctx, const char *buf, size_t *len) { + size_t decodedLen = 0, off = 0; + nn_Value tmpval; + switch((nn_NetworkValueTag)buf[0]) { + case NN_NETVAL_NULL: + *len = 1; + val->type = NN_VAL_NULL; + return NN_OK; + case NN_NETVAL_TRUE: + case NN_NETVAL_FALSE: + *len = 1; + val->type = NN_VAL_BOOL; + val->boolean = buf[0] == NN_NETVAL_TRUE; + return NN_OK; + case NN_NETVAL_NUM: + *len = 1 + sizeof(double); + val->type = NN_VAL_NUM; + nn_memcpy(&val->number, buf + 1, sizeof(double)); + return NN_OK; + case NN_NETVAL_STR: + nn_memcpy(&decodedLen, buf + 1, sizeof(size_t)); + val->type = NN_VAL_STR; + val->string = nn_alloc(ctx, sizeof(nn_String) + decodedLen + 1); + if(val->string == NULL) return NN_ENOMEM; + val->string->ctx = *ctx; + val->string->refc = 1; + val->string->len = decodedLen; + nn_memcpy(val->string->data, buf + 1 + sizeof(size_t), decodedLen); + val->string->data[decodedLen] = '\0'; + *len = 1 + sizeof(size_t) + decodedLen; + return NN_OK; + case NN_NETVAL_RESOURCE: + val->type = NN_VAL_USERDATA; + nn_memcpy(&val->userdataIdx, buf + 1, sizeof(size_t)); + *len = 1 + sizeof(size_t); + return NN_OK; + case NN_NETVAL_TABLE: + val->type = NN_VAL_TABLE; + nn_memcpy(&decodedLen, buf + 1, sizeof(size_t)); + val->table = nn_alloc(ctx, sizeof(nn_Table) + sizeof(nn_Value) * decodedLen * 2); + if(val->table == NULL) return NN_ENOMEM; + val->table->ctx = *ctx; + val->table->refc = 1; + val->table->len = decodedLen; + off = 1 + sizeof(size_t); + for(size_t i = 0; i < decodedLen*2; i++) { + size_t tmplen = 0; + nn_Exit e = nn_decodeNetworkValue(&tmpval, ctx, buf + off, &tmplen); + if(e) { + for(size_t j = 0; j < i; j++) nn_dropValue(val->table->vals[j]); + return e; + } + val->table->vals[i] = tmpval; + off += tmplen; + } + *len = off; + return NN_OK; + } + *len = 1; + val->type = NN_VAL_NULL; + return NN_OK; +} -nn_Exit nn_pushModemMessage(nn_Computer *computer, const char *modemAddress, const char *sender, int port, double distance, const nn_EncodedNetworkContents *contents); +nn_Exit nn_pushNetworkContents(nn_Computer *C, const nn_EncodedNetworkContents *contents) { + nn_Value val; + size_t off = 0; + for(size_t i = 0; i < contents->valueCount; i++) { + size_t len = 0; + nn_Exit e = nn_decodeNetworkValue(&val, &C->universe->ctx, contents->buf + off, &len); + if(e) return e; + e = nn_pushvalue(C, val); + if(e) { + nn_dropValue(val); + return e; + } + off += len; + } + return NN_OK; +} + +nn_Exit nn_pushModemMessage(nn_Computer *C, const char *modemAddress, const char *sender, int port, double distance, const nn_EncodedNetworkContents *contents) { + size_t signalVals = 5 + contents->valueCount; + nn_Exit e = nn_pushstring(C, "modem_message"); + if(e) return e; + e = nn_pushstring(C, modemAddress); + if(e) return e; + e = nn_pushstring(C, sender); + if(e) return e; + e = nn_pushinteger(C, port); + if(e) return e; + e = nn_pushnumber(C, distance); + if(e) return e; + e = nn_pushNetworkContents(C, contents); + if(e) return e; + return nn_pushSignal(C, signalVals); +} typedef enum nn_EENum { NN_EENUM_GETSIZE, @@ -4133,6 +4239,7 @@ static nn_Exit nn_drvHandler(nn_ComponentRequest *request) { e = state->handler(&dreq); if(e) return e; request->returnCount = 1; + if(dreq.getlabel.len == 0) return nn_pushnull(C); return nn_pushlstring(C, dreq.getlabel.buf, dreq.getlabel.len); } if(method == NN_DRVNUM_SETLABEL) { @@ -4249,6 +4356,248 @@ bool nn_mergeDrives(nn_Drive *merged, const nn_Drive *drives, size_t len) { return true; } +static size_t nn_flash_writesAdded(nn_Context *ctx, const nn_NandFlash *flash) { + double x = nn_randfi(ctx); + double m = 1; + // TODO: use O(log N) algorithm instead of O(N) + for(size_t i = 0; i < flash->writeAmplificationExponent; i++) m *= x; + + size_t max = flash->maxWriteAmplification * flash->cellLevel; + size_t amount = m * max; + if(amount < 1) amount = 1; + if(amount > max) amount = max; + return amount; +} + +typedef enum nn_FlashNum { + NN_FLASHNUM_GETCAPACITY, + NN_FLASHNUM_GETSECTORSIZE, + NN_FLASHNUM_GETLAYERS, + NN_FLASHNUM_GETWEARLEVEL, + NN_FLASHNUM_ISRO, + NN_FLASHNUM_GETLABEL, + NN_FLASHNUM_SETLABEL, + NN_FLASHNUM_READSECTOR, + NN_FLASHNUM_WRITESECTOR, + NN_FLASHNUM_READBYTE, + NN_FLASHNUM_READUBYTE, + NN_FLASHNUM_WRITEBYTE, + + NN_FLASHNUM_COUNT, +} nn_FlashNum; + +typedef struct nn_FlashState { + nn_Context *ctx; + nn_NandFlash flash; + nn_FlashHandler *handler; +} nn_FlashState; + +static nn_Exit nn_flashHandler(nn_ComponentRequest *request) { + nn_Context *ctx = request->ctx; + nn_Computer *C = request->computer; + nn_FlashState *state = request->classState; + + nn_FlashRequest freq; + freq.ctx = ctx; + freq.computer = C; + freq.state = request->state; + freq.flash = &state->flash; + 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) { + freq.action = NN_FLASH_DROP; + state->handler(&freq); + nn_free(ctx, state, sizeof(*state)); + return NN_OK; + } + size_t ss = state->flash.sectorSize; + size_t sectorCount = state->flash.capacity / ss; + size_t maxWrite = state->flash.maxWriteCount; + nn_FlashNum method = request->methodIdx; + if(method == NN_FLASHNUM_GETCAPACITY) { + request->returnCount = 1; + return nn_pushinteger(C, state->flash.capacity); + } + if(method == NN_FLASHNUM_GETSECTORSIZE) { + request->returnCount = 1; + return nn_pushinteger(C, ss); + } + if(method == NN_FLASHNUM_GETLAYERS) { + request->returnCount = 1; + return nn_pushinteger(C, state->flash.cellLevel); + } + if(method == NN_FLASHNUM_GETWEARLEVEL) { + freq.action = NN_FLASH_GETWRITES; + e = state->handler(&freq); + if(e) return e; + request->returnCount = 1; + // would crash the math + if(maxWrite == 0) return nn_pushnumber(C, 100.0); + if(sectorCount == 0) return nn_pushnumber(C, 100.0); + double num = freq.writeCount * 100.0 / sectorCount / maxWrite; + return nn_pushnumber(C, num); + } + if(method == NN_FLASHNUM_ISRO) { + freq.action = NN_FLASH_ISRO; + e = state->handler(&freq); + if(e) return e; + request->returnCount = 1; + return nn_pushbool(C, freq.readonly); + } + if(method == NN_FLASHNUM_GETLABEL) { + freq.action = NN_FLASH_GETLABEL; + char buf[NN_MAX_LABEL]; + freq.getlabel.buf = buf; + freq.getlabel.len = NN_MAX_LABEL; + e = state->handler(&freq); + if(e) return e; + request->returnCount = 1; + if(freq.getlabel.len == 0) return nn_pushnull(C); + return nn_pushlstring(C, freq.getlabel.buf, freq.getlabel.len); + } + if(method == NN_FLASHNUM_SETLABEL) { + e = nn_defaultstring(C, 0, ""); + if(e) return e; + if(nn_checkstring(C, 0, "bad argument #1 (string expected)")) return NN_EBADCALL; + freq.action = NN_FLASH_SETLABEL; + freq.setlabel.buf = nn_tolstring(C, 0, &freq.setlabel.len); + e = state->handler(&freq); + if(e) return e; + request->returnCount = 1; + return nn_pushlstring(C, freq.setlabel.buf, freq.setlabel.len); + } + if(method == NN_FLASHNUM_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; + } + nn_costComponent(C, request->compAddress, state->flash.readsPerTick); + nn_removeEnergy(C, state->flash.dataEnergyCost * ss); + + char *sector = nn_alloc(ctx, ss); + if(sector == NULL) return NN_ENOMEM; + + freq.action = NN_FLASH_READSECTOR; + freq.readsector.sec = sec; + freq.readsector.buf = sector; + e = state->handler(&freq); + 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(method == NN_FLASHNUM_WRITESECTOR) { + if(nn_checkinteger(C, 0, "bad argument #1 (integer expected)")) return NN_EBADCALL; + if(nn_checkstring(C, 1, "bad argument #2 (string 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; + } + freq.action = NN_FLASH_GETWRITES; + e = state->handler(&freq); + if(e) return e; + if(freq.writeCount >= maxWrite * sectorCount) { + nn_setError(C, "flash is not conductive enough"); + return NN_EBADCALL; + } + + nn_costComponent(C, request->compAddress, state->flash.writesPerTick); + nn_removeEnergy(C, state->flash.dataEnergyCost * ss); + + size_t len; + const char *sector = nn_tolstring(C, 1, &len); + if(len != ss) { + nn_setError(C, "incorrect sector size"); + return NN_EBADCALL; + } + + freq.action = NN_FLASH_WRITESECTOR; + freq.writesector.sec = sec; + freq.writesector.buf = sector; + freq.writesector.writesAdded = nn_flash_writesAdded(ctx, &state->flash); + e = state->handler(&freq); + if(e) return e; + + request->returnCount = 1; + return nn_pushbool(C, true); + } + + if(C) nn_setError(C, "nandflash: not implemented yet"); + return NN_EBADCALL; +} + +nn_Component *nn_createFlash(nn_Universe *universe, const char *address, const nn_NandFlash *drive, void *state, nn_FlashHandler *handler) { + nn_Component *c = nn_createComponent(universe, address, "nandflash"); + if(c == NULL) return NULL; + const nn_Method methods[NN_FLASHNUM_COUNT] = { + [NN_FLASHNUM_GETCAPACITY] = {"getCapacity", "function(): integer - Get the capacity of the flash storage", NN_DIRECT}, + [NN_FLASHNUM_GETSECTORSIZE] = {"getSectorSize", "function(): integer - Get the logical sector size", NN_DIRECT}, + [NN_FLASHNUM_GETLAYERS] = {"getLayers", "function(): integer - Get the amount of bits in a cell", NN_DIRECT}, + [NN_FLASHNUM_GETWEARLEVEL] = {"getWearLevel", "function(): number - Gets a number from 0 to 100 indicitive of estimated drive damage", NN_DIRECT}, + [NN_FLASHNUM_ISRO] = {"isReadonly", "function(): boolean - Checks whether the NAND is read-only", NN_DIRECT}, + [NN_FLASHNUM_GETLABEL] = {"getLabel", "function(): string? - Get the label of the flash storage", NN_DIRECT}, + [NN_FLASHNUM_SETLABEL] = {"setLabel", "function(label?: string): string - Set the label, which may be truncated", NN_INDIRECT}, + [NN_FLASHNUM_READSECTOR] = {"readSector", "function(sector: integer): string - Read contents of a logical sector", NN_DIRECT}, + [NN_FLASHNUM_WRITESECTOR] = {"writeSector", "function(sector: integer): string - Write contents of a logical sector, may lead to multiple real writes", NN_DIRECT}, + [NN_FLASHNUM_READBYTE] = {"readByte", "function(byte: integer): integer - Read an individual signed byte", NN_DIRECT}, + [NN_FLASHNUM_READUBYTE] = {"readUByte", "function(byte: integer): integer - Read an individual unsigned byte", NN_DIRECT}, + [NN_FLASHNUM_WRITEBYTE] = {"writeByte", "function(byte: integer, value: integer): boolean - Write a byte"}, + }; + nn_Exit e = nn_setComponentMethodsArray(c, methods, NN_FLASHNUM_COUNT); + if(e) { + nn_dropComponent(c); + return NULL; + } + nn_Context *ctx = &universe->ctx; + nn_FlashState *drvstate = nn_alloc(ctx, sizeof(*drvstate)); + if(drvstate == NULL) { + nn_dropComponent(c); + return NULL; + } + drvstate->ctx = ctx; + drvstate->flash = *drive; + drvstate->handler = handler; + nn_setComponentState(c, state); + nn_setComponentClassState(c, drvstate); + nn_setComponentHandler(c, nn_flashHandler); + return c; +} + +bool nn_mergeFlash(nn_NandFlash *merged, const nn_NandFlash *flash, size_t len) { + if(len == 0) return false; + *merged = flash[0]; + for(size_t i = 1; i < len; i++) { + nn_NandFlash f = flash[i]; + + merged->readsPerTick += f.readsPerTick; + merged->writesPerTick += f.writesPerTick; + merged->dataEnergyCost += f.dataEnergyCost; + merged->capacity += f.capacity; + merged->maxWriteCount += f.maxWriteCount; + merged->maxWriteAmplification += f.maxWriteAmplification; + merged->writeAmplificationExponent += f.writeAmplificationExponent; + merged->cellLevel += f.cellLevel; + } + merged->readsPerTick /= len; + merged->writesPerTick /= len; + merged->dataEnergyCost /= len; + merged->maxWriteCount /= len; + merged->maxWriteAmplification /= len; + merged->writeAmplificationExponent /= len; + merged->cellLevel /= len; + return true; +} + typedef enum nn_ScreenNum { NN_SCRNUM_ISON, NN_SCRNUM_TURNON, diff --git a/src/neonucleus.h b/src/neonucleus.h index 1253308..795b947 100644 --- a/src/neonucleus.h +++ b/src/neonucleus.h @@ -273,8 +273,11 @@ void nn_unlock(nn_Context *ctx, nn_Lock *lock); double nn_currentTime(nn_Context *ctx); +// generate a random RNG from 0 to the maximum size_t nn_rand(nn_Context *ctx); +// generate a random float [0, 1) double nn_randf(nn_Context *ctx); +// generate a random float [0, 1] double nn_randfi(nn_Context *ctx); typedef char nn_uuid[37]; @@ -1103,7 +1106,7 @@ typedef struct nn_Drive { // 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. + // Set to 0 for *infinite*, effectively an SSD with infinite lifespan. // 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. @@ -1123,8 +1126,6 @@ typedef struct nn_Drive { extern const nn_Drive nn_defaultDrives[4]; extern const nn_Drive nn_floppyDrive; -extern const nn_Drive nn_defaultSSDs[4]; -extern const nn_Drive nn_floppySSD; typedef enum nn_DriveAction { // drive gone @@ -1139,8 +1140,6 @@ typedef enum nn_DriveAction { NN_DRIVE_READSECTOR, // write a sector NN_DRIVE_WRITESECTOR, - // read a byte - NN_DRIVE_READBYTE, // write a byte NN_DRIVE_WRITEBYTE, // is drive read-only @@ -1173,11 +1172,6 @@ typedef struct nn_DriveRequest { size_t sector; const char *buf; } writeSector; - struct { - // 1-indexed - size_t byte; - unsigned char value; - } readByte; struct { // 1-indexed size_t byte; @@ -1193,6 +1187,99 @@ nn_Component *nn_createDrive(nn_Universe *universe, const char *address, const n bool nn_mergeDrives(nn_Drive *merged, const nn_Drive *drives, size_t len); +typedef struct nn_NandFlash { + // capacity of flash + size_t capacity; + // sector size + size_t sectorSize; + // reads per tick + size_t readsPerTick; + // writes per tick + size_t writesPerTick; + // The layering, in bits. + // 1 is SLC, 2 is MLC, 3 is TLC, etc. + // This number may amplify how quickly the total write count increases. + size_t cellLevel; + // the maximum amount of write amplification. + // Set to 0 to disable amplification RNG. + // The game will generate, using Context RNG, a real number from [0, 1] + // then raise it to writeAmplificationExponent, + // then multiply it by this number, and by the cell level. + // then clamp it to be at least 1 and at most this maximum. + unsigned int maxWriteAmplification; + int writeAmplificationExponent; + // the maximum amount of writes *per sector.* + // Set to 0 to make the nandflash eternal. + size_t maxWriteCount; + // how much per byte + double dataEnergyCost; +} nn_NandFlash; + +typedef enum nn_FlashAction { + NN_FLASH_DROP, + NN_FLASH_GETLABEL, + NN_FLASH_SETLABEL, + NN_FLASH_ISRO, + // read a sector + NN_FLASH_READSECTOR, + // write a sector + // also adds an amount of writes + NN_FLASH_WRITESECTOR, + // write a sector + // also adds an amount of writes + NN_FLASH_WRITEBYTE, + // get the amount of writes + NN_FLASH_GETWRITES, +} nn_FlashAction; + +typedef struct nn_FlashRequest { + nn_Context *ctx; + nn_Computer *computer; + void *state; + const nn_NandFlash *flash; + nn_FlashAction action; + union { + struct { + char *buf; + size_t len; + } getlabel; + struct { + const char *buf; + size_t len; + } setlabel; + struct { + char *buf; + // 1-indexed + size_t sec; + } readsector; + struct { + const char *buf; + // 1-indexed + size_t sec; + // how many writes to add + size_t writesAdded; + } writesector; + struct { + size_t byte; + char val; + // how many writes to add + size_t writesAdded; + } writebyte; + // for GETWRITES + size_t writeCount; + bool readonly; + }; +} nn_FlashRequest; + +typedef nn_Exit (nn_FlashHandler)(nn_FlashRequest *request); + +extern const nn_NandFlash nn_defaultSSDs[4]; +extern const nn_NandFlash nn_floppySSD; + +nn_Component *nn_createFlash(nn_Universe *universe, const char *address, const nn_NandFlash *drive, void *state, nn_FlashHandler *handler); + +bool nn_mergeFlash(nn_NandFlash *merged, const nn_NandFlash *flash, size_t len); + // Screen class typedef enum nn_ScreenFeatures {