From dade49781fc15e4f2862d02451119881731df498 Mon Sep 17 00:00:00 2001 From: Warren Jayd Date: Sun, 31 May 2026 17:40:32 +0800 Subject: [PATCH 1/7] Bootstrap Monar phase-1 skeleton --- README.md | 57 ++++++ bsp/README.md | 6 + examples/README.md | 5 + include/monar/config.h | 12 ++ include/monar/device.h | 69 +++++++ include/monar/errno.h | 18 ++ include/monar/monar.h | 11 ++ include/monar/osal.h | 17 ++ include/monar/time.h | 9 + include/monar/types.h | 23 +++ runtimes/baremetal/README.md | 13 ++ runtimes/baremetal/mn_runtime_baremetal.c | 39 ++++ silicon/README.md | 6 + src/core/README.md | 6 + src/device/mn_device.c | 220 ++++++++++++++++++++++ src/device/mn_device_registry.c | 197 +++++++++++++++++++ src/internal/mn_device_internal.h | 32 ++++ src/internal/mn_osal_backend.h | 19 ++ src/osal/mn_osal.c | 82 ++++++++ tests/README.md | 17 ++ tests/host/test_main.c | 203 ++++++++++++++++++++ tools/README.md | 5 + 22 files changed, 1066 insertions(+) create mode 100644 README.md create mode 100644 bsp/README.md create mode 100644 examples/README.md create mode 100644 include/monar/config.h create mode 100644 include/monar/device.h create mode 100644 include/monar/errno.h create mode 100644 include/monar/monar.h create mode 100644 include/monar/osal.h create mode 100644 include/monar/time.h create mode 100644 include/monar/types.h create mode 100644 runtimes/baremetal/README.md create mode 100644 runtimes/baremetal/mn_runtime_baremetal.c create mode 100644 silicon/README.md create mode 100644 src/core/README.md create mode 100644 src/device/mn_device.c create mode 100644 src/device/mn_device_registry.c create mode 100644 src/internal/mn_device_internal.h create mode 100644 src/internal/mn_osal_backend.h create mode 100644 src/osal/mn_osal.c create mode 100644 tests/README.md create mode 100644 tests/host/test_main.c create mode 100644 tools/README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..389026d --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# Monar + +Monar is an embedded framework whose goal is: + +> Free your application from MCU and RTOS dependencies. + +This repository, `grape-wzy/monar`, stores the open-source implementation code. +The private design guidance, architecture notes, and review checklists live in +`grape-wzy/monar_desing_guidance`. + +## First-milestone status + +This branch bootstraps the first minimal framework skeleton: + +- public headers under `include/monar/` +- platform-independent core under `src/` +- a minimal baremetal runtime backend under `runtimes/baremetal/` +- host-side smoke tests under `tests/host/` + +The current scope intentionally stays small. It does not implement STM32 +silicon adapters, RTOS backends, Modbus, WS2812B, or complete SPI/I2C/UART +drivers yet. + +## Repository layout + +```text +include/monar/ public Monar headers +src/ platform-independent framework core +runtimes/ runtime backends +silicon/ silicon and vendor SDK adaptation +bsp/ board-level integration +tests/ host-side and future target-side tests +examples/ user-facing examples +tools/ optional development helpers +``` + +## Current design decisions + +- public symbols use the `mn_` / `MN_` prefixes +- public device handles are opaque +- the framework core does not depend directly on RTOS or vendor HAL headers +- `mn_osal_init()` is expected before device registry mutation and device use +- device registration is static-allocation-friendly and bounded by + `MN_CFG_DEVICE_REGISTRY_MAX` +- duplicate `resource_key` registration is rejected to avoid ambiguous + device/bus/carrier ownership of the same hardware resource + +## Host validation + +The initial tests live in `tests/host/test_main.c` and cover: + +- deterministic Monar error codes +- minimal device registry registration rules +- basic `mn_device_open` / `close` / `read` guards + +The current runtime backend is a minimal baremetal placeholder. Unsupported +runtime services are intentionally left for later milestones. diff --git a/bsp/README.md b/bsp/README.md new file mode 100644 index 0000000..c65f024 --- /dev/null +++ b/bsp/README.md @@ -0,0 +1,6 @@ +# BSP + +Board-level integration and device registration belong here. + +The first milestone keeps this directory as a placeholder so the repository +structure matches the design guidance. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..48d8a20 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,5 @@ +# Examples + +User-facing examples will live here. + +The first milestone does not add runtime or silicon-specific examples yet. diff --git a/include/monar/config.h b/include/monar/config.h new file mode 100644 index 0000000..b9861a8 --- /dev/null +++ b/include/monar/config.h @@ -0,0 +1,12 @@ +#ifndef MONAR_CONFIG_H +#define MONAR_CONFIG_H + +#define MN_CFG_DEVICE_REGISTRY_MAX 16u + +#define MN_CFG_RUNTIME_BAREMETAL 1 +#define MN_CFG_RUNTIME_POSIX 0 +#define MN_CFG_RUNTIME_FREERTOS 0 +#define MN_CFG_RUNTIME_RTTHREAD 0 +#define MN_CFG_RUNTIME_ZEPHYR 0 + +#endif /* MONAR_CONFIG_H */ diff --git a/include/monar/device.h b/include/monar/device.h new file mode 100644 index 0000000..5931767 --- /dev/null +++ b/include/monar/device.h @@ -0,0 +1,69 @@ +#ifndef MONAR_DEVICE_H +#define MONAR_DEVICE_H + +#include + +#include + +#define MN_DEVICE_NAME_MAX 32u + +#define MN_DEVICE_CAP_READ (1u << 0) +#define MN_DEVICE_CAP_WRITE (1u << 1) +#define MN_DEVICE_CAP_IOCTL (1u << 2) + +#define MN_DEVICE_OPEN_READ (1u << 0) +#define MN_DEVICE_OPEN_WRITE (1u << 1) +#define MN_DEVICE_OPEN_RDWR (MN_DEVICE_OPEN_READ | MN_DEVICE_OPEN_WRITE) + +typedef struct mn_device *mn_device_t; + +typedef enum mn_device_class { + MN_DEVICE_CLASS_GENERIC = 0, + MN_DEVICE_CLASS_ENDPOINT = 1, + MN_DEVICE_CLASS_BUS = 2, + MN_DEVICE_CLASS_CARRIER = 3 +} mn_device_class_t; + +typedef struct mn_device_ops { + mn_status_t (*open)(mn_device_t device, mn_u32_t flags); + mn_status_t (*close)(mn_device_t device); + mn_status_t (*read)(mn_device_t device, void *buffer, size_t size, + size_t *out_size); + mn_status_t (*write)(mn_device_t device, const void *buffer, size_t size, + size_t *out_size); + mn_status_t (*ioctl)(mn_device_t device, mn_u32_t cmd, void *arg); +} mn_device_ops_t; + +typedef struct mn_device_descriptor { + const char *name; + mn_device_class_t device_class; + mn_u32_t capability_flags; + const void *resource_key; + const mn_device_ops_t *ops; + void *driver_data; +} mn_device_descriptor_t; + +/* + * Registration is intended for framework integration code such as BSP, + * silicon adapters, and tests. Application code should normally consume + * already-registered devices through mn_device_open(). + */ +mn_status_t mn_device_register(const mn_device_descriptor_t *descriptor, + mn_device_t *out_device); +mn_status_t mn_device_find(const char *name, mn_device_t *out_device); + +mn_status_t mn_device_open(const char *name, mn_u32_t flags, + mn_device_t *out_device); +mn_status_t mn_device_close(mn_device_t device); +mn_status_t mn_device_read(mn_device_t device, void *buffer, size_t size, + size_t *out_size); +mn_status_t mn_device_write(mn_device_t device, const void *buffer, + size_t size, size_t *out_size); +mn_status_t mn_device_ioctl(mn_device_t device, mn_u32_t cmd, void *arg); + +const char *mn_device_get_name(mn_device_t device); +mn_device_class_t mn_device_get_class(mn_device_t device); +mn_u32_t mn_device_get_capabilities(mn_device_t device); +void *mn_device_get_driver_data(mn_device_t device); + +#endif /* MONAR_DEVICE_H */ diff --git a/include/monar/errno.h b/include/monar/errno.h new file mode 100644 index 0000000..e229174 --- /dev/null +++ b/include/monar/errno.h @@ -0,0 +1,18 @@ +#ifndef MONAR_ERRNO_H +#define MONAR_ERRNO_H + +#define MN_EOK 0 +#define MN_EPERM 1 +#define MN_ENOENT 2 +#define MN_EIO 5 +#define MN_EBADF 9 +#define MN_EAGAIN 11 +#define MN_ENOMEM 12 +#define MN_EBUSY 16 +#define MN_EEXIST 17 +#define MN_ENODEV 19 +#define MN_EINVAL 22 +#define MN_ENOSYS 38 +#define MN_ETIMEDOUT 110 + +#endif /* MONAR_ERRNO_H */ diff --git a/include/monar/monar.h b/include/monar/monar.h new file mode 100644 index 0000000..fc5a391 --- /dev/null +++ b/include/monar/monar.h @@ -0,0 +1,11 @@ +#ifndef MONAR_MONAR_H +#define MONAR_MONAR_H + +#include +#include +#include +#include +#include +#include + +#endif /* MONAR_MONAR_H */ diff --git a/include/monar/osal.h b/include/monar/osal.h new file mode 100644 index 0000000..e92bffa --- /dev/null +++ b/include/monar/osal.h @@ -0,0 +1,17 @@ +#ifndef MONAR_OSAL_H +#define MONAR_OSAL_H + +#include + +#include + +typedef mn_uptr_t mn_osal_critical_state_t; + +mn_status_t mn_osal_init(void); +bool mn_osal_is_initialized(void); +const char *mn_osal_runtime_name(void); +bool mn_osal_is_in_isr(void); +mn_osal_critical_state_t mn_osal_critical_enter(void); +void mn_osal_critical_exit(mn_osal_critical_state_t state); + +#endif /* MONAR_OSAL_H */ diff --git a/include/monar/time.h b/include/monar/time.h new file mode 100644 index 0000000..5e2bb11 --- /dev/null +++ b/include/monar/time.h @@ -0,0 +1,9 @@ +#ifndef MONAR_TIME_H +#define MONAR_TIME_H + +#include + +#define MN_TIMEOUT_NO_WAIT ((mn_timeout_t)0) +#define MN_TIMEOUT_FOREVER ((mn_timeout_t)-1) + +#endif /* MONAR_TIME_H */ diff --git a/include/monar/types.h b/include/monar/types.h new file mode 100644 index 0000000..5570e13 --- /dev/null +++ b/include/monar/types.h @@ -0,0 +1,23 @@ +#ifndef MONAR_TYPES_H +#define MONAR_TYPES_H + +#include +#include +#include + +typedef int8_t mn_i8_t; +typedef uint8_t mn_u8_t; +typedef int16_t mn_i16_t; +typedef uint16_t mn_u16_t; +typedef int32_t mn_i32_t; +typedef uint32_t mn_u32_t; +typedef int64_t mn_i64_t; +typedef uint64_t mn_u64_t; +typedef intptr_t mn_iptr_t; +typedef uintptr_t mn_uptr_t; + +typedef mn_i32_t mn_status_t; +typedef mn_i32_t mn_timeout_t; +typedef void *mn_handle_t; + +#endif /* MONAR_TYPES_H */ diff --git a/runtimes/baremetal/README.md b/runtimes/baremetal/README.md new file mode 100644 index 0000000..67e77dd --- /dev/null +++ b/runtimes/baremetal/README.md @@ -0,0 +1,13 @@ +# Baremetal Runtime + +This backend is the first-stage minimal runtime placeholder for Monar. + +Current behavior: + +- `mn_osal_init()` succeeds +- critical sections are implemented as no-op stubs +- ISR detection always reports `false` + +This is intentional for the bootstrap milestone. Future milestones can replace +the no-op critical section behavior with target-specific interrupt save/restore +logic without changing the public Monar API. diff --git a/runtimes/baremetal/mn_runtime_baremetal.c b/runtimes/baremetal/mn_runtime_baremetal.c new file mode 100644 index 0000000..8de4e5f --- /dev/null +++ b/runtimes/baremetal/mn_runtime_baremetal.c @@ -0,0 +1,39 @@ +#include + +#include + +#include "mn_osal_backend.h" + +static mn_status_t mn_runtime_baremetal_init(void) +{ + return MN_EOK; +} + +static bool mn_runtime_baremetal_is_in_isr(void) +{ + return false; +} + +static mn_osal_critical_state_t mn_runtime_baremetal_critical_enter(void) +{ + return 0; +} + +static void mn_runtime_baremetal_critical_exit( + mn_osal_critical_state_t state) +{ + (void)state; +} + +const mn_runtime_ops_t *mn_runtime_get_ops(void) +{ + static const mn_runtime_ops_t runtime = { + .name = "baremetal", + .init = mn_runtime_baremetal_init, + .is_in_isr = mn_runtime_baremetal_is_in_isr, + .critical_enter = mn_runtime_baremetal_critical_enter, + .critical_exit = mn_runtime_baremetal_critical_exit, + }; + + return &runtime; +} diff --git a/silicon/README.md b/silicon/README.md new file mode 100644 index 0000000..2667bdc --- /dev/null +++ b/silicon/README.md @@ -0,0 +1,6 @@ +# Silicon + +Platform-specific silicon and vendor SDK adaptation code belongs here. + +The first milestone intentionally does not add STM32 or other vendor adapters +yet. diff --git a/src/core/README.md b/src/core/README.md new file mode 100644 index 0000000..f4d7773 --- /dev/null +++ b/src/core/README.md @@ -0,0 +1,6 @@ +# Core + +Platform-independent core helpers belong here. + +The first milestone keeps core support minimal and focuses on the public API, +device registry skeleton, and OSAL/runtime boundary. diff --git a/src/device/mn_device.c b/src/device/mn_device.c new file mode 100644 index 0000000..8b6829c --- /dev/null +++ b/src/device/mn_device.c @@ -0,0 +1,220 @@ +#include + +#include +#include +#include + +#include "mn_device_internal.h" + +static bool mn_device_open_flags_are_valid(mn_u32_t flags) +{ + mn_u32_t allowed; + + allowed = MN_DEVICE_OPEN_READ | MN_DEVICE_OPEN_WRITE; + return (flags & ~allowed) == 0u; +} + +static mn_status_t mn_device_prepare_transition(mn_device_t device, + mn_device_state_t expected, mn_device_state_t next) +{ + mn_osal_critical_state_t state; + + state = mn_osal_critical_enter(); + if (device->state != expected) { + mn_osal_critical_exit(state); + return (expected == MN_DEVICE_STATE_OPENED) ? -MN_EBADF : -MN_EBUSY; + } + + device->state = next; + mn_osal_critical_exit(state); + return MN_EOK; +} + +static void mn_device_finish_transition(mn_device_t device, + mn_device_state_t next) +{ + mn_osal_critical_state_t state; + + state = mn_osal_critical_enter(); + device->state = next; + mn_osal_critical_exit(state); +} + +mn_status_t mn_device_open(const char *name, mn_u32_t flags, + mn_device_t *out_device) +{ + mn_device_t device; + mn_status_t ret; + + if (!mn_device_open_flags_are_valid(flags) || out_device == NULL) { + return -MN_EINVAL; + } + + ret = mn_device_registry_lookup(name, &device); + if (ret != MN_EOK) { + return ret; + } + + ret = mn_device_prepare_transition(device, MN_DEVICE_STATE_REGISTERED, + MN_DEVICE_STATE_OPENING); + if (ret != MN_EOK) { + return ret; + } + + if (device->ops->open != NULL) { + ret = device->ops->open(device, flags); + if (ret != MN_EOK) { + mn_device_finish_transition(device, MN_DEVICE_STATE_REGISTERED); + return ret; + } + } + + mn_device_finish_transition(device, MN_DEVICE_STATE_OPENED); + *out_device = device; + return MN_EOK; +} + +mn_status_t mn_device_close(mn_device_t device) +{ + mn_status_t ret; + + if (!mn_osal_is_initialized()) { + return -MN_EPERM; + } + + if (!mn_device_registry_is_valid_handle(device)) { + return -MN_EBADF; + } + + ret = mn_device_prepare_transition(device, MN_DEVICE_STATE_OPENED, + MN_DEVICE_STATE_CLOSING); + if (ret != MN_EOK) { + return ret; + } + + if (device->ops->close != NULL) { + ret = device->ops->close(device); + if (ret != MN_EOK) { + mn_device_finish_transition(device, MN_DEVICE_STATE_OPENED); + return ret; + } + } + + mn_device_finish_transition(device, MN_DEVICE_STATE_REGISTERED); + return MN_EOK; +} + +mn_status_t mn_device_read(mn_device_t device, void *buffer, size_t size, + size_t *out_size) +{ + if (!mn_osal_is_initialized()) { + return -MN_EPERM; + } + + if (!mn_device_registry_is_valid_handle(device)) { + return -MN_EBADF; + } + + if (size > 0u && buffer == NULL) { + return -MN_EINVAL; + } + + if (device->state != MN_DEVICE_STATE_OPENED) { + return -MN_EBADF; + } + + if ((device->capability_flags & MN_DEVICE_CAP_READ) == 0u || + device->ops->read == NULL) { + return -MN_ENOSYS; + } + + return device->ops->read(device, buffer, size, out_size); +} + +mn_status_t mn_device_write(mn_device_t device, const void *buffer, + size_t size, size_t *out_size) +{ + if (!mn_osal_is_initialized()) { + return -MN_EPERM; + } + + if (!mn_device_registry_is_valid_handle(device)) { + return -MN_EBADF; + } + + if (size > 0u && buffer == NULL) { + return -MN_EINVAL; + } + + if (device->state != MN_DEVICE_STATE_OPENED) { + return -MN_EBADF; + } + + if ((device->capability_flags & MN_DEVICE_CAP_WRITE) == 0u || + device->ops->write == NULL) { + return -MN_ENOSYS; + } + + return device->ops->write(device, buffer, size, out_size); +} + +mn_status_t mn_device_ioctl(mn_device_t device, mn_u32_t cmd, void *arg) +{ + (void)cmd; + (void)arg; + + if (!mn_osal_is_initialized()) { + return -MN_EPERM; + } + + if (!mn_device_registry_is_valid_handle(device)) { + return -MN_EBADF; + } + + if (device->state != MN_DEVICE_STATE_OPENED) { + return -MN_EBADF; + } + + if ((device->capability_flags & MN_DEVICE_CAP_IOCTL) == 0u || + device->ops->ioctl == NULL) { + return -MN_ENOSYS; + } + + return device->ops->ioctl(device, cmd, arg); +} + +const char *mn_device_get_name(mn_device_t device) +{ + if (!mn_device_registry_is_valid_handle(device)) { + return NULL; + } + + return device->name; +} + +mn_device_class_t mn_device_get_class(mn_device_t device) +{ + if (!mn_device_registry_is_valid_handle(device)) { + return MN_DEVICE_CLASS_GENERIC; + } + + return device->device_class; +} + +mn_u32_t mn_device_get_capabilities(mn_device_t device) +{ + if (!mn_device_registry_is_valid_handle(device)) { + return 0u; + } + + return device->capability_flags; +} + +void *mn_device_get_driver_data(mn_device_t device) +{ + if (!mn_device_registry_is_valid_handle(device)) { + return NULL; + } + + return device->driver_data; +} diff --git a/src/device/mn_device_registry.c b/src/device/mn_device_registry.c new file mode 100644 index 0000000..47c61da --- /dev/null +++ b/src/device/mn_device_registry.c @@ -0,0 +1,197 @@ +#include + +#include +#include +#include + +#include "mn_device_internal.h" + +static struct mn_device g_mn_device_registry[MN_CFG_DEVICE_REGISTRY_MAX]; + +static bool mn_device_name_is_valid(const char *name) +{ + size_t len; + + if (name == NULL || name[0] == '\0') { + return false; + } + + len = 0; + while (name[len] != '\0') { + if (len >= MN_DEVICE_NAME_MAX - 1u) { + return false; + } + ++len; + } + + return true; +} + +static bool mn_device_name_equals(const char *lhs, const char *rhs) +{ + size_t i; + + if (lhs == NULL || rhs == NULL) { + return false; + } + + for (i = 0; lhs[i] != '\0' || rhs[i] != '\0'; ++i) { + if (lhs[i] != rhs[i]) { + return false; + } + } + + return true; +} + +static bool mn_device_class_is_valid(mn_device_class_t device_class) +{ + return device_class == MN_DEVICE_CLASS_GENERIC || + device_class == MN_DEVICE_CLASS_ENDPOINT || + device_class == MN_DEVICE_CLASS_BUS || + device_class == MN_DEVICE_CLASS_CARRIER; +} + +static mn_status_t mn_device_registry_validate_descriptor( + const mn_device_descriptor_t *descriptor) +{ + if (!mn_osal_is_initialized()) { + return -MN_EPERM; + } + + if (descriptor == NULL) { + return -MN_EINVAL; + } + + if (!mn_device_name_is_valid(descriptor->name)) { + return -MN_EINVAL; + } + + if (!mn_device_class_is_valid(descriptor->device_class)) { + return -MN_EINVAL; + } + + if (descriptor->ops == NULL) { + return -MN_EINVAL; + } + + return MN_EOK; +} + +mn_status_t mn_device_register(const mn_device_descriptor_t *descriptor, + mn_device_t *out_device) +{ + mn_osal_critical_state_t state; + size_t i; + mn_status_t ret; + + ret = mn_device_registry_validate_descriptor(descriptor); + if (ret != MN_EOK) { + return ret; + } + + state = mn_osal_critical_enter(); + for (i = 0; i < MN_CFG_DEVICE_REGISTRY_MAX; ++i) { + if (!g_mn_device_registry[i].is_registered) { + continue; + } + + if (mn_device_name_equals(g_mn_device_registry[i].name, + descriptor->name)) { + mn_osal_critical_exit(state); + return -MN_EEXIST; + } + + if (descriptor->resource_key != NULL && + g_mn_device_registry[i].resource_key == + descriptor->resource_key) { + mn_osal_critical_exit(state); + return -MN_EEXIST; + } + } + + for (i = 0; i < MN_CFG_DEVICE_REGISTRY_MAX; ++i) { + if (g_mn_device_registry[i].is_registered) { + continue; + } + + g_mn_device_registry[i].name = descriptor->name; + g_mn_device_registry[i].device_class = descriptor->device_class; + g_mn_device_registry[i].capability_flags = + descriptor->capability_flags; + g_mn_device_registry[i].resource_key = descriptor->resource_key; + g_mn_device_registry[i].ops = descriptor->ops; + g_mn_device_registry[i].driver_data = descriptor->driver_data; + g_mn_device_registry[i].state = MN_DEVICE_STATE_REGISTERED; + g_mn_device_registry[i].is_registered = true; + + if (out_device != NULL) { + *out_device = &g_mn_device_registry[i]; + } + + mn_osal_critical_exit(state); + return MN_EOK; + } + + mn_osal_critical_exit(state); + return -MN_ENOMEM; +} + +mn_status_t mn_device_registry_lookup(const char *name, mn_device_t *out_device) +{ + mn_osal_critical_state_t state; + size_t i; + + if (!mn_osal_is_initialized()) { + return -MN_EPERM; + } + + if (!mn_device_name_is_valid(name) || out_device == NULL) { + return -MN_EINVAL; + } + + state = mn_osal_critical_enter(); + for (i = 0; i < MN_CFG_DEVICE_REGISTRY_MAX; ++i) { + if (!g_mn_device_registry[i].is_registered) { + continue; + } + + if (mn_device_name_equals(g_mn_device_registry[i].name, name)) { + *out_device = &g_mn_device_registry[i]; + mn_osal_critical_exit(state); + return MN_EOK; + } + } + + mn_osal_critical_exit(state); + return -MN_ENOENT; +} + +bool mn_device_registry_is_valid_handle(mn_device_t device) +{ + size_t i; + bool found; + mn_osal_critical_state_t state; + + if (device == NULL) { + return false; + } + + found = false; + state = mn_osal_critical_enter(); + for (i = 0; i < MN_CFG_DEVICE_REGISTRY_MAX; ++i) { + if (&g_mn_device_registry[i] == device && + g_mn_device_registry[i].is_registered) { + found = true; + break; + } + } + mn_osal_critical_exit(state); + + return found; +} + +mn_status_t mn_device_find(const char *name, mn_device_t *out_device) +{ + return mn_device_registry_lookup(name, out_device); +} diff --git a/src/internal/mn_device_internal.h b/src/internal/mn_device_internal.h new file mode 100644 index 0000000..675b296 --- /dev/null +++ b/src/internal/mn_device_internal.h @@ -0,0 +1,32 @@ +#ifndef MONAR_SRC_INTERNAL_MN_DEVICE_INTERNAL_H +#define MONAR_SRC_INTERNAL_MN_DEVICE_INTERNAL_H + +#include + +#include +#include +#include + +typedef enum mn_device_state { + MN_DEVICE_STATE_UNUSED = 0, + MN_DEVICE_STATE_REGISTERED, + MN_DEVICE_STATE_OPENING, + MN_DEVICE_STATE_OPENED, + MN_DEVICE_STATE_CLOSING +} mn_device_state_t; + +struct mn_device { + const char *name; + mn_device_class_t device_class; + mn_u32_t capability_flags; + const void *resource_key; + const mn_device_ops_t *ops; + void *driver_data; + mn_device_state_t state; + bool is_registered; +}; + +mn_status_t mn_device_registry_lookup(const char *name, mn_device_t *out_device); +bool mn_device_registry_is_valid_handle(mn_device_t device); + +#endif /* MONAR_SRC_INTERNAL_MN_DEVICE_INTERNAL_H */ diff --git a/src/internal/mn_osal_backend.h b/src/internal/mn_osal_backend.h new file mode 100644 index 0000000..f430e2b --- /dev/null +++ b/src/internal/mn_osal_backend.h @@ -0,0 +1,19 @@ +#ifndef MONAR_SRC_INTERNAL_MN_OSAL_BACKEND_H +#define MONAR_SRC_INTERNAL_MN_OSAL_BACKEND_H + +#include + +#include +#include + +typedef struct mn_runtime_ops { + const char *name; + mn_status_t (*init)(void); + bool (*is_in_isr)(void); + mn_osal_critical_state_t (*critical_enter)(void); + void (*critical_exit)(mn_osal_critical_state_t state); +} mn_runtime_ops_t; + +const mn_runtime_ops_t *mn_runtime_get_ops(void); + +#endif /* MONAR_SRC_INTERNAL_MN_OSAL_BACKEND_H */ diff --git a/src/osal/mn_osal.c b/src/osal/mn_osal.c new file mode 100644 index 0000000..d06a5f3 --- /dev/null +++ b/src/osal/mn_osal.c @@ -0,0 +1,82 @@ +#include +#include + +#include "mn_osal_backend.h" + +static bool g_mn_osal_initialized; + +mn_status_t mn_osal_init(void) +{ + const mn_runtime_ops_t *runtime; + mn_status_t ret; + + if (g_mn_osal_initialized) { + return MN_EOK; + } + + runtime = mn_runtime_get_ops(); + if (runtime == NULL || runtime->init == NULL) { + return -MN_ENOSYS; + } + + ret = runtime->init(); + if (ret != MN_EOK) { + return ret; + } + + g_mn_osal_initialized = true; + return MN_EOK; +} + +bool mn_osal_is_initialized(void) +{ + return g_mn_osal_initialized; +} + +const char *mn_osal_runtime_name(void) +{ + const mn_runtime_ops_t *runtime; + + runtime = mn_runtime_get_ops(); + if (runtime == NULL || runtime->name == NULL) { + return "unknown"; + } + + return runtime->name; +} + +bool mn_osal_is_in_isr(void) +{ + const mn_runtime_ops_t *runtime; + + runtime = mn_runtime_get_ops(); + if (runtime == NULL || runtime->is_in_isr == NULL) { + return false; + } + + return runtime->is_in_isr(); +} + +mn_osal_critical_state_t mn_osal_critical_enter(void) +{ + const mn_runtime_ops_t *runtime; + + runtime = mn_runtime_get_ops(); + if (runtime == NULL || runtime->critical_enter == NULL) { + return 0; + } + + return runtime->critical_enter(); +} + +void mn_osal_critical_exit(mn_osal_critical_state_t state) +{ + const mn_runtime_ops_t *runtime; + + runtime = mn_runtime_get_ops(); + if (runtime == NULL || runtime->critical_exit == NULL) { + return; + } + + runtime->critical_exit(state); +} diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..6c82a8d --- /dev/null +++ b/tests/README.md @@ -0,0 +1,17 @@ +# Tests + +Host-side and future target-side tests belong here. + +The current bootstrap milestone adds a small host test at `tests/host/`. + +Example host build command: + +```text +cc -std=c11 -Wall -Wextra -Iinclude -Isrc/internal \ + src/osal/mn_osal.c \ + src/device/mn_device.c \ + src/device/mn_device_registry.c \ + runtimes/baremetal/mn_runtime_baremetal.c \ + tests/host/test_main.c \ + -o monar_host_test +``` diff --git a/tests/host/test_main.c b/tests/host/test_main.c new file mode 100644 index 0000000..8ba2fb1 --- /dev/null +++ b/tests/host/test_main.c @@ -0,0 +1,203 @@ +#include +#include +#include + +#include + +typedef struct test_device_context { + size_t open_calls; + size_t close_calls; + size_t read_calls; + size_t write_calls; + size_t ioctl_calls; +} test_device_context_t; + +static mn_status_t test_open(mn_device_t device, mn_u32_t flags) +{ + test_device_context_t *ctx; + + (void)flags; + + ctx = mn_device_get_driver_data(device); + assert(ctx != NULL); + ctx->open_calls++; + return MN_EOK; +} + +static mn_status_t test_close(mn_device_t device) +{ + test_device_context_t *ctx; + + ctx = mn_device_get_driver_data(device); + assert(ctx != NULL); + ctx->close_calls++; + return MN_EOK; +} + +static mn_status_t test_read(mn_device_t device, void *buffer, size_t size, + size_t *out_size) +{ + static const char payload[] = "monar"; + size_t count; + test_device_context_t *ctx; + + ctx = mn_device_get_driver_data(device); + assert(ctx != NULL); + ctx->read_calls++; + + count = size; + if (count > sizeof(payload) - 1u) { + count = sizeof(payload) - 1u; + } + + if (count > 0u) { + memcpy(buffer, payload, count); + } + + if (out_size != NULL) { + *out_size = count; + } + + return MN_EOK; +} + +static mn_status_t test_write(mn_device_t device, const void *buffer, + size_t size, size_t *out_size) +{ + test_device_context_t *ctx; + + assert(buffer != NULL || size == 0u); + + ctx = mn_device_get_driver_data(device); + assert(ctx != NULL); + ctx->write_calls++; + + if (out_size != NULL) { + *out_size = size; + } + + return MN_EOK; +} + +static mn_status_t test_ioctl(mn_device_t device, mn_u32_t cmd, void *arg) +{ + test_device_context_t *ctx; + + (void)cmd; + (void)arg; + + ctx = mn_device_get_driver_data(device); + assert(ctx != NULL); + ctx->ioctl_calls++; + return MN_EOK; +} + +static void test_registry_rules(void) +{ + static test_device_context_t ctx_a; + static test_device_context_t ctx_b; + static const int resource_a; + static const int resource_b; + static const mn_device_ops_t ops = { + .open = test_open, + .close = test_close, + .read = test_read, + .write = test_write, + .ioctl = test_ioctl, + }; + const mn_device_descriptor_t dev_a = { + .name = "uart0", + .device_class = MN_DEVICE_CLASS_ENDPOINT, + .capability_flags = MN_DEVICE_CAP_READ | MN_DEVICE_CAP_WRITE | + MN_DEVICE_CAP_IOCTL, + .resource_key = &resource_a, + .ops = &ops, + .driver_data = &ctx_a, + }; + const mn_device_descriptor_t duplicate_name = { + .name = "uart0", + .device_class = MN_DEVICE_CLASS_ENDPOINT, + .capability_flags = MN_DEVICE_CAP_READ, + .resource_key = &resource_b, + .ops = &ops, + .driver_data = &ctx_b, + }; + const mn_device_descriptor_t duplicate_resource = { + .name = "spi0", + .device_class = MN_DEVICE_CLASS_BUS, + .capability_flags = MN_DEVICE_CAP_READ, + .resource_key = &resource_a, + .ops = &ops, + .driver_data = &ctx_b, + }; + mn_device_t unused; + + assert(mn_device_register(&dev_a, NULL) == MN_EOK); + assert(mn_device_register(&duplicate_name, NULL) == -MN_EEXIST); + assert(mn_device_register(&duplicate_resource, NULL) == -MN_EEXIST); + assert(mn_device_find("missing0", &unused) == -MN_ENOENT); +} + +static void test_device_api_guards(void) +{ + static test_device_context_t ctx; + static const int resource_c; + static const mn_device_ops_t ops = { + .open = test_open, + .close = test_close, + .read = test_read, + .write = NULL, + .ioctl = NULL, + }; + const mn_device_descriptor_t descriptor = { + .name = "sensor0", + .device_class = MN_DEVICE_CLASS_ENDPOINT, + .capability_flags = MN_DEVICE_CAP_READ, + .resource_key = &resource_c, + .ops = &ops, + .driver_data = &ctx, + }; + mn_device_t device; + mn_device_t looked_up; + char buffer[8]; + size_t count; + + assert(mn_device_register(&descriptor, &device) == MN_EOK); + assert(mn_device_find("sensor0", &looked_up) == MN_EOK); + assert(looked_up == device); + + assert(mn_device_open(NULL, 0u, &device) == -MN_EINVAL); + assert(mn_device_open("missing1", 0u, &device) == -MN_ENOENT); + assert(mn_device_read(device, buffer, sizeof(buffer), &count) == -MN_EBADF); + assert(mn_device_open("sensor0", MN_DEVICE_OPEN_RDWR, &device) == MN_EOK); + assert(mn_device_open("sensor0", MN_DEVICE_OPEN_RDWR, &looked_up) == + -MN_EBUSY); + assert(mn_device_read(device, NULL, sizeof(buffer), &count) == -MN_EINVAL); + assert(mn_device_read(device, buffer, sizeof(buffer), &count) == MN_EOK); + assert(count == 5u); + assert(mn_device_write(device, buffer, sizeof(buffer), &count) == + -MN_ENOSYS); + assert(mn_device_ioctl(device, 0u, NULL) == -MN_ENOSYS); + assert(mn_device_close(device) == MN_EOK); + assert(mn_device_close(device) == -MN_EBADF); + assert(ctx.open_calls == 1u); + assert(ctx.close_calls == 1u); + assert(ctx.read_calls == 1u); + assert(ctx.write_calls == 0u); + assert(ctx.ioctl_calls == 0u); +} + +int main(void) +{ + assert(mn_osal_is_initialized() == false); + assert(mn_osal_init() == MN_EOK); + assert(mn_osal_is_initialized() == true); + assert(strcmp(mn_osal_runtime_name(), "baremetal") == 0); + assert(mn_osal_is_in_isr() == false); + + test_registry_rules(); + test_device_api_guards(); + + puts("host tests passed"); + return 0; +} diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000..a779338 --- /dev/null +++ b/tools/README.md @@ -0,0 +1,5 @@ +# Tools + +Optional development helpers and validation utilities belong here. + +The first milestone keeps this directory as a placeholder. From 6a9532e81757c1acc79f00af78893fbfa58d6eef Mon Sep 17 00:00:00 2001 From: Warren Jayd Date: Sun, 31 May 2026 21:49:07 +0800 Subject: [PATCH 2/7] Add CMake and STM32 build skeleton --- .clangd | 2 + .gitignore | 13 ++ .vscode/extensions.json | 7 + .vscode/settings.json | 28 ++++ .vscode/tasks.json | 77 +++++++++++ CMakeLists.txt | 122 ++++++++++++++++++ CMakePresets.json | 53 ++++++++ Makefile | 37 ++++++ README.md | 55 ++++++++ bsp/boards/st/nucleo_f407zg/README.md | 14 ++ bsp/boards/st/nucleo_f407zg/board_init.c | 9 ++ bsp/boards/st/nucleo_f407zg/include/board.h | 6 + .../st/nucleo_f407zg/include/board_config.h | 8 ++ .../nucleo_f407zg/linker/stm32f407zg_flash.ld | 45 +++++++ .../startup/startup_stm32f407xx.c | 73 +++++++++++ cmake/toolchains/arm-none-eabi-gcc.cmake | 54 ++++++++ examples/minimal/main.c | 16 +++ tests/README.md | 10 ++ 18 files changed, 629 insertions(+) create mode 100644 .clangd create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 CMakeLists.txt create mode 100644 CMakePresets.json create mode 100644 Makefile create mode 100644 bsp/boards/st/nucleo_f407zg/README.md create mode 100644 bsp/boards/st/nucleo_f407zg/board_init.c create mode 100644 bsp/boards/st/nucleo_f407zg/include/board.h create mode 100644 bsp/boards/st/nucleo_f407zg/include/board_config.h create mode 100644 bsp/boards/st/nucleo_f407zg/linker/stm32f407zg_flash.ld create mode 100644 bsp/boards/st/nucleo_f407zg/startup/startup_stm32f407xx.c create mode 100644 cmake/toolchains/arm-none-eabi-gcc.cmake create mode 100644 examples/minimal/main.c diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..29788b8 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + CompilationDatabase: . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ff39f59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +build/ +compile_commands.json + +*.o +*.obj +*.a +*.lib +*.d +*.elf +*.bin +*.hex +*.map +*.exe diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..1de7b23 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "llvm-vs-code-extensions.vscode-clangd" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9b33923 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,28 @@ +{ + "cmake.useCMakePresets": "always", + "cmake.configureOnOpen": false, + "cmake.cmakePath": "${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin\\cmake.exe", + "cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json", + "cmake.configureEnvironment": { + "PATH": "${env:PATH};${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin", + "MONAR_ARM_GCC_ROOT": "${env:MONAR_ARM_GCC_ROOT}" + }, + "cmake.buildEnvironment": { + "PATH": "${env:PATH};${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin", + "MONAR_ARM_GCC_ROOT": "${env:MONAR_ARM_GCC_ROOT}" + }, + "terminal.integrated.env.windows": { + "PATH": "${env:PATH};${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin", + "MONAR_ARM_GCC_ROOT": "${env:MONAR_ARM_GCC_ROOT}" + }, + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", + "C_Cpp.default.compileCommands": "${workspaceFolder}\\compile_commands.json", + "C_Cpp.default.compilerPath": "${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin\\gcc.exe", + "C_Cpp.default.intelliSenseMode": "windows-gcc-x64", + "clangd.arguments": [ + "--compile-commands-dir=${workspaceFolder}" + ], + "files.associations": { + "*.ld": "ld" + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..e89b2be --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,77 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "cmake: configure host", + "type": "shell", + "command": "cmake", + "args": [ + "--preset", + "host-debug" + ], + "group": "build", + "problemMatcher": [ + "$gcc" + ] + }, + { + "label": "cmake: build host", + "type": "shell", + "command": "cmake", + "args": [ + "--build", + "--preset", + "host-debug" + ], + "group": "build", + "dependsOn": "cmake: configure host", + "problemMatcher": [ + "$gcc" + ] + }, + { + "label": "cmake: run host test", + "type": "shell", + "command": "ctest", + "args": [ + "--preset", + "host-debug" + ], + "dependsOn": "cmake: build host", + "problemMatcher": [ + "$gcc" + ] + }, + { + "label": "cmake: configure stm32f407", + "type": "shell", + "command": "cmake", + "args": [ + "--preset", + "stm32f407-debug" + ], + "group": "build", + "problemMatcher": [ + "$gcc" + ] + }, + { + "label": "cmake: build stm32f407", + "type": "shell", + "command": "cmake", + "args": [ + "--build", + "--preset", + "stm32f407-debug" + ], + "dependsOn": "cmake: configure stm32f407", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$gcc" + ] + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..294e21d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,122 @@ +cmake_minimum_required(VERSION 3.20) + +project(Monar VERSION 0.1.0 LANGUAGES C ASM) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +enable_testing() + +set(MONAR_PLATFORM "host" CACHE STRING + "Monar build platform: host or stm32f407") +set_property(CACHE MONAR_PLATFORM PROPERTY STRINGS host stm32f407) + +set(MONAR_COMMON_SOURCES + src/osal/mn_osal.c + src/device/mn_device.c + src/device/mn_device_registry.c + runtimes/baremetal/mn_runtime_baremetal.c +) + +add_library(monar_core STATIC ${MONAR_COMMON_SOURCES}) +target_include_directories(monar_core + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/internal +) +target_compile_features(monar_core PUBLIC c_std_11) + +if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(monar_core PRIVATE -Wall -Wextra -Wpedantic) +endif() + +if(CMAKE_EXPORT_COMPILE_COMMANDS) + add_custom_target(monar_sync_compile_commands ALL + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json + ${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json + BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json + COMMENT "Sync compile_commands.json to repository root" + VERBATIM + ) +endif() + +if(MONAR_PLATFORM STREQUAL "host") + add_executable(monar_host_test tests/host/test_main.c) + target_link_libraries(monar_host_test PRIVATE monar_core) + target_include_directories(monar_host_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src/internal + ) + add_test(NAME monar_host_smoke COMMAND monar_host_test) +elseif(MONAR_PLATFORM STREQUAL "stm32f407") + set(MONAR_STM32_BOARD_ROOT + ${CMAKE_CURRENT_SOURCE_DIR}/bsp/boards/st/nucleo_f407zg) + set(MONAR_STM32_LINKER_SCRIPT + ${MONAR_STM32_BOARD_ROOT}/linker/stm32f407zg_flash.ld) + + add_executable(monar_stm32f407_minimal.elf + ${MONAR_STM32_BOARD_ROOT}/startup/startup_stm32f407xx.c + ${MONAR_STM32_BOARD_ROOT}/board_init.c + examples/minimal/main.c + ) + + target_link_libraries(monar_stm32f407_minimal.elf PRIVATE monar_core) + target_include_directories(monar_stm32f407_minimal.elf PRIVATE + ${MONAR_STM32_BOARD_ROOT}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src/internal + ) + target_compile_definitions(monar_stm32f407_minimal.elf PRIVATE + STM32F407xx + ) + + if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(monar_core PRIVATE + -mcpu=cortex-m4 + -mthumb + -ffreestanding + -fdata-sections + -ffunction-sections + ) + target_compile_options(monar_stm32f407_minimal.elf PRIVATE + -mcpu=cortex-m4 + -mthumb + -ffreestanding + -fdata-sections + -ffunction-sections + ) + target_link_options(monar_stm32f407_minimal.elf PRIVATE + -mcpu=cortex-m4 + -mthumb + -nostartfiles + -nostdlib + -Wl,--gc-sections + -Wl,--build-id=none + -Wl,-Map=${CMAKE_CURRENT_BINARY_DIR}/monar_stm32f407_minimal.map + -T${MONAR_STM32_LINKER_SCRIPT} + ) + endif() + + if(CMAKE_OBJCOPY) + add_custom_command(TARGET monar_stm32f407_minimal.elf POST_BUILD + COMMAND ${CMAKE_OBJCOPY} -O ihex + $ + ${CMAKE_CURRENT_BINARY_DIR}/monar_stm32f407_minimal.hex + COMMAND ${CMAKE_OBJCOPY} -O binary + $ + ${CMAKE_CURRENT_BINARY_DIR}/monar_stm32f407_minimal.bin + VERBATIM + ) + endif() + + if(MONAR_SIZE_UTIL) + add_custom_command(TARGET monar_stm32f407_minimal.elf POST_BUILD + COMMAND ${MONAR_SIZE_UTIL} $ + VERBATIM + ) + endif() +else() + message(FATAL_ERROR "Unsupported MONAR_PLATFORM='${MONAR_PLATFORM}'") +endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..570daf0 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,53 @@ +{ + "version": 5, + "cmakeMinimumRequired": { + "major": 3, + "minor": 20, + "patch": 0 + }, + "configurePresets": [ + { + "name": "host-debug", + "displayName": "Host Debug", + "generator": "MinGW Makefiles", + "binaryDir": "${sourceDir}/build/host-debug", + "cacheVariables": { + "MONAR_PLATFORM": "host", + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_C_COMPILER": "$env{LOCALAPPDATA}/Microsoft/WinGet/Packages/BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe/mingw64/bin/gcc.exe", + "CMAKE_MAKE_PROGRAM": "$env{LOCALAPPDATA}/Microsoft/WinGet/Packages/BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe/mingw64/bin/mingw32-make.exe" + } + }, + { + "name": "stm32f407-debug", + "displayName": "STM32F407 Debug", + "generator": "MinGW Makefiles", + "binaryDir": "${sourceDir}/build/stm32f407-debug", + "toolchainFile": "${sourceDir}/cmake/toolchains/arm-none-eabi-gcc.cmake", + "cacheVariables": { + "MONAR_PLATFORM": "stm32f407", + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_MAKE_PROGRAM": "$env{LOCALAPPDATA}/Microsoft/WinGet/Packages/BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe/mingw64/bin/mingw32-make.exe" + } + } + ], + "buildPresets": [ + { + "name": "host-debug", + "configurePreset": "host-debug" + }, + { + "name": "stm32f407-debug", + "configurePreset": "stm32f407-debug" + } + ], + "testPresets": [ + { + "name": "host-debug", + "configurePreset": "host-debug", + "output": { + "outputOnFailure": true + } + } + ] +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3e5980e --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +CMAKE ?= cmake +CTEST ?= ctest +HOST_PRESET ?= host-debug +STM32_PRESET ?= stm32f407-debug +HOST_BUILD_DIR ?= build/host-debug +STM32_BUILD_DIR ?= build/stm32f407-debug + +.PHONY: host-configure host-build host-test stm32-configure stm32-build clean + +host-sync-compile-commands: + $(CMAKE) -E copy_if_different $(HOST_BUILD_DIR)/compile_commands.json compile_commands.json + +stm32-sync-compile-commands: + $(CMAKE) -E copy_if_different $(STM32_BUILD_DIR)/compile_commands.json compile_commands.json + +host-configure: + $(CMAKE) --preset $(HOST_PRESET) + $(MAKE) host-sync-compile-commands + +host-build: host-configure + $(CMAKE) --build --preset $(HOST_PRESET) + $(MAKE) host-sync-compile-commands + +host-test: host-build + $(CTEST) --preset $(HOST_PRESET) + +stm32-configure: + $(CMAKE) --preset $(STM32_PRESET) + $(MAKE) stm32-sync-compile-commands + +stm32-build: stm32-configure + $(CMAKE) --build --preset $(STM32_PRESET) + $(MAKE) stm32-sync-compile-commands + +clean: + $(CMAKE) -E rm -rf build + $(CMAKE) -E rm -f compile_commands.json diff --git a/README.md b/README.md index 389026d..7abe225 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,61 @@ examples/ user-facing examples tools/ optional development helpers ``` +## Build system + +The repository now includes: + +- root `CMakeLists.txt` as the primary build description +- `CMakePresets.json` for host and STM32F407 configure/build presets +- root `Makefile` as a thin wrapper around CMake commands +- `.vscode/` configuration for CMake Tools, IntelliSense, navigation, and + compile command generation +- root `compile_commands.json`, synced from the active CMake preset build for + editor navigation and semantic completion + +### Host build + +```text +make host-build +make host-test +``` + +This configures `build/host-debug/`, exports `compile_commands.json`, builds the +host smoke test, and runs it. + +### STM32 example build + +```text +make stm32-build +``` + +This configures `build/stm32f407-debug/` and produces: + +- `monar_stm32f407_minimal.elf` +- `monar_stm32f407_minimal.bin` +- `monar_stm32f407_minimal.hex` + +under the STM32 build directory. + +The STM32 target is currently a BSP/startup skeleton for `nucleo_f407zg`. It is +meant to validate the embedded build/output path without pulling in STM32 HAL +or silicon adaptation yet. + +## VSCode integration + +The committed `.vscode/` settings are set up so that: + +- CMake Tools uses the repository presets directly +- the local WinLibs `cmake`, `gcc`, and `mingw32-make` toolchain is available + to VSCode tasks and terminals +- `compile_commands.json` is copied to the repository root, which both + `ms-vscode.cpptools` and `clangd` use for navigation and completion +- switching the active CMake preset in VSCode lets the editor follow either the + host build flags or the STM32 target build flags + +If the Arm GNU toolchain is not already on your system `PATH`, set +`MONAR_ARM_GCC_ROOT` before configuring the `stm32f407-debug` preset. + ## Current design decisions - public symbols use the `mn_` / `MN_` prefixes diff --git a/bsp/boards/st/nucleo_f407zg/README.md b/bsp/boards/st/nucleo_f407zg/README.md new file mode 100644 index 0000000..266dac3 --- /dev/null +++ b/bsp/boards/st/nucleo_f407zg/README.md @@ -0,0 +1,14 @@ +# NUCLEO-F407ZG BSP Skeleton + +This board directory provides a minimal linker script, startup file, and board +initialization stub so the repository can produce STM32F407 `elf`, `bin`, and +`hex` artifacts through CMake. + +It is intentionally small: + +- no STM32 HAL integration +- no silicon adapter +- no peripheral driver registration yet + +The purpose is to establish a usable embedded build/output path for future +Monar stages. diff --git a/bsp/boards/st/nucleo_f407zg/board_init.c b/bsp/boards/st/nucleo_f407zg/board_init.c new file mode 100644 index 0000000..f5f2e2c --- /dev/null +++ b/bsp/boards/st/nucleo_f407zg/board_init.c @@ -0,0 +1,9 @@ +#include "board.h" + +void SystemInit(void) +{ +} + +void mn_board_init(void) +{ +} diff --git a/bsp/boards/st/nucleo_f407zg/include/board.h b/bsp/boards/st/nucleo_f407zg/include/board.h new file mode 100644 index 0000000..bf76f4d --- /dev/null +++ b/bsp/boards/st/nucleo_f407zg/include/board.h @@ -0,0 +1,6 @@ +#ifndef MONAR_BSP_BOARDS_ST_NUCLEO_F407ZG_BOARD_H +#define MONAR_BSP_BOARDS_ST_NUCLEO_F407ZG_BOARD_H + +void mn_board_init(void); + +#endif /* MONAR_BSP_BOARDS_ST_NUCLEO_F407ZG_BOARD_H */ diff --git a/bsp/boards/st/nucleo_f407zg/include/board_config.h b/bsp/boards/st/nucleo_f407zg/include/board_config.h new file mode 100644 index 0000000..04919d1 --- /dev/null +++ b/bsp/boards/st/nucleo_f407zg/include/board_config.h @@ -0,0 +1,8 @@ +#ifndef MONAR_BSP_BOARDS_ST_NUCLEO_F407ZG_BOARD_CONFIG_H +#define MONAR_BSP_BOARDS_ST_NUCLEO_F407ZG_BOARD_CONFIG_H + +#define MN_BOARD_NAME "nucleo_f407zg" +#define MN_BOARD_CPU "cortex-m4" +#define MN_BOARD_VENDOR "st" + +#endif /* MONAR_BSP_BOARDS_ST_NUCLEO_F407ZG_BOARD_CONFIG_H */ diff --git a/bsp/boards/st/nucleo_f407zg/linker/stm32f407zg_flash.ld b/bsp/boards/st/nucleo_f407zg/linker/stm32f407zg_flash.ld new file mode 100644 index 0000000..be7b4de --- /dev/null +++ b/bsp/boards/st/nucleo_f407zg/linker/stm32f407zg_flash.ld @@ -0,0 +1,45 @@ +ENTRY(Reset_Handler) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K +} + +_estack = ORIGIN(RAM) + LENGTH(RAM); + +SECTIONS +{ + .isr_vector : + { + KEEP(*(.isr_vector)) + } > FLASH + + .text : + { + *(.text*) + *(.rodata*) + . = ALIGN(4); + } > FLASH + + _sidata = LOADADDR(.data); + + .data : AT (ADDR(.text) + SIZEOF(.text)) + { + . = ALIGN(4); + _sdata = .; + *(.data*) + . = ALIGN(4); + _edata = .; + } > RAM + + .bss (NOLOAD) : + { + . = ALIGN(4); + _sbss = .; + *(.bss*) + *(COMMON) + . = ALIGN(4); + _ebss = .; + } > RAM +} diff --git a/bsp/boards/st/nucleo_f407zg/startup/startup_stm32f407xx.c b/bsp/boards/st/nucleo_f407zg/startup/startup_stm32f407xx.c new file mode 100644 index 0000000..79dba28 --- /dev/null +++ b/bsp/boards/st/nucleo_f407zg/startup/startup_stm32f407xx.c @@ -0,0 +1,73 @@ +#include + +void SystemInit(void); +int main(void); + +extern uint32_t _estack; +extern uint32_t _sidata; +extern uint32_t _sdata; +extern uint32_t _edata; +extern uint32_t _sbss; +extern uint32_t _ebss; + +void Reset_Handler(void); +static void mn_default_handler(void); + +void NMI_Handler(void) __attribute__((weak, alias("mn_default_handler"))); +void HardFault_Handler(void) __attribute__((weak, alias("mn_default_handler"))); +void MemManage_Handler(void) __attribute__((weak, alias("mn_default_handler"))); +void BusFault_Handler(void) __attribute__((weak, alias("mn_default_handler"))); +void UsageFault_Handler(void) __attribute__((weak, alias("mn_default_handler"))); +void SVC_Handler(void) __attribute__((weak, alias("mn_default_handler"))); +void DebugMon_Handler(void) __attribute__((weak, alias("mn_default_handler"))); +void PendSV_Handler(void) __attribute__((weak, alias("mn_default_handler"))); +void SysTick_Handler(void) __attribute__((weak, alias("mn_default_handler"))); + +__attribute__((section(".isr_vector"))) +void (*const mn_vector_table[])(void) = { + (void (*)(void))(&_estack), + Reset_Handler, + NMI_Handler, + HardFault_Handler, + MemManage_Handler, + BusFault_Handler, + UsageFault_Handler, + 0, + 0, + 0, + 0, + SVC_Handler, + DebugMon_Handler, + 0, + PendSV_Handler, + SysTick_Handler +}; + +void Reset_Handler(void) +{ + uint32_t *src; + uint32_t *dst; + + src = &_sidata; + dst = &_sdata; + while (dst < &_edata) { + *dst++ = *src++; + } + + dst = &_sbss; + while (dst < &_ebss) { + *dst++ = 0u; + } + + SystemInit(); + (void)main(); + + while (1) { + } +} + +static void mn_default_handler(void) +{ + while (1) { + } +} diff --git a/cmake/toolchains/arm-none-eabi-gcc.cmake b/cmake/toolchains/arm-none-eabi-gcc.cmake new file mode 100644 index 0000000..0e56c66 --- /dev/null +++ b/cmake/toolchains/arm-none-eabi-gcc.cmake @@ -0,0 +1,54 @@ +set(CMAKE_SYSTEM_NAME Generic) +set(CMAKE_SYSTEM_PROCESSOR arm) +set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY) + +set(MONAR_ARM_GCC_ROOT "$ENV{MONAR_ARM_GCC_ROOT}" CACHE PATH + "Root directory of the Arm GNU Toolchain") + +if(NOT MONAR_ARM_GCC_ROOT) + set(_mn_arm_gcc_candidates) + file(GLOB _mn_arm_gcc_candidates + "C:/Program Files/Arm GNU Toolchain arm-none-eabi/*" + "C:/Program Files (x86)/Arm GNU Toolchain arm-none-eabi/*" + "C:/My Program/Arm GNU Toolchain arm-none-eabi/*" + ) + list(SORT _mn_arm_gcc_candidates ORDER DESCENDING) + list(LENGTH _mn_arm_gcc_candidates _mn_arm_gcc_candidate_count) + if(_mn_arm_gcc_candidate_count GREATER 0) + list(GET _mn_arm_gcc_candidates 0 MONAR_ARM_GCC_ROOT) + endif() +endif() + +find_program(CMAKE_C_COMPILER + NAMES arm-none-eabi-gcc + HINTS ${MONAR_ARM_GCC_ROOT} + PATH_SUFFIXES bin + REQUIRED +) + +find_program(CMAKE_ASM_COMPILER + NAMES arm-none-eabi-gcc + HINTS ${MONAR_ARM_GCC_ROOT} + PATH_SUFFIXES bin + REQUIRED +) + +find_program(CMAKE_AR + NAMES arm-none-eabi-ar + HINTS ${MONAR_ARM_GCC_ROOT} + PATH_SUFFIXES bin +) + +find_program(CMAKE_OBJCOPY + NAMES arm-none-eabi-objcopy + HINTS ${MONAR_ARM_GCC_ROOT} + PATH_SUFFIXES bin +) + +find_program(MONAR_SIZE_UTIL + NAMES arm-none-eabi-size + HINTS ${MONAR_ARM_GCC_ROOT} + PATH_SUFFIXES bin +) + +set(CMAKE_EXECUTABLE_SUFFIX ".elf") diff --git a/examples/minimal/main.c b/examples/minimal/main.c new file mode 100644 index 0000000..dc4a3e5 --- /dev/null +++ b/examples/minimal/main.c @@ -0,0 +1,16 @@ +#include + +#include "board.h" + +int main(void) +{ + mn_status_t ret; + + mn_board_init(); + + ret = mn_osal_init(); + (void)ret; + + while (1) { + } +} diff --git a/tests/README.md b/tests/README.md index 6c82a8d..214d671 100644 --- a/tests/README.md +++ b/tests/README.md @@ -15,3 +15,13 @@ cc -std=c11 -Wall -Wextra -Iinclude -Isrc/internal \ tests/host/test_main.c \ -o monar_host_test ``` + +The repository-level CMake and Make wrappers now automate this flow through: + +```text +make host-build +make host-test +``` + +`make host-test` runs the registered CTest target, and the active build also +syncs `compile_commands.json` to the repository root for editor tooling. From 27cfdd8ae1cef12477a1dbc164a28fdaa5485b55 Mon Sep 17 00:00:00 2001 From: Warren Jayd Date: Sun, 31 May 2026 22:42:47 +0800 Subject: [PATCH 3/7] Fix phase-1 review findings --- .vscode/settings.json | 6 ++-- CMakeLists.txt | 9 +++++ Makefile | 4 ++- README.md | 15 ++++++-- include/monar/device.h | 27 +++++++++++++++ make.cmd | 12 +++++++ src/device/mn_device.c | 29 +++++++++++++++- src/device/mn_device_registry.c | 27 +++++++++++++++ src/internal/mn_device_internal.h | 1 + tests/README.md | 5 +++ tests/host/test_main.c | 57 ++++++++++++++++++++++++++++--- 11 files changed, 180 insertions(+), 12 deletions(-) create mode 100644 make.cmd diff --git a/.vscode/settings.json b/.vscode/settings.json index 9b33923..2059843 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,15 +4,15 @@ "cmake.cmakePath": "${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin\\cmake.exe", "cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json", "cmake.configureEnvironment": { - "PATH": "${env:PATH};${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin", + "PATH": "${workspaceFolder};${env:PATH};${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin", "MONAR_ARM_GCC_ROOT": "${env:MONAR_ARM_GCC_ROOT}" }, "cmake.buildEnvironment": { - "PATH": "${env:PATH};${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin", + "PATH": "${workspaceFolder};${env:PATH};${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin", "MONAR_ARM_GCC_ROOT": "${env:MONAR_ARM_GCC_ROOT}" }, "terminal.integrated.env.windows": { - "PATH": "${env:PATH};${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin", + "PATH": "${workspaceFolder};${env:PATH};${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin", "MONAR_ARM_GCC_ROOT": "${env:MONAR_ARM_GCC_ROOT}" }, "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", diff --git a/CMakeLists.txt b/CMakeLists.txt index 294e21d..1971242 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,15 @@ if(MONAR_PLATFORM STREQUAL "host") target_include_directories(monar_host_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/internal ) + if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(monar_core PRIVATE -Werror) + target_compile_options(monar_host_test PRIVATE + -Wall + -Wextra + -Wpedantic + -Werror + ) + endif() add_test(NAME monar_host_smoke COMMAND monar_host_test) elseif(MONAR_PLATFORM STREQUAL "stm32f407") set(MONAR_STM32_BOARD_ROOT diff --git a/Makefile b/Makefile index 3e5980e..09e368e 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,9 @@ STM32_PRESET ?= stm32f407-debug HOST_BUILD_DIR ?= build/host-debug STM32_BUILD_DIR ?= build/stm32f407-debug -.PHONY: host-configure host-build host-test stm32-configure stm32-build clean +.PHONY: test host-configure host-build host-test stm32-configure stm32-build clean + +test: host-test host-sync-compile-commands: $(CMAKE) -E copy_if_different $(HOST_BUILD_DIR)/compile_commands.json compile_commands.json diff --git a/README.md b/README.md index 7abe225..13409cb 100644 --- a/README.md +++ b/README.md @@ -49,12 +49,17 @@ The repository now includes: ### Host build ```text +make test make host-build make host-test ``` -This configures `build/host-debug/`, exports `compile_commands.json`, builds the -host smoke test, and runs it. +`make test` is the short repository-root entry for the current host smoke test. +It configures `build/host-debug/`, exports `compile_commands.json`, builds the +host test with private `src/internal/` headers on the include path, and runs it. +On Windows, the repository also provides `make.cmd` as a thin wrapper around +the installed WinLibs `mingw32-make`, so `make test` works in the VSCode +integrated terminal with the committed workspace settings. ### STM32 example build @@ -97,6 +102,9 @@ If the Arm GNU toolchain is not already on your system `PATH`, set - `mn_osal_init()` is expected before device registry mutation and device use - device registration is static-allocation-friendly and bounded by `MN_CFG_DEVICE_REGISTRY_MAX` +- registered descriptors are retained by reference, so `name`, `ops`, + `resource_key`, and `driver_data` must stay valid for the lifetime of the + device - duplicate `resource_key` registration is rejected to avoid ambiguous device/bus/carrier ownership of the same hardware resource @@ -106,7 +114,8 @@ The initial tests live in `tests/host/test_main.c` and cover: - deterministic Monar error codes - minimal device registry registration rules -- basic `mn_device_open` / `close` / `read` guards +- `mn_device_open` flag, capability, and stale-handle guards +- internal-only registry reset between host test cases The current runtime backend is a minimal baremetal placeholder. Unsupported runtime services are intentionally left for later milestones. diff --git a/include/monar/device.h b/include/monar/device.h index 5931767..17e24ea 100644 --- a/include/monar/device.h +++ b/include/monar/device.h @@ -11,6 +11,12 @@ #define MN_DEVICE_CAP_WRITE (1u << 1) #define MN_DEVICE_CAP_IOCTL (1u << 2) +/* + * Open flags declare which data-path capabilities the caller intends to use. + * A zero flag value is a control-only open with no read/write request. This is + * intended for metadata or ioctl-style access, such as a control endpoint that + * does not expose a data stream. + */ #define MN_DEVICE_OPEN_READ (1u << 0) #define MN_DEVICE_OPEN_WRITE (1u << 1) #define MN_DEVICE_OPEN_RDWR (MN_DEVICE_OPEN_READ | MN_DEVICE_OPEN_WRITE) @@ -34,6 +40,19 @@ typedef struct mn_device_ops { mn_status_t (*ioctl)(mn_device_t device, mn_u32_t cmd, void *arg); } mn_device_ops_t; +/* + * Monar phase-1 registration is static-allocation-friendly: the registry keeps + * these pointer fields directly instead of copying them. The following members + * must therefore remain valid for the full lifetime of the registered device: + * + * - name + * - ops + * - resource_key + * - driver_data + * + * Do not pass stack-allocated temporary names, operation tables, or other + * short-lived objects to mn_device_register(). + */ typedef struct mn_device_descriptor { const char *name; mn_device_class_t device_class; @@ -47,11 +66,19 @@ typedef struct mn_device_descriptor { * Registration is intended for framework integration code such as BSP, * silicon adapters, and tests. Application code should normally consume * already-registered devices through mn_device_open(). + * + * The descriptor's pointer members are retained by reference. Their storage + * must outlive the registered device. */ mn_status_t mn_device_register(const mn_device_descriptor_t *descriptor, mn_device_t *out_device); mn_status_t mn_device_find(const char *name, mn_device_t *out_device); +/* + * mn_device_open() validates requested read/write intent against the device + * capability flags before calling the driver open operation. Unsupported read + * or write requests fail with -MN_ENOSYS. + */ mn_status_t mn_device_open(const char *name, mn_u32_t flags, mn_device_t *out_device); mn_status_t mn_device_close(mn_device_t device); diff --git a/make.cmd b/make.cmd new file mode 100644 index 0000000..a9ff7a4 --- /dev/null +++ b/make.cmd @@ -0,0 +1,12 @@ +@echo off +setlocal + +set "MN_MAKE=%LOCALAPPDATA%\Microsoft\WinGet\Packages\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\mingw64\bin\mingw32-make.exe" + +if not exist "%MN_MAKE%" ( + echo mingw32-make.exe not found at: + echo %MN_MAKE% + exit /b 1 +) + +"%MN_MAKE%" %* diff --git a/src/device/mn_device.c b/src/device/mn_device.c index 8b6829c..fc8100e 100644 --- a/src/device/mn_device.c +++ b/src/device/mn_device.c @@ -14,6 +14,22 @@ static bool mn_device_open_flags_are_valid(mn_u32_t flags) return (flags & ~allowed) == 0u; } +static mn_status_t mn_device_open_flags_match_capabilities( + const struct mn_device *device, mn_u32_t flags) +{ + if ((flags & MN_DEVICE_OPEN_READ) != 0u && + (device->capability_flags & MN_DEVICE_CAP_READ) == 0u) { + return -MN_ENOSYS; + } + + if ((flags & MN_DEVICE_OPEN_WRITE) != 0u && + (device->capability_flags & MN_DEVICE_CAP_WRITE) == 0u) { + return -MN_ENOSYS; + } + + return MN_EOK; +} + static mn_status_t mn_device_prepare_transition(mn_device_t device, mn_device_state_t expected, mn_device_state_t next) { @@ -46,7 +62,13 @@ mn_status_t mn_device_open(const char *name, mn_u32_t flags, mn_device_t device; mn_status_t ret; - if (!mn_device_open_flags_are_valid(flags) || out_device == NULL) { + if (out_device == NULL) { + return -MN_EINVAL; + } + + *out_device = NULL; + + if (!mn_device_open_flags_are_valid(flags)) { return -MN_EINVAL; } @@ -55,6 +77,11 @@ mn_status_t mn_device_open(const char *name, mn_u32_t flags, return ret; } + ret = mn_device_open_flags_match_capabilities(device, flags); + if (ret != MN_EOK) { + return ret; + } + ret = mn_device_prepare_transition(device, MN_DEVICE_STATE_REGISTERED, MN_DEVICE_STATE_OPENING); if (ret != MN_EOK) { diff --git a/src/device/mn_device_registry.c b/src/device/mn_device_registry.c index 47c61da..69538c6 100644 --- a/src/device/mn_device_registry.c +++ b/src/device/mn_device_registry.c @@ -85,6 +85,10 @@ mn_status_t mn_device_register(const mn_device_descriptor_t *descriptor, size_t i; mn_status_t ret; + if (out_device != NULL) { + *out_device = NULL; + } + ret = mn_device_registry_validate_descriptor(descriptor); if (ret != MN_EOK) { return ret; @@ -142,6 +146,10 @@ mn_status_t mn_device_registry_lookup(const char *name, mn_device_t *out_device) mn_osal_critical_state_t state; size_t i; + if (out_device != NULL) { + *out_device = NULL; + } + if (!mn_osal_is_initialized()) { return -MN_EPERM; } @@ -167,6 +175,25 @@ mn_status_t mn_device_registry_lookup(const char *name, mn_device_t *out_device) return -MN_ENOENT; } +void mn_device_registry_reset_for_test(void) +{ + mn_osal_critical_state_t state; + size_t i; + + state = mn_osal_critical_enter(); + for (i = 0; i < MN_CFG_DEVICE_REGISTRY_MAX; ++i) { + g_mn_device_registry[i].name = NULL; + g_mn_device_registry[i].device_class = MN_DEVICE_CLASS_GENERIC; + g_mn_device_registry[i].capability_flags = 0u; + g_mn_device_registry[i].resource_key = NULL; + g_mn_device_registry[i].ops = NULL; + g_mn_device_registry[i].driver_data = NULL; + g_mn_device_registry[i].state = MN_DEVICE_STATE_UNUSED; + g_mn_device_registry[i].is_registered = false; + } + mn_osal_critical_exit(state); +} + bool mn_device_registry_is_valid_handle(mn_device_t device) { size_t i; diff --git a/src/internal/mn_device_internal.h b/src/internal/mn_device_internal.h index 675b296..13f92e2 100644 --- a/src/internal/mn_device_internal.h +++ b/src/internal/mn_device_internal.h @@ -28,5 +28,6 @@ struct mn_device { mn_status_t mn_device_registry_lookup(const char *name, mn_device_t *out_device); bool mn_device_registry_is_valid_handle(mn_device_t device); +void mn_device_registry_reset_for_test(void); #endif /* MONAR_SRC_INTERNAL_MN_DEVICE_INTERNAL_H */ diff --git a/tests/README.md b/tests/README.md index 214d671..b7c9048 100644 --- a/tests/README.md +++ b/tests/README.md @@ -19,9 +19,14 @@ cc -std=c11 -Wall -Wextra -Iinclude -Isrc/internal \ The repository-level CMake and Make wrappers now automate this flow through: ```text +make test make host-build make host-test ``` +`make test` is the repository-root smoke-test entry. It currently runs a +single-process host test, with an internal-only registry reset helper used to +keep test cases independent without exposing any public reset API. + `make host-test` runs the registered CTest target, and the active build also syncs `compile_commands.json` to the repository root for editor tooling. diff --git a/tests/host/test_main.c b/tests/host/test_main.c index 8ba2fb1..3d59cc9 100644 --- a/tests/host/test_main.c +++ b/tests/host/test_main.c @@ -4,6 +4,8 @@ #include +#include "mn_device_internal.h" + typedef struct test_device_context { size_t open_calls; size_t close_calls; @@ -131,17 +133,28 @@ static void test_registry_rules(void) .driver_data = &ctx_b, }; mn_device_t unused; + mn_device_t stale; + + mn_device_registry_reset_for_test(); + stale = (mn_device_t)&ctx_b; assert(mn_device_register(&dev_a, NULL) == MN_EOK); - assert(mn_device_register(&duplicate_name, NULL) == -MN_EEXIST); - assert(mn_device_register(&duplicate_resource, NULL) == -MN_EEXIST); + assert(mn_device_register(&duplicate_name, &stale) == -MN_EEXIST); + assert(stale == NULL); + stale = (mn_device_t)&ctx_b; + assert(mn_device_register(&duplicate_resource, &stale) == -MN_EEXIST); + assert(stale == NULL); + unused = (mn_device_t)&ctx_a; assert(mn_device_find("missing0", &unused) == -MN_ENOENT); + assert(unused == NULL); } static void test_device_api_guards(void) { static test_device_context_t ctx; + static test_device_context_t control_ctx; static const int resource_c; + static const int resource_d; static const mn_device_ops_t ops = { .open = test_open, .close = test_close, @@ -149,6 +162,13 @@ static void test_device_api_guards(void) .write = NULL, .ioctl = NULL, }; + static const mn_device_ops_t control_ops = { + .open = test_open, + .close = test_close, + .read = NULL, + .write = NULL, + .ioctl = test_ioctl, + }; const mn_device_descriptor_t descriptor = { .name = "sensor0", .device_class = MN_DEVICE_CLASS_ENDPOINT, @@ -157,21 +177,49 @@ static void test_device_api_guards(void) .ops = &ops, .driver_data = &ctx, }; + const mn_device_descriptor_t control_descriptor = { + .name = "control0", + .device_class = MN_DEVICE_CLASS_ENDPOINT, + .capability_flags = MN_DEVICE_CAP_IOCTL, + .resource_key = &resource_d, + .ops = &control_ops, + .driver_data = &control_ctx, + }; mn_device_t device; mn_device_t looked_up; + mn_device_t control_device; char buffer[8]; size_t count; + mn_device_registry_reset_for_test(); assert(mn_device_register(&descriptor, &device) == MN_EOK); + assert(mn_device_register(&control_descriptor, &control_device) == MN_EOK); assert(mn_device_find("sensor0", &looked_up) == MN_EOK); assert(looked_up == device); + looked_up = (mn_device_t)&ctx; assert(mn_device_open(NULL, 0u, &device) == -MN_EINVAL); + assert(device == NULL); + assert(mn_device_open("sensor0", 4u, &device) == -MN_EINVAL); + assert(device == NULL); assert(mn_device_open("missing1", 0u, &device) == -MN_ENOENT); + assert(device == NULL); assert(mn_device_read(device, buffer, sizeof(buffer), &count) == -MN_EBADF); - assert(mn_device_open("sensor0", MN_DEVICE_OPEN_RDWR, &device) == MN_EOK); - assert(mn_device_open("sensor0", MN_DEVICE_OPEN_RDWR, &looked_up) == + assert(mn_device_open("sensor0", MN_DEVICE_OPEN_WRITE, &device) == + -MN_ENOSYS); + assert(device == NULL); + assert(mn_device_open("sensor0", MN_DEVICE_OPEN_RDWR, &device) == + -MN_ENOSYS); + assert(device == NULL); + assert(mn_device_open("control0", 0u, &control_device) == MN_EOK); + assert(control_ctx.open_calls == 1u); + assert(mn_device_ioctl(control_device, 0u, NULL) == MN_EOK); + assert(control_ctx.ioctl_calls == 1u); + assert(mn_device_close(control_device) == MN_EOK); + assert(mn_device_open("sensor0", MN_DEVICE_OPEN_READ, &device) == MN_EOK); + assert(mn_device_open("sensor0", MN_DEVICE_OPEN_READ, &looked_up) == -MN_EBUSY); + assert(looked_up == NULL); assert(mn_device_read(device, NULL, sizeof(buffer), &count) == -MN_EINVAL); assert(mn_device_read(device, buffer, sizeof(buffer), &count) == MN_EOK); assert(count == 5u); @@ -185,6 +233,7 @@ static void test_device_api_guards(void) assert(ctx.read_calls == 1u); assert(ctx.write_calls == 0u); assert(ctx.ioctl_calls == 0u); + assert(control_ctx.close_calls == 1u); } int main(void) From cd8dd0c71295da82bcede8e5a64376af4a7d9580 Mon Sep 17 00:00:00 2001 From: Warren Jayd Date: Sun, 31 May 2026 23:12:25 +0800 Subject: [PATCH 4/7] Refactor CMake into modular targets --- .clangd | 2 +- .gitignore | 2 + .vscode/settings.json | 13 +-- CMakeLists.txt | 124 +++------------------ CMakePresets.json | 22 ++-- CMakeUserPresets.example.json | 46 ++++++++ Makefile | 17 +-- README.md | 74 +++++++++--- bsp/CMakeLists.txt | 1 + bsp/boards/CMakeLists.txt | 3 + bsp/boards/st/CMakeLists.txt | 3 + bsp/boards/st/nucleo_f407zg/CMakeLists.txt | 23 ++++ cmake/monar_options.cmake | 50 +++++++++ examples/CMakeLists.txt | 3 + examples/minimal/CMakeLists.txt | 47 ++++++++ make.cmd | 21 +++- runtimes/CMakeLists.txt | 5 + runtimes/baremetal/CMakeLists.txt | 3 + src/CMakeLists.txt | 18 +++ src/device/CMakeLists.txt | 4 + src/osal/CMakeLists.txt | 3 + tests/CMakeLists.txt | 3 + tests/README.md | 8 +- tests/host/CMakeLists.txt | 12 ++ 24 files changed, 351 insertions(+), 156 deletions(-) create mode 100644 CMakeUserPresets.example.json create mode 100644 bsp/CMakeLists.txt create mode 100644 bsp/boards/CMakeLists.txt create mode 100644 bsp/boards/st/CMakeLists.txt create mode 100644 bsp/boards/st/nucleo_f407zg/CMakeLists.txt create mode 100644 cmake/monar_options.cmake create mode 100644 examples/CMakeLists.txt create mode 100644 examples/minimal/CMakeLists.txt create mode 100644 runtimes/CMakeLists.txt create mode 100644 runtimes/baremetal/CMakeLists.txt create mode 100644 src/CMakeLists.txt create mode 100644 src/device/CMakeLists.txt create mode 100644 src/osal/CMakeLists.txt create mode 100644 tests/CMakeLists.txt create mode 100644 tests/host/CMakeLists.txt diff --git a/.clangd b/.clangd index 29788b8..2e63ad5 100644 --- a/.clangd +++ b/.clangd @@ -1,2 +1,2 @@ CompileFlags: - CompilationDatabase: . + CompilationDatabase: build/host-debug diff --git a/.gitignore b/.gitignore index ff39f59..08bfc46 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ build/ +.cache/ +CMakeUserPresets.json compile_commands.json *.o diff --git a/.vscode/settings.json b/.vscode/settings.json index 2059843..a2d2e04 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,26 +1,21 @@ { "cmake.useCMakePresets": "always", "cmake.configureOnOpen": false, - "cmake.cmakePath": "${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin\\cmake.exe", - "cmake.copyCompileCommands": "${workspaceFolder}/compile_commands.json", "cmake.configureEnvironment": { - "PATH": "${workspaceFolder};${env:PATH};${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin", + "PATH": "${workspaceFolder};${env:PATH}", "MONAR_ARM_GCC_ROOT": "${env:MONAR_ARM_GCC_ROOT}" }, "cmake.buildEnvironment": { - "PATH": "${workspaceFolder};${env:PATH};${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin", + "PATH": "${workspaceFolder};${env:PATH}", "MONAR_ARM_GCC_ROOT": "${env:MONAR_ARM_GCC_ROOT}" }, "terminal.integrated.env.windows": { - "PATH": "${workspaceFolder};${env:PATH};${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin", + "PATH": "${workspaceFolder};${env:PATH}", "MONAR_ARM_GCC_ROOT": "${env:MONAR_ARM_GCC_ROOT}" }, "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", - "C_Cpp.default.compileCommands": "${workspaceFolder}\\compile_commands.json", - "C_Cpp.default.compilerPath": "${env:LOCALAPPDATA}\\Microsoft\\WinGet\\Packages\\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\\mingw64\\bin\\gcc.exe", - "C_Cpp.default.intelliSenseMode": "windows-gcc-x64", "clangd.arguments": [ - "--compile-commands-dir=${workspaceFolder}" + "--compile-commands-dir=${workspaceFolder}/build/host-debug" ], "files.associations": { "*.ld": "ld" diff --git a/CMakeLists.txt b/CMakeLists.txt index 1971242..06e9be2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,7 @@ cmake_minimum_required(VERSION 3.20) +include(cmake/monar_options.cmake) + project(Monar VERSION 0.1.0 LANGUAGES C ASM) set(CMAKE_C_STANDARD 11) @@ -7,125 +9,31 @@ set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -enable_testing() +if(MONAR_BUILD_TESTS) + enable_testing() +endif() -set(MONAR_PLATFORM "host" CACHE STRING - "Monar build platform: host or stm32f407") -set_property(CACHE MONAR_PLATFORM PROPERTY STRINGS host stm32f407) +add_subdirectory(src) +add_subdirectory(runtimes) -set(MONAR_COMMON_SOURCES - src/osal/mn_osal.c - src/device/mn_device.c - src/device/mn_device_registry.c - runtimes/baremetal/mn_runtime_baremetal.c -) +if(MONAR_PLATFORM STREQUAL "stm32f407") + add_subdirectory(bsp) +endif() -add_library(monar_core STATIC ${MONAR_COMMON_SOURCES}) -target_include_directories(monar_core - PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/include - PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src/internal -) -target_compile_features(monar_core PUBLIC c_std_11) +if(MONAR_BUILD_EXAMPLES) + add_subdirectory(examples) +endif() -if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") - target_compile_options(monar_core PRIVATE -Wall -Wextra -Wpedantic) +if(MONAR_BUILD_TESTS) + add_subdirectory(tests) endif() if(CMAKE_EXPORT_COMPILE_COMMANDS) - add_custom_target(monar_sync_compile_commands ALL + add_custom_target(monar_sync_compile_commands COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/compile_commands.json ${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json - BYPRODUCTS ${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json COMMENT "Sync compile_commands.json to repository root" VERBATIM ) endif() - -if(MONAR_PLATFORM STREQUAL "host") - add_executable(monar_host_test tests/host/test_main.c) - target_link_libraries(monar_host_test PRIVATE monar_core) - target_include_directories(monar_host_test PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/src/internal - ) - if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") - target_compile_options(monar_core PRIVATE -Werror) - target_compile_options(monar_host_test PRIVATE - -Wall - -Wextra - -Wpedantic - -Werror - ) - endif() - add_test(NAME monar_host_smoke COMMAND monar_host_test) -elseif(MONAR_PLATFORM STREQUAL "stm32f407") - set(MONAR_STM32_BOARD_ROOT - ${CMAKE_CURRENT_SOURCE_DIR}/bsp/boards/st/nucleo_f407zg) - set(MONAR_STM32_LINKER_SCRIPT - ${MONAR_STM32_BOARD_ROOT}/linker/stm32f407zg_flash.ld) - - add_executable(monar_stm32f407_minimal.elf - ${MONAR_STM32_BOARD_ROOT}/startup/startup_stm32f407xx.c - ${MONAR_STM32_BOARD_ROOT}/board_init.c - examples/minimal/main.c - ) - - target_link_libraries(monar_stm32f407_minimal.elf PRIVATE monar_core) - target_include_directories(monar_stm32f407_minimal.elf PRIVATE - ${MONAR_STM32_BOARD_ROOT}/include - ${CMAKE_CURRENT_SOURCE_DIR}/src/internal - ) - target_compile_definitions(monar_stm32f407_minimal.elf PRIVATE - STM32F407xx - ) - - if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") - target_compile_options(monar_core PRIVATE - -mcpu=cortex-m4 - -mthumb - -ffreestanding - -fdata-sections - -ffunction-sections - ) - target_compile_options(monar_stm32f407_minimal.elf PRIVATE - -mcpu=cortex-m4 - -mthumb - -ffreestanding - -fdata-sections - -ffunction-sections - ) - target_link_options(monar_stm32f407_minimal.elf PRIVATE - -mcpu=cortex-m4 - -mthumb - -nostartfiles - -nostdlib - -Wl,--gc-sections - -Wl,--build-id=none - -Wl,-Map=${CMAKE_CURRENT_BINARY_DIR}/monar_stm32f407_minimal.map - -T${MONAR_STM32_LINKER_SCRIPT} - ) - endif() - - if(CMAKE_OBJCOPY) - add_custom_command(TARGET monar_stm32f407_minimal.elf POST_BUILD - COMMAND ${CMAKE_OBJCOPY} -O ihex - $ - ${CMAKE_CURRENT_BINARY_DIR}/monar_stm32f407_minimal.hex - COMMAND ${CMAKE_OBJCOPY} -O binary - $ - ${CMAKE_CURRENT_BINARY_DIR}/monar_stm32f407_minimal.bin - VERBATIM - ) - endif() - - if(MONAR_SIZE_UTIL) - add_custom_command(TARGET monar_stm32f407_minimal.elf POST_BUILD - COMMAND ${MONAR_SIZE_UTIL} $ - VERBATIM - ) - endif() -else() - message(FATAL_ERROR "Unsupported MONAR_PLATFORM='${MONAR_PLATFORM}'") -endif() diff --git a/CMakePresets.json b/CMakePresets.json index 570daf0..24fcdb4 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -6,28 +6,36 @@ "patch": 0 }, "configurePresets": [ + { + "name": "base-debug", + "hidden": true, + "generator": "MinGW Makefiles", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "MONAR_RUNTIME": "baremetal" + } + }, { "name": "host-debug", "displayName": "Host Debug", - "generator": "MinGW Makefiles", + "inherits": "base-debug", "binaryDir": "${sourceDir}/build/host-debug", "cacheVariables": { "MONAR_PLATFORM": "host", - "CMAKE_BUILD_TYPE": "Debug", - "CMAKE_C_COMPILER": "$env{LOCALAPPDATA}/Microsoft/WinGet/Packages/BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe/mingw64/bin/gcc.exe", - "CMAKE_MAKE_PROGRAM": "$env{LOCALAPPDATA}/Microsoft/WinGet/Packages/BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe/mingw64/bin/mingw32-make.exe" + "MONAR_BUILD_TESTS": "ON", + "MONAR_BUILD_EXAMPLES": "OFF" } }, { "name": "stm32f407-debug", "displayName": "STM32F407 Debug", - "generator": "MinGW Makefiles", + "inherits": "base-debug", "binaryDir": "${sourceDir}/build/stm32f407-debug", "toolchainFile": "${sourceDir}/cmake/toolchains/arm-none-eabi-gcc.cmake", "cacheVariables": { "MONAR_PLATFORM": "stm32f407", - "CMAKE_BUILD_TYPE": "Debug", - "CMAKE_MAKE_PROGRAM": "$env{LOCALAPPDATA}/Microsoft/WinGet/Packages/BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe/mingw64/bin/mingw32-make.exe" + "MONAR_BUILD_TESTS": "OFF", + "MONAR_BUILD_EXAMPLES": "ON" } } ], diff --git a/CMakeUserPresets.example.json b/CMakeUserPresets.example.json new file mode 100644 index 0000000..05a7bb1 --- /dev/null +++ b/CMakeUserPresets.example.json @@ -0,0 +1,46 @@ +{ + "version": 5, + "configurePresets": [ + { + "name": "local-mingw-env", + "hidden": true, + "environment": { + "PATH": "C:/msys64/mingw64/bin;$penv{PATH}", + "MONAR_ARM_GCC_ROOT": "C:/Program Files (x86)/Arm GNU Toolchain arm-none-eabi/12.2 mpacbti-rel1" + } + }, + { + "name": "host-debug-local", + "inherits": [ + "host-debug", + "local-mingw-env" + ] + }, + { + "name": "stm32f407-debug-local", + "inherits": [ + "stm32f407-debug", + "local-mingw-env" + ] + } + ], + "buildPresets": [ + { + "name": "host-debug-local", + "configurePreset": "host-debug-local" + }, + { + "name": "stm32f407-debug-local", + "configurePreset": "stm32f407-debug-local" + } + ], + "testPresets": [ + { + "name": "host-debug-local", + "configurePreset": "host-debug-local", + "output": { + "outputOnFailure": true + } + } + ] +} diff --git a/Makefile b/Makefile index 09e368e..d6cb934 100644 --- a/Makefile +++ b/Makefile @@ -2,37 +2,32 @@ CMAKE ?= cmake CTEST ?= ctest HOST_PRESET ?= host-debug STM32_PRESET ?= stm32f407-debug -HOST_BUILD_DIR ?= build/host-debug -STM32_BUILD_DIR ?= build/stm32f407-debug -.PHONY: test host-configure host-build host-test stm32-configure stm32-build clean +.PHONY: test host-configure host-build host-test host-sync-compile-commands \ + stm32-configure stm32-build stm32-sync-compile-commands clean test: host-test -host-sync-compile-commands: - $(CMAKE) -E copy_if_different $(HOST_BUILD_DIR)/compile_commands.json compile_commands.json +host-sync-compile-commands: host-configure + $(CMAKE) --build --preset $(HOST_PRESET) --target monar_sync_compile_commands -stm32-sync-compile-commands: - $(CMAKE) -E copy_if_different $(STM32_BUILD_DIR)/compile_commands.json compile_commands.json +stm32-sync-compile-commands: stm32-configure + $(CMAKE) --build --preset $(STM32_PRESET) --target monar_sync_compile_commands host-configure: $(CMAKE) --preset $(HOST_PRESET) - $(MAKE) host-sync-compile-commands host-build: host-configure $(CMAKE) --build --preset $(HOST_PRESET) - $(MAKE) host-sync-compile-commands host-test: host-build $(CTEST) --preset $(HOST_PRESET) stm32-configure: $(CMAKE) --preset $(STM32_PRESET) - $(MAKE) stm32-sync-compile-commands stm32-build: stm32-configure $(CMAKE) --build --preset $(STM32_PRESET) - $(MAKE) stm32-sync-compile-commands clean: $(CMAKE) -E rm -rf build diff --git a/README.md b/README.md index 13409cb..7f5f5c0 100644 --- a/README.md +++ b/README.md @@ -34,17 +34,38 @@ examples/ user-facing examples tools/ optional development helpers ``` +The CMake build is organized the same way: each buildable directory owns its +own `CMakeLists.txt`, and the repository-root `CMakeLists.txt` only coordinates +options and conditional subdirectories. + ## Build system The repository now includes: -- root `CMakeLists.txt` as the primary build description -- `CMakePresets.json` for host and STM32F407 configure/build presets -- root `Makefile` as a thin wrapper around CMake commands -- `.vscode/` configuration for CMake Tools, IntelliSense, navigation, and - compile command generation -- root `compile_commands.json`, synced from the active CMake preset build for - editor navigation and semantic completion +- a root `CMakeLists.txt` that coordinates modular subdirectory builds +- per-directory `CMakeLists.txt` files under `src/`, `runtimes/`, `bsp/`, + `examples/`, and `tests/` +- portable `CMakePresets.json` for host and STM32F407 configure/build presets +- `CMakeUserPresets.example.json` as a local-machine override template +- a root `Makefile` as a thin wrapper around CMake commands +- `.vscode/` configuration for CMake Tools, IntelliSense, and navigation + +The key root-level CMake cache variables are: + +```text +MONAR_PLATFORM host | stm32f407 +MONAR_RUNTIME baremetal +MONAR_BUILD_TESTS ON | OFF +MONAR_BUILD_EXAMPLES ON | OFF +``` + +Host and STM32 builds are intentionally isolated: + +- the host preset builds `src/`, the selected runtime backend, and host tests +- the STM32 preset builds `src/`, the selected runtime backend, the selected + BSP, and the selected example target +- host tests are not compiled into the STM32 target build +- STM32 startup/BSP/example files are not compiled into the host test build ### Host build @@ -55,11 +76,11 @@ make host-test ``` `make test` is the short repository-root entry for the current host smoke test. -It configures `build/host-debug/`, exports `compile_commands.json`, builds the +It configures `build/host-debug/`, builds the host test with private `src/internal/` headers on the include path, and runs it. On Windows, the repository also provides `make.cmd` as a thin wrapper around -the installed WinLibs `mingw32-make`, so `make test` works in the VSCode -integrated terminal with the committed workspace settings. +`mingw32-make`, so `make test` works in the VSCode integrated terminal when a +local MinGW toolchain is available. ### STM32 example build @@ -79,17 +100,38 @@ The STM32 target is currently a BSP/startup skeleton for `nucleo_f407zg`. It is meant to validate the embedded build/output path without pulling in STM32 HAL or silicon adaptation yet. +## Local toolchain setup + +The committed `CMakePresets.json` is intentionally portable and does not embed +machine-local compiler paths. + +Recommended options: + +- put `cmake`, `gcc`, and `mingw32-make` on `PATH` for host builds +- put `arm-none-eabi-gcc` on `PATH`, or set `MONAR_ARM_GCC_ROOT`, for STM32 + builds +- copy `CMakeUserPresets.example.json` to `CMakeUserPresets.json` and adjust it + for your local Windows installation if you need pinned local paths or PATH + overrides + +The build directory remains the default location for generated +`compile_commands.json`. If you want a repository-root copy for tooling, use an +explicit command such as: + +```text +make host-sync-compile-commands +``` + ## VSCode integration The committed `.vscode/` settings are set up so that: - CMake Tools uses the repository presets directly -- the local WinLibs `cmake`, `gcc`, and `mingw32-make` toolchain is available - to VSCode tasks and terminals -- `compile_commands.json` is copied to the repository root, which both - `ms-vscode.cpptools` and `clangd` use for navigation and completion -- switching the active CMake preset in VSCode lets the editor follow either the - host build flags or the STM32 target build flags +- the repository root is on terminal `PATH`, so `make.cmd` is directly usable +- `ms-vscode.cmake-tools` remains the cpptools configuration provider +- `clangd` uses the host build directory compile database by default +- switching the active CMake preset in VSCode keeps host and STM32 builds + separate If the Arm GNU toolchain is not already on your system `PATH`, set `MONAR_ARM_GCC_ROOT` before configuring the `stm32f407-debug` preset. diff --git a/bsp/CMakeLists.txt b/bsp/CMakeLists.txt new file mode 100644 index 0000000..c47e549 --- /dev/null +++ b/bsp/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(boards) diff --git a/bsp/boards/CMakeLists.txt b/bsp/boards/CMakeLists.txt new file mode 100644 index 0000000..d99a06d --- /dev/null +++ b/bsp/boards/CMakeLists.txt @@ -0,0 +1,3 @@ +if(MONAR_PLATFORM STREQUAL "stm32f407") + add_subdirectory(st) +endif() diff --git a/bsp/boards/st/CMakeLists.txt b/bsp/boards/st/CMakeLists.txt new file mode 100644 index 0000000..27d5b31 --- /dev/null +++ b/bsp/boards/st/CMakeLists.txt @@ -0,0 +1,3 @@ +if(MONAR_PLATFORM STREQUAL "stm32f407") + add_subdirectory(nucleo_f407zg) +endif() diff --git a/bsp/boards/st/nucleo_f407zg/CMakeLists.txt b/bsp/boards/st/nucleo_f407zg/CMakeLists.txt new file mode 100644 index 0000000..5c9c020 --- /dev/null +++ b/bsp/boards/st/nucleo_f407zg/CMakeLists.txt @@ -0,0 +1,23 @@ +set_property(GLOBAL PROPERTY MONAR_ACTIVE_BSP_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/startup/startup_stm32f407xx.c + ${CMAKE_CURRENT_SOURCE_DIR}/board_init.c +) + +set_property(GLOBAL PROPERTY MONAR_ACTIVE_BSP_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +set_property(GLOBAL PROPERTY MONAR_ACTIVE_BSP_COMPILE_DEFINITIONS + STM32F407xx +) + +set_property(GLOBAL PROPERTY MONAR_ACTIVE_BSP_LINKER_SCRIPT + ${CMAKE_CURRENT_SOURCE_DIR}/linker/stm32f407zg_flash.ld +) + +if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + set_source_files_properties( + ${CMAKE_CURRENT_SOURCE_DIR}/startup/startup_stm32f407xx.c + PROPERTIES COMPILE_OPTIONS "-Wno-pedantic" + ) +endif() diff --git a/cmake/monar_options.cmake b/cmake/monar_options.cmake new file mode 100644 index 0000000..9b40bc4 --- /dev/null +++ b/cmake/monar_options.cmake @@ -0,0 +1,50 @@ +include_guard(GLOBAL) + +set(MONAR_PLATFORM "host" CACHE STRING + "Monar build platform: host or stm32f407") +set_property(CACHE MONAR_PLATFORM PROPERTY STRINGS host stm32f407) + +set(MONAR_RUNTIME "baremetal" CACHE STRING + "Monar runtime backend: baremetal") +set_property(CACHE MONAR_RUNTIME PROPERTY STRINGS baremetal) + +option(MONAR_BUILD_TESTS "Build Monar test targets" ON) +option(MONAR_BUILD_EXAMPLES "Build Monar example targets" ON) + +function(monar_apply_common_warnings target) + if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(${target} PRIVATE + -Wall + -Wextra + -Wpedantic + -Werror + ) + endif() +endfunction() + +function(monar_apply_embedded_compile_flags target) + if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(${target} PRIVATE + -mcpu=cortex-m4 + -mthumb + -ffreestanding + -fdata-sections + -ffunction-sections + ) + endif() +endfunction() + +function(monar_apply_stm32f407_link_options target linker_script) + if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + target_link_options(${target} PRIVATE + -mcpu=cortex-m4 + -mthumb + -nostartfiles + -nostdlib + -Wl,--gc-sections + -Wl,--build-id=none + -Wl,-Map=${CMAKE_CURRENT_BINARY_DIR}/${target}.map + -T${linker_script} + ) + endif() +endfunction() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..759b848 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,3 @@ +if(MONAR_PLATFORM STREQUAL "stm32f407") + add_subdirectory(minimal) +endif() diff --git a/examples/minimal/CMakeLists.txt b/examples/minimal/CMakeLists.txt new file mode 100644 index 0000000..48e4604 --- /dev/null +++ b/examples/minimal/CMakeLists.txt @@ -0,0 +1,47 @@ +get_property(monar_bsp_sources GLOBAL PROPERTY MONAR_ACTIVE_BSP_SOURCES) +get_property(monar_bsp_include_dirs GLOBAL PROPERTY MONAR_ACTIVE_BSP_INCLUDE_DIRS) +get_property(monar_bsp_compile_definitions GLOBAL PROPERTY + MONAR_ACTIVE_BSP_COMPILE_DEFINITIONS) +get_property(monar_bsp_linker_script GLOBAL PROPERTY MONAR_ACTIVE_BSP_LINKER_SCRIPT) + +if(NOT monar_bsp_sources OR NOT monar_bsp_linker_script) + message(FATAL_ERROR + "MONAR_PLATFORM='${MONAR_PLATFORM}' requires BSP sources before " + "building examples/minimal") +endif() + +add_executable(monar_stm32f407_minimal.elf + ${CMAKE_CURRENT_SOURCE_DIR}/main.c + ${monar_bsp_sources} +) + +target_link_libraries(monar_stm32f407_minimal.elf PRIVATE monar_core) +target_include_directories(monar_stm32f407_minimal.elf PRIVATE + ${monar_bsp_include_dirs} +) +target_compile_definitions(monar_stm32f407_minimal.elf PRIVATE + ${monar_bsp_compile_definitions} +) + +monar_apply_embedded_compile_flags(monar_stm32f407_minimal.elf) +monar_apply_stm32f407_link_options(monar_stm32f407_minimal.elf + ${monar_bsp_linker_script}) + +if(CMAKE_OBJCOPY) + add_custom_command(TARGET monar_stm32f407_minimal.elf POST_BUILD + COMMAND ${CMAKE_OBJCOPY} -O ihex + $ + ${CMAKE_CURRENT_BINARY_DIR}/monar_stm32f407_minimal.hex + COMMAND ${CMAKE_OBJCOPY} -O binary + $ + ${CMAKE_CURRENT_BINARY_DIR}/monar_stm32f407_minimal.bin + VERBATIM + ) +endif() + +if(MONAR_SIZE_UTIL) + add_custom_command(TARGET monar_stm32f407_minimal.elf POST_BUILD + COMMAND ${MONAR_SIZE_UTIL} $ + VERBATIM + ) +endif() diff --git a/make.cmd b/make.cmd index a9ff7a4..50e5bb2 100644 --- a/make.cmd +++ b/make.cmd @@ -1,12 +1,27 @@ @echo off setlocal -set "MN_MAKE=%LOCALAPPDATA%\Microsoft\WinGet\Packages\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\mingw64\bin\mingw32-make.exe" +set "MN_MAKE=" +set "MN_MAKE_DIR=" -if not exist "%MN_MAKE%" ( - echo mingw32-make.exe not found at: +where /q mingw32-make.exe +if %ERRORLEVEL%==0 ( + set "MN_MAKE=mingw32-make.exe" +) + +if not defined MN_MAKE ( + set "MN_MAKE=%LOCALAPPDATA%\Microsoft\WinGet\Packages\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\mingw64\bin\mingw32-make.exe" + set "MN_MAKE_DIR=%LOCALAPPDATA%\Microsoft\WinGet\Packages\BrechtSanders.WinLibs.POSIX.UCRT_Microsoft.Winget.Source_8wekyb3d8bbwe\mingw64\bin" +) + +if not exist "%MN_MAKE%" if /I not "%MN_MAKE%"=="mingw32-make.exe" ( + echo mingw32-make.exe not found on PATH or at: echo %MN_MAKE% exit /b 1 ) +if defined MN_MAKE_DIR ( + set "PATH=%MN_MAKE_DIR%;%PATH%" +) + "%MN_MAKE%" %* diff --git a/runtimes/CMakeLists.txt b/runtimes/CMakeLists.txt new file mode 100644 index 0000000..f5b0992 --- /dev/null +++ b/runtimes/CMakeLists.txt @@ -0,0 +1,5 @@ +if(MONAR_RUNTIME STREQUAL "baremetal") + add_subdirectory(baremetal) +else() + message(FATAL_ERROR "Unsupported MONAR_RUNTIME='${MONAR_RUNTIME}'") +endif() diff --git a/runtimes/baremetal/CMakeLists.txt b/runtimes/baremetal/CMakeLists.txt new file mode 100644 index 0000000..d4f6aa7 --- /dev/null +++ b/runtimes/baremetal/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(monar_core PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/mn_runtime_baremetal.c +) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..140e2be --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,18 @@ +add_library(monar_core STATIC) + +target_include_directories(monar_core + PUBLIC + ${PROJECT_SOURCE_DIR}/include + PRIVATE + ${PROJECT_SOURCE_DIR}/src/internal +) + +target_compile_features(monar_core PUBLIC c_std_11) +monar_apply_common_warnings(monar_core) + +if(MONAR_PLATFORM STREQUAL "stm32f407") + monar_apply_embedded_compile_flags(monar_core) +endif() + +add_subdirectory(device) +add_subdirectory(osal) diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt new file mode 100644 index 0000000..8aaf228 --- /dev/null +++ b/src/device/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(monar_core PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/mn_device.c + ${CMAKE_CURRENT_SOURCE_DIR}/mn_device_registry.c +) diff --git a/src/osal/CMakeLists.txt b/src/osal/CMakeLists.txt new file mode 100644 index 0000000..486af56 --- /dev/null +++ b/src/osal/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(monar_core PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/mn_osal.c +) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..eb4d1d0 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,3 @@ +if(MONAR_PLATFORM STREQUAL "host") + add_subdirectory(host) +endif() diff --git a/tests/README.md b/tests/README.md index b7c9048..74468f4 100644 --- a/tests/README.md +++ b/tests/README.md @@ -29,4 +29,10 @@ single-process host test, with an internal-only registry reset helper used to keep test cases independent without exposing any public reset API. `make host-test` runs the registered CTest target, and the active build also -syncs `compile_commands.json` to the repository root for editor tooling. +leaves `compile_commands.json` in the host build directory by default. + +If editor tooling needs a repository-root copy, use the explicit sync target: + +```text +make host-sync-compile-commands +``` diff --git a/tests/host/CMakeLists.txt b/tests/host/CMakeLists.txt new file mode 100644 index 0000000..313fb84 --- /dev/null +++ b/tests/host/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(monar_host_test + ${CMAKE_CURRENT_SOURCE_DIR}/test_main.c +) + +target_link_libraries(monar_host_test PRIVATE monar_core) +target_include_directories(monar_host_test PRIVATE + ${PROJECT_SOURCE_DIR}/src/internal +) + +monar_apply_common_warnings(monar_host_test) + +add_test(NAME monar_host_smoke COMMAND monar_host_test) From 6f15ee65142daabd864d8cf89c84532b78498c53 Mon Sep 17 00:00:00 2001 From: Warren Jayd Date: Sun, 31 May 2026 23:21:41 +0800 Subject: [PATCH 5/7] Fix ARM compile option diagnostics --- .clangd | 7 +++++++ cmake/monar_options.cmake | 25 +++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.clangd b/.clangd index 2e63ad5..4010dec 100644 --- a/.clangd +++ b/.clangd @@ -1,2 +1,9 @@ +If: + PathMatch: (bsp/.*|examples/.*) +CompileFlags: + CompilationDatabase: build/stm32f407-debug + Compiler: arm-none-eabi-gcc + Add: [--target=arm-none-eabi] +--- CompileFlags: CompilationDatabase: build/host-debug diff --git a/cmake/monar_options.cmake b/cmake/monar_options.cmake index 9b40bc4..3064406 100644 --- a/cmake/monar_options.cmake +++ b/cmake/monar_options.cmake @@ -11,6 +11,25 @@ set_property(CACHE MONAR_RUNTIME PROPERTY STRINGS baremetal) option(MONAR_BUILD_TESTS "Build Monar test targets" ON) option(MONAR_BUILD_EXAMPLES "Build Monar example targets" ON) +function(monar_is_arm_embedded_compiler out_var) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU") + if(CMAKE_C_COMPILER MATCHES "arm-none-eabi") + set(${out_var} TRUE PARENT_SCOPE) + return() + endif() + endif() + + if(CMAKE_C_COMPILER_ID STREQUAL "Clang") + if(CMAKE_C_COMPILER_TARGET MATCHES "^arm.*eabi" OR + CMAKE_C_COMPILER MATCHES "arm-none-eabi") + set(${out_var} TRUE PARENT_SCOPE) + return() + endif() + endif() + + set(${out_var} FALSE PARENT_SCOPE) +endfunction() + function(monar_apply_common_warnings target) if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(${target} PRIVATE @@ -23,7 +42,8 @@ function(monar_apply_common_warnings target) endfunction() function(monar_apply_embedded_compile_flags target) - if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + monar_is_arm_embedded_compiler(monar_has_arm_embedded_compiler) + if(monar_has_arm_embedded_compiler) target_compile_options(${target} PRIVATE -mcpu=cortex-m4 -mthumb @@ -35,7 +55,8 @@ function(monar_apply_embedded_compile_flags target) endfunction() function(monar_apply_stm32f407_link_options target linker_script) - if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + monar_is_arm_embedded_compiler(monar_has_arm_embedded_compiler) + if(monar_has_arm_embedded_compiler) target_link_options(${target} PRIVATE -mcpu=cortex-m4 -mthumb From 97fec85003d29be068c137125f58ca3e2219320c Mon Sep 17 00:00:00 2001 From: Warren Jayd Date: Sun, 31 May 2026 23:53:17 +0800 Subject: [PATCH 6/7] Add phase-2 CI and foundation docs --- .github/workflows/ci.yml | 93 +++++++++++++ README.md | 52 ++++++- bsp/README.md | 16 ++- docs/architecture.md | 54 ++++++++ docs/build.md | 53 +++++++ docs/runtime-silicon-bsp.md | 52 +++++++ examples/minimal/main.c | 4 +- include/monar/osal.h | 11 ++ runtimes/README.md | 16 +++ runtimes/baremetal/README.md | 4 + silicon/README.md | 16 ++- src/internal/mn_osal_backend.h | 5 + tests/README.md | 19 ++- tests/host/CMakeLists.txt | 2 + tests/host/test_device.c | 242 ++++++++++++++++++++++++++++++++ tests/host/test_host.h | 7 + tests/host/test_main.c | 246 +-------------------------------- tests/host/test_osal.c | 13 ++ 18 files changed, 652 insertions(+), 253 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 docs/architecture.md create mode 100644 docs/build.md create mode 100644 docs/runtime-silicon-bsp.md create mode 100644 runtimes/README.md create mode 100644 tests/host/test_device.c create mode 100644 tests/host/test_host.h create mode 100644 tests/host/test_osal.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d19dd72 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,93 @@ +name: CI + +on: + pull_request: + workflow_dispatch: + +jobs: + host: + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup MinGW host tools + uses: msys2/setup-msys2@v2 + with: + update: true + install: >- + mingw-w64-ucrt-x86_64-gcc + mingw-w64-ucrt-x86_64-cmake + mingw-w64-ucrt-x86_64-make + + - name: Add MinGW tools to PATH + shell: pwsh + run: | + "C:\msys64\ucrt64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: make test + shell: pwsh + run: make test + + - name: Build host preset + shell: pwsh + run: cmake --build --preset host-debug + + - name: Run host ctest + shell: pwsh + run: ctest --preset host-debug + + stm32f407: + runs-on: windows-latest + needs: host + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup MinGW host tools + uses: msys2/setup-msys2@v2 + with: + update: true + install: >- + mingw-w64-ucrt-x86_64-gcc + mingw-w64-ucrt-x86_64-cmake + mingw-w64-ucrt-x86_64-make + + - name: Add MinGW tools to PATH + shell: pwsh + run: | + "C:\msys64\ucrt64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + + - name: Install Arm GNU Toolchain + shell: pwsh + run: | + winget install --id Arm.ArmGnuToolchain -e --accept-package-agreements --accept-source-agreements --silent + + $roots = @( + 'C:\Program Files (x86)\Arm GNU Toolchain arm-none-eabi', + 'C:\Program Files\Arm GNU Toolchain arm-none-eabi' + ) + $selected = $null + + foreach ($root in $roots) { + if (Test-Path $root) { + $selected = Get-ChildItem -Path $root -Directory | + Sort-Object Name -Descending | + Select-Object -First 1 -ExpandProperty FullName + if ($selected) { + break + } + } + } + + if (-not $selected) { + throw 'Arm GNU Toolchain installation path not found.' + } + + "MONAR_ARM_GCC_ROOT=$selected" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Build stm32f407 preset + shell: pwsh + run: cmake --build --preset stm32f407-debug diff --git a/README.md b/README.md index 7f5f5c0..35278dd 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,17 @@ The current scope intentionally stays small. It does not implement STM32 silicon adapters, RTOS backends, Modbus, WS2812B, or complete SPI/I2C/UART drivers yet. +## Phase-2 foundation + +Phase 2 focuses on engineering foundation work before large driver features: + +- CI validation +- runtime backend interface clarification +- runtime / silicon / BSP boundary documentation +- minimal lifecycle conventions +- host test structure cleanup +- documentation for future programmers + ## Repository layout ```text @@ -38,6 +49,12 @@ The CMake build is organized the same way: each buildable directory owns its own `CMakeLists.txt`, and the repository-root `CMakeLists.txt` only coordinates options and conditional subdirectories. +Additional architecture documentation: + +- `docs/architecture.md` +- `docs/build.md` +- `docs/runtime-silicon-bsp.md` + ## Build system The repository now includes: @@ -136,11 +153,27 @@ The committed `.vscode/` settings are set up so that: If the Arm GNU toolchain is not already on your system `PATH`, set `MONAR_ARM_GCC_ROOT` before configuring the `stm32f407-debug` preset. +## Lifecycle convention + +The current intended initialization order is: + +```text +runtime / OSAL init +framework core init point (currently implicit) +BSP board init and device registration +application logic +``` + +The current minimal example keeps `main()` simple and follows this sequence by +calling `mn_osal_init()` before `mn_board_init()`. + ## Current design decisions - public symbols use the `mn_` / `MN_` prefixes - public device handles are opaque - the framework core does not depend directly on RTOS or vendor HAL headers +- public APIs must not expose RTOS, HAL, vendor SDK, register, IRQ-internal, or + silicon-private objects - `mn_osal_init()` is expected before device registry mutation and device use - device registration is static-allocation-friendly and bounded by `MN_CFG_DEVICE_REGISTRY_MAX` @@ -149,13 +182,30 @@ If the Arm GNU toolchain is not already on your system `PATH`, set device - duplicate `resource_key` registration is rejected to avoid ambiguous device/bus/carrier ownership of the same hardware resource +- `runtimes/`, `silicon/`, and `bsp/` have distinct responsibilities and must + not be collapsed into deprecated `ports/` or `chips/` source trees + +## CI + +GitHub Actions workflows live under `.github/workflows/`. + +The current CI foundation validates: + +- `make test` +- `cmake --build --preset host-debug` +- `ctest --preset host-debug` + +and also keeps a separate STM32F407 build job for: + +- `cmake --build --preset stm32f407-debug` ## Host validation -The initial tests live in `tests/host/test_main.c` and cover: +The host tests live under `tests/host/` and currently cover: - deterministic Monar error codes - minimal device registry registration rules +- bare-metal OSAL init/runtime reporting - `mn_device_open` flag, capability, and stale-handle guards - internal-only registry reset between host test cases diff --git a/bsp/README.md b/bsp/README.md index c65f024..888bc8c 100644 --- a/bsp/README.md +++ b/bsp/README.md @@ -1,6 +1,16 @@ # BSP -Board-level integration and device registration belong here. +`bsp/` owns concrete board integration and device-instance selection. -The first milestone keeps this directory as a placeholder so the repository -structure matches the design guidance. +Rules: + +- board startup files, linker scripts, pins, clocks, and MCU model selection + belong here +- future BSP code may register concrete Monar device instances for a board +- BSP code may depend on selected silicon adaptation, but application code + should consume Monar APIs rather than BSP internals +- vendor-family adaptation logic does not belong here; it belongs under + `silicon/` + +The current `bsp/boards/st/nucleo_f407zg/` directory is the phase-2 reference +board skeleton for STM32F407 build validation. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..b78ef4a --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,54 @@ +# Monar Architecture + +## Mission + +Monar is an embedded framework whose goal is to keep application code away from +direct MCU, RTOS, and vendor SDK dependencies. + +## Directory responsibilities + +```text +include/monar/ public Monar APIs only +src/ platform-independent framework core +runtimes/ bare-metal, OS, and RTOS runtime backends +silicon/ MCU vendor/family/SDK adaptation +bsp/ concrete board resources and device-instance selection +tests/ host-side tests and future target-side validation +examples/ minimal application examples +tools/ optional development helpers +``` + +Rules: + +- public APIs stay under `include/monar/` +- internal headers stay private under `src/internal/` or other private paths +- `runtimes/`, `silicon/`, and `bsp/` must be treated as different layers +- formal source directories named `ports/` or `chips/` must not be introduced + +## Public API rule + +Public Monar APIs must not expose: + +- RTOS task, semaphore, queue, or scheduler types +- vendor HAL or SDK handle types +- MCU registers or raw peripheral base addresses +- IRQ internal objects +- silicon-private adaptation objects + +## Current lifecycle convention + +Phase-2 keeps initialization simple and explicit: + +```text +runtime / OSAL init +framework core init point (currently implicit after OSAL init) +BSP board init and device registration +application loop / entry logic +``` + +The current minimal example reflects that order by calling `mn_osal_init()` +before `mn_board_init()`. + +Monar does not add a large init framework in phase 2. Future milestones may add +a dedicated framework-core init hook or linker-section registration model, but +that is not required yet. diff --git a/docs/build.md b/docs/build.md new file mode 100644 index 0000000..314d9ac --- /dev/null +++ b/docs/build.md @@ -0,0 +1,53 @@ +# Build Guide + +## Supported phase-2 validation commands + +```text +make test +cmake --build --preset host-debug +ctest --preset host-debug +cmake --build --preset stm32f407-debug +``` + +## Presets + +Committed presets stay portable: + +- `host-debug` +- `stm32f407-debug` + +Machine-local compiler paths should go in `CMakeUserPresets.json`, which is +ignored by git. A starter template is provided in +`CMakeUserPresets.example.json`. + +## Windows local setup + +Host build requirements: + +- `cmake` +- `ctest` +- `gcc` +- `mingw32-make` + +STM32F407 build requirements: + +- everything above +- Arm GNU Toolchain with `arm-none-eabi-gcc` +- either `arm-none-eabi-gcc` on `PATH`, or `MONAR_ARM_GCC_ROOT` set + +The repository `make.cmd` wrapper exists so `make test` can work in a normal +Windows terminal when `mingw32-make` is available on `PATH`. + +## Generated files + +Normal builds keep generated files under `build/`. + +- `compile_commands.json` stays in the selected build directory by default +- if a repository-root copy is needed for tooling, use an explicit sync target + rather than making source-tree mutation part of every build + +## CI expectation + +Pull requests should at least validate host build and host tests +automatically. The repository CI also keeps a separate STM32F407 build job so +the embedded output path stays visible. diff --git a/docs/runtime-silicon-bsp.md b/docs/runtime-silicon-bsp.md new file mode 100644 index 0000000..69ef619 --- /dev/null +++ b/docs/runtime-silicon-bsp.md @@ -0,0 +1,52 @@ +# Runtime, Silicon, and BSP Boundary + +## Runtime + +`runtimes/` provides the selected runtime backend. + +Responsibilities: + +- OSAL backend init +- critical-section entry and exit +- ISR-context detection +- future scheduler interaction for RTOS backends + +Non-responsibilities: + +- board pin selection +- linker scripts +- vendor HAL peripheral adaptation + +## Silicon + +`silicon/` adapts MCU vendor, family, and SDK capabilities into Monar-facing +driver primitives. + +Examples of future silicon responsibilities: + +- STM32 HAL or LL wrapper code +- family-specific interrupt helpers +- SDK-specific peripheral primitive adaptation + +A future STM32 HAL adapter belongs under `silicon/st/...`. + +## BSP + +`bsp/` selects concrete board resources and device instances. + +Responsibilities: + +- startup file selection +- linker script selection +- pin and clock configuration +- board-level init order +- registration of concrete Monar device instances for the selected board + +Examples: + +- `bsp/boards/st/nucleo_f407zg/` is the current STM32F407 board skeleton + +## Application boundary + +Application code should depend on Monar APIs. It should not call into +silicon-private or BSP-private objects as its stable interface. diff --git a/examples/minimal/main.c b/examples/minimal/main.c index dc4a3e5..29f4fb8 100644 --- a/examples/minimal/main.c +++ b/examples/minimal/main.c @@ -6,11 +6,11 @@ int main(void) { mn_status_t ret; - mn_board_init(); - ret = mn_osal_init(); (void)ret; + mn_board_init(); + while (1) { } } diff --git a/include/monar/osal.h b/include/monar/osal.h index e92bffa..0446f11 100644 --- a/include/monar/osal.h +++ b/include/monar/osal.h @@ -7,10 +7,21 @@ typedef mn_uptr_t mn_osal_critical_state_t; +/* + * The public OSAL surface is the only runtime-facing API visible to Monar + * applications. Runtime, RTOS, and bare-metal backend details remain private + * under runtimes/ and internal headers. + */ mn_status_t mn_osal_init(void); bool mn_osal_is_initialized(void); const char *mn_osal_runtime_name(void); bool mn_osal_is_in_isr(void); + +/* + * Critical sections are delegated to the selected runtime backend. Future RTOS + * integrations should let the backend own scheduler/context-switch interrupt + * behavior instead of duplicating CPU-specific logic in framework core code. + */ mn_osal_critical_state_t mn_osal_critical_enter(void); void mn_osal_critical_exit(mn_osal_critical_state_t state); diff --git a/runtimes/README.md b/runtimes/README.md new file mode 100644 index 0000000..b6a8031 --- /dev/null +++ b/runtimes/README.md @@ -0,0 +1,16 @@ +# Runtimes + +`runtimes/` owns Monar runtime backends, including bare-metal and future OS or +RTOS integrations. + +Rules: + +- public runtime-facing APIs stay under `include/monar/osal.h` +- framework core calls the private backend contract from + `src/internal/mn_osal_backend.h` +- runtime backends decide critical-section mechanics, ISR detection, and any + future scheduler interaction +- framework core must not duplicate context-switch interrupt handling or expose + RTOS-private objects through public Monar APIs + +The phase-2 baseline keeps only the minimal bare-metal backend buildable. diff --git a/runtimes/baremetal/README.md b/runtimes/baremetal/README.md index 67e77dd..22c5e9d 100644 --- a/runtimes/baremetal/README.md +++ b/runtimes/baremetal/README.md @@ -11,3 +11,7 @@ Current behavior: This is intentional for the bootstrap milestone. Future milestones can replace the no-op critical section behavior with target-specific interrupt save/restore logic without changing the public Monar API. + +For future RTOS integrations, context-switch-related interrupts and critical +section semantics should be owned by the selected runtime backend rather than +duplicated in framework core code. diff --git a/silicon/README.md b/silicon/README.md index 2667bdc..3a0916b 100644 --- a/silicon/README.md +++ b/silicon/README.md @@ -1,6 +1,16 @@ # Silicon -Platform-specific silicon and vendor SDK adaptation code belongs here. +`silicon/` owns MCU vendor, family, and SDK adaptation layers. -The first milestone intentionally does not add STM32 or other vendor adapters -yet. +Rules: + +- vendor SDK or HAL wrappers belong here, not under `bsp/` +- silicon code should adapt MCU-family capabilities into Monar driver-facing + primitives +- public Monar headers must not expose vendor HAL handles, registers, IRQ + internals, or silicon-private objects +- board-level pin maps, concrete clocks, startup files, linker scripts, and + device-instance registration do not belong here + +A future STM32 HAL adapter belongs under `silicon/st/...`, while concrete board +selection stays under `bsp/`. diff --git a/src/internal/mn_osal_backend.h b/src/internal/mn_osal_backend.h index f430e2b..59c1ebf 100644 --- a/src/internal/mn_osal_backend.h +++ b/src/internal/mn_osal_backend.h @@ -14,6 +14,11 @@ typedef struct mn_runtime_ops { void (*critical_exit)(mn_osal_critical_state_t state); } mn_runtime_ops_t; +/* + * This backend contract is private to framework core and runtime + * implementations. Public headers must not expose RTOS handles, vendor HAL + * types, registers, IRQ objects, or other silicon-private details here. + */ const mn_runtime_ops_t *mn_runtime_get_ops(void); #endif /* MONAR_SRC_INTERNAL_MN_OSAL_BACKEND_H */ diff --git a/tests/README.md b/tests/README.md index 74468f4..447502e 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,7 +2,8 @@ Host-side and future target-side tests belong here. -The current bootstrap milestone adds a small host test at `tests/host/`. +The current phase-2 baseline keeps host tests under `tests/host/` and does not +compile them into STM32 target builds. Example host build command: @@ -31,6 +32,22 @@ keep test cases independent without exposing any public reset API. `make host-test` runs the registered CTest target, and the active build also leaves `compile_commands.json` in the host build directory by default. +The host tests are now split by module: + +```text +tests/host/test_osal.c +tests/host/test_device.c +tests/host/test_main.c +``` + +Current coverage includes: + +- OSAL init/runtime reporting +- device registry reset behavior +- stale-handle prevention on failed register/find/open paths +- open-mode semantics +- unsupported operation handling + If editor tooling needs a repository-root copy, use the explicit sync target: ```text diff --git a/tests/host/CMakeLists.txt b/tests/host/CMakeLists.txt index 313fb84..5e44e91 100644 --- a/tests/host/CMakeLists.txt +++ b/tests/host/CMakeLists.txt @@ -1,4 +1,6 @@ add_executable(monar_host_test + ${CMAKE_CURRENT_SOURCE_DIR}/test_device.c + ${CMAKE_CURRENT_SOURCE_DIR}/test_osal.c ${CMAKE_CURRENT_SOURCE_DIR}/test_main.c ) diff --git a/tests/host/test_device.c b/tests/host/test_device.c new file mode 100644 index 0000000..2bda812 --- /dev/null +++ b/tests/host/test_device.c @@ -0,0 +1,242 @@ +#include +#include + +#include + +#include "mn_device_internal.h" + +typedef struct test_device_context { + size_t open_calls; + size_t close_calls; + size_t read_calls; + size_t write_calls; + size_t ioctl_calls; +} test_device_context_t; + +static mn_status_t test_open(mn_device_t device, mn_u32_t flags) +{ + test_device_context_t *ctx; + + (void)flags; + + ctx = mn_device_get_driver_data(device); + assert(ctx != NULL); + ctx->open_calls++; + return MN_EOK; +} + +static mn_status_t test_close(mn_device_t device) +{ + test_device_context_t *ctx; + + ctx = mn_device_get_driver_data(device); + assert(ctx != NULL); + ctx->close_calls++; + return MN_EOK; +} + +static mn_status_t test_read(mn_device_t device, void *buffer, size_t size, + size_t *out_size) +{ + static const char payload[] = "monar"; + size_t count; + test_device_context_t *ctx; + + ctx = mn_device_get_driver_data(device); + assert(ctx != NULL); + ctx->read_calls++; + + count = size; + if (count > sizeof(payload) - 1u) { + count = sizeof(payload) - 1u; + } + + if (count > 0u) { + memcpy(buffer, payload, count); + } + + if (out_size != NULL) { + *out_size = count; + } + + return MN_EOK; +} + +static mn_status_t test_write(mn_device_t device, const void *buffer, + size_t size, size_t *out_size) +{ + test_device_context_t *ctx; + + assert(buffer != NULL || size == 0u); + + ctx = mn_device_get_driver_data(device); + assert(ctx != NULL); + ctx->write_calls++; + + if (out_size != NULL) { + *out_size = size; + } + + return MN_EOK; +} + +static mn_status_t test_ioctl(mn_device_t device, mn_u32_t cmd, void *arg) +{ + test_device_context_t *ctx; + + (void)cmd; + (void)arg; + + ctx = mn_device_get_driver_data(device); + assert(ctx != NULL); + ctx->ioctl_calls++; + return MN_EOK; +} + +static void test_registry_rules(void) +{ + static test_device_context_t ctx_a; + static test_device_context_t ctx_b; + static const int resource_a; + static const int resource_b; + static const mn_device_ops_t ops = { + .open = test_open, + .close = test_close, + .read = test_read, + .write = test_write, + .ioctl = test_ioctl, + }; + const mn_device_descriptor_t dev_a = { + .name = "uart0", + .device_class = MN_DEVICE_CLASS_ENDPOINT, + .capability_flags = MN_DEVICE_CAP_READ | MN_DEVICE_CAP_WRITE | + MN_DEVICE_CAP_IOCTL, + .resource_key = &resource_a, + .ops = &ops, + .driver_data = &ctx_a, + }; + const mn_device_descriptor_t duplicate_name = { + .name = "uart0", + .device_class = MN_DEVICE_CLASS_ENDPOINT, + .capability_flags = MN_DEVICE_CAP_READ, + .resource_key = &resource_b, + .ops = &ops, + .driver_data = &ctx_b, + }; + const mn_device_descriptor_t duplicate_resource = { + .name = "spi0", + .device_class = MN_DEVICE_CLASS_BUS, + .capability_flags = MN_DEVICE_CAP_READ, + .resource_key = &resource_a, + .ops = &ops, + .driver_data = &ctx_b, + }; + mn_device_t unused; + mn_device_t stale; + + mn_device_registry_reset_for_test(); + stale = (mn_device_t)&ctx_b; + + assert(mn_device_register(&dev_a, NULL) == MN_EOK); + assert(mn_device_register(&duplicate_name, &stale) == -MN_EEXIST); + assert(stale == NULL); + stale = (mn_device_t)&ctx_b; + assert(mn_device_register(&duplicate_resource, &stale) == -MN_EEXIST); + assert(stale == NULL); + unused = (mn_device_t)&ctx_a; + assert(mn_device_find("missing0", &unused) == -MN_ENOENT); + assert(unused == NULL); +} + +static void test_device_api_guards(void) +{ + static test_device_context_t ctx; + static test_device_context_t control_ctx; + static const int resource_c; + static const int resource_d; + static const mn_device_ops_t ops = { + .open = test_open, + .close = test_close, + .read = test_read, + .write = NULL, + .ioctl = NULL, + }; + static const mn_device_ops_t control_ops = { + .open = test_open, + .close = test_close, + .read = NULL, + .write = NULL, + .ioctl = test_ioctl, + }; + const mn_device_descriptor_t descriptor = { + .name = "sensor0", + .device_class = MN_DEVICE_CLASS_ENDPOINT, + .capability_flags = MN_DEVICE_CAP_READ, + .resource_key = &resource_c, + .ops = &ops, + .driver_data = &ctx, + }; + const mn_device_descriptor_t control_descriptor = { + .name = "control0", + .device_class = MN_DEVICE_CLASS_ENDPOINT, + .capability_flags = MN_DEVICE_CAP_IOCTL, + .resource_key = &resource_d, + .ops = &control_ops, + .driver_data = &control_ctx, + }; + mn_device_t device; + mn_device_t looked_up; + mn_device_t control_device; + char buffer[8]; + size_t count; + + mn_device_registry_reset_for_test(); + assert(mn_device_register(&descriptor, &device) == MN_EOK); + assert(mn_device_register(&control_descriptor, &control_device) == MN_EOK); + assert(mn_device_find("sensor0", &looked_up) == MN_EOK); + assert(looked_up == device); + + looked_up = (mn_device_t)&ctx; + assert(mn_device_open(NULL, 0u, &device) == -MN_EINVAL); + assert(device == NULL); + assert(mn_device_open("sensor0", 4u, &device) == -MN_EINVAL); + assert(device == NULL); + assert(mn_device_open("missing1", 0u, &device) == -MN_ENOENT); + assert(device == NULL); + assert(mn_device_read(device, buffer, sizeof(buffer), &count) == -MN_EBADF); + assert(mn_device_open("sensor0", MN_DEVICE_OPEN_WRITE, &device) == + -MN_ENOSYS); + assert(device == NULL); + assert(mn_device_open("sensor0", MN_DEVICE_OPEN_RDWR, &device) == + -MN_ENOSYS); + assert(device == NULL); + assert(mn_device_open("control0", 0u, &control_device) == MN_EOK); + assert(control_ctx.open_calls == 1u); + assert(mn_device_ioctl(control_device, 0u, NULL) == MN_EOK); + assert(control_ctx.ioctl_calls == 1u); + assert(mn_device_close(control_device) == MN_EOK); + assert(mn_device_open("sensor0", MN_DEVICE_OPEN_READ, &device) == MN_EOK); + assert(mn_device_open("sensor0", MN_DEVICE_OPEN_READ, &looked_up) == + -MN_EBUSY); + assert(looked_up == NULL); + assert(mn_device_read(device, NULL, sizeof(buffer), &count) == -MN_EINVAL); + assert(mn_device_read(device, buffer, sizeof(buffer), &count) == MN_EOK); + assert(count == 5u); + assert(mn_device_write(device, buffer, sizeof(buffer), &count) == + -MN_ENOSYS); + assert(mn_device_ioctl(device, 0u, NULL) == -MN_ENOSYS); + assert(mn_device_close(device) == MN_EOK); + assert(mn_device_close(device) == -MN_EBADF); + assert(ctx.open_calls == 1u); + assert(ctx.close_calls == 1u); + assert(ctx.read_calls == 1u); + assert(ctx.write_calls == 0u); + assert(ctx.ioctl_calls == 0u); + assert(control_ctx.close_calls == 1u); +} + +void mn_test_device(void) +{ + test_registry_rules(); + test_device_api_guards(); +} diff --git a/tests/host/test_host.h b/tests/host/test_host.h new file mode 100644 index 0000000..1a317ad --- /dev/null +++ b/tests/host/test_host.h @@ -0,0 +1,7 @@ +#ifndef MONAR_TESTS_HOST_TEST_HOST_H +#define MONAR_TESTS_HOST_TEST_HOST_H + +void mn_test_osal(void); +void mn_test_device(void); + +#endif /* MONAR_TESTS_HOST_TEST_HOST_H */ diff --git a/tests/host/test_main.c b/tests/host/test_main.c index 3d59cc9..edfc289 100644 --- a/tests/host/test_main.c +++ b/tests/host/test_main.c @@ -1,251 +1,11 @@ -#include #include -#include -#include - -#include "mn_device_internal.h" - -typedef struct test_device_context { - size_t open_calls; - size_t close_calls; - size_t read_calls; - size_t write_calls; - size_t ioctl_calls; -} test_device_context_t; - -static mn_status_t test_open(mn_device_t device, mn_u32_t flags) -{ - test_device_context_t *ctx; - - (void)flags; - - ctx = mn_device_get_driver_data(device); - assert(ctx != NULL); - ctx->open_calls++; - return MN_EOK; -} - -static mn_status_t test_close(mn_device_t device) -{ - test_device_context_t *ctx; - - ctx = mn_device_get_driver_data(device); - assert(ctx != NULL); - ctx->close_calls++; - return MN_EOK; -} - -static mn_status_t test_read(mn_device_t device, void *buffer, size_t size, - size_t *out_size) -{ - static const char payload[] = "monar"; - size_t count; - test_device_context_t *ctx; - - ctx = mn_device_get_driver_data(device); - assert(ctx != NULL); - ctx->read_calls++; - - count = size; - if (count > sizeof(payload) - 1u) { - count = sizeof(payload) - 1u; - } - - if (count > 0u) { - memcpy(buffer, payload, count); - } - - if (out_size != NULL) { - *out_size = count; - } - - return MN_EOK; -} - -static mn_status_t test_write(mn_device_t device, const void *buffer, - size_t size, size_t *out_size) -{ - test_device_context_t *ctx; - - assert(buffer != NULL || size == 0u); - - ctx = mn_device_get_driver_data(device); - assert(ctx != NULL); - ctx->write_calls++; - - if (out_size != NULL) { - *out_size = size; - } - - return MN_EOK; -} - -static mn_status_t test_ioctl(mn_device_t device, mn_u32_t cmd, void *arg) -{ - test_device_context_t *ctx; - - (void)cmd; - (void)arg; - - ctx = mn_device_get_driver_data(device); - assert(ctx != NULL); - ctx->ioctl_calls++; - return MN_EOK; -} - -static void test_registry_rules(void) -{ - static test_device_context_t ctx_a; - static test_device_context_t ctx_b; - static const int resource_a; - static const int resource_b; - static const mn_device_ops_t ops = { - .open = test_open, - .close = test_close, - .read = test_read, - .write = test_write, - .ioctl = test_ioctl, - }; - const mn_device_descriptor_t dev_a = { - .name = "uart0", - .device_class = MN_DEVICE_CLASS_ENDPOINT, - .capability_flags = MN_DEVICE_CAP_READ | MN_DEVICE_CAP_WRITE | - MN_DEVICE_CAP_IOCTL, - .resource_key = &resource_a, - .ops = &ops, - .driver_data = &ctx_a, - }; - const mn_device_descriptor_t duplicate_name = { - .name = "uart0", - .device_class = MN_DEVICE_CLASS_ENDPOINT, - .capability_flags = MN_DEVICE_CAP_READ, - .resource_key = &resource_b, - .ops = &ops, - .driver_data = &ctx_b, - }; - const mn_device_descriptor_t duplicate_resource = { - .name = "spi0", - .device_class = MN_DEVICE_CLASS_BUS, - .capability_flags = MN_DEVICE_CAP_READ, - .resource_key = &resource_a, - .ops = &ops, - .driver_data = &ctx_b, - }; - mn_device_t unused; - mn_device_t stale; - - mn_device_registry_reset_for_test(); - stale = (mn_device_t)&ctx_b; - - assert(mn_device_register(&dev_a, NULL) == MN_EOK); - assert(mn_device_register(&duplicate_name, &stale) == -MN_EEXIST); - assert(stale == NULL); - stale = (mn_device_t)&ctx_b; - assert(mn_device_register(&duplicate_resource, &stale) == -MN_EEXIST); - assert(stale == NULL); - unused = (mn_device_t)&ctx_a; - assert(mn_device_find("missing0", &unused) == -MN_ENOENT); - assert(unused == NULL); -} - -static void test_device_api_guards(void) -{ - static test_device_context_t ctx; - static test_device_context_t control_ctx; - static const int resource_c; - static const int resource_d; - static const mn_device_ops_t ops = { - .open = test_open, - .close = test_close, - .read = test_read, - .write = NULL, - .ioctl = NULL, - }; - static const mn_device_ops_t control_ops = { - .open = test_open, - .close = test_close, - .read = NULL, - .write = NULL, - .ioctl = test_ioctl, - }; - const mn_device_descriptor_t descriptor = { - .name = "sensor0", - .device_class = MN_DEVICE_CLASS_ENDPOINT, - .capability_flags = MN_DEVICE_CAP_READ, - .resource_key = &resource_c, - .ops = &ops, - .driver_data = &ctx, - }; - const mn_device_descriptor_t control_descriptor = { - .name = "control0", - .device_class = MN_DEVICE_CLASS_ENDPOINT, - .capability_flags = MN_DEVICE_CAP_IOCTL, - .resource_key = &resource_d, - .ops = &control_ops, - .driver_data = &control_ctx, - }; - mn_device_t device; - mn_device_t looked_up; - mn_device_t control_device; - char buffer[8]; - size_t count; - - mn_device_registry_reset_for_test(); - assert(mn_device_register(&descriptor, &device) == MN_EOK); - assert(mn_device_register(&control_descriptor, &control_device) == MN_EOK); - assert(mn_device_find("sensor0", &looked_up) == MN_EOK); - assert(looked_up == device); - - looked_up = (mn_device_t)&ctx; - assert(mn_device_open(NULL, 0u, &device) == -MN_EINVAL); - assert(device == NULL); - assert(mn_device_open("sensor0", 4u, &device) == -MN_EINVAL); - assert(device == NULL); - assert(mn_device_open("missing1", 0u, &device) == -MN_ENOENT); - assert(device == NULL); - assert(mn_device_read(device, buffer, sizeof(buffer), &count) == -MN_EBADF); - assert(mn_device_open("sensor0", MN_DEVICE_OPEN_WRITE, &device) == - -MN_ENOSYS); - assert(device == NULL); - assert(mn_device_open("sensor0", MN_DEVICE_OPEN_RDWR, &device) == - -MN_ENOSYS); - assert(device == NULL); - assert(mn_device_open("control0", 0u, &control_device) == MN_EOK); - assert(control_ctx.open_calls == 1u); - assert(mn_device_ioctl(control_device, 0u, NULL) == MN_EOK); - assert(control_ctx.ioctl_calls == 1u); - assert(mn_device_close(control_device) == MN_EOK); - assert(mn_device_open("sensor0", MN_DEVICE_OPEN_READ, &device) == MN_EOK); - assert(mn_device_open("sensor0", MN_DEVICE_OPEN_READ, &looked_up) == - -MN_EBUSY); - assert(looked_up == NULL); - assert(mn_device_read(device, NULL, sizeof(buffer), &count) == -MN_EINVAL); - assert(mn_device_read(device, buffer, sizeof(buffer), &count) == MN_EOK); - assert(count == 5u); - assert(mn_device_write(device, buffer, sizeof(buffer), &count) == - -MN_ENOSYS); - assert(mn_device_ioctl(device, 0u, NULL) == -MN_ENOSYS); - assert(mn_device_close(device) == MN_EOK); - assert(mn_device_close(device) == -MN_EBADF); - assert(ctx.open_calls == 1u); - assert(ctx.close_calls == 1u); - assert(ctx.read_calls == 1u); - assert(ctx.write_calls == 0u); - assert(ctx.ioctl_calls == 0u); - assert(control_ctx.close_calls == 1u); -} +#include "test_host.h" int main(void) { - assert(mn_osal_is_initialized() == false); - assert(mn_osal_init() == MN_EOK); - assert(mn_osal_is_initialized() == true); - assert(strcmp(mn_osal_runtime_name(), "baremetal") == 0); - assert(mn_osal_is_in_isr() == false); - - test_registry_rules(); - test_device_api_guards(); + mn_test_osal(); + mn_test_device(); puts("host tests passed"); return 0; diff --git a/tests/host/test_osal.c b/tests/host/test_osal.c new file mode 100644 index 0000000..18ab2d3 --- /dev/null +++ b/tests/host/test_osal.c @@ -0,0 +1,13 @@ +#include +#include + +#include + +void mn_test_osal(void) +{ + assert(mn_osal_is_initialized() == false); + assert(mn_osal_init() == MN_EOK); + assert(mn_osal_is_initialized() == true); + assert(strcmp(mn_osal_runtime_name(), "baremetal") == 0); + assert(mn_osal_is_in_isr() == false); +} From fdb64580f2f1fff090e7b46843d953005b1ead26 Mon Sep 17 00:00:00 2001 From: Warren Jayd Date: Mon, 1 Jun 2026 00:16:59 +0800 Subject: [PATCH 7/7] Fix STM32 CI configure step --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d19dd72..b5ac329 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,6 +88,10 @@ jobs: "MONAR_ARM_GCC_ROOT=$selected" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + - name: Configure stm32f407 preset + shell: pwsh + run: cmake --preset stm32f407-debug + - name: Build stm32f407 preset shell: pwsh run: cmake --build --preset stm32f407-debug