Introduce the freestanding C17 vocabulary, exact-width kernel types, alignment helpers, compiler built-ins, memory primitives, and the initial task/thread platform layer. Preserve hardware-specific assembly outside this portable layer.
Signed-off-by: Breno Rodrigues Alves <[email protected]> --- modern/Makefile | 57 +++ modern/freestanding/limits.h | 29 ++ modern/freestanding/stdalign.h | 14 + modern/freestanding/stdarg.h | 17 + modern/freestanding/stdatomic.h | 138 +++++++ modern/freestanding/stdbool.h | 14 + modern/freestanding/stddef.h | 17 + modern/freestanding/stdint.h | 99 +++++ modern/freestanding/stdnoreturn.h | 11 + modern/freestanding/time.h | 23 ++ modern/include/mach/c17/base.h | 333 +++++++++++++++++ modern/include/threads.h | 104 ++++++ modern/src/core/mem.c | 140 +++++++ modern/src/core/threads.c | 594 ++++++++++++++++++++++++++++++ modern/tests/fake_hw.c | 220 +++++++++++ modern/tests/fake_hw.h | 50 +++ modern/tests/test.h | 51 +++ modern/tests/test_threads.c | 99 +++++ 18 files changed, 2010 insertions(+) create mode 100644 modern/Makefile create mode 100644 modern/freestanding/limits.h create mode 100644 modern/freestanding/stdalign.h create mode 100644 modern/freestanding/stdarg.h create mode 100644 modern/freestanding/stdatomic.h create mode 100644 modern/freestanding/stdbool.h create mode 100644 modern/freestanding/stddef.h create mode 100644 modern/freestanding/stdint.h create mode 100644 modern/freestanding/stdnoreturn.h create mode 100644 modern/freestanding/time.h create mode 100644 modern/include/mach/c17/base.h create mode 100644 modern/include/threads.h create mode 100644 modern/src/core/mem.c create mode 100644 modern/src/core/threads.c create mode 100644 modern/tests/fake_hw.c create mode 100644 modern/tests/fake_hw.h create mode 100644 modern/tests/test.h create mode 100644 modern/tests/test_threads.c diff --git a/modern/Makefile b/modern/Makefile new file mode 100644 index 00000000..5a3776b3 --- /dev/null +++ b/modern/Makefile @@ -0,0 +1,57 @@ +# Makefile for the C17 Mach platform layer. +# +# The library is built exactly as required by the project brief: +# +# -std=c17 -Wall -Wextra -Wpedantic -nostdinc -ffreestanding +# +# `-nostdinc` is paired with a small freestanding header set in +# freestanding/ so that no hosted libc header can leak into kernel code. +# Tests are hosted programs and link the same archive. + +CC ?= gcc +AR ?= ar +CFLAGS ?= -O2 -g +WARNINGS = -Wall -Wextra -Wpedantic -Wshadow -Wstrict-prototypes +CSTD = -std=c17 +FREESTANDING = $(CSTD) $(WARNINGS) -nostdinc -ffreestanding +INCLUDES = -Iinclude -isystem freestanding +HOST_INCLUDES = -Iinclude + +BUILD = build +LIB = $(BUILD)/libmachc17.a +LIB_SRCS = $(sort $(wildcard src/*/*.c)) +LIB_OBJS = $(patsubst src/%.c,$(BUILD)/obj/%.o,$(LIB_SRCS)) + +TEST_SRCS = $(sort $(wildcard tests/test_*.c)) +TEST_NONTEST = $(sort $(filter-out tests/test_%.c,$(wildcard tests/*.c))) +TEST_BINS = $(patsubst tests/test_%.c,$(BUILD)/test_%,$(TEST_SRCS)) + +.PHONY: all lib check test clean + +all: lib + +lib: $(LIB) + +$(LIB): $(LIB_OBJS) + @mkdir -p $(dir $@) + $(AR) rcs $@ $^ + +$(BUILD)/obj/%.o: src/%.c + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) $(FREESTANDING) $(INCLUDES) -c $< -o $@ + +$(BUILD)/test_%: tests/test_%.c $(LIB) $(TEST_NONTEST) + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) $(CSTD) $(WARNINGS) $(HOST_INCLUDES) $< $(TEST_NONTEST) \ + $(LIB) -o $@ + +check: $(TEST_BINS) + @set -e; for test in $(TEST_BINS); do \ + echo "running $$test"; \ + $$test; \ + done + +test: check + +clean: + rm -rf $(BUILD) diff --git a/modern/freestanding/limits.h b/modern/freestanding/limits.h new file mode 100644 index 00000000..392997ba --- /dev/null +++ b/modern/freestanding/limits.h @@ -0,0 +1,29 @@ +/* Freestanding <limits.h> for the C17 Mach platform. + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GNU Mach. See stdint.h for the license. */ + +#ifndef _MACH_FREESTANDING_LIMITS_H +#define _MACH_FREESTANDING_LIMITS_H 1 + +#define CHAR_BIT __CHAR_BIT__ +#define SCHAR_MIN (-SCHAR_MAX - 1) +#define SCHAR_MAX __SCHAR_MAX__ +#define UCHAR_MAX (SCHAR_MAX * 2 + 1) +#define CHAR_MIN SCHAR_MIN +#define CHAR_MAX SCHAR_MAX +#define MB_LEN_MAX 1 +#define SHRT_MIN (-SHRT_MAX - 1) +#define SHRT_MAX __SHRT_MAX__ +#define USHRT_MAX (SHRT_MAX * 2 + 1) +#define INT_MIN (-INT_MAX - 1) +#define INT_MAX __INT_MAX__ +#define UINT_MAX (INT_MAX * 2U + 1U) +#define LONG_MIN (-LONG_MAX - 1L) +#define LONG_MAX __LONG_MAX__ +#define ULONG_MAX (LONG_MAX * 2UL + 1UL) +#define LLONG_MIN (-LLONG_MAX - 1LL) +#define LLONG_MAX __LONG_LONG_MAX__ +#define ULLONG_MAX (LLONG_MAX * 2ULL + 1ULL) + +#endif /* _MACH_FREESTANDING_LIMITS_H */ diff --git a/modern/freestanding/stdalign.h b/modern/freestanding/stdalign.h new file mode 100644 index 00000000..453ae62b --- /dev/null +++ b/modern/freestanding/stdalign.h @@ -0,0 +1,14 @@ +/* Freestanding <stdalign.h> for the C17 Mach platform. + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GNU Mach. See stdint.h for the license. */ + +#ifndef _MACH_FREESTANDING_STDALIGN_H +#define _MACH_FREESTANDING_STDALIGN_H 1 + +#define alignas _Alignas +#define alignof _Alignof +#define __alignas_is_defined 1 +#define __alignof_is_defined 1 + +#endif /* _MACH_FREESTANDING_STDALIGN_H */ diff --git a/modern/freestanding/stdarg.h b/modern/freestanding/stdarg.h new file mode 100644 index 00000000..a84d4e79 --- /dev/null +++ b/modern/freestanding/stdarg.h @@ -0,0 +1,17 @@ +/* Freestanding <stdarg.h> for the C17 Mach platform. + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GNU Mach. See stdint.h for the license. */ + +#ifndef _MACH_FREESTANDING_STDARG_H +#define _MACH_FREESTANDING_STDARG_H 1 + +typedef __builtin_va_list va_list; +typedef __builtin_va_list __gnuc_va_list; + +#define va_start(ap, last) __builtin_va_start (ap, last) +#define va_end(ap) __builtin_va_end (ap) +#define va_arg(ap, type) __builtin_va_arg (ap, type) +#define va_copy(dest, src) __builtin_va_copy (dest, src) + +#endif /* _MACH_FREESTANDING_STDARG_H */ diff --git a/modern/freestanding/stdatomic.h b/modern/freestanding/stdatomic.h new file mode 100644 index 00000000..4f435557 --- /dev/null +++ b/modern/freestanding/stdatomic.h @@ -0,0 +1,138 @@ +/* Freestanding <stdatomic.h> for the C17 Mach platform. + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GNU Mach. See stdint.h for the license. + + ISO C17 makes <stdatomic.h> optional in freestanding environments. + GNU Mach nonetheless promises C17 atomics everywhere, so this header + maps the standard names onto the compiler's __atomic builtins. The + mapping is deliberately thin: it keeps the standard spelling in the + kernel while letting the compiler pick the narrowest instruction that + respects the requested ordering. */ + +#ifndef _MACH_FREESTANDING_STDATOMIC_H +#define _MACH_FREESTANDING_STDATOMIC_H 1 + +#include <stdbool.h> +#include <stdint.h> +#include <stddef.h> + +typedef enum +{ + memory_order_relaxed = __ATOMIC_RELAXED, + memory_order_consume = __ATOMIC_CONSUME, + memory_order_acquire = __ATOMIC_ACQUIRE, + memory_order_release = __ATOMIC_RELEASE, + memory_order_acq_rel = __ATOMIC_ACQ_REL, + memory_order_seq_cst = __ATOMIC_SEQ_CST +} memory_order; + +typedef _Atomic _Bool atomic_bool; +typedef _Atomic char atomic_char; +typedef _Atomic signed char atomic_schar; +typedef _Atomic unsigned char atomic_uchar; +typedef _Atomic short atomic_short; +typedef _Atomic unsigned short atomic_ushort; +typedef _Atomic int atomic_int; +typedef _Atomic unsigned int atomic_uint; +typedef _Atomic long atomic_long; +typedef _Atomic unsigned long atomic_ulong; +typedef _Atomic long long atomic_llong; +typedef _Atomic unsigned long long atomic_ullong; +typedef _Atomic intptr_t atomic_intptr_t; +typedef _Atomic uintptr_t atomic_uintptr_t; +typedef _Atomic size_t atomic_size_t; +typedef _Atomic ptrdiff_t atomic_ptrdiff_t; +typedef _Atomic intmax_t atomic_intmax_t; +typedef _Atomic uintmax_t atomic_uintmax_t; + +#define ATOMIC_VAR_INIT(value) (value) +#define ATOMIC_FLAG_INIT { false } + +typedef struct +{ + _Atomic bool value; +} atomic_flag; + +#define atomic_init(obj, value) \ + __atomic_store_n ((obj), (value), __ATOMIC_RELAXED) + +#define atomic_load(obj) \ + __atomic_load_n ((obj), __ATOMIC_SEQ_CST) +#define atomic_load_explicit(obj, order) \ + __atomic_load_n ((obj), (order)) + +#define atomic_store(obj, value) \ + __atomic_store_n ((obj), (value), __ATOMIC_SEQ_CST) +#define atomic_store_explicit(obj, value, order) \ + __atomic_store_n ((obj), (value), (order)) + +#define atomic_exchange(obj, value) \ + __atomic_exchange_n ((obj), (value), __ATOMIC_SEQ_CST) +#define atomic_exchange_explicit(obj, value, order) \ + __atomic_exchange_n ((obj), (value), (order)) + +#define atomic_compare_exchange_strong(obj, expected, desired) \ + __atomic_compare_exchange_n ((obj), (expected), (desired), 0, \ + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) +#define atomic_compare_exchange_weak(obj, expected, desired) \ + __atomic_compare_exchange_n ((obj), (expected), (desired), 1, \ + __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST) +#define atomic_compare_exchange_strong_explicit(obj, expected, desired, \ + success, failure) \ + __atomic_compare_exchange_n ((obj), (expected), (desired), 0, \ + (success), (failure)) +#define atomic_compare_exchange_weak_explicit(obj, expected, desired, \ + success, failure) \ + __atomic_compare_exchange_n ((obj), (expected), (desired), 1, \ + (success), (failure)) + +#define atomic_fetch_add(obj, value) \ + __atomic_fetch_add ((obj), (value), __ATOMIC_SEQ_CST) +#define atomic_fetch_add_explicit(obj, value, order) \ + __atomic_fetch_add ((obj), (value), (order)) +#define atomic_fetch_sub(obj, value) \ + __atomic_fetch_sub ((obj), (value), __ATOMIC_SEQ_CST) +#define atomic_fetch_sub_explicit(obj, value, order) \ + __atomic_fetch_sub ((obj), (value), (order)) +#define atomic_fetch_or(obj, value) \ + __atomic_fetch_or ((obj), (value), __ATOMIC_SEQ_CST) +#define atomic_fetch_or_explicit(obj, value, order) \ + __atomic_fetch_or ((obj), (value), (order)) +#define atomic_fetch_and(obj, value) \ + __atomic_fetch_and ((obj), (value), __ATOMIC_SEQ_CST) +#define atomic_fetch_and_explicit(obj, value, order) \ + __atomic_fetch_and ((obj), (value), (order)) + +#define atomic_thread_fence(order) __atomic_thread_fence (order) +#define atomic_signal_fence(order) __atomic_signal_fence (order) + +static inline bool +atomic_flag_test_and_set (volatile atomic_flag *flag) +{ + return __atomic_exchange_n (&flag->value, true, __ATOMIC_SEQ_CST); +} + +static inline bool +atomic_flag_test_and_set_explicit (volatile atomic_flag *flag, + memory_order order) +{ + return __atomic_exchange_n (&flag->value, true, order); +} + +static inline void +atomic_flag_clear (volatile atomic_flag *flag) +{ + __atomic_store_n (&flag->value, false, __ATOMIC_SEQ_CST); +} + +static inline void +atomic_flag_clear_explicit (volatile atomic_flag *flag, memory_order order) +{ + __atomic_store_n (&flag->value, false, order); +} + +#define atomic_is_lock_free(obj) __atomic_is_lock_free (sizeof (*(obj)), (obj)) +#define kill_dependency(value) (value) + +#endif /* _MACH_FREESTANDING_STDATOMIC_H */ diff --git a/modern/freestanding/stdbool.h b/modern/freestanding/stdbool.h new file mode 100644 index 00000000..469453e8 --- /dev/null +++ b/modern/freestanding/stdbool.h @@ -0,0 +1,14 @@ +/* Freestanding <stdbool.h> for the C17 Mach platform. + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GNU Mach. See stdint.h for the license. */ + +#ifndef _MACH_FREESTANDING_STDBOOL_H +#define _MACH_FREESTANDING_STDBOOL_H 1 + +#define bool _Bool +#define true 1 +#define false 0 +#define __bool_true_false_are_defined 1 + +#endif /* _MACH_FREESTANDING_STDBOOL_H */ diff --git a/modern/freestanding/stddef.h b/modern/freestanding/stddef.h new file mode 100644 index 00000000..35977583 --- /dev/null +++ b/modern/freestanding/stddef.h @@ -0,0 +1,17 @@ +/* Freestanding <stddef.h> for the C17 Mach platform. + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GNU Mach. See stdint.h for the license. */ + +#ifndef _MACH_FREESTANDING_STDDEF_H +#define _MACH_FREESTANDING_STDDEF_H 1 + +typedef __SIZE_TYPE__ size_t; +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef __WCHAR_TYPE__ wchar_t; +typedef long double max_align_t; + +#define NULL ((void *) 0) +#define offsetof(type, member) __builtin_offsetof (type, member) + +#endif /* _MACH_FREESTANDING_STDDEF_H */ diff --git a/modern/freestanding/stdint.h b/modern/freestanding/stdint.h new file mode 100644 index 00000000..c66ad833 --- /dev/null +++ b/modern/freestanding/stdint.h @@ -0,0 +1,99 @@ +/* Freestanding <stdint.h> for the C17 Mach platform. + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GNU Mach. + + GNU Mach is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + GNU Mach is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301, USA. */ + +#ifndef _MACH_FREESTANDING_STDINT_H +#define _MACH_FREESTANDING_STDINT_H 1 + +/* The kernel is compiled with -nostdinc so that no hosted header can + leak into it. GCC still exposes the target's exact-width types + through its predefined macros, which is all ISO C requires here. */ + +typedef __INT8_TYPE__ int8_t; +typedef __INT16_TYPE__ int16_t; +typedef __INT32_TYPE__ int32_t; +typedef __INT64_TYPE__ int64_t; + +typedef __UINT8_TYPE__ uint8_t; +typedef __UINT16_TYPE__ uint16_t; +typedef __UINT32_TYPE__ uint32_t; +typedef __UINT64_TYPE__ uint64_t; + +typedef __INT_LEAST8_TYPE__ int_least8_t; +typedef __INT_LEAST16_TYPE__ int_least16_t; +typedef __INT_LEAST32_TYPE__ int_least32_t; +typedef __INT_LEAST64_TYPE__ int_least64_t; + +typedef __UINT_LEAST8_TYPE__ uint_least8_t; +typedef __UINT_LEAST16_TYPE__ uint_least16_t; +typedef __UINT_LEAST32_TYPE__ uint_least32_t; +typedef __UINT_LEAST64_TYPE__ uint_least64_t; + +typedef __INT_FAST8_TYPE__ int_fast8_t; +typedef __INT_FAST16_TYPE__ int_fast16_t; +typedef __INT_FAST32_TYPE__ int_fast32_t; +typedef __INT_FAST64_TYPE__ int_fast64_t; + +typedef __UINT_FAST8_TYPE__ uint_fast8_t; +typedef __UINT_FAST16_TYPE__ uint_fast16_t; +typedef __UINT_FAST32_TYPE__ uint_fast32_t; +typedef __UINT_FAST64_TYPE__ uint_fast64_t; + +typedef __INTPTR_TYPE__ intptr_t; +typedef __UINTPTR_TYPE__ uintptr_t; +typedef __INTMAX_TYPE__ intmax_t; +typedef __UINTMAX_TYPE__ uintmax_t; + +#define INT8_MIN (-INT8_MAX - 1) +#define INT16_MIN (-INT16_MAX - 1) +#define INT32_MIN (-INT32_MAX - 1) +#define INT64_MIN (-INT64_MAX - 1) + +#define INT8_MAX __INT8_MAX__ +#define INT16_MAX __INT16_MAX__ +#define INT32_MAX __INT32_MAX__ +#define INT64_MAX __INT64_MAX__ + +#define UINT8_MAX __UINT8_MAX__ +#define UINT16_MAX __UINT16_MAX__ +#define UINT32_MAX __UINT32_MAX__ +#define UINT64_MAX __UINT64_MAX__ + +#define INTPTR_MIN (-INTPTR_MAX - 1) +#define INTPTR_MAX __INTPTR_MAX__ +#define UINTPTR_MAX __UINTPTR_MAX__ + +#define INTMAX_MIN (-INTMAX_MAX - 1) +#define INTMAX_MAX __INTMAX_MAX__ +#define UINTMAX_MAX __UINTMAX_MAX__ + +#define SIZE_MAX __SIZE_MAX__ + +#define INT8_C(c) c +#define INT16_C(c) c +#define INT32_C(c) c +#define INT64_C(c) c##L +#define UINT8_C(c) c +#define UINT16_C(c) c +#define UINT32_C(c) c##U +#define UINT64_C(c) c##UL +#define INTMAX_C(c) c##L +#define UINTMAX_C(c) c##UL + +#endif /* _MACH_FREESTANDING_STDINT_H */ diff --git a/modern/freestanding/stdnoreturn.h b/modern/freestanding/stdnoreturn.h new file mode 100644 index 00000000..a7d91921 --- /dev/null +++ b/modern/freestanding/stdnoreturn.h @@ -0,0 +1,11 @@ +/* Freestanding <stdnoreturn.h> for the C17 Mach platform. + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GNU Mach. See stdint.h for the license. */ + +#ifndef _MACH_FREESTANDING_STDNORETURN_H +#define _MACH_FREESTANDING_STDNORETURN_H 1 + +#define noreturn _Noreturn + +#endif /* _MACH_FREESTANDING_STDNORETURN_H */ diff --git a/modern/freestanding/time.h b/modern/freestanding/time.h new file mode 100644 index 00000000..0a088ac7 --- /dev/null +++ b/modern/freestanding/time.h @@ -0,0 +1,23 @@ +/* Freestanding <time.h> for the C17 Mach platform. + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GNU Mach. See stdint.h for the license. */ + +#ifndef _MACH_FREESTANDING_TIME_H +#define _MACH_FREESTANDING_TIME_H 1 + +#include <stdint.h> + +typedef int64_t time_t; +typedef int64_t clock_t; + +struct timespec +{ + time_t tv_sec; + long tv_nsec; +}; + +#define TIME_UTC 1 +#define CLOCKS_PER_SEC 1000000 + +#endif /* _MACH_FREESTANDING_TIME_H */ diff --git a/modern/include/mach/c17/base.h b/modern/include/mach/c17/base.h new file mode 100644 index 00000000..7ee4c1e9 --- /dev/null +++ b/modern/include/mach/c17/base.h @@ -0,0 +1,333 @@ +/* Base definitions for the C17 Mach kernel platform. + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GNU Mach. + + GNU Mach is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. */ + +#ifndef _MACH_C17_BASE_H +#define _MACH_C17_BASE_H 1 + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdalign.h> +#include <limits.h> + +/* --------------------------------------------------------------------- + Compiler vocabulary. + + The kernel is built by GCC with -ffreestanding, so the only portable + vocabulary available is the ISO C17 one plus the attributes below. + Every attribute is spelled once, here, so that the rest of the tree + reads as plain C17. */ + +#define MACH_INLINE static inline +#define MACH_NOINLINE __attribute__ ((noinline)) +#define MACH_NORETURN __attribute__ ((noreturn)) +#define MACH_UNUSED __attribute__ ((unused)) +#define MACH_WARN_UNUSED __attribute__ ((warn_unused_result)) +#define MACH_PACKED __attribute__ ((packed)) +#define MACH_ALIGNAS(value) __attribute__ ((aligned (value))) +#define MACH_ALIGNOF(type) _Alignof (type) + +/* A cache line is the unit of false-sharing avoidance. Structures that + are written by different CPUs are padded to this boundary and the hot + fields are separated by whole lines. */ +#define MACH_CACHELINE 64u +#define MACH_CACHELINE_ALIGN MACH_ALIGNAS (MACH_CACHELINE) + +#define MACH_PAGE_SHIFT 12u +#define MACH_PAGE_SIZE (1u << MACH_PAGE_SHIFT) +#define MACH_PAGE_MASK (MACH_PAGE_SIZE - 1u) + +#define MACH_KB (1024ull) +#define MACH_MB (1024ull * MACH_KB) +#define MACH_GB (1024ull * MACH_MB) + +#define MACH_FALSE 0 +#define MACH_TRUE 1 + +#define MACH_ARRAY_SIZE(array) (sizeof (array) / sizeof ((array)[0])) + +#define MACH_CONTAINER_OF(pointer, type, member) \ + ((type *) ((char *) (pointer) - offsetof (type, member))) + +#define MACH_LIKELY(expr) __builtin_expect (!!(expr), 1) +#define MACH_UNLIKELY(expr) __builtin_expect (!!(expr), 0) +#define MACH_STATIC_ASSERT(condition, message) \ + _Static_assert (condition, message) + +/* --------------------------------------------------------------------- + Machine words and kernel identifiers. */ + +typedef uint64_t mach_phys_t; +typedef uint64_t mach_vaddr_t; +typedef uint64_t mach_vm_size_t; +typedef uint64_t mach_vm_offset_t; +typedef uint64_t mach_time_t; /* Nanoseconds, monotonic. */ +typedef uint32_t mach_cpu_id_t; +typedef uint32_t mach_thread_id_t; +typedef uint32_t mach_task_id_t; +typedef uint32_t mach_port_name_t; +typedef uint32_t mach_msg_size_t; +typedef int32_t mach_msg_id_t; +typedef int32_t mach_priority_t; +typedef uint32_t mach_flags_t; + +#define MACH_PORT_NULL ((mach_port_name_t) 0) +#define MACH_PORT_DEAD ((mach_port_name_t) ~0u) +#define MACH_THREAD_NULL ((mach_thread_id_t) 0) +#define MACH_CPU_NULL ((mach_cpu_id_t) ~0u) + +/* Kernel result codes. Mach traditionally uses kern_return_t; the + modern layer keeps the numeric contract while using an enumerated + type so that -Wpedantic diagnose accidental integer mixing. */ +typedef enum mach_error +{ + MACH_OK = 0, + MACH_EPERM = 1, + MACH_ENOENT = 2, + MACH_EINTR = 4, + MACH_EIO = 5, + MACH_EAGAIN = 11, + MACH_ENOMEM = 12, + MACH_EBUSY = 16, + MACH_ENODEV = 19, + MACH_EINVAL = 22, + MACH_ERANGE = 34, + MACH_ENOSYS = 78, + MACH_ETIMEDOUT = 110 +} mach_error_t; + +/* --------------------------------------------------------------------- + Intrusive lists. + + The scheduler, the VM free lists and the IPC wait queues all need + O(1) insertion and removal without allocating. A single header is + enough for all three and keeps the data structures cache-friendly. */ + +struct mach_list +{ + struct mach_list *next; + struct mach_list *prev; +}; + +MACH_INLINE void +mach_list_init (struct mach_list *head) +{ + head->next = head; + head->prev = head; +} + +MACH_INLINE bool +mach_list_empty (const struct mach_list *head) +{ + return head->next == head; +} + +MACH_INLINE void +mach_list_insert_between (struct mach_list *link, + struct mach_list *prev, + struct mach_list *next) +{ + link->next = next; + link->prev = prev; + prev->next = link; + next->prev = link; +} + +MACH_INLINE void +mach_list_add_tail (struct mach_list *head, struct mach_list *link) +{ + mach_list_insert_between (link, head->prev, head); +} + +MACH_INLINE void +mach_list_add_head (struct mach_list *head, struct mach_list *link) +{ + mach_list_insert_between (link, head, head->next); +} + +MACH_INLINE void +mach_list_remove (struct mach_list *link) +{ + link->prev->next = link->next; + link->next->prev = link->prev; + link->next = link; + link->prev = link; +} + +/* Move every element of SRC to the tail of DST and reinitialise SRC. + Used by the scheduler when it drains a remote run queue. */ +MACH_INLINE void +mach_list_splice_tail (struct mach_list *dst, struct mach_list *src) +{ + struct mach_list *first; + struct mach_list *last; + + if (mach_list_empty (src)) + return; + + first = src->next; + last = src->prev; + first->prev = dst->prev; + dst->prev->next = first; + last->next = dst; + dst->prev = last; + mach_list_init (src); +} + +#define MACH_LIST_ENTRY(link, type, member) \ + MACH_CONTAINER_OF (link, type, member) + +/* --------------------------------------------------------------------- + Bit operations. */ + +MACH_INLINE void +mach_bit_set (volatile uint64_t *word, unsigned int bit) +{ + *word |= (uint64_t) 1 << (bit & 63u); +} + +MACH_INLINE void +mach_bit_clear (volatile uint64_t *word, unsigned int bit) +{ + *word &= ~((uint64_t) 1 << (bit & 63u)); +} + +MACH_INLINE bool +mach_bit_test (const volatile uint64_t *word, unsigned int bit) +{ + return (*word >> (bit & 63u)) & 1u; +} + +MACH_INLINE unsigned int +mach_ffs64 (uint64_t value) +{ + return value == 0 ? 64u : (unsigned int) __builtin_ctzll (value); +} + +MACH_INLINE unsigned int +mach_fls64 (uint64_t value) +{ + return value == 0 ? 0u : 64u - (unsigned int) __builtin_clzll (value); +} + +MACH_INLINE unsigned int +mach_popcount64 (uint64_t value) +{ + return (unsigned int) __builtin_popcountll (value); +} + +/* --------------------------------------------------------------------- + Arithmetic helpers. + + _Generic keeps the call sites free of casts: aligning a size_t, a + physical address or a 32-bit length all use the same spelling, but + the compiler still checks the operand type. */ + +MACH_INLINE size_t +mach_align_up_size (size_t value, size_t alignment) +{ + return (value + alignment - 1u) & ~(alignment - 1u); +} + +MACH_INLINE uint64_t +mach_align_up_u64 (uint64_t value, uint64_t alignment) +{ + return (value + alignment - 1u) & ~(alignment - 1u); +} + +MACH_INLINE uint32_t +mach_align_up_u32 (uint32_t value, uint32_t alignment) +{ + return (value + alignment - 1u) & ~(alignment - 1u); +} + +#define mach_align_up(value, alignment) _Generic ((value), \ + size_t: mach_align_up_size, \ + uint64_t: mach_align_up_u64, \ + uint32_t: mach_align_up_u32, \ + int: mach_align_up_u32) ((value), (alignment)) + +MACH_INLINE uint64_t +mach_align_down_u64 (uint64_t value, uint64_t alignment) +{ + return value & ~(alignment - 1u); +} + +MACH_INLINE uint64_t +mach_div_round_up_u64 (uint64_t value, uint64_t divisor) +{ + return (value + divisor - 1u) / divisor; +} + +MACH_INLINE uint64_t +mach_min_u64 (uint64_t a, uint64_t b) +{ + return a < b ? a : b; +} + +MACH_INLINE uint64_t +mach_max_u64 (uint64_t a, uint64_t b) +{ + return a > b ? a : b; +} + +#define mach_min(a, b) _Generic ((a), \ + uint64_t: mach_min_u64, \ + size_t: mach_min_u64) ((a), (b)) + +#define mach_max(a, b) _Generic ((a), \ + uint64_t: mach_max_u64, \ + size_t: mach_max_u64) ((a), (b)) + +MACH_INLINE uint64_t +mach_log2_floor_u64 (uint64_t value) +{ + return value == 0 ? 0 : (uint64_t) (63 - __builtin_clzll (value)); +} + +MACH_INLINE uint64_t +mach_log2_ceil_u64 (uint64_t value) +{ + return value <= 1 ? 0 : mach_log2_floor_u64 (value - 1) + 1; +} + +/* --------------------------------------------------------------------- + Memory primitives. + + A freestanding compiler is allowed to turn even a trivial loop into a + call to memcpy or memset, so the kernel provides definitions for the + small set of primitives GCC may emit. The public names below are the + ones kernel code should use; the standard spellings live in mem.c. */ + +void *mach_memcpy (void *restrict dest, const void *restrict src, size_t n); +void *mach_memmove (void *dest, const void *src, size_t n); +void *mach_memset (void *dest, int value, size_t n); +int mach_memcmp (const void *left, const void *right, size_t n); +size_t mach_strlen (const char *string); +char *mach_strcpy (char *restrict dest, const char *restrict src); +int mach_strcmp (const char *left, const char *right); + +/* A short, portability-friendly delay for spin loops. The pause + instruction lets an SMT sibling make progress on x86; on other + architectures the compiler barrier is sufficient because the loop + already contains an acquire load. */ +MACH_INLINE void +mach_cpu_relax (void) +{ +#if defined (__x86_64__) || defined (__i386__) + __asm__ __volatile__ ("pause" ::: "memory"); +#elif defined (__aarch64__) + __asm__ __volatile__ ("yield" ::: "memory"); +#else + __asm__ __volatile__ ("" ::: "memory"); +#endif +} + +#endif /* _MACH_C17_BASE_H */ diff --git a/modern/include/threads.h b/modern/include/threads.h new file mode 100644 index 00000000..4bd44bcf --- /dev/null +++ b/modern/include/threads.h @@ -0,0 +1,104 @@ +/* C17 threads interface implemented by the Mach kernel. + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GNU Mach. + + GNU Mach is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3, or (at your option) + any later version. + + In a freestanding kernel <threads.h> does not exist, yet the C17 + memory and threading model is exactly what the IPC and scheduler code + need. The kernel therefore provides the standard interface itself, + implemented on top of the hybrid scheduler. Kernel code uses the + standard names; user space linked against this library gets the same + semantics without a hidden libc dependency. */ + +#ifndef _MACH_C17_THREADS_H +#define _MACH_C17_THREADS_H 1 + +#include <time.h> + +#include <mach/c17/base.h> +#include <mach/c17/atomic.h> +#include <mach/c17/sched.h> + +typedef struct mach_thread *thrd_t; +typedef int (*thrd_start_t) (void *); + +enum +{ + thrd_success = 0, + thrd_nomem = 1, + thrd_timedout = 2, + thrd_busy = 3, + thrd_error = 4 +}; + +enum +{ + mtx_plain = 0x1, + mtx_recursive = 0x2, + mtx_timed = 0x4 +}; + +typedef struct mach_mutex +{ + mach_atomic_u32 state; /* 0 free, 1 held, 2 contended. */ + struct mach_thread *owner; + mach_priority_t owner_priority; + uint32_t recursion; + uint32_t flags; +} mtx_t; + +typedef struct mach_cond +{ + mach_atomic_u32 sequence; + uint32_t waiters; +} cnd_t; + +typedef struct mach_once +{ + mach_atomic_u32 state; +} once_flag; + +#define ONCE_FLAG_INIT { 0u } + +typedef uint32_t tss_t; +#define TSS_DTOR_ITERATIONS 4 + +typedef void (*tss_dtor_t) (void *); + +int thrd_create (thrd_t *thread, thrd_start_t start, void *argument); +int thrd_join (thrd_t thread, int *result); +int thrd_detach (thrd_t thread); +int thrd_equal (thrd_t left, thrd_t right); +thrd_t thrd_current (void); +int thrd_sleep (const struct timespec *duration, struct timespec *remaining); +void thrd_yield (void); +MACH_NORETURN void thrd_exit (int result); + +int mtx_init (mtx_t *mutex, int type); +void mtx_destroy (mtx_t *mutex); +int mtx_lock (mtx_t *mutex); +int mtx_trylock (mtx_t *mutex); +int mtx_timedlock (mtx_t *mutex, const struct timespec *time_point); +int mtx_unlock (mtx_t *mutex); + +int cnd_init (cnd_t *condition); +void cnd_destroy (cnd_t *condition); +int cnd_signal (cnd_t *condition); +int cnd_broadcast (cnd_t *condition); +int cnd_wait (cnd_t *condition, mtx_t *mutex); +int cnd_timedwait (cnd_t *condition, mtx_t *mutex, + const struct timespec *time_point); + +void call_once (once_flag *flag, void (*function) (void)); + +int tss_create (tss_t *key, tss_dtor_t destructor); +void tss_delete (tss_t key); +void *tss_get (tss_t key); +int tss_set (tss_t key, void *value); + +#endif /* _MACH_C17_THREADS_H */ diff --git a/modern/src/core/mem.c b/modern/src/core/mem.c new file mode 100644 index 00000000..fde77c71 --- /dev/null +++ b/modern/src/core/mem.c @@ -0,0 +1,140 @@ +/* Freestanding memory primitives. + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GNU Mach. See base.h for the license. + + GCC may replace a loop with a call to memcpy or memset even in + freestanding mode. Defining the standard names here keeps the kernel + link self-contained and gives the compiler a definition it can + optimise against. */ + +#include <stddef.h> + +#include <mach/c17/base.h> + +void * +mach_memcpy (void *restrict destination, const void *restrict source, size_t n) +{ + unsigned char *to = destination; + const unsigned char *from = source; + + while (n-- != 0) + *to++ = *from++; + + return destination; +} + +void * +mach_memmove (void *destination, const void *source, size_t n) +{ + unsigned char *to = destination; + const unsigned char *from = source; + + if (to < from) + while (n-- != 0) + *to++ = *from++; + else if (to > from) + { + to += n; + from += n; + while (n-- != 0) + *--to = *--from; + } + + return destination; +} + +void * +mach_memset (void *destination, int value, size_t n) +{ + unsigned char *to = destination; + + while (n-- != 0) + *to++ = (unsigned char) value; + + return destination; +} + +int +mach_memcmp (const void *left, const void *right, size_t n) +{ + const unsigned char *a = left; + const unsigned char *b = right; + + while (n-- != 0) + { + if (*a != *b) + return (int) *a - (int) *b; + a++; + b++; + } + + return 0; +} + +size_t +mach_strlen (const char *string) +{ + const char *end = string; + + while (*end != '\0') + end++; + + return (size_t) (end - string); +} + +char * +mach_strcpy (char *restrict destination, const char *restrict source) +{ + char *result = destination; + + do + *destination++ = *source; + while (*source++ != '\0'); + + return result; +} + +int +mach_strcmp (const char *left, const char *right) +{ + while (*left != '\0' && *left == *right) + { + left++; + right++; + } + + return (int) (unsigned char) *left - (int) (unsigned char) *right; +} + +/* The compiler may emit calls to the standard spellings. */ + +void * +memcpy (void *restrict destination, const void *restrict source, size_t n) +{ + return mach_memcpy (destination, source, n); +} + +void * +memmove (void *destination, const void *source, size_t n) +{ + return mach_memmove (destination, source, n); +} + +void * +memset (void *destination, int value, size_t n) +{ + return mach_memset (destination, value, n); +} + +int +memcmp (const void *left, const void *right, size_t n) +{ + return mach_memcmp (left, right, n); +} + +size_t +strlen (const char *string) +{ + return mach_strlen (string); +} diff --git a/modern/src/core/threads.c b/modern/src/core/threads.c new file mode 100644 index 00000000..f52813cf --- /dev/null +++ b/modern/src/core/threads.c @@ -0,0 +1,594 @@ +/* C17 threads, mutexes and condition variables on the Mach scheduler. + Copyright (C) 2026 Free Software Foundation, Inc. + + This file is part of GNU Mach. See base.h for the license. + + Kernel code cannot call a hosted threading library, so the kernel is + the implementation of <threads.h>. The fast paths are lock-free; + contention falls back to yielding, which is always safe because the + scheduler always has an idle thread to run. When a real context + switch is available (mach_sched_ops.switch_cpu) yield performs one. */ + +#include <threads.h> + +#define MACH_THRD_POOL 256u + +static struct mach_thread mach_thread_pool[MACH_THRD_POOL]; +static thrd_start_t mach_start_functions[MACH_THRD_POOL]; +static void *mach_start_arguments[MACH_THRD_POOL]; +static mach_atomic_bool mach_thread_used[MACH_THRD_POOL]; +static mach_atomic_u32 mach_thread_pool_ready; +static mach_atomic_bool mach_running_cooperative; + +static tss_dtor_t mach_tss_destructors[MACH_SCHED_TSS_KEYS]; +static mach_atomic_bool mach_tss_used[MACH_SCHED_TSS_KEYS]; + +static struct mach_thread mach_bootstrap_thread; + +/* Code that runs before the scheduler has a current thread (early boot, + or a hosted test's main) still needs a stable mutex owner identity. */ +static struct mach_thread * +mutex_self (void) +{ + struct mach_thread *thread = mach_sched_current_thread (); + + return thread != NULL ? thread : &mach_bootstrap_thread; +} + +static void +thread_system_init (void) +{ + uint32_t index; + + if (atomic_load_explicit (&mach_thread_pool_ready, memory_order_acquire)) + return; + + for (index = 0; index < MACH_THRD_POOL; index++) + { + atomic_init (&mach_thread_used[index], false); + mach_start_functions[index] = NULL; + } + + for (index = 0; index < MACH_SCHED_TSS_KEYS; index++) + { + atomic_init (&mach_tss_used[index], false); + mach_tss_destructors[index] = NULL; + } + + atomic_init (&mach_running_cooperative, false); + + if (mach_sched_cpu_count () == 0) + { + mach_sched_init (NULL); + mach_sched_register_cpu (0, MACH_CPU_PERFORMANCE, 1024); + } + + atomic_store_explicit (&mach_thread_pool_ready, 1u, memory_order_release); +} + +static struct mach_thread * +thread_allocate (void) +{ + uint32_t index; + + for (index = 0; index < MACH_THRD_POOL; index++) + if (!atomic_exchange_explicit (&mach_thread_used[index], true, + memory_order_acquire)) + return &mach_thread_pool[index]; + + return NULL; +} + +static void +thread_release (struct mach_thread *thread) +{ + size_t index = (size_t) (thread - mach_thread_pool); + + if (index < MACH_THRD_POOL) + { + mach_start_functions[index] = NULL; + atomic_store_explicit (&mach_thread_used[index], false, + memory_order_release); + } +} + +static void +thread_trampoline (void *argument) +{ + struct mach_thread *thread = argument; + size_t index = (size_t) (thread - mach_thread_pool); + thrd_start_t start = mach_start_functions[index]; + + thread->exit_status = start (mach_start_arguments[index]); +} + +/* Run one ready thread to completion when no context switch primitive is + available. This is the cooperative mode used by the test harness and + by early boot before the first context switch is installed. */ + +static void +thread_run_ready (void) +{ + uint32_t cpu_index; + + if (mach_sched_current_thread () != NULL) + return; + + for (cpu_index = 0; cpu_index < mach_sched_cpu_count (); cpu_index++) + { + struct mach_cpu *cpu = mach_sched_cpu_at (cpu_index); + struct mach_thread *thread = mach_sched_pick_next (cpu); + + if (thread == NULL) + continue; + + atomic_store_explicit (&mach_running_cooperative, true, + memory_order_relaxed); + thread->state = MACH_THREAD_RUNNING; + cpu->current = thread; + thread->cpu = cpu; + mach_sched_account_switch (cpu, NULL, thread); + + thread->exit_status = 0; + thread->entry (thread->argument); + + thread->state = MACH_THREAD_ZOMBIE; + thread->flags &= ~MACH_THREAD_NEED_RESCHED; + mach_sched_account_switch (cpu, thread, NULL); + cpu->current = NULL; + + if (thread->joiner != NULL) + mach_sched_wake (thread->joiner); + + if (thread->detached) + thread_release (thread); + + atomic_store_explicit (&mach_running_cooperative, false, + memory_order_relaxed); + return; + } +} + +int +thrd_create (thrd_t *thread_out, thrd_start_t start, void *argument) +{ + struct mach_thread *thread; + size_t index; + + if (thread_out == NULL || start == NULL) + return thrd_error; + + thread_system_init (); + + thread = thread_allocate (); + if (thread == NULL) + return thrd_nomem; + + index = (size_t) (thread - mach_thread_pool); + mach_start_functions[index] = start; + mach_start_arguments[index] = argument; + + if (mach_sched_thread_init (thread, MACH_PRIO_TIMESHARE, + MACH_SCHED_TIMESHARE, thread_trampoline, + thread) != MACH_OK) + { + thread_release (thread); + return thrd_error; + } + + mach_sched_make_ready (thread); + *thread_out = thread; + return thrd_success; +} + +int +thrd_join (thrd_t thread, int *result) +{ + if (thread == NULL) + return thrd_error; + + while (thread->state != MACH_THREAD_ZOMBIE) + thrd_yield (); + + if (result != NULL) + *result = thread->exit_status; + + /* Joining transfers ownership of the thread object back to the + implementation. A detached thread is released by thrd_exit. */ + if (!thread->detached) + thread_release (thread); + + return thrd_success; +} + +int +thrd_detach (thrd_t thread) +{ + if (thread == NULL) + return thrd_error; + + thread->detached = true; + + if (thread->state == MACH_THREAD_ZOMBIE) + thread_release (thread); + + return thrd_success; +} + +int +thrd_equal (thrd_t left, thrd_t right) +{ + return left == right; +} + +thrd_t +thrd_current (void) +{ + return mach_sched_current_thread (); +} + +int +thrd_sleep (const struct timespec *duration, struct timespec *remaining) +{ + mach_time_t deadline; + + (void) remaining; + + if (duration == NULL) + { + thrd_yield (); + return 0; + } + + deadline = mach_sched_now_ns () + + (mach_time_t) duration->tv_sec * 1000000000ull + + (mach_time_t) duration->tv_nsec; + + if (mach_sched_now_ns () == 0) + { + /* No monotonic clock is installed yet: a cooperative yield is the + best approximation of sleeping during early boot. */ + thrd_yield (); + return 0; + } + + while (mach_sched_now_ns () < deadline) + thrd_yield (); + + return 0; +} + +void +thrd_yield (void) +{ + if (mach_sched_current_thread () != NULL + && mach_sched_current_thread ()->cpu != NULL) + mach_sched_yield (); + else + thread_run_ready (); +} + +MACH_NORETURN void +thrd_exit (int result) +{ + struct mach_thread *thread = mach_sched_current_thread (); + + if (thread != NULL) + { + thread->exit_status = result; + thread->state = MACH_THREAD_ZOMBIE; + } + + for (;;) + { + thrd_yield (); + if (thread != NULL && thread->detached) + thread_release (thread); + } +} + +int +mtx_init (mtx_t *mutex, int type) +{ + if (mutex == NULL) + return thrd_error; + + atomic_init (&mutex->state, 0u); + mutex->owner = NULL; + mutex->owner_priority = 0; + mutex->recursion = 0; + mutex->flags = (uint32_t) type; + return thrd_success; +} + +void +mtx_destroy (mtx_t *mutex) +{ + (void) mutex; +} + +static bool +mtx_acquire (mtx_t *mutex) +{ + uint32_t expected = 0; + + return atomic_compare_exchange_strong_explicit (&mutex->state, &expected, + 1u, memory_order_acquire, + memory_order_relaxed); +} + +int +mtx_lock (mtx_t *mutex) +{ + struct mach_thread *self; + + if (mutex == NULL) + return thrd_error; + + self = mutex_self (); + + if ((mutex->flags & mtx_recursive) != 0 && mutex->owner == self) + { + mutex->recursion++; + return thrd_success; + } + + if (!mtx_acquire (mutex)) + { + struct mach_thread *owner = mutex->owner; + + if (owner != NULL && owner != &mach_bootstrap_thread + && self != &mach_bootstrap_thread) + mach_sched_donate (owner, self->priority); + + while (!mtx_acquire (mutex)) + thrd_yield (); + + if (owner != NULL && owner != &mach_bootstrap_thread + && self != &mach_bootstrap_thread) + mach_sched_undonate (owner, self->priority); + } + + mutex->owner = self; + mutex->owner_priority = self->priority; + mutex->recursion = 1; + return thrd_success; +} + +int +mtx_trylock (mtx_t *mutex) +{ + struct mach_thread *self; + + if (mutex == NULL) + return thrd_error; + + self = mutex_self (); + + if ((mutex->flags & mtx_recursive) != 0 && mutex->owner == self) + { + mutex->recursion++; + return thrd_success; + } + + if (!mtx_acquire (mutex)) + return thrd_busy; + + mutex->owner = self; + mutex->owner_priority = self->priority; + mutex->recursion = 1; + return thrd_success; +} + +int +mtx_timedlock (mtx_t *mutex, const struct timespec *time_point) +{ + mach_time_t deadline; + + if (time_point == NULL) + return thrd_error; + + deadline = (mach_time_t) time_point->tv_sec * 1000000000ull + + (mach_time_t) time_point->tv_nsec; + + for (;;) + { + if (mtx_trylock (mutex) == thrd_success) + return thrd_success; + + if (mach_sched_now_ns () >= deadline) + return thrd_timedout; + + thrd_yield (); + } +} + +int +mtx_unlock (mtx_t *mutex) +{ + if (mutex == NULL) + return thrd_error; + + if (mutex->recursion > 1) + { + mutex->recursion--; + return thrd_success; + } + + mutex->owner = NULL; + mutex->recursion = 0; + atomic_store_explicit (&mutex->state, 0u, memory_order_release); + return thrd_success; +} + +int +cnd_init (cnd_t *condition) +{ + if (condition == NULL) + return thrd_error; + + atomic_init (&condition->sequence, 0u); + condition->waiters = 0; + return thrd_success; +} + +void +cnd_destroy (cnd_t *condition) +{ + (void) condition; +} + +int +cnd_signal (cnd_t *condition) +{ + if (condition == NULL) + return thrd_error; + + if (condition->waiters != 0) + atomic_fetch_add_explicit (&condition->sequence, 1u, + memory_order_release); + return thrd_success; +} + +int +cnd_broadcast (cnd_t *condition) +{ + if (condition == NULL) + return thrd_error; + + if (condition->waiters != 0) + atomic_fetch_add_explicit (&condition->sequence, 1u, + memory_order_release); + return thrd_success; +} + +static int +cnd_wait_until (cnd_t *condition, mtx_t *mutex, mach_time_t deadline, + bool timed) +{ + uint32_t sequence; + + if (condition == NULL || mutex == NULL) + return thrd_error; + + sequence = atomic_load_explicit (&condition->sequence, + memory_order_acquire); + condition->waiters++; + mtx_unlock (mutex); + + for (;;) + { + if (atomic_load_explicit (&condition->sequence, + memory_order_acquire) != sequence) + break; + + if (timed && mach_sched_now_ns () >= deadline) + { + condition->waiters--; + mtx_lock (mutex); + return thrd_timedout; + } + + thrd_yield (); + } + + condition->waiters--; + mtx_lock (mutex); + return thrd_success; +} + +int +cnd_wait (cnd_t *condition, mtx_t *mutex) +{ + return cnd_wait_until (condition, mutex, 0, false); +} + +int +cnd_timedwait (cnd_t *condition, mtx_t *mutex, + const struct timespec *time_point) +{ + mach_time_t deadline; + + if (time_point == NULL) + return thrd_error; + + deadline = (mach_time_t) time_point->tv_sec * 1000000000ull + + (mach_time_t) time_point->tv_nsec; + return cnd_wait_until (condition, mutex, deadline, true); +} + +void +call_once (once_flag *flag, void (*function) (void)) +{ + uint32_t expected = 0; + + if (flag == NULL || function == NULL) + return; + + if (atomic_compare_exchange_strong_explicit (&flag->state, &expected, 1u, + memory_order_acquire, + memory_order_relaxed)) + { + function (); + atomic_store_explicit (&flag->state, 2u, memory_order_release); + return; + } + + while (atomic_load_explicit (&flag->state, memory_order_acquire) != 2u) + thrd_yield (); +} + +int +tss_create (tss_t *key, tss_dtor_t destructor) +{ + uint32_t index; + + thread_system_init (); + + if (key == NULL) + return thrd_error; + + for (index = 0; index < MACH_SCHED_TSS_KEYS; index++) + if (!atomic_exchange_explicit (&mach_tss_used[index], true, + memory_order_acquire)) + { + mach_tss_destructors[index] = destructor; + *key = index; + return thrd_success; + } + + return thrd_error; +} + +void +tss_delete (tss_t key) +{ + if (key >= MACH_SCHED_TSS_KEYS) + return; + + mach_tss_destructors[key] = NULL; + atomic_store_explicit (&mach_tss_used[key], false, memory_order_release); +} + +void * +tss_get (tss_t key) +{ + struct mach_thread *thread = mutex_self (); + + if (key >= MACH_SCHED_TSS_KEYS) + return NULL; + + return thread->tss[key]; +} + +int +tss_set (tss_t key, void *value) +{ + struct mach_thread *thread = mutex_self (); + + if (key >= MACH_SCHED_TSS_KEYS) + return thrd_error; + + thread->tss[key] = value; + return thrd_success; +} diff --git a/modern/tests/fake_hw.c b/modern/tests/fake_hw.c new file mode 100644 index 00000000..092f46c5 --- /dev/null +++ b/modern/tests/fake_hw.c @@ -0,0 +1,220 @@ +/* Shared fake MMIO and DMA backends for the hosted driver tests. + + The kernel drivers never touch a caller pointer, so the tests can + drive them entirely through these two abstractions. The MMIO + backend is a flat byte array; tests that need register semantics + install their own callbacks and delegate the rest here. The DMA + backend is a bump allocator over a page-aligned arena, which makes + physical addresses stable and easy to inspect. */ + +#include "fake_hw.h" + +#include <string.h> + +static uint8_t * +fake_translate (struct fake_mmio_region *region, mach_phys_t address) +{ + if (region == NULL || region->bytes == NULL + || address < region->base + || address - region->base + 8u > region->size) + return NULL; + + return region->bytes + (address - region->base); +} + +uint8_t +fake_mmio_read8 (void *context, mach_phys_t address) +{ + uint8_t *pointer = fake_translate (context, address); + uint8_t value = 0; + + if (pointer != NULL) + memcpy (&value, pointer, sizeof (value)); + return value; +} + +uint16_t +fake_mmio_read16 (void *context, mach_phys_t address) +{ + uint8_t *pointer = fake_translate (context, address); + uint16_t value = 0; + + if (pointer != NULL) + memcpy (&value, pointer, sizeof (value)); + return value; +} + +uint32_t +fake_mmio_read32 (void *context, mach_phys_t address) +{ + uint8_t *pointer = fake_translate (context, address); + uint32_t value = 0; + + if (pointer != NULL) + memcpy (&value, pointer, sizeof (value)); + return value; +} + +uint64_t +fake_mmio_read64 (void *context, mach_phys_t address) +{ + uint8_t *pointer = fake_translate (context, address); + uint64_t value = 0; + + if (pointer != NULL) + memcpy (&value, pointer, sizeof (value)); + return value; +} + +void +fake_mmio_write8 (void *context, mach_phys_t address, uint8_t value) +{ + uint8_t *pointer = fake_translate (context, address); + + if (pointer != NULL) + memcpy (pointer, &value, sizeof (value)); +} + +void +fake_mmio_write16 (void *context, mach_phys_t address, uint16_t value) +{ + uint8_t *pointer = fake_translate (context, address); + + if (pointer != NULL) + memcpy (pointer, &value, sizeof (value)); +} + +void +fake_mmio_write32 (void *context, mach_phys_t address, uint32_t value) +{ + uint8_t *pointer = fake_translate (context, address); + + if (pointer != NULL) + memcpy (pointer, &value, sizeof (value)); +} + +void +fake_mmio_write64 (void *context, mach_phys_t address, uint64_t value) +{ + uint8_t *pointer = fake_translate (context, address); + + if (pointer != NULL) + memcpy (pointer, &value, sizeof (value)); +} + +void +fake_mmio_fence (void *context) +{ + (void) context; +} + +const struct mach_mmio_ops fake_mmio_ops = { + fake_mmio_read8, + fake_mmio_read16, + fake_mmio_read32, + fake_mmio_read64, + fake_mmio_write8, + fake_mmio_write16, + fake_mmio_write32, + fake_mmio_write64, + fake_mmio_fence +}; + +struct mach_mmio +fake_mmio_create (struct fake_mmio_region *region) +{ + struct mach_mmio mmio; + + mmio.ops = &fake_mmio_ops; + mmio.context = region; + mmio.base = region != NULL ? region->base : 0; + mmio.length = region != NULL ? region->size : 0; + return mmio; +} + +void +fake_dma_init (struct fake_dma_arena *arena, void *memory, size_t size) +{ + arena->memory = memory; + arena->size = size; + arena->next = 0; +} + +mach_error_t +fake_dma_alloc (void *context, size_t size, size_t alignment, + mach_phys_t *address, void **virtual_address) +{ + struct fake_dma_arena *arena = context; + size_t offset; + + if (arena == NULL || address == NULL || virtual_address == NULL + || size == 0) + return MACH_EINVAL; + + if (alignment < 16u) + alignment = 16u; + + offset = (arena->next + alignment - 1u) & ~(alignment - 1u); + if (offset + size > arena->size) + return MACH_ENOMEM; + + arena->next = offset + size; + *address = offset; + *virtual_address = arena->memory + offset; + memset (arena->memory + offset, 0, size); + return MACH_OK; +} + +void +fake_dma_free (void *context, size_t size, mach_phys_t address, + void *virtual_address) +{ + (void) context; + (void) size; + (void) address; + (void) virtual_address; +} + +mach_error_t +fake_dma_map (void *context, mach_phys_t address, size_t size, + mach_phys_t *device_address) +{ + (void) context; + (void) size; + if (device_address != NULL) + *device_address = address; + return MACH_OK; +} + +void +fake_dma_unmap (void *context, mach_phys_t device_address, size_t size) +{ + (void) context; + (void) device_address; + (void) size; +} + +void +fake_dma_sync (void *context) +{ + (void) context; +} + +const struct mach_dma_ops fake_dma_ops = { + fake_dma_alloc, + fake_dma_free, + fake_dma_map, + fake_dma_unmap, + fake_dma_sync +}; + +struct mach_dma +fake_dma_create (struct fake_dma_arena *arena) +{ + struct mach_dma dma; + + dma.ops = &fake_dma_ops; + dma.context = arena; + dma.mask = ~0ull; + return dma; +} diff --git a/modern/tests/fake_hw.h b/modern/tests/fake_hw.h new file mode 100644 index 00000000..99b850e7 --- /dev/null +++ b/modern/tests/fake_hw.h @@ -0,0 +1,50 @@ +/* Shared fake MMIO and DMA backends for the hosted driver tests. */ + +#ifndef _MACH_TEST_FAKE_HW_H +#define _MACH_TEST_FAKE_HW_H 1 + +#include <mach/c17/driver.h> + +struct fake_mmio_region +{ + uint8_t *bytes; + size_t size; + mach_phys_t base; +}; + +uint8_t fake_mmio_read8 (void *context, mach_phys_t address); +uint16_t fake_mmio_read16 (void *context, mach_phys_t address); +uint32_t fake_mmio_read32 (void *context, mach_phys_t address); +uint64_t fake_mmio_read64 (void *context, mach_phys_t address); +void fake_mmio_write8 (void *context, mach_phys_t address, uint8_t value); +void fake_mmio_write16 (void *context, mach_phys_t address, uint16_t value); +void fake_mmio_write32 (void *context, mach_phys_t address, uint32_t value); +void fake_mmio_write64 (void *context, mach_phys_t address, uint64_t value); +void fake_mmio_fence (void *context); + +extern const struct mach_mmio_ops fake_mmio_ops; + +struct mach_mmio fake_mmio_create (struct fake_mmio_region *region); + +struct fake_dma_arena +{ + uint8_t *memory; + size_t size; + size_t next; +}; + +void fake_dma_init (struct fake_dma_arena *arena, void *memory, size_t size); +mach_error_t fake_dma_alloc (void *context, size_t size, size_t alignment, + mach_phys_t *address, void **virtual_address); +void fake_dma_free (void *context, size_t size, mach_phys_t address, + void *virtual_address); +mach_error_t fake_dma_map (void *context, mach_phys_t address, size_t size, + mach_phys_t *device_address); +void fake_dma_unmap (void *context, mach_phys_t device_address, size_t size); +void fake_dma_sync (void *context); + +extern const struct mach_dma_ops fake_dma_ops; + +struct mach_dma fake_dma_create (struct fake_dma_arena *arena); + +#endif /* _MACH_TEST_FAKE_HW_H */ diff --git a/modern/tests/test.h b/modern/tests/test.h new file mode 100644 index 00000000..a9fdc881 --- /dev/null +++ b/modern/tests/test.h @@ -0,0 +1,51 @@ +/* Minimal hosted test harness for the C17 Mach platform. */ + +#ifndef _MACH_TEST_H +#define _MACH_TEST_H 1 + +#include <stdio.h> +#include <string.h> + +static int mach_test_failures; + +#define CHECK(condition) \ + do \ + { \ + if (!(condition)) \ + { \ + printf (" FAIL %s:%d: %s\n", __FILE__, __LINE__, #condition); \ + mach_test_failures++; \ + } \ + } \ + while (0) + +#define CHECK_EQ(left, right) \ + do \ + { \ + unsigned long long test_left = (unsigned long long) (left); \ + unsigned long long test_right = (unsigned long long) (right); \ + if (test_left != test_right) \ + { \ + printf (" FAIL %s:%d: %s == %s (0x%llx != 0x%llx)\n", \ + __FILE__, __LINE__, #left, #right, test_left, \ + test_right); \ + mach_test_failures++; \ + } \ + } \ + while (0) + +#define TEST_MAIN(name) \ + int main (void) \ + { \ + printf ("%s\n", name); \ + run_tests (); \ + if (mach_test_failures != 0) \ + { \ + printf (" %d failure(s)\n", mach_test_failures); \ + return 1; \ + } \ + printf (" ok\n"); \ + return 0; \ + } + +#endif /* _MACH_TEST_H */ diff --git a/modern/tests/test_threads.c b/modern/tests/test_threads.c new file mode 100644 index 00000000..73cafc41 --- /dev/null +++ b/modern/tests/test_threads.c @@ -0,0 +1,99 @@ +/* Tests for the C17 threads, mutexes, once and TSS. */ + +#include <threads.h> + +#include "test.h" + +static int worker_counter; +static tss_t worker_key; +static int tss_seen; +static once_flag once = ONCE_FLAG_INIT; +static int once_counter; +static mtx_t recursive_mutex; + +static int +worker (void *argument) +{ + worker_counter += (int) (long) argument; + tss_set (worker_key, (void *) 0x1234); + tss_seen = (int) (long) tss_get (worker_key); + return 42; +} + +static void +once_function (void) +{ + once_counter++; +} + +static void +test_threads (void) +{ + thrd_t first; + thrd_t second; + int result = 0; + + CHECK_EQ (thrd_create (&first, worker, (void *) 1L), thrd_success); + CHECK_EQ (thrd_create (&second, worker, (void *) 2L), thrd_success); + CHECK_EQ (thrd_join (first, &result), thrd_success); + CHECK_EQ (result, 42); + CHECK_EQ (thrd_join (second, NULL), thrd_success); + CHECK_EQ (worker_counter, 3); + CHECK_EQ (tss_seen, 0x1234); +} + +static void +test_mutex (void) +{ + mtx_t mutex; + + CHECK_EQ (mtx_init (&mutex, mtx_plain), thrd_success); + CHECK_EQ (mtx_lock (&mutex), thrd_success); + CHECK_EQ (mtx_trylock (&mutex), thrd_busy); + CHECK_EQ (mtx_unlock (&mutex), thrd_success); + CHECK_EQ (mtx_trylock (&mutex), thrd_success); + CHECK_EQ (mtx_unlock (&mutex), thrd_success); + + CHECK_EQ (mtx_init (&recursive_mutex, mtx_recursive), thrd_success); + CHECK_EQ (mtx_lock (&recursive_mutex), thrd_success); + CHECK_EQ (mtx_lock (&recursive_mutex), thrd_success); + CHECK_EQ (mtx_unlock (&recursive_mutex), thrd_success); + CHECK_EQ (mtx_unlock (&recursive_mutex), thrd_success); + CHECK_EQ (mtx_trylock (&recursive_mutex), thrd_success); + CHECK_EQ (mtx_unlock (&recursive_mutex), thrd_success); +} + +static void +test_once_and_tss (void) +{ + tss_t second_key; + + CHECK_EQ (tss_create (&second_key, NULL), thrd_success); + CHECK_EQ (tss_set (second_key, (void *) 0x55), thrd_success); + CHECK_EQ ((long) tss_get (second_key), 0x55); + tss_delete (second_key); + + call_once (&once, once_function); + call_once (&once, once_function); + CHECK_EQ (once_counter, 1); + + { + cnd_t condition; + + CHECK_EQ (cnd_init (&condition), thrd_success); + CHECK_EQ (cnd_signal (&condition), thrd_success); + CHECK_EQ (cnd_broadcast (&condition), thrd_success); + cnd_destroy (&condition); + } +} + +static void +run_tests (void) +{ + CHECK_EQ (tss_create (&worker_key, NULL), thrd_success); + test_threads (); + test_mutex (); + test_once_and_tss (); +} + +TEST_MAIN ("threads") -- 2.56.0
