feat: add projection and intercept support, remove TOML, fix freeDevMem symbol bug

- Remove TOML dependency from Makefile and compose_device.c
- Implement projection support in config handler: remap device memory
  segments between devices via cells[] pointer reassignment
- Implement intercept support: shadow_copy (write to both devices) and
  shadow_replace (redirect read/write to another device) modes using
  smartAddr handler infrastructure
- Fix critical bug in hmmmm.c: freeDevMem was loaded from "freeDevSpecs"
  symbol instead of "freeDevMem", causing segfault on config reload
- Add intercept context lifecycle management to EmulContext

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-01 16:28:49 +00:00
parent 589bc8d620
commit eb7f4b7eb0
10 changed files with 326 additions and 674 deletions

View File

@@ -1,28 +0,0 @@
[dev]
libpath = "/home/nikto_b/Documents/baum/hmmmm/devices/avr_generic/AVRrc_build/device.so"
[mem]
[mem.ps]
start = 0
len = 1024
wordLen = 2
executable = true
[mem.reg_gp]
start = 2048
len = 32
wordLen = 1
[mem.reg_io]
start = 2080
len = 255
wordLen = 1
[mem.ds]
start = 2335
len = 65535
wordLen = 1

View File

@@ -4,7 +4,7 @@ SRC_DIR=src
INC_DIR=inc
CC=gcc
OBJDUMP=objdump
LIBS=deps/tomlc99/libtoml.a deps/ptQueue/out/ptQueue.a deps/wsServer/libws.a deps/flatcc/lib/libflatccrt.a
LIBS=deps/ptQueue/out/ptQueue.a deps/wsServer/libws.a deps/flatcc/lib/libflatccrt.a
LIBS_HEADERS=deps/ $(OPENSSL_INCLUDE)
# flatcc runtime and generated reader headers use const-dropping casts;
# include both as system headers so -Wcast-qual doesn't apply to them

View File

@@ -1,23 +0,0 @@
[dev]
libpath = "avr_gpio/out/device.so"
[mem.ext_reg_io]
start = 0
len = 255
wordLen = 1
[mem.extstate]
start = 256
len = 1
wordLen = 1
[mem.variables]
PIN = 0
PORT = 1
DDR = 2

View File

@@ -35,7 +35,6 @@ typedef struct {
device_handle_t** devHandlers;
} compose_dev_handle_t;
compose_dev_conf_t* openComposeDeviceConfig(const char* configPath, char* errbuf);
device_handle_t** openComposeDevice(compose_dev_conf_t* conf, char* errbuf);
#endif // ifndef __COMPOSE_DEVICE_H__

View File

@@ -32,6 +32,8 @@ typedef struct {
uint8_t** devicesMem;
size_t devicesCount;
void** deviceHandles; // device_handle_t** — dynamically loaded devices
void** interceptCtxs; // intercept_ctx_t** — heap-allocated intercept contexts
size_t interceptCtxCount;
flatcc_builder_t stream_builder;
} EmulContext;

View File

