diff --git a/extra/libmetal/0001-add-qnx-support.patch b/extra/libmetal/0001-add-qnx-support.patch new file mode 100644 index 0000000..b771341 --- /dev/null +++ b/extra/libmetal/0001-add-qnx-support.patch @@ -0,0 +1,1981 @@ +diff --git a/README.md b/README.md +index 0cc5181..0a7648d 100644 +--- a/README.md ++++ b/README.md +@@ -106,6 +106,31 @@ of Zephyr CMake project. Here is how to build libmetal for Zephyr: + $ make VERBOSE=1 run + ``` + ++### Building for QNX ++ ++To build libmetal for QNX, QNX SDP as well as configuration files from [ports/libmetal](https://github.com/qnx-ports/build-files/tree/main/ports/libmetal) are required. Here is how to build: ++ ++``` ++# Create QNX workspace and install source code ++mkdir -p ~/qnx_workspace && cd ~/qnx_workspace ++git clone https://github.com/qnx-ports/build-files ++git clone https://github.com/OpenAMP/libmetal ++ ++# Source the environment ++source ~/qnx800/qnxsdp-env.sh ++ ++#Build libmetal ++make -C build-files/ports/libmetal install -j4 ++``` ++ ++`x86_64` and `aarch64` specific `libmetal.so*` files will be created under `build-files/ports/libmetal//build` as well as be installed in SDP under `/target/qnx//usr/local/lib` ++ ++**Note**: Custom build options or installation paths can be added to QNX build. For example ++ ++``` ++WITH_STATIC_LIB=ON WITH_TESTS=ON make -C build-files/ports/libmetal install -j4 USE_INSTALL_ROOT=true INSTALL_ROOT_nto= ++``` ++ + ## Interfaces + + The following subsections give an overview of interfaces provided by libmetal. +diff --git a/cmake/options.cmake b/cmake/options.cmake +index 1a7e6db..5e4c8e9 100644 +--- a/cmake/options.cmake ++++ b/cmake/options.cmake +@@ -71,7 +71,11 @@ option (WITH_FUNC_LINE_LOG "Log with function name, line number prefix" OFF) + + option (WITH_DOC "Build with documentation" ON) + ++if (QNX) ++set_property (GLOBAL PROPERTY "PROJECT_EC_FLAGS" -Wall -Wextra) ++else (QNX) + set_property (GLOBAL PROPERTY "PROJECT_EC_FLAGS" -Wall -Werror -Wextra) ++endif (QNX) + + if (NOT DEFINED PROJECT_VENDOR) + set (PROJECT_VENDOR "none") +diff --git a/lib/system/qnx/CMakeLists.txt b/lib/system/qnx/CMakeLists.txt +new file mode 100644 +index 0000000..f9120f8 +--- /dev/null ++++ b/lib/system/qnx/CMakeLists.txt +@@ -0,0 +1,23 @@ ++collect (PROJECT_LIB_HEADERS alloc.h) ++collect (PROJECT_LIB_HEADERS assert.h) ++collect (PROJECT_LIB_HEADERS cache.h) ++collect (PROJECT_LIB_HEADERS condition.h) ++collect (PROJECT_LIB_HEADERS io.h) ++collect (PROJECT_LIB_HEADERS irq.h) ++collect (PROJECT_LIB_HEADERS log.h) ++collect (PROJECT_LIB_HEADERS mutex.h) ++collect (PROJECT_LIB_HEADERS sleep.h) ++collect (PROJECT_LIB_HEADERS sys.h) ++ ++collect (PROJECT_LIB_SOURCES condition.c) ++collect (PROJECT_LIB_SOURCES device.c) ++collect (PROJECT_LIB_SOURCES init.c) ++collect (PROJECT_LIB_SOURCES io.c) ++collect (PROJECT_LIB_SOURCES irq.c) ++collect (PROJECT_LIB_SOURCES shmem.c) ++collect (PROJECT_LIB_SOURCES time.c) ++collect (PROJECT_LIB_SOURCES utilities.c) ++ ++if (QNX_CACHE) ++collect (PROJECT_LIB_DEPS cache) ++endif (QNX_CACHE) +diff --git a/lib/system/qnx/alloc.h b/lib/system/qnx/alloc.h +new file mode 100644 +index 0000000..442db32 +--- /dev/null ++++ b/lib/system/qnx/alloc.h +@@ -0,0 +1,39 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/alloc.h ++ * @brief QNX libmetal memory allocation definitions. ++ */ ++ ++#ifndef __METAL_ALLOC__H__ ++#error "Include metal/alloc.h instead of metal/qnx/alloc.h" ++#endif ++ ++#ifndef __METAL_QNX_ALLOC__H__ ++#define __METAL_QNX_ALLOC__H__ ++ ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++static inline void *__metal_allocate_memory(unsigned int size) ++{ ++ return malloc(size); ++} ++ ++static inline void __metal_free_memory(void *ptr) ++{ ++ free(ptr); ++} ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* __METAL_QNX_ALLOC__H__ */ +diff --git a/lib/system/qnx/assert.h b/lib/system/qnx/assert.h +new file mode 100644 +index 0000000..1c728c6 +--- /dev/null ++++ b/lib/system/qnx/assert.h +@@ -0,0 +1,27 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/assert.h ++ * @brief QNX libmetal assertion support. ++ */ ++ ++#ifndef __METAL_ASSERT__H__ ++#error "Include metal/assert.h instead of metal/qnx/assert.h" ++#endif ++ ++#ifndef __METAL_QNX_ASSERT__H__ ++#define __METAL_QNX_ASSERT__H__ ++ ++#include ++ ++/** ++ * @brief Assertion macro for QNX-based applications. ++ * @param cond Condition to evaluate. ++ */ ++#define metal_sys_assert(cond) assert(cond) ++ ++#endif /* __METAL_QNX_ASSERT__H__ */ +diff --git a/lib/system/qnx/cache.h b/lib/system/qnx/cache.h +new file mode 100644 +index 0000000..4341a05 +--- /dev/null ++++ b/lib/system/qnx/cache.h +@@ -0,0 +1,53 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/cache.h ++ * @brief QNX cache operation primitives for libmetal. ++ */ ++ ++#ifndef __METAL_CACHE__H__ ++#error "Include metal/cache.h instead of metal/qnx/cache.h" ++#endif ++ ++#ifndef __METAL_QNX_CACHE__H__ ++#define __METAL_QNX_CACHE__H__ ++ ++#include ++#include ++#if QNX_CACHE ++#include ++#endif ++#include ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#if QNX_CACHE ++extern struct cache_ctrl __qnx_cache_control; ++#endif ++ ++static inline void __metal_cache_flush(void *addr, unsigned int len) ++{ ++#if QNX_CACHE ++ CACHE_FLUSH(&__qnx_cache_control, addr, (uint64_t)__qnx_get_physical_address(addr, len), len); ++#endif ++} ++ ++static inline void __metal_cache_invalidate(void *addr, unsigned int len) ++{ ++#if QNX_CACHE ++ CACHE_INVAL(&__qnx_cache_control, addr, (uint64_t)__qnx_get_physical_address(addr, len), len); ++#endif ++} ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* __METAL_QNX_CACHE__H__ */ +diff --git a/lib/system/qnx/condition.c b/lib/system/qnx/condition.c +new file mode 100644 +index 0000000..c6eca5f +--- /dev/null ++++ b/lib/system/qnx/condition.c +@@ -0,0 +1,19 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/condition.c ++ * @brief QNX libmetal condition variable handling. ++ */ ++ ++#include ++ ++int metal_condition_wait(struct metal_condition *cv, metal_mutex_t *m) ++{ ++ int ret = pthread_cond_wait(&cv->cond, m); ++ ++ return (ret == EOK) ? 0 : -ret; ++} +diff --git a/lib/system/qnx/condition.h b/lib/system/qnx/condition.h +new file mode 100644 +index 0000000..35964bb +--- /dev/null ++++ b/lib/system/qnx/condition.h +@@ -0,0 +1,59 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/condition.h ++ * @brief QNX condition variable primitives for libmetal. ++ */ ++ ++#ifndef __METAL_CONDITION__H__ ++#error "Include metal/condition.h instead of metal/qnx/condition.h" ++#endif ++ ++#ifndef __METAL_QNX_CONDITION__H__ ++#define __METAL_QNX_CONDITION__H__ ++ ++#include ++#include ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++struct metal_condition { ++ pthread_cond_t cond; ++}; ++ ++/* ++ * METAL_CONDITION_INIT - static metal condition variable initialization ++ */ ++#define METAL_CONDITION_INIT { PTHREAD_COND_INITIALIZER } ++ ++static inline void metal_condition_init(struct metal_condition *cv) ++{ ++ metal_assert(pthread_cond_init(&cv->cond, NULL) == EOK); ++} ++ ++static inline int metal_condition_signal(struct metal_condition *cv) ++{ ++ int ret = pthread_cond_signal(&cv->cond); ++ ++ return (ret == EOK) ? 0 : -ret; ++} ++ ++static inline int metal_condition_broadcast(struct metal_condition *cv) ++{ ++ int ret = pthread_cond_broadcast(&cv->cond); ++ ++ return (ret == EOK) ? 0 : -ret; ++} ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* __METAL_QNX_CONDITION__H__ */ +diff --git a/lib/system/qnx/device.c b/lib/system/qnx/device.c +new file mode 100644 +index 0000000..dd60b39 +--- /dev/null ++++ b/lib/system/qnx/device.c +@@ -0,0 +1,29 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/device.c ++ * @brief QNX libmetal device operations. ++ */ ++ ++#include ++ ++int metal_generic_dev_sys_open(struct metal_device *dev) ++{ ++ struct metal_io_region *io; ++ unsigned int i, ret; ++ ++ /* map I/O memory regions */ ++ for (i = 0; i < dev->num_regions; i++) { ++ io = &dev->regions[i]; ++ ret = metal_sys_io_mem_map(io); ++ if (ret != 0) ++ return ret; ++ } ++ ++ return 0; ++} ++ +diff --git a/lib/system/qnx/init.c b/lib/system/qnx/init.c +new file mode 100644 +index 0000000..ff08b20 +--- /dev/null ++++ b/lib/system/qnx/init.c +@@ -0,0 +1,85 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/init.c ++ * @brief QNX libmetal initialization. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "irq.h" ++ ++struct metal_state _metal; ++#if QNX_CACHE ++struct cache_ctrl __qnx_cache_control; ++#endif QNX_CACHE ++static int GENERIC_BUS_REGISTER; ++ ++static void metal_init_page_sizes(void) ++{ ++ _metal.page_size = sysconf(_SC_PAGESIZE); ++ _metal.page_shift = metal_log2(_metal.page_size); ++} ++ ++int metal_sys_init(const struct metal_init_params *params) ++{ ++ int ret; ++ ++ /* Initialize page size */ ++ metal_init_page_sizes(); ++ ++ /* Initialize IRQ */ ++ ret = metal_qnx_irq_init(); ++ if (ret != 0) { ++ metal_log(METAL_LOG_ERROR, "irq init failed - %s\n", ++ strerror(-ret)); ++ return ret; ++ } ++ ++#if QNX_CACHE ++ /* Initialize cache */ ++ memset(&__qnx_cache_control, 0, sizeof(__qnx_cache_control)); ++ __qnx_cache_control.fd = NOFD; ++ ++ ret = cache_init(0, &__qnx_cache_control, NULL); ++ if (ret == -1) { ++ metal_log(METAL_LOG_ERROR, "cache init failed - %s\n", ++ strerror(errno)); ++ return -errno; ++ } ++#endif ++ ++ /* Initialize generic bus */ ++ ret = metal_bus_register(&metal_generic_bus); ++ if (ret != 0) { ++ metal_log(METAL_LOG_DEBUG, "generic bus init failed - %s\n", ++ strerror(-ret)); ++ } else { ++ GENERIC_BUS_REGISTER = 1; ++ } ++ ++ metal_unused(params); ++ ++ return 0; ++} ++ ++void metal_sys_finish(void) ++{ ++#if QNX_CACHE ++ cache_fini(&__qnx_cache_control); ++#endif ++ metal_qnx_irq_shutdown(); ++ if (GENERIC_BUS_REGISTER) { ++ metal_bus_unregister(&metal_generic_bus); ++ } ++} ++ +diff --git a/lib/system/qnx/io.c b/lib/system/qnx/io.c +new file mode 100644 +index 0000000..10c00c6 +--- /dev/null ++++ b/lib/system/qnx/io.c +@@ -0,0 +1,45 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/io.c ++ * @brief QNX libmetal io operations. ++ */ ++ ++#include ++#include ++ ++int metal_sys_io_mem_map(struct metal_io_region *io) ++{ ++ if (io->virt) ++ return 0; ++ ++ void *addr; ++ int flags, prot; ++ ++ flags = (io->mem_flags & ~PROT_MASK) | MAP_PHYS; ++ prot = (io->mem_flags & PROT_MASK) | PROT_READ | PROT_WRITE; ++ addr = mmap64(NULL, io->size, prot, flags, NOFD, *((off64_t *)io->physmap)); ++ if (addr == MAP_FAILED) ++ return -errno; ++ ++ io->virt = addr; ++ return 0; ++} ++ ++int metal_sys_io_mem_unmap(struct metal_io_region *io) ++{ ++ int ret; ++ ++ if (!io->virt) ++ return 0; ++ ++ ret = munmap(io->virt, io->size); ++ if (ret != 0) ++ return -errno; ++ ++ return 0; ++} +diff --git a/lib/system/qnx/io.h b/lib/system/qnx/io.h +new file mode 100644 +index 0000000..b3ab0f5 +--- /dev/null ++++ b/lib/system/qnx/io.h +@@ -0,0 +1,45 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/io.h ++ * @brief QNX specific io definitions. ++ */ ++ ++#ifndef __METAL_IO__H__ ++#error "Include metal/io.h instead of metal/qnx/io.h" ++#endif ++ ++#ifndef __METAL_QNX_IO__H__ ++#define __METAL_QNX_IO__H__ ++ ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#ifdef METAL_INTERNAL ++ ++/** ++ * @brief memory mapping for an I/O region ++ * @param[in] io metal_io_region to map in process address space ++ */ ++int metal_sys_io_mem_map(struct metal_io_region *io); ++ ++/** ++ * @brief unmapping for an I/O region ++ * @param[in] io metal_io_region to unmap ++ */ ++int metal_sys_io_mem_unmap(struct metal_io_region *io); ++ ++#endif ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* __METAL_QNX_IO__H__ */ +diff --git a/lib/system/qnx/irq.c b/lib/system/qnx/irq.c +new file mode 100644 +index 0000000..cec6415 +--- /dev/null ++++ b/lib/system/qnx/irq.c +@@ -0,0 +1,256 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/** ++ * @file qnx/irq.c ++ * @brief QNX libmetal irq operations. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define MAX_IRQS 1024 /* maximum number of irqs */ ++ ++#define INTR_PULSE_CODE _PULSE_CODE_MINAVAIL ++ ++#define INTR_PRIORITY SIGEV_PULSE_PRIO_INHERIT ++ ++static int chid; /* QNX interrupt handling thread channel id */ ++ ++static int self_coid; /* QNX interrupt handling thread connectionn id */ ++ ++static bool irq_handling_stop; /* stop interrupts handling */ ++ ++static pthread_t irq_pthread; /* irq handling thread id */ ++ ++static struct metal_irq irqs[MAX_IRQS]; /* QNX IRQs array */ ++ ++static int irqs_ids[MAX_IRQS]; /* QNX registered IRQs IDs array */ ++ ++/* Static functions */ ++static void metal_qnx_irq_set_enable(struct metal_irq_controller *irq_cntr, ++ int irq, unsigned int state); ++ ++/* QNX IRQ controller */ ++static METAL_IRQ_CONTROLLER_DECLARE(qnx_irq_cntr, ++ 0, MAX_IRQS, ++ NULL, ++ metal_qnx_irq_set_enable, NULL, ++ irqs); ++ ++unsigned int metal_irq_save_disable(void) ++{ ++ InterruptDisable(); ++ return 0; ++} ++ ++void metal_irq_restore_enable(unsigned int flags) ++{ ++ InterruptEnable(); ++ metal_unused(flags); ++} ++ ++static void metal_qnx_irq_set_enable(struct metal_irq_controller *irq_cntr, ++ int irq, unsigned int state) ++{ ++ if (irq < irq_cntr->irq_base || ++ irq >= irq_cntr->irq_base + irq_cntr->irq_num) { ++ metal_log(METAL_LOG_ERROR, "%s: invalid irq %d\n", ++ __func__, irq); ++ return; ++ } ++ ++ if (state == METAL_IRQ_ENABLE) { ++ struct sigevent event; ++ ++ SIGEV_PULSE_INIT(&event, self_coid, INTR_PRIORITY, INTR_PULSE_CODE, irq); ++ irqs_ids[irq] = InterruptAttachEvent(irq, &event, _NTO_INTR_FLAGS_NO_UNMASK); ++ if (irqs_ids[irq] == -1) { ++ metal_log(METAL_LOG_ERROR, ++ "%s: unable to enable irq %d\n", __func__, irq); ++ } ++ } else if (state == METAL_IRQ_DISABLE) { ++ if (irqs_ids[irq] == -1) { ++ metal_log(METAL_LOG_ERROR, ++ "%s: request to disable irq %d which is not enabled\n", ++ __func__, irq); ++ } else if (InterruptDetach(irqs_ids[irq]) == -1) { ++ metal_log(METAL_LOG_ERROR, ++ "%s: unable to disable irq %d\n", ++ __func__, irq); ++ } ++ } else { ++ metal_log(METAL_LOG_ERROR, ++ "%s: unknown requested interrupt state %d for irq %d\n", ++ __func__, state, irq); ++ } ++} ++ ++/** ++ * @brief IRQ handler ++ * @param[in] args not used. required for pthread. ++ */ ++static void *metal_qnx_irq_handling(void *args) ++{ ++ struct _pulse msg; ++ ++ metal_unused(args); ++ ++ while (1) { ++ /* block here waiting for interrupt pulse */ ++ int rcvid = MsgReceivePulse(chid, &msg, sizeof(msg), NULL); ++ ++ if (rcvid == -1) { ++ if (!irq_handling_stop) { ++ metal_log(METAL_LOG_ERROR, ++ "MsgReceivePulse failed: %s\n", strerror(errno)); ++ } ++ break; ++ } ++ ++ if (msg.code == _PULSE_CODE_DISCONNECT) { ++ ConnectDetach(msg.scoid); ++ continue; ++ } ++ ++ if (msg.code != INTR_PULSE_CODE) { ++ metal_log(METAL_LOG_WARNING, ++ "Unhandled pulse code: %d\n", msg.code); ++ continue; ++ } ++ ++ int irq = msg.value.sival_int; ++ int irq_id = irqs_ids[irq]; ++ ++ if (irq_id != -1) { ++ metal_log(METAL_LOG_DEBUG, ++ "Received interrupt on vector %d (0x%.4hX)\n", ++ irq, (uint16_t)irq); ++ ++ /* call handler for received irq if enabled */ ++ if (metal_irq_handle(&irqs[irq], irq) != METAL_IRQ_HANDLED) { ++ metal_log(METAL_LOG_WARNING, ++ "IRQ %d (0x%.4hX) unhandled\n", ++ irq, (uint16_t)irq); ++ } ++ ++ /* enable receiving of new interrupts */ ++ int ret = __QNX__ < 800 ? InterruptUnmask(irq, irq_id) : InterruptUnmask(0, irq_id); ++ ++ if (ret == -1) { ++ metal_log(METAL_LOG_ERROR, ++ "Unable to unmask the interrupt %d (0x%.4hX)\n", ++ irq, (uint16_t)irq); ++ } ++ } else { ++ metal_log(METAL_LOG_ERROR, ++ "Received interrupt on unregistered vector %d (0x%.4hX)\n", ++ irq, (uint16_t)irq); ++ } ++ } ++ ++ return NULL; ++} ++ ++/** ++ * @brief irq handling initialization ++ * @return 0 on success, non-zero on failure ++ */ ++int metal_qnx_irq_init(void) ++{ ++ int ret; ++ ++ memset(&irqs, 0, sizeof(irqs)); ++ ++ for (int i = 0; i < MAX_IRQS; i++) { ++ irqs_ids[i] = -1; ++ } ++ ++ chid = ChannelCreate(_NTO_CHF_DISCONNECT | _NTO_CHF_UNBLOCK); ++ if (chid == -1) { ++ metal_log(METAL_LOG_ERROR, ++ "Unable to create channel: chid %d: %s\n", ++ chid, strerror(errno)); ++ return -EAGAIN; ++ } ++ ++ self_coid = ConnectAttach(0, 0, chid, _NTO_SIDE_CHANNEL, _NTO_COF_CLOEXEC); ++ if (self_coid == -1) { ++ metal_log(METAL_LOG_ERROR, ++ "Unable to attach connection: coid %d: %s\n", ++ self_coid, strerror(errno)); ++ return -EAGAIN; ++ } ++ ++ irq_handling_stop = false; ++ ret = metal_irq_register_controller(&qnx_irq_cntr); ++ if (ret < 0) { ++ metal_log(METAL_LOG_ERROR, ++ "QNX IRQ controller failed to register.\n"); ++ return -EINVAL; ++ } ++ ++ ret = pthread_create(&irq_pthread, NULL, metal_qnx_irq_handling, NULL); ++ if (ret != EOK) { ++ metal_log(METAL_LOG_ERROR, "Failed to create IRQ thread: %d.\n", ++ ret); ++ return -EAGAIN; ++ } ++ ++ return 0; ++} ++ ++/** ++ * @brief irq handling shutdown ++ */ ++void metal_qnx_irq_shutdown(void) ++{ ++ int ret; ++ ++ metal_log(METAL_LOG_DEBUG, "%s\n", __func__); ++ ++ irq_handling_stop = true; ++ ++ for (int irq = 0; irq < MAX_IRQS; irq++) { ++ if (irqs_ids[irq] != -1) { ++ ret = InterruptDetach(irqs_ids[irq]); ++ if (ret == -1) { ++ metal_log(METAL_LOG_ERROR, ++ "Unable to detach connection: coid %d: %s\n", ++ self_coid, strerror(errno)); ++ } ++ } ++ } ++ ++ ret = ChannelDestroy(chid); ++ if (ret == -1) { ++ metal_log(METAL_LOG_ERROR, ++ "Failed to destroy channel: chid %d: %s\n", ++ chid, strerror(errno)); ++ } ++ ++ ret = pthread_join(irq_pthread, NULL); ++ if (ret != EOK) { ++ metal_log(METAL_LOG_ERROR, ++ "Failed to join IRQ thread: %d.\n", ret); ++ } ++} +diff --git a/lib/system/qnx/irq.h b/lib/system/qnx/irq.h +new file mode 100644 +index 0000000..1db270b +--- /dev/null ++++ b/lib/system/qnx/irq.h +@@ -0,0 +1,45 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/irq.h ++ * @brief QNX libmetal irq definitions. ++ */ ++ ++#ifndef __METAL_IRQ__H__ ++#error "Include metal/irq.h instead of metal/qnx/irq.h" ++#endif ++ ++#ifndef __METAL_QNX_IRQ__H__ ++#ifdef METAL_INTERNAL ++ ++#include ++ ++#endif /* METAL_INTERNAL */ ++#define __METAL_QNX_IRQ__H__ ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#ifdef METAL_INTERNAL ++/** ++ * @brief irq initialization ++ */ ++int metal_qnx_irq_init(void); ++ ++/** ++ * @brief irq shutdown ++ */ ++void metal_qnx_irq_shutdown(void); ++ ++#endif /* METAL_INTERNAL */ ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* __METAL_QNX_IRQ__H__ */ +diff --git a/lib/system/qnx/log.h b/lib/system/qnx/log.h +new file mode 100644 +index 0000000..4ae4a73 +--- /dev/null ++++ b/lib/system/qnx/log.h +@@ -0,0 +1,27 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/log.h ++ * @brief QNX libmetal log handler definition. ++ */ ++ ++#ifndef __METAL_METAL_LOG__H__ ++#error "Include metal/log.h instead of metal/qnx/log.h" ++#endif ++ ++#ifndef __METAL_QNX_LOG__H__ ++#define __METAL_QNX_LOG__H__ ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* __METAL_QNX_LOG__H__ */ +diff --git a/lib/system/qnx/mutex.h b/lib/system/qnx/mutex.h +new file mode 100644 +index 0000000..df28511 +--- /dev/null ++++ b/lib/system/qnx/mutex.h +@@ -0,0 +1,74 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/mutex.h ++ * @brief QNX mutex primitives for libmetal. ++ */ ++ ++#ifndef __METAL_MUTEX__H__ ++#error "Include metal/mutex.h instead of metal/qnx/mutex.h" ++#endif ++ ++#ifndef __METAL_QNX_MUTEX__H__ ++#define __METAL_QNX_MUTEX__H__ ++ ++#include ++#include ++#include ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++typedef pthread_mutex_t metal_mutex_t; ++ ++/* ++ * METAL_MUTEX_DEFINE - used for defining and initializing a global or ++ * static singleton mutex ++ */ ++#define METAL_MUTEX_DEFINE(m) metal_mutex_t m = PTHREAD_MUTEX_INITIALIZER ++ ++static inline void __metal_mutex_init(metal_mutex_t *mutex) ++{ ++ metal_assert(pthread_mutex_init(mutex, NULL) == EOK); ++} ++ ++static inline void __metal_mutex_deinit(metal_mutex_t *mutex) ++{ ++ metal_assert(pthread_mutex_destroy(mutex) == EOK); ++} ++ ++static inline int __metal_mutex_try_acquire(metal_mutex_t *mutex) ++{ ++ int ret = pthread_mutex_trylock(mutex); ++ ++ return (ret != EOK) ? 0 : 1; ++} ++ ++static inline void __metal_mutex_acquire(metal_mutex_t *mutex) ++{ ++ metal_assert(pthread_mutex_lock(mutex) == EOK); ++} ++ ++static inline void __metal_mutex_release(metal_mutex_t *mutex) ++{ ++ metal_assert(pthread_mutex_unlock(mutex) == EOK); ++} ++ ++static inline int __metal_mutex_is_acquired(metal_mutex_t *mutex) ++{ ++ int ret = pthread_mutex_trylock(mutex); ++ ++ return (ret == EBUSY) ? 1 : 0; ++} ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* __METAL_QNX_MUTEX__H__ */ +diff --git a/lib/system/qnx/shmem.c b/lib/system/qnx/shmem.c +new file mode 100644 +index 0000000..92fb336 +--- /dev/null ++++ b/lib/system/qnx/shmem.c +@@ -0,0 +1,103 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/shmem.c ++ * @brief QNX libmetal shared memory handling. ++ */ ++ ++#include ++#include ++ ++static void metal_shmem_io_close(struct metal_io_region *io) ++{ ++ metal_unmap(io->virt, io->size); ++ free((void *)io->physmap); ++} ++ ++static const struct metal_io_ops metal_shmem_io_ops = { ++ NULL, NULL, NULL, NULL, NULL, metal_shmem_io_close, NULL, NULL ++}; ++ ++static int metal_shmem_try_map(int fd, size_t size, struct metal_io_region **result) ++{ ++ size_t pages, page, phys_size; ++ struct metal_io_region *io; ++ metal_phys_addr_t *phys; ++ void *mem; ++ uint32_t *virt; ++ int ret; ++ ++ size = metal_align_up(size, _metal.page_size); ++ pages = size / _metal.page_size; ++ ++ ret = metal_map(fd, 0, size, 1, 0, &mem); ++ if (ret) { ++ metal_log(METAL_LOG_WARNING, ++ "failed to mmap shmem %ld - %s\n", ++ size, strerror(-ret)); ++ return ret; ++ } ++ ++ ret = mlock(mem, size); ++ if (ret == -1) { ++ metal_log(METAL_LOG_WARNING, "failed to mlock shmem - %s\n", ++ strerror(errno)); ++ } ++ ++ phys_size = sizeof(*phys) * pages; ++ phys = malloc(phys_size); ++ if (!phys) { ++ metal_unmap(mem, size); ++ return -ENOMEM; ++ } ++ io = malloc(sizeof(*io)); ++ if (!io) { ++ free(phys); ++ metal_unmap(mem, size); ++ return -ENOMEM; ++ } ++ ++ for (virt = mem, page = 0; page < pages; ++page) { ++ size_t offset = page * _metal.page_size; ++ ++ ret = mem_offset64(virt + offset, NOFD, size, (off64_t *)&phys[page], NULL); ++ if (ret) { ++ phys[page] = METAL_BAD_OFFSET; ++ } ++ } ++ metal_io_init(io, mem, phys, size, _metal.page_shift, 0, ++ &metal_shmem_io_ops); ++ *result = io; ++ ++ return 0; ++} ++ ++int metal_shmem_open(const char *name, size_t size, ++ struct metal_io_region **result) ++{ ++ int ret, fd; ++ ++ ret = metal_shmem_open_generic(name, size, result); ++ if (ret != -ENOENT) ++ return ret; ++ ++ ret = metal_open(name, 1); ++ if (ret < 0) { ++ metal_log(METAL_LOG_ERROR, "failed to open %s\n shmem", name); ++ return ret; ++ } ++ fd = ret; ++ ++ ret = metal_shmem_try_map(fd, size, result); ++ if (ret) { ++ metal_log(METAL_LOG_ERROR, "failed to map %s shmem\n", name); ++ return ret; ++ } ++ ++ close(fd); ++ return 0; ++} +diff --git a/lib/system/qnx/sleep.h b/lib/system/qnx/sleep.h +new file mode 100644 +index 0000000..57ac6bf +--- /dev/null ++++ b/lib/system/qnx/sleep.h +@@ -0,0 +1,40 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/sleep.h ++ * @brief QNX sleep primitives for libmetal. ++ */ ++ ++#ifndef __METAL_SLEEP__H__ ++#error "Include metal/sleep.h instead of metal/qnx/sleep.h" ++#endif ++ ++#ifndef __METAL_QNX_SLEEP__H__ ++#define __METAL_QNX_SLEEP__H__ ++ ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#define NS_PER_US (1000) ++ ++static inline int __metal_sleep_usec(unsigned int usec) ++{ ++ struct timespec ts; ++ ++ ts.tv_sec = 0; ++ ts.tv_nsec = usec * NS_PER_US; ++ return nanosleep(&ts, NULL); ++} ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* __METAL_QNX_SLEEP__H__ */ +diff --git a/lib/system/qnx/sys.h b/lib/system/qnx/sys.h +new file mode 100644 +index 0000000..d37bd61 +--- /dev/null ++++ b/lib/system/qnx/sys.h +@@ -0,0 +1,76 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/sys.h ++ * @brief QNX system primitives for libmetal. ++ */ ++ ++#ifndef __METAL_SYS__H__ ++#error "Include metal/sys.h instead of metal/qnx/sys.h" ++#endif ++ ++#ifndef __METAL_QNX_SYS__H__ ++#define __METAL_QNX_SYS__H__ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#define METAL_INVALID_VADDR NULL ++ ++#define metal_yield() metal_cpu_yield() ++ ++#define __qnx_get_physical_address(addr, len) ({ \ ++ off64_t qnx_offset = 0; \ ++ metal_assert(mem_offset64(addr, NOFD, len, &qnx_offset, NULL) == 0); \ ++ (qnx_offset); \ ++}) ++ ++struct metal_device; ++ ++/** Structure of qnx specific libmetal runtime state. */ ++struct metal_state { ++ ++ /** Common (system independent) data. */ ++ struct metal_common_state common; ++ ++ /** system page size. */ ++ unsigned long page_size; ++ ++ /** system page shift. */ ++ unsigned long page_shift; ++}; ++ ++#ifdef METAL_INTERNAL ++extern int metal_open(const char *path, int shm); ++extern int metal_map(int fd, off_t offset, size_t size, int expand, ++ int flags, void **result); ++extern int metal_unmap(void *mem, size_t size); ++ ++#endif /* METAL_INTERNAL */ ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* __METAL_QNX_SYS__H__ */ +diff --git a/lib/system/qnx/time.c b/lib/system/qnx/time.c +new file mode 100644 +index 0000000..fb8e6b0 +--- /dev/null ++++ b/lib/system/qnx/time.c +@@ -0,0 +1,30 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file qnx/time.c ++ * @brief QNX libmetal time handling. ++ */ ++ ++#include ++#include ++ ++#define NS_PER_S (1000 * 1000 * 1000) ++ ++unsigned long long metal_get_timestamp(void) ++{ ++ unsigned long long time = 0; ++ ++ struct timespec ts; ++ if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1) { ++ metal_log(METAL_LOG_ERROR, "%s failed!\n", __func__); ++ return time; ++ } ++ time = ts.tv_sec * NS_PER_S; ++ time += ts.tv_nsec; ++ return 0; ++} ++ +diff --git a/lib/system/qnx/utilities.c b/lib/system/qnx/utilities.c +new file mode 100644 +index 0000000..aaff606 +--- /dev/null ++++ b/lib/system/qnx/utilities.c +@@ -0,0 +1,98 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++/* ++ * @file utilities.c ++ * @brief QNX libmetal utility functions. ++ */ ++ ++#include ++#include ++#include ++ ++/** ++ * @brief Open (or create) a file. ++ * ++ * This function opens or creates a file with read/write permissions and the ++ * O_CLOEXEC flag set. ++ * ++ * @param[in] path File path to open. ++ * @param[in] shm Open shared memory (via shm_open) if non-zero. ++ * @return File descriptor. ++ */ ++int metal_open(const char *path, int shm) ++{ ++ const int flags = O_RDWR | O_CREAT | O_CLOEXEC; ++ const int mode = S_IRUSR | S_IWUSR; ++ int fd; ++ ++ if (!path || !strlen(path)) ++ return -EINVAL; ++ ++ fd = shm ? shm_open(path, flags, mode) : open(path, flags, mode); ++ return fd == -1 ? -errno : fd; ++} ++ ++/** ++ * @brief Map a segment of a file/device. ++ * ++ * This function maps a segment of a file or device into the process address ++ * space, after optionally expanding the file if necessary. If required, the ++ * file is expanded to hold the requested map area. This is done under and ++ * advisory lock, and therefore the called must not have an advisory lock on ++ * the file being mmapped. ++ * ++ * @param[in] fd File descriptor to map. ++ * @param[in] offset Offset in file to map. ++ * @param[in] size Size of region to map. ++ * @param[in] expand Allow file expansion via ftruncate if non-zero. ++ * @param[in] flags Flags for mmap(), MAP_SHARED included implicitly. ++ * @param[out] result Returned pointer to new memory map. ++ * @return 0 on success, or -errno on error. ++ */ ++int metal_map(int fd, off_t offset, size_t size, int expand, int flags, ++ void **result) ++{ ++ int prot = PROT_READ | PROT_WRITE, error = 0; ++ void *mem; ++ ++ flags |= MAP_SHARED; ++ ++ if (fd < 0) { ++ fd = NOFD; ++ flags = MAP_PRIVATE | MAP_ANON; ++ } else if (expand) { ++ off_t reqsize = offset + size; ++ struct stat stat; ++ ++ if (!error) ++ error = fstat(fd, &stat); ++ if (!error && stat.st_size < reqsize) ++ error = ftruncate(fd, reqsize); ++ if (error) ++ return -errno; ++ } ++ ++ mem = mmap(NULL, size, prot, flags, fd, offset); ++ if (mem == MAP_FAILED) ++ return -errno; ++ *result = mem; ++ return 0; ++} ++ ++/** ++ * @brief Unmap a segment of the process address space. ++ * ++ * This function unmaps a segment of the process address space. ++ * ++ * @param[in] mem Segment to unmap. ++ * @param[in] size Size of region to unmap. ++ * @return 0 on success, or -errno on error. ++ */ ++int metal_unmap(void *mem, size_t size) ++{ ++ return munmap(mem, size) != 0 ? -errno : 0; ++} +diff --git a/test/system/qnx/CMakeLists.txt b/test/system/qnx/CMakeLists.txt +new file mode 100644 +index 0000000..0e2f927 +--- /dev/null ++++ b/test/system/qnx/CMakeLists.txt +@@ -0,0 +1,13 @@ ++collect (PROJECT_LIB_TESTS main.c) ++collect (PROJECT_LIB_TESTS atomic.c) ++collect (PROJECT_LIB_TESTS mutex.c) ++collect (PROJECT_LIB_TESTS shmem.c) ++collect (PROJECT_LIB_TESTS condition.c) ++collect (PROJECT_LIB_TESTS threads.c) ++collect (PROJECT_LIB_TESTS spinlock.c) ++collect (PROJECT_LIB_TESTS alloc.c) ++collect (PROJECT_LIB_TESTS irq.c) ++ ++if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_MACHINE}) ++ add_subdirectory(${PROJECT_MACHINE}) ++endif (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_MACHINE}) +diff --git a/test/system/qnx/alloc.c b/test/system/qnx/alloc.c +new file mode 100644 +index 0000000..d347eaf +--- /dev/null ++++ b/test/system/qnx/alloc.c +@@ -0,0 +1,28 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++#include ++ ++#include "metal-test.h" ++#include ++#include ++ ++static int alloc(void) ++{ ++ void *ptr; ++ ++ ptr = metal_allocate_memory(400); ++ if (!ptr) { ++ metal_log(METAL_LOG_DEBUG, "failed to allocate memory\n"); ++ return -errno; ++ } ++ ++ metal_free_memory(ptr); ++ ++ return 0; ++} ++ ++METAL_ADD_TEST(alloc); +diff --git a/test/system/qnx/atomic.c b/test/system/qnx/atomic.c +new file mode 100644 +index 0000000..cfbee58 +--- /dev/null ++++ b/test/system/qnx/atomic.c +@@ -0,0 +1,47 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++#include ++#include ++ ++#include "metal-test.h" ++#include ++#include ++ ++static const int atomic_test_count = 1000; ++ ++static void *atomic_thread(void *arg) ++{ ++ atomic_int *c = arg; ++ int i; ++ ++ for (i = 0; i < atomic_test_count; ++i) ++ atomic_fetch_add(c, 1); ++ ++ return NULL; ++} ++ ++static int atomic(void) ++{ ++ const int threads = 10; ++ atomic_int counter = ATOMIC_VAR_INIT(0); ++ int value, error; ++ ++ error = metal_run(threads, atomic_thread, &counter); ++ if (!error) { ++ value = atomic_load(&counter); ++ value -= atomic_test_count * threads; ++ if (value) { ++ metal_log(METAL_LOG_DEBUG, "counter mismatch, delta = %d\n", ++ value); ++ error = -EINVAL; ++ } ++ } ++ ++ return error; ++} ++ ++METAL_ADD_TEST(atomic); +diff --git a/test/system/qnx/condition.c b/test/system/qnx/condition.c +new file mode 100644 +index 0000000..81a087a +--- /dev/null ++++ b/test/system/qnx/condition.c +@@ -0,0 +1,101 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++#include ++ ++#include "metal-test.h" ++#include ++#include ++#include ++#include ++ ++#define COUNTER_MAX 10 ++ ++#define THREADS 10 ++ ++METAL_MUTEX_DEFINE(lock); ++static struct metal_condition nempty_condv = METAL_CONDITION_INIT; ++static struct metal_condition nfull_condv = METAL_CONDITION_INIT; ++static unsigned int counter; ++ ++static void *consumer_thread(void *arg) ++{ ++ (void)arg; ++ metal_mutex_acquire(&lock); ++ while (!counter) ++ metal_condition_wait(&nempty_condv, &lock); ++ counter--; ++ metal_condition_signal(&nfull_condv); ++ metal_mutex_release(&lock); ++ ++ return NULL; ++} ++ ++static void *producer_thread(void *arg) ++{ ++ (void)arg; ++ metal_mutex_acquire(&lock); ++ while (counter == COUNTER_MAX) ++ metal_condition_wait(&nfull_condv, &lock); ++ counter++; ++ metal_condition_signal(&nempty_condv); ++ metal_mutex_release(&lock); ++ ++ return NULL; ++} ++ ++static int condition(void) ++{ ++ int ret; ++ int ts_created; ++ pthread_t tids[THREADS]; ++ ++ /** TC1 consumer threads go first */ ++ /** create 10 consumer threads first */ ++ ret = metal_run_noblock(THREADS, consumer_thread, NULL, tids, ++ &ts_created); ++ if (ret < 0) { ++ metal_log(METAL_LOG_ERROR, "Failed to create consumer thread: %d.\n", ++ ret); ++ goto out; ++ } ++ ++ /** create 10 producer threads next */ ++ ret = metal_run(THREADS, producer_thread, NULL); ++ if (ret < 0) { ++ metal_log(METAL_LOG_ERROR, "Failed to create producer thread: %d.\n", ++ ret); ++ goto out; ++ } ++ ++ /** wait for consumer threads to finish */ ++ metal_finish_threads(THREADS, (void *)tids); ++ ++ /** TC2 producer threads go first */ ++ /** create 10 producer threads first */ ++ ret = metal_run_noblock(THREADS, producer_thread, NULL, tids, ++ &ts_created); ++ if (ret < 0) { ++ metal_log(METAL_LOG_ERROR, "Failed to create consumer thread: %d.\n", ++ ret); ++ goto out; ++ } ++ ++ /** create 10 consumer threads next */ ++ ret = metal_run(THREADS, consumer_thread, NULL); ++ if (ret < 0) { ++ metal_log(METAL_LOG_ERROR, "Failed to create producer thread: %d.\n", ++ ret); ++ goto out; ++ } ++ ++out: ++ /** wait for producer threads to finish */ ++ metal_finish_threads(THREADS, (void *)tids); ++ return ret; ++} ++ ++METAL_ADD_TEST(condition); +diff --git a/test/system/qnx/irq.c b/test/system/qnx/irq.c +new file mode 100644 +index 0000000..cb253c8 +--- /dev/null ++++ b/test/system/qnx/irq.c +@@ -0,0 +1,68 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++#include ++#include ++ ++#define METAL_INTERNAL ++ ++#include "metal-test.h" ++#include ++#include ++#include ++#define BASE 1000 ++ ++static int irq_handler(int irq, void *arg) ++{ ++ (void)irq; ++ (void)arg; ++ ++ return 0; ++} ++ ++static int irq(void) ++{ ++ int ret; ++ char *err_msg = ""; ++ enum metal_log_level mll = metal_get_log_level(); ++ int i, tst_irq[2]; ++ ++ for (i = 0; i < 2; i++) { ++ tst_irq[i] = BASE + i; ++ metal_log(METAL_LOG_DEBUG, "%s: %d interrupt associated with irq %d\n", ++ __func__, i, tst_irq[i]); ++ } ++ ++ ret = metal_irq_register(tst_irq[0], irq_handler, (void *)1); ++ if (ret) { ++ err_msg = "register irq 0 fail drv_id\n"; ++ goto out; ++ } ++ ret = metal_irq_register(tst_irq[1], irq_handler, (void *)1); ++ if (ret) { ++ err_msg = "register irq 1 fail drv_id\n"; ++ goto out; ++ } ++ ++ metal_irq_unregister(tst_irq[0]); ++ ret = metal_irq_register(tst_irq[0], irq_handler, (void *)1); ++ if (ret) { ++ err_msg = "register irq 0 fail drv_id\n"; ++ goto out; ++ } ++ metal_irq_unregister(tst_irq[0]); ++ metal_irq_unregister(tst_irq[1]); ++ ++out: ++ metal_set_log_level(mll); ++ if ((err_msg[0] != '\0') && !ret) ++ ret = -EINVAL; ++ if (ret) ++ metal_log(METAL_LOG_DEBUG, "%s", err_msg); ++ return ret; ++} ++ ++METAL_ADD_TEST(irq); +diff --git a/test/system/qnx/main.c b/test/system/qnx/main.c +new file mode 100644 +index 0000000..d340bb2 +--- /dev/null ++++ b/test/system/qnx/main.c +@@ -0,0 +1,16 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++#include "metal-test.h" ++ ++int main(void) ++{ ++ int status; ++ ++ status = metal_tests_run(NULL); ++ ++ return status; ++} +diff --git a/test/system/qnx/mutex.c b/test/system/qnx/mutex.c +new file mode 100644 +index 0000000..4149a2d +--- /dev/null ++++ b/test/system/qnx/mutex.c +@@ -0,0 +1,44 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++#include ++ ++#include "metal-test.h" ++#include ++#include ++ ++static const int mutex_test_count = 1000; ++ ++static void *mutex_thread(void *arg) ++{ ++ metal_mutex_t *l = arg; ++ int i; ++ ++ for (i = 0; i < mutex_test_count; i++) { ++ metal_mutex_acquire(l); ++ usleep(1); ++ metal_mutex_release(l); ++ } ++ ++ return NULL; ++} ++ ++static int mutex(void) ++{ ++ metal_mutex_t lock; ++ const int threads = 10; ++ int error; ++ ++ metal_mutex_init(&lock); ++ ++ error = metal_run(threads, mutex_thread, &lock); ++ ++ metal_mutex_deinit(&lock); ++ ++ return error; ++} ++ ++METAL_ADD_TEST(mutex); +diff --git a/test/system/qnx/rpi4/CMakeLists.txt b/test/system/qnx/rpi4/CMakeLists.txt +new file mode 100644 +index 0000000..12ce3e4 +--- /dev/null ++++ b/test/system/qnx/rpi4/CMakeLists.txt +@@ -0,0 +1 @@ ++collect (PROJECT_LIB_TESTS device.c) +diff --git a/test/system/qnx/rpi4/device.c b/test/system/qnx/rpi4/device.c +new file mode 100644 +index 0000000..215f2ce +--- /dev/null ++++ b/test/system/qnx/rpi4/device.c +@@ -0,0 +1,83 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++#include ++ ++#include "metal-test.h" ++#include ++ ++#define SHM_NAME "shmram" ++#define SHM_BASE_ADDR 0x3b3f0000 ++#define EXPECTED_ID 0x10 ++ ++#define DEFAULT_PAGE_SHIFT 12 ++#define DEFAULT_PAGE_MASK (-1UL) ++ ++const metal_phys_addr_t shm_phys = { ++ SHM_BASE_ADDR, ++}; ++ ++static struct metal_device metal_dev_table[] = { ++ { ++ /* Shared memory device */ ++ .name = "shmram", ++ .bus = NULL, ++ .num_regions = 1, ++ .regions = { ++ { ++ .virt = NULL, ++ .physmap = &shm_phys, ++ .size = 0x10000, ++ .page_shift = DEFAULT_PAGE_SHIFT, ++ .page_mask = DEFAULT_PAGE_MASK, ++ .mem_flags = MAP_SHARED, ++ .ops = {NULL}, ++ }, ++ }, ++ .node = {NULL}, ++ .irq_num = 0, ++ .irq_info = NULL, ++ }, ++}; ++ ++static int device(void) ++{ ++ struct metal_device *shm_dev = &metal_dev_table[0]; ++ struct metal_io_region *io; ++ int error, idcode; ++ ++ error = metal_register_generic_device(shm_dev); ++ if (error) { ++ metal_log(METAL_LOG_DEBUG, "device registration failed - %s\n", ++ strerror(-error)); ++ return error; ++ } ++ ++ error = metal_device_open("generic", "shmram", &shm_dev); ++ if (error) { ++ metal_log(METAL_LOG_DEBUG, "device opening failed - %s\n", ++ strerror(-errno)); ++ return error; ++ } ++ ++ io = metal_device_io_region(shm_dev, 0); ++ if (!io) { ++ metal_device_close(shm_dev); ++ return -ENODEV; ++ } ++ ++ idcode = metal_io_read32(io, 0); ++ if (idcode != EXPECTED_ID) { ++ metal_log(METAL_LOG_DEBUG, "Read id code %d but expected %d\n", ++ idcode, EXPECTED_ID); ++ } ++ ++ metal_device_close(shm_dev); ++ ++ return 0; ++} ++ ++METAL_ADD_TEST(device) +diff --git a/test/system/qnx/shmem.c b/test/system/qnx/shmem.c +new file mode 100644 +index 0000000..7cb1a0b +--- /dev/null ++++ b/test/system/qnx/shmem.c +@@ -0,0 +1,58 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++#include "metal-test.h" ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++static atomic_int nb_err = ATOMIC_VAR_INIT(0); ++ ++static const int shmem_threads = 10; ++ ++static void *shmem_child(void *arg) ++{ ++ const char *name = arg; ++ void *virt; ++ struct metal_io_region *io; ++ unsigned long phys; ++ size_t size = 1 * 1024 * 1024; ++ int error; ++ ++ error = metal_shmem_open(name, size, &io); ++ if (error) { ++ metal_log(METAL_LOG_ERROR, "Failed shmem_open: %d.\n", error); ++ atomic_fetch_add(&nb_err, 1); ++ return NULL; ++ } ++ ++ virt = metal_io_virt(io, 0); ++ phys = metal_io_phys(io, 0); ++ if (phys != METAL_BAD_OFFSET) { ++ if (virt != metal_io_phys_to_virt(io, phys)) { ++ atomic_fetch_add(&nb_err, 1); ++ metal_log(METAL_LOG_ERROR, "Failed virt != phys.\n"); ++ } ++ if (phys != metal_io_virt_to_phys(io, virt)) { ++ atomic_fetch_add(&nb_err, 1); ++ metal_log(METAL_LOG_ERROR, "Failed phys != virt.\n"); ++ } ++ } ++ ++ metal_io_finish(io); ++ return NULL; ++} ++ ++static int shmem(void) ++{ ++ return atomic_load(&nb_err) || metal_run(shmem_threads, shmem_child, "/foo"); ++} ++ ++METAL_ADD_TEST(shmem); +diff --git a/test/system/qnx/spinlock.c b/test/system/qnx/spinlock.c +new file mode 100644 +index 0000000..0856867 +--- /dev/null ++++ b/test/system/qnx/spinlock.c +@@ -0,0 +1,50 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++#include ++ ++#include "metal-test.h" ++#include ++#include ++ ++static const int spinlock_test_count = 1000; ++static unsigned int total; ++ ++static void *spinlock_thread(void *arg) ++{ ++ struct metal_spinlock *l = arg; ++ int i; ++ ++ for (i = 0; i < spinlock_test_count; ++i) { ++ metal_spinlock_acquire(l); ++ total++; ++ metal_spinlock_release(l); ++ } ++ ++ return NULL; ++} ++ ++static int spinlock(void) ++{ ++ struct metal_spinlock lock = METAL_SPINLOCK_INIT; ++ const int threads = 10; ++ int value, error; ++ ++ error = metal_run(threads, spinlock_thread, &lock); ++ if (!error) { ++ value = total; ++ value -= spinlock_test_count * threads; ++ if (value) { ++ metal_log(METAL_LOG_DEBUG, "counter mismatch, detla = %d\n", ++ value); ++ error = -EINVAL; ++ } ++ } ++ ++ return error; ++} ++ ++METAL_ADD_TEST(spinlock); +diff --git a/test/system/qnx/threads.c b/test/system/qnx/threads.c +new file mode 100644 +index 0000000..22a1362 +--- /dev/null ++++ b/test/system/qnx/threads.c +@@ -0,0 +1,61 @@ ++/* ++ * Copyright (c) 2025, BlackBerry Limited. All rights reserved. ++ * ++ * SPDX-License-Identifier: BSD-3-Clause ++ */ ++ ++#include ++#include ++#include ++#include "metal-test.h" ++ ++int metal_run(int threads, metal_thread_t child, void *arg) ++{ ++ pthread_t tids[threads]; ++ int error, ts_created; ++ ++ error = metal_run_noblock(threads, child, arg, tids, &ts_created); ++ ++ metal_finish_threads(ts_created, (void *)tids); ++ ++ return error; ++} ++ ++int metal_run_noblock(int threads, metal_thread_t child, ++ void *arg, void *tids, int *threads_out) ++{ ++ int error, i; ++ pthread_t *tid_p = (pthread_t *)tids; ++ ++ if (!tids) { ++ metal_log(METAL_LOG_ERROR, "invalid argument, tids is NULL.\n"); ++ return -EINVAL; ++ } ++ ++ error = 0; ++ for (i = 0; i < threads; ++i) { ++ error = pthread_create(&tid_p[i], NULL, child, arg); ++ if (error != EOK) { ++ metal_log(METAL_LOG_ERROR, "failed to create thread - %s\n", ++ strerror(error)); ++ break; ++ } ++ } ++ ++ *threads_out = i; ++ return -error; ++} ++ ++void metal_finish_threads(int threads, void *tids) ++{ ++ int i; ++ pthread_t *tid_p = (pthread_t *)tids; ++ ++ if (!tids) { ++ metal_log(METAL_LOG_ERROR, "invalid argument, tids is NULL.\n"); ++ return; ++ } ++ ++ for (i = 0; i < threads; ++i) ++ (void)pthread_join(tid_p[i], NULL); ++} diff --git a/extra/libmetal/APKBUILD b/extra/libmetal/APKBUILD new file mode 100644 index 0000000..9f67c8e --- /dev/null +++ b/extra/libmetal/APKBUILD @@ -0,0 +1,48 @@ +# Maintainer: Deep Chordia +pkgname=libmetal +pkgver=2025.10.1 +pkgrel=0 +pkgdesc="An abstraction layer across RTOS, baremetal, and user-space Linux environments" +url="https://github.com/OpenAMP/libmetal" +arch="all" +license="BSD-3-Clause" +makedepends=" + cmake + ninja" +subpackages="$pkgname-dev" +source="$pkgname-$pkgver.tar.gz::https://github.com/OpenAMP/libmetal/archive/refs/tags/v$pkgver.tar.gz + 0001-add-qnx-support.patch" + +build() { + if [ -f "/usr/include/sys/cache.h" ]; then + CFLAGS="$CFLAGS -DQNX_CACHE" + WITH_CACHE="-DQNX_CACHE=ON" + else + WITH_CACHE="-DQNX_CACHE=OFF" + fi + CFLAGS="$CFLAGS -D__EXT_UNIX_SOURCE -Wno-unused-parameter -Wno-deprecated-pragma" \ + cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DWITH_STATIC_LIB=OFF \ + -DWITH_SHARED_LIB=ON \ + -DWITH_TESTS=ON \ + -DWITH_EXAMPLES=ON \ + -DMACHINE=qemu \ + -DQNX=1 \ + $WITH_CACHE + cmake --build build +} + +check() { + ctest --test-dir build +} + +package() { + DESTDIR="$pkgdir" cmake --install build +} + +sha512sums=" +5227b18821525e870ac0cb925e56ae6837bcd626ffb97d7f8da0998a25330a3829d76ec4f6596713c6634553bbeda29382f2642a9facca5a06ed74094bfd51d0 libmetal-2025.10.1.tar.gz +f7f132e1e0bc11696463f63fe0e20e77e9e14d61b9d07e437d2ed98952d50cf71faec1d2e2df92c7ffcf0065b73d84214e88a1b861d13d90d676302169982d82 0001-add-qnx-support.patch +"