lince_defaults¶
Reference implementations of the I* interfaces, used by the standalone
build. An SMP2 wrapper, a Python binding, or a custom test harness can
replace any of them by calling the matching Emulator::set_* setter.
Depends on lince_interfaces only — there are no dependencies on the
core, bus, or peripherals.
Source layout¶
src/defaults/
├── CMakeLists.txt
└── src/
├── stdout_logger.cpp
├── stdout_char_device.cpp
├── debug_publisher.cpp
└── null_fault_injector.cpp
The headers are intentionally co-located with the .cpp files (one
per implementation) because no other module is supposed to consume
them by type — consumers see them through the I* interfaces.
StdoutLogger¶
ILogger implementation that writes to stderr via fprintf.
Supports four severity levels (Debug, Info, Warn, Error) and a
configurable threshold.
class StdoutLogger final : public ILogger {
public:
explicit StdoutLogger(LogLevel threshold = LogLevel::Info);
void log(LogLevel, std::string_view) override;
void set_threshold(LogLevel);
private:
LogLevel threshold_;
};
The CLI sets the threshold to Debug when --verbose is passed.
StdoutCharDevice¶
ICharacterDevice implementation that writes TX bytes via putchar
(effectively unbuffered — the implementation calls fflush(stdout)
after each byte to keep RTEMS UART output interleaved with logs in
predictable order). RX bytes come from getchar() in non-blocking
mode (O_NONBLOCK on STDIN_FILENO).
class StdoutCharDevice final : public ICharacterDevice {
public:
StdoutCharDevice();
void transmit(std::uint8_t) override;
bool receive(std::uint8_t&) override;
};
NullFaultInjector¶
IFaultInjector no-op implementation. Returns "no fault, hardware
healthy" for every call. The hooks exist in the core (read paths,
register accesses) so that a Level-3 implementation can be plugged in
later without refactoring.
class NullFaultInjector final : public IFaultInjector {
public:
bool inject_register_fault(CoreId, std::uint8_t /*reg*/) override
{ return false; }
bool inject_memory_fault(PhysAddr, AccessSize) override { return false; }
void publish(IPublisher&) override {}
};
Why have a no-op interface at all? Two reasons:
- The call sites in the core are part of the design — adding them later would mean changing every read path. Better to wire the interface in once and noop the implementation.
- SMP2 wrappers may surface fault-injection as model operations; the no-op default lets the standalone build ignore them entirely.
DebugPublisher¶
IPublisher implementation that dumps every registered observable
field to stderr as JSON when its dump() method is called. Used by
the CLI's --verbose mode and by integration tests that want to
snapshot peripheral state.
class DebugPublisher final : public IPublisher {
public:
void register_field(std::string_view name, FieldType,
FieldGetter, FieldSetter) override;
void dump() const;
};
A future SMP2 wrapper replaces this with Smp::IPublication, which
has the same shape (named fields, typed getters/setters).
Why no header per class?¶
Consumers of lince_defaults see only the I* interfaces — there is
no need to expose StdoutLogger as a type. The implementation is
chosen via:
…which is what lince_app/main.cpp does. Library users in other
contexts substitute their own implementations and never include any
header from lince_defaults at all.