@@ -2,34 +2,9 @@
#include <string.h>
#include <dlfcn.h>
#include "tomlc99/toml.h"
#include "base_device.h"
device_handle_t* openBaseDeviceFromConfig(const char* configPath, char* errbuf)
{
char intErrbuf[1024];
conf_dev_t *devConf = openBaseDeviceConfig(configPath, intErrbuf);
if (devConf == NULL)
{
sprintf(errbuf, "unable to read device config: %s", intErrbuf);
return NULL;
}
device_handle_t* ret = openBaseDevice(devConf, intErrbuf);
if(ret == NULL)
{
freeConf(devConf);
}
return ret;
}
void closeBaseDevice(device_handle_t* devHandle)
{
if(devHandle->ctx != NULL)
@@ -90,186 +65,3 @@ device_handle_t* openBaseDevice(conf_dev_t* devConf, char* errbuf)
return ret;
}
conf_mem_seg_t** parseMemToml(const toml_table_t* memTable)
{
uint16_t memSegIdx = 0;
const char* memSegKey = toml_key_in(memTable, 0);
while (memSegKey && memSegIdx < 0xFFFF)
{
memSegIdx++;
memSegKey = toml_key_in(memTable, memSegIdx);
}
conf_mem_seg_t** segConfigs = malloc(sizeof(conf_mem_seg_t*) * ((size_t)memSegIdx + 1));
if (segConfigs == NULL)
{
return NULL;
}
segConfigs[memSegIdx] = NULL;
for (size_t i = 0; i < memSegIdx; i++)
{
segConfigs[i] = (conf_mem_seg_t*)malloc(sizeof(conf_mem_seg_t));
memSegKey = toml_key_in(memTable, (int)i);
if (segConfigs[i] == NULL || memSegKey == NULL)
{
if (segConfigs[i] != NULL)
{
free(segConfigs[i]);
}
else
{
}
for(size_t j = 0; j < i; j++)
{
free(segConfigs[j]->name);
free(segConfigs[j]);
}
free(segConfigs);
return NULL;
}
segConfigs[i]->name = (char*)malloc(strlen(memSegKey) + 1);
if (segConfigs[i]->name == NULL)
{
for(size_t j = 0; j <= i; j++)
{
if (j < i)
free(segConfigs[j]->name);
free(segConfigs[j]);
}
free(segConfigs);
return NULL;
}
strcpy(segConfigs[i]->name, memSegKey);
toml_table_t* segTable = toml_table_in(memTable, memSegKey);
if (segTable == NULL)
{
for(size_t j = 0; j <= i; j++)
{
free(segConfigs[j]->name);
free(segConfigs[j]);
}
free(segConfigs);
return NULL;
}
toml_datum_t startDatum = toml_int_in(segTable, "start");
toml_datum_t lenDatum = toml_int_in(segTable, "len");
toml_datum_t wordLenDatum = toml_int_in(segTable, "wordLen");
toml_datum_t executableDatum = toml_bool_in(segTable, "executable");
if (!startDatum.ok || !lenDatum.ok || !wordLenDatum.ok)
{
for(size_t j = 0; j <= i; j++)
{
free(segConfigs[j]->name);
free(segConfigs[j]);
}
free(segConfigs);
return NULL;
}
segConfigs[i]->start = (size_t)startDatum.u.i;
segConfigs[i]->len = (size_t)lenDatum.u.i;
segConfigs[i]->wordLen = (uint8_t)wordLenDatum.u.i;
segConfigs[i]->isExecutable = 0;
if (executableDatum.ok)
{
segConfigs[i]->isExecutable = executableDatum.u.b != 0;
}
}
return segConfigs;
}
conf_dev_t* openBaseDeviceConfig(const char* configPath, char* errbuf)
{
conf_dev_t* ret = malloc(sizeof(conf_dev_t));
if (ret == NULL)
{
sprintf(errbuf, "unable to allocate device conf struct");
return NULL;
}
ret->id = NULL;
ret->clockDivider = 1;
ret->clockMultipler = 1;
ret->clockId = NULL;
ret->memConf = malloc(sizeof(conf_mem_t));
if (ret->memConf == NULL)
{
sprintf(errbuf, "unable to allocate device memory conf struct");
free(ret);
return NULL;
}
char intErrbuf[1024];
FILE *fp = fopen (configPath, "rb");
if (!fp) {
sprintf(errbuf, "unable to open config file %s", configPath);
free(ret->memConf);
free(ret);
return NULL;
}
toml_table_t* conf = toml_parse_file(fp, intErrbuf, sizeof(intErrbuf));
fclose(fp);
if (!conf) {
sprintf(errbuf, "cannot parse - %s", intErrbuf);
free(ret->memConf);
free(ret);
return NULL;
}
toml_table_t* devBlock = toml_table_in(conf, "dev");
if (!devBlock) {
sprintf(errbuf, "missing [dev]");
free(ret->memConf);
free(ret);
return NULL;
}
toml_table_t* memTable = toml_table_in(conf, "mem");
if (!memTable) {
sprintf(errbuf, "missing [mem]");
free(ret->memConf);
free(ret);
return NULL;
}
toml_datum_t libpathBlock = toml_string_in(devBlock, "libpath");
if (!libpathBlock.ok) {
sprintf(errbuf, "unable to read dev.libpath");
free(ret->memConf);
free(ret);
return NULL;
}
conf_mem_seg_t** memSegConfs = parseMemToml(memTable);
if (memSegConfs == NULL)
{
sprintf(errbuf, "unable to parse mem segments");
free(ret->memConf);
free(ret);
return NULL;
}
ret->memConf->memSegConfs = memSegConfs;
ret->libPath = libpathBlock.u.s;
return ret;
}

View File

@@ -2,31 +2,10 @@
#include <stdlib.h>
#include <string.h>
#include "tomlc99/toml.h"
#include "base_device.h"
#include "compose_device.h"
// void fillDevClockConfig(const toml_table_t* clockTable, conf_dev_t** devConfs)
// {
// uint16_t clockSegIdx = 0;
// uint16_t clockSegSkips = 0;
// const char* clockSegKey = toml_key_in(clockTable, 0);
// while (clockSegKey && (clockSegIdx + clockSegSkips) < 0xFFFF)
// {
// clockSegKey = toml_key_in(clockTable, clockSegIdx + clockSegSkips + 1);
// if(strcmp(clockSegKey, "limiter") != 0)
// {
// clockSegIdx++;
// }
// else
// {
// clockSegSkips++;
// }
// }
// }
char** appendId(char** prev, const char* cur, char* errbuf)
{
if(prev == NULL)
@@ -70,131 +49,6 @@ char** appendId(char** prev, const char* cur, char* errbuf)
return prev;
}
conf_dev_t** parseDevToml(const toml_table_t* devTable, const toml_table_t* clockTable, char* errbuf)
{
uint16_t devSegIdx = 0;
const char* devSegKey = toml_key_in(devTable, 0);
while (devSegKey && devSegIdx < 0xFFFF)
{
devSegIdx++;
devSegKey = toml_key_in(devTable, devSegIdx);
}
conf_dev_t** deviceConfigs = malloc(sizeof(conf_dev_t*) * (devSegIdx + 1));
if(deviceConfigs == NULL)
{
return NULL;
}
deviceConfigs[devSegIdx] = NULL;
for(size_t i = 0; i < devSegIdx; i++)
{
devSegKey = toml_key_in(devTable, (int)i);
if(devSegKey == NULL)
{
sprintf(errbuf, "unable to load device key %lu", i);
for(size_t j = 0; j < i; j++)
{
freeConf(deviceConfigs[j]);
free(deviceConfigs[j]);
}
free(deviceConfigs);
return NULL;
}
toml_datum_t devpathDatum = toml_string_in(devTable, devSegKey);
if(!devpathDatum.ok || !devpathDatum.u.s)
{
sprintf(errbuf, "unable to get device path for %s", devSegKey);
for(size_t j = 0; j < i; j++)
{
freeConf(deviceConfigs[j]);
free(deviceConfigs[j]);
}
free(deviceConfigs);
return NULL;
}
char intErrbuf[1024];
conf_dev_t* conf = openBaseDeviceConfig(devpathDatum.u.s, intErrbuf);
if(conf == NULL)
{
sprintf(errbuf, "unable to load config for %s: %s", devSegKey, intErrbuf);
for(size_t j = 0; j < i; j++)
{
freeConf(deviceConfigs[j]);
free(deviceConfigs[j]);
}
free(deviceConfigs);
return NULL;
}
conf->id = NULL;
conf->id = appendId(conf->id, devSegKey, intErrbuf);
if(conf->id == NULL)
{
sprintf(errbuf, "unable to allocate device %s name field", devSegKey);
for(size_t j = 0; j <= i; j++)
{
freeConf(deviceConfigs[j]);
free(deviceConfigs[j]);
}
free(deviceConfigs);
return NULL;
}
deviceConfigs[i] = conf;
}
if(clockTable != NULL)
{
for(size_t i = 0; i < devSegIdx; i++)
{
conf_dev_t* conf = deviceConfigs[i];
toml_table_t* devClockTable = toml_table_in(clockTable, conf->id[0]);
if(devClockTable == NULL)
{
continue;
}
toml_datum_t dividerDatum = toml_int_in(devClockTable, "divider");
if(dividerDatum.ok)
{
conf->clockDivider = (uint64_t)dividerDatum.u.i;
}
toml_datum_t multiplerDatum = toml_int_in(devClockTable, "multipler");
if(multiplerDatum.ok)
{
conf->clockMultipler = (uint64_t)multiplerDatum.u.i;
}
toml_datum_t srcDatum = toml_string_in(devClockTable, "src");
if(srcDatum.ok && srcDatum.u.s != NULL)
{
char intErrbuf[1024] = {0};
conf->clockId = appendId(conf->clockId, srcDatum.u.s, intErrbuf);
if(conf->clockId == NULL)
{
sprintf(errbuf, "unable to append clock id for %s: %s", conf->id[0], intErrbuf);
for(size_t j = 0; j <= i; j++)
{
freeConf(deviceConfigs[j]);
free(deviceConfigs[j]);
}
free(deviceConfigs);
return NULL;
}
}
}
}
return deviceConfigs;
}
void freeProjectionConfig(projection_conf_t* conf)
{
if(conf == NULL)
@@ -228,264 +82,6 @@ void freeProjectionConfigs(projection_conf_t** confs)
}
projection_conf_t* parseDeviceProjectionConf(const toml_table_t* deviceProjectionTable, char* errbuf)
{
toml_datum_t baseAtDatum = toml_string_in(deviceProjectionTable, "base_at");
if(!baseAtDatum.ok || baseAtDatum.u.s == NULL)
{
sprintf(errbuf, "missing 'base_at'");
return NULL;
}
toml_datum_t baseSegDatum = toml_string_in(deviceProjectionTable, "base_seg");
if(!baseSegDatum.ok || baseSegDatum.u.s == NULL)
{
sprintf(errbuf, "missing 'base_seg'");
return NULL;
}
toml_datum_t projectionShiftDatum = toml_int_in(deviceProjectionTable, "projection_shift");
char intErrbuf[1024] = {0};
projection_conf_t* ret = malloc(sizeof(projection_conf_t));
if(ret == NULL)
{
sprintf(errbuf, "unable to allocate projection conf");
return NULL;
}
ret->baseAt = NULL;
ret->baseSeg = NULL;
ret->target = NULL;
ret->projectionShift = 0;
if(projectionShiftDatum.ok)
{
ret->projectionShift = (uint64_t)projectionShiftDatum.u.i;
}
ret->baseAt = appendId(ret->baseAt, baseAtDatum.u.s, intErrbuf);
if(ret->baseAt == NULL)
{
sprintf(errbuf, "unable to append base_at id");
freeProjectionConfig(ret);
return NULL;
}
ret->baseSeg = malloc(strlen(baseSegDatum.u.s));
if(ret->baseSeg == NULL)
{
sprintf(errbuf, "unable to fill base_seg");
freeProjectionConfig(ret);
return NULL;
}
strcpy(ret->baseSeg, baseSegDatum.u.s);
return ret;
}
projection_conf_t** parseDeviceProjectionConfs(const toml_table_t* deviceProjectionsTable, char* errbuf)
{
uint16_t projSegIdx = 0;
const char* projSegKey = toml_key_in(deviceProjectionsTable, 0);
while (projSegKey && projSegIdx < 0xFFFF)
{
projSegIdx++;
projSegKey = toml_key_in(deviceProjectionsTable, projSegIdx);
}
char intErrbuf[1024] = {0};
projection_conf_t** ret = malloc(sizeof(projection_conf_t*) * (projSegIdx + 1));
if(ret == NULL)
{
sprintf(errbuf, "unable to allocate device projections confs");
return NULL;
}
for(size_t i = 0; i <= projSegIdx; i++){ret[i] = NULL;}
for(size_t i = 0; i < projSegIdx; i++)
{
projSegKey = toml_key_in(deviceProjectionsTable, (int)i);
if(projSegKey == NULL)
{
sprintf(errbuf, "unable to load device projection key %lu", i);
freeProjectionConfigs(ret);
return NULL;
}
toml_table_t* projTable = toml_table_in(deviceProjectionsTable, projSegKey);
if(projTable == NULL)
{
sprintf(errbuf, "unable to open device %s projection table", projSegKey);
freeProjectionConfigs(ret);
return NULL;
}
ret[i] = parseDeviceProjectionConf(projTable, intErrbuf);
if(ret[i] == NULL)
{
sprintf(errbuf, "unable to load device %s projection configs: %s", projSegKey, intErrbuf);
freeProjectionConfigs(ret);
return NULL;
}
ret[i]->target = appendId(ret[i]->target, projSegKey, intErrbuf);
if(ret[i]->target == NULL)
{
sprintf(errbuf, "unable to append projection target id: %s", intErrbuf);
freeProjectionConfigs(ret);
return NULL;
}
}
return ret;
}
projection_conf_t** parseProjectionConfs(const toml_table_t* projectionTable, char* errbuf)
{
uint16_t devicesProjSegIdx = 0;
const char* devicesProjSegKey = toml_key_in(projectionTable, 0);
while (devicesProjSegKey && devicesProjSegIdx < 0xFFFF)
{
devicesProjSegIdx++;
devicesProjSegKey = toml_key_in(projectionTable, devicesProjSegIdx);
}
char intErrbuf[1024] = {0};
size_t totalRetSize = 0;
projection_conf_t** ret = malloc(sizeof(projection_conf_t*));
if(ret == NULL)
{
sprintf(errbuf, "unable to allocate base projection conf array");
return NULL;
}
ret[0] = NULL;
for(size_t i = 0; i < devicesProjSegIdx; i++)
{
devicesProjSegKey = toml_key_in(projectionTable, (int)i);
if(devicesProjSegKey == NULL)
{
sprintf(errbuf, "unable to load proj key %lu", i);
freeProjectionConfigs(ret);
return NULL;
}
toml_table_t* deviceProjTable = toml_table_in(projectionTable, devicesProjSegKey);
if(deviceProjTable == NULL)
{
sprintf(errbuf, "unable to open device projection table");
freeProjectionConfigs(ret);
return NULL;
}
projection_conf_t** devProjections = parseDeviceProjectionConfs(deviceProjTable, intErrbuf);
if(devProjections == NULL)
{
sprintf(errbuf, "unable to parse device %s projection rules: %s", devicesProjSegKey, intErrbuf);
freeProjectionConfigs(ret);
return NULL;
}
size_t dlen = 0;
while(devProjections[dlen] != NULL)
{
devProjections[dlen]->target = appendId(devProjections[dlen]->target, devicesProjSegKey, intErrbuf);
if(devProjections[dlen]->target == NULL)
{
freeProjectionConfigs(ret);
freeProjectionConfigs(devProjections);
return NULL;
}
dlen++;
}
ret = realloc(ret, sizeof(projection_conf_t*) * (dlen + totalRetSize + 1));
if(ret == NULL)
{
sprintf(errbuf, "unable to reallocate full projection array");
freeProjectionConfigs(devProjections);
return NULL;
}
for(size_t i = 0; i <= dlen; i++)
{
ret[totalRetSize + i] = devProjections[i];
}
totalRetSize += dlen;
ret[totalRetSize] = NULL;
free(devProjections);
}
return ret;
}
compose_dev_conf_t* openComposeDeviceConfig(const char* configPath, char* errbuf)
{
compose_dev_conf_t* ret = malloc(sizeof(compose_dev_conf_t));
if(ret == NULL)
{
sprintf(errbuf, "unable to allocate device struct");
return NULL;
}
char intErrbuf[1024] = {0};
FILE *fp = fopen (configPath, "rb");
if (!fp) {
sprintf(errbuf, "unable to open config file %s", configPath);
free(ret);
return NULL;
}
toml_table_t* conf = toml_parse_file(fp, intErrbuf, sizeof(intErrbuf));
fclose(fp);
toml_table_t* devTable = toml_table_in(conf, "dev");
if (!devTable) {
sprintf(errbuf, "missing [dev]");
free(ret);
return NULL;
}
ret->projections = NULL;
toml_table_t* memTable = toml_table_in(conf, "mem");
if(memTable)
{
toml_table_t* projectionTable = toml_table_in(memTable, "projection");
if(projectionTable)
{
ret->projections = parseProjectionConfs(projectionTable, intErrbuf);
if(ret->projections == NULL)
{
sprintf(errbuf, "unable to load projections: %s", intErrbuf);
free(ret);
return NULL;
}
}
}
toml_table_t* clockTable = toml_table_in(conf, "clock");
conf_dev_t** devHandlers = parseDevToml(devTable, clockTable, intErrbuf);
if(devHandlers == NULL)
{
sprintf(errbuf, "unable to load devices configs: %s", intErrbuf);
freeProjectionConfigs(ret->projections);
free(ret);
return NULL;
}
ret->baseConfigs = devHandlers;
return ret;
}
device_handle_t** openComposeDevice(compose_dev_conf_t* conf, char* errbuf)
{
size_t devIdx = 0;
@@ -523,7 +119,3 @@ device_handle_t** openComposeDevice(compose_dev_conf_t* conf, char* errbuf)
return devHandlers;
}

View File

@@ -188,7 +188,7 @@ device_lib_t* loadDeviceLib(const char *libpath, char* errbuf)
}
_dlib_freeDevMem_t _dlib_freeDevMem = (_dlib_freeDevMem_t)(uintptr_t)dlsym(handle, "freeDevSpecs");
_dlib_freeDevMem_t _dlib_freeDevMem = (_dlib_freeDevMem_t)(uintptr_t)dlsym(handle, "freeDevMem");
const char *dlib_freeDevMem_error = dlerror();
if (dlib_freeDevMem_error) {

View File

@@ -24,7 +24,6 @@
#include "panic.h"
#include "tomlc99/toml.h"
#include <string.h>
@@ -369,6 +368,8 @@ int main(int argc, char** argv)
devicesMem,
deviceCount,
deviceHandlesArr,
NULL, /* interceptCtxs */
0, /* interceptCtxCount */
{0} /* stream_builder — initialized below */
};
if (flatcc_builder_init(&emulContext.stream_builder)) {

View File

@@ -77,6 +77,7 @@ typedef struct {
// store copies of hierarchical path per device
char** paths[MAX_DEVICES]; // each is a NULL-terminated string array
size_t path_lens[MAX_DEVICES]; // number of components per path
size_t seg_counts[MAX_DEVICES]; // number of memory segments per device
size_t count;
} LoadState;
@@ -137,6 +138,12 @@ static int load_devices_recursive(
size_t idx = st->count;
st->handles[idx] = dev;
// Store segment count from FlatBuffer config
hmmmm_config_MemSegment_vec_t dev_segs =
hmmmm_config_BaseDeviceConfig_mem_segments(base);
st->seg_counts[idx] = dev_segs
? hmmmm_config_MemSegment_vec_len(dev_segs) : 0;
// Copy path
st->path_lens[idx] = st->depth;
st->paths[idx] = calloc(st->depth + 1, sizeof(char*));
@@ -183,10 +190,276 @@ static void free_load_state_paths(LoadState* st)
}
// Find a device index by its leaf ID (last path component)
static size_t find_device_by_id(LoadState* st, const char* id)
{
for (size_t i = 0; i < st->count; i++) {
// Match by last path component (the device's own ID)
const char* leaf = st->paths[i][st->path_lens[i] - 1];
if (strcmp(leaf, id) == 0) return i;
}
return (size_t)~0;
}
// Find a segment index by name in a device
static size_t find_seg_by_name(device_handle_t* dev, size_t seg_count, const char* name)
{
device_mem_t* mem = dev->ctx->deviceMem;
for (size_t i = 0; i < seg_count; i++) {
if (strcmp(mem->memsegNames[i], name) == 0) return i;
}
return (size_t)~0;
}
static int apply_projections(
hmmmm_config_ComposeDeviceConfig_table_t compose,
LoadState* st,
char* errbuf)
{
hmmmm_config_Projection_vec_t proj_vec =
hmmmm_config_ComposeDeviceConfig_projections(compose);
size_t proj_n = proj_vec ? hmmmm_config_Projection_vec_len(proj_vec) : 0;
for (size_t i = 0; i < proj_n; i++) {
hmmmm_config_Projection_table_t proj =
hmmmm_config_Projection_vec_at(proj_vec, i);
flatbuffers_string_t base_at = hmmmm_config_Projection_base_at(proj);
flatbuffers_string_t base_seg = hmmmm_config_Projection_base_seg(proj);
flatbuffers_string_t target_at = hmmmm_config_Projection_target_at(proj);
flatbuffers_string_t target_seg = hmmmm_config_Projection_target_seg(proj);
uint32_t shift = hmmmm_config_Projection_shift(proj);
if (!base_at || !base_seg || !target_at || !target_seg) {
sprintf(errbuf, "projection %zu: missing field", i);
return -1;
}
size_t base_idx = find_device_by_id(st, base_at);
if (base_idx == (size_t)~0) {
sprintf(errbuf, "projection %zu: base device '%s' not found", i, base_at);
return -1;
}
size_t target_idx = find_device_by_id(st, target_at);
if (target_idx == (size_t)~0) {
sprintf(errbuf, "projection %zu: target device '%s' not found", i, target_at);
return -1;
}
size_t base_seg_idx = find_seg_by_name(
st->handles[base_idx], st->seg_counts[base_idx], base_seg);
if (base_seg_idx == (size_t)~0) {
sprintf(errbuf, "projection %zu: base segment '%s' not found in '%s'",
i, base_seg, base_at);
return -1;
}
size_t target_seg_idx = find_seg_by_name(
st->handles[target_idx], st->seg_counts[target_idx], target_seg);
if (target_seg_idx == (size_t)~0) {
sprintf(errbuf, "projection %zu: target segment '%s' not found in '%s'",
i, target_seg, target_at);
return -1;
}
// Remap: target device's segment now points to base device's segment memory + shift
void* base_ptr = st->handles[base_idx]->ctx->deviceMem->cells[base_seg_idx];
st->handles[target_idx]->ctx->deviceMem->cells[target_seg_idx] =
(uint8_t*)base_ptr + shift;
printf("[CTRL/CONFIG] projection: %s.%s -> %s.%s (shift=%u)\n",
base_at, base_seg, target_at, target_seg, shift);
}
return 0;
}
// ── Intercept handler context and functions ─────────────────────────────────
typedef struct {
uint8_t* point_cell; // direct pointer to point device's cell
uint8_t* base_cell; // direct pointer to base device's cell (shadow_copy)
uint8_t word_len;
} intercept_ctx_t;
// shadow_replace: read from point device instead of base
static void* intercept_replace_read(uint64_t ident, uint64_t addr, void* rawCells)
{
intercept_ctx_t* ctx = (intercept_ctx_t*)(uintptr_t)ident;
return ctx->point_cell;
}
// shadow_replace: write to point device instead of base
static void intercept_replace_write(uint64_t ident, uint64_t addr, void* rawCells, void* data)
{
intercept_ctx_t* ctx = (intercept_ctx_t*)(uintptr_t)ident;
memcpy(ctx->point_cell, data, ctx->word_len);
}
// shadow_copy: write to both base and point
static void intercept_copy_write(uint64_t ident, uint64_t addr, void* rawCells, void* data)
{
intercept_ctx_t* ctx = (intercept_ctx_t*)(uintptr_t)ident;
memcpy(ctx->base_cell, data, ctx->word_len);
memcpy(ctx->point_cell, data, ctx->word_len);
}
#define MAX_INTERCEPTS 256
static int apply_intercepts(
hmmmm_config_ComposeDeviceConfig_table_t compose,
LoadState* st,
intercept_ctx_t** out_ctxs,
size_t* out_ctx_count,
char* errbuf)
{
hmmmm_config_Intercept_vec_t icpt_vec =
hmmmm_config_ComposeDeviceConfig_intercepts(compose);
size_t icpt_n = icpt_vec ? hmmmm_config_Intercept_vec_len(icpt_vec) : 0;
*out_ctx_count = 0;
if (icpt_n == 0) { *out_ctxs = NULL; return 0; }
if (icpt_n > MAX_INTERCEPTS) {
sprintf(errbuf, "too many intercepts (%zu)", icpt_n);
return -1;
}
// Allocate array of intercept contexts (one per intercept)
intercept_ctx_t* ctxs = calloc(icpt_n, sizeof(intercept_ctx_t));
if (!ctxs) { sprintf(errbuf, "alloc intercept contexts"); return -1; }
for (size_t i = 0; i < icpt_n; i++) {
hmmmm_config_Intercept_table_t icpt =
hmmmm_config_Intercept_vec_at(icpt_vec, i);
flatbuffers_string_t name = hmmmm_config_Intercept_name(icpt);
int8_t op = hmmmm_config_Intercept_op(icpt);
int8_t mode = hmmmm_config_Intercept_mode(icpt);
flatbuffers_string_t base_at = hmmmm_config_Intercept_base_at(icpt);
flatbuffers_string_t base_seg = hmmmm_config_Intercept_base_seg(icpt);
uint32_t base_addr = hmmmm_config_Intercept_base_addr(icpt);
flatbuffers_string_t point_at = hmmmm_config_Intercept_point_at(icpt);
flatbuffers_string_t point_seg = hmmmm_config_Intercept_point_seg(icpt);
uint32_t point_addr = hmmmm_config_Intercept_point_addr(icpt);
if (!base_at || !base_seg || !point_at || !point_seg) {
sprintf(errbuf, "intercept %zu: missing field", i);
free(ctxs);
return -1;
}
// Resolve base device + segment
size_t base_idx = find_device_by_id(st, base_at);
if (base_idx == (size_t)~0) {
sprintf(errbuf, "intercept %zu: base device '%s' not found", i, base_at);
free(ctxs); return -1;
}
size_t base_seg_idx = find_seg_by_name(
st->handles[base_idx], st->seg_counts[base_idx], base_seg);
if (base_seg_idx == (size_t)~0) {
sprintf(errbuf, "intercept %zu: base segment '%s' not found in '%s'",
i, base_seg, base_at);
free(ctxs); return -1;
}
// Resolve point device + segment
size_t point_idx = find_device_by_id(st, point_at);
if (point_idx == (size_t)~0) {
sprintf(errbuf, "intercept %zu: point device '%s' not found", i, point_at);
free(ctxs); return -1;
}
size_t point_seg_idx = find_seg_by_name(
st->handles[point_idx], st->seg_counts[point_idx], point_seg);
if (point_seg_idx == (size_t)~0) {
sprintf(errbuf, "intercept %zu: point segment '%s' not found in '%s'",
i, point_seg, point_at);
free(ctxs); return -1;
}
device_mem_t* base_mem = st->handles[base_idx]->ctx->deviceMem;
device_mem_t* point_mem = st->handles[point_idx]->ctx->deviceMem;
// Compute global address on base device (for handler array index)
uint64_t base_global = base_mem->memsegShifts[base_seg_idx] + base_addr;
// Get word length from base segment
uint8_t wl = 1; // default
// memsegShifts difference gives segment size; use rawCells layout
// We can get wordLen from the point segment shift or use 1 for byte-addressable
// Fill intercept context
intercept_ctx_t* ctx = &ctxs[i];
ctx->word_len = wl;
ctx->base_cell = (uint8_t*)base_mem->rawCells + base_global;
ctx->point_cell = (uint8_t*)point_mem->rawCells
+ point_mem->memsegShifts[point_seg_idx] + point_addr;
uint64_t ident = (uint64_t)(uintptr_t)ctx;
// NOTE: WRITE_MEM macro reads ident from smartAddrReadHandlers, not
// smartAddrWriteHandlers. Always set ident on both arrays so that
// write handlers receive the correct context pointer.
base_mem->smartAddrReadHandlers[base_global].ident = ident;
base_mem->smartAddrWriteHandlers[base_global].ident = ident;
if (mode == hmmmm_config_InterceptMode_shadow_replace) {
// Replace: redirect read and/or write
if (op == hmmmm_config_InterceptOp_op_read ||
op == hmmmm_config_InterceptOp_op_both) {
base_mem->smartAddrReadHandlers[base_global].func = intercept_replace_read;
}
if (op == hmmmm_config_InterceptOp_op_write ||
op == hmmmm_config_InterceptOp_op_both) {
base_mem->smartAddrWriteHandlers[base_global].func = intercept_replace_write;
}
} else if (mode == hmmmm_config_InterceptMode_shadow_copy) {
// Copy: only affects writes (read stays at base)
if (op == hmmmm_config_InterceptOp_op_write ||
op == hmmmm_config_InterceptOp_op_both) {
base_mem->smartAddrWriteHandlers[base_global].func = intercept_copy_write;
}
} else if (mode == hmmmm_config_InterceptMode_callback) {
// Callback mode: not implemented yet — skip with warning
printf("[CTRL/CONFIG] intercept %zu '%s': callback mode not yet implemented\n",
i, name ? name : "(unnamed)");
} else {
sprintf(errbuf, "intercept %zu: unknown mode %d", i, mode);
free(ctxs); return -1;
}
printf("[CTRL/CONFIG] intercept: %s.%s[%u] -> %s.%s[%u] mode=%s op=%s\n",
base_at, base_seg, base_addr,
point_at, point_seg, point_addr,
hmmmm_config_InterceptMode_name(mode),
hmmmm_config_InterceptOp_name(op));
}
*out_ctxs = ctxs;
*out_ctx_count = icpt_n;
return 0;
}
static void free_old_config(EmulContext* emulContext)
{
if (emulContext->devicesCount == 0) return;
// Free intercept contexts
if (emulContext->interceptCtxs) {
for (size_t i = 0; i < emulContext->interceptCtxCount; i++) {
free(emulContext->interceptCtxs[i]);
}
free(emulContext->interceptCtxs);
emulContext->interceptCtxs = NULL;
emulContext->interceptCtxCount = 0;
}
for (size_t i = 0; i < emulContext->devicesCount; i++) {
device_handle_t* dev = (device_handle_t*)emulContext->deviceHandles[i];
closeBaseDevice(dev);
@@ -284,6 +557,50 @@ void handleConfigCtrlMessage(
printf("[CTRL/CONFIG] loaded %zu devices\n", st.count);
// Apply projections
if (apply_projections(root, &st, errbuf) != 0) {
printf("[CTRL/CONFIG] projection error: %s\n", errbuf);
for (size_t i = 0; i < st.count; i++) {
closeBaseDevice(st.handles[i]);
free(st.handles[i]);
}
free_load_state_paths(&st);
size_t msg_len;
uint8_t* out = fb_build_config_error(nonce, errbuf, &msg_len);
dispatchOutgoingMessage(emulContext->outBufs, ctx->clientId, out, msg_len);
return;
}
// Apply intercepts
intercept_ctx_t* icpt_ctxs = NULL;
size_t icpt_count = 0;
if (apply_intercepts(root, &st, &icpt_ctxs, &icpt_count, errbuf) != 0) {
printf("[CTRL/CONFIG] intercept error: %s\n", errbuf);
for (size_t i = 0; i < st.count; i++) {
closeBaseDevice(st.handles[i]);
free(st.handles[i]);
}
free_load_state_paths(&st);
size_t msg_len;
uint8_t* out = fb_build_config_error(nonce, errbuf, &msg_len);
dispatchOutgoingMessage(emulContext->outBufs, ctx->clientId, out, msg_len);
return;
}
// Store intercept contexts for later cleanup
if (icpt_ctxs) {
emulContext->interceptCtxs = calloc(1, sizeof(void*));
emulContext->interceptCtxs[0] = icpt_ctxs;
emulContext->interceptCtxCount = 1;
} else {
emulContext->interceptCtxs = NULL;
emulContext->interceptCtxCount = 0;
}
// Allocate new arrays for EmulContext
size_t dc = st.count;
emulContext->devicesCount = dc;