From 5f86dffa059e05f06cf8988d2f9512d0b408a8ce Mon Sep 17 00:00:00 2001 From: Luke Howard Date: Sun, 19 Jul 2026 21:09:38 +1000 Subject: [PATCH 1/2] Reimplement the Linux HAL in native Swift using @c Migrate the entire swifthal_* HAL from ~2,900 lines of C to native Swift, exporting the identical C ABI via the Swift 6.3 @c attribute (SE-0495) so SwiftIO/AsyncSwiftIO link against it unchanged. Structure - Split the single C target into CLinuxHalSwiftIO (residual C) and a new Swift target LinuxHalSwiftIO (the public product), which @_exported imports the C module so `import LinuxHalSwiftIO` still surfaces the HAL declarations. - All subsystems are now Swift: spi, i2c, uart, gpio, os, platform, fs, timer, counter, and the adc/eth/i2s/lcd/pwm/wifi stubs. - gpio and timer use the Swift Dispatch overlay (no libdispatch/Blocks C runtime); gpio drives libgpiod directly. The interrupt block entry point, consumed only by AsyncSwiftIO, is now a native Swift closure API rather than an Objective-C block. Minimal C that remains (things Swift cannot express), named with the existing `swifthal___` internal convention: - swift_platform_hwcycle.c: hwcycle_get/to_ns (inline assembly). - swift_uart_native.c: termios2 line configuration (asm/termbits.h conflicts with Glibc's termios.h, so it cannot be exposed to Swift). - swift_hal_native.h: static-inline wrappers for ioctl request-number macros (SPI/I2C), variadic mq_open, and syslog, plus SPI mode-bit constants. Pre-existing bugs fixed along the way - os mq_recv tested `!= 0`, but mq_receive returns a byte count, so it returned -errno on every successful receive; corrected to `< 0`. - The RNG was defined as swiftHal_randomGet, which never matched the declared swifthal_random_get symbol and so could not be linked; renamed to match. - The SPI loopback test built SPI in a stored property, crashing test discovery on any machine without SPI hardware; made it lazy. Toolchain: CI bumped swift:6.2 -> swift:6.3 (@c requires 6.3). Verification: swift build (library, examples, tests) is green; a symbol audit confirms each swifthal_* symbol is defined exactly once (Swift for logic, C only for hwcycle + termios2); HALNativeTests (semaphore limit, mutex, RNG, uptime) pass, and an mq round-trip / sleep smoke check passed during development. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_019ps1JiW8Nh8mbFERFeR9DV --- .github/workflows/swift.yml | 2 +- Package.swift | 24 +- .../include/swift_gpio_native.h} | 26 +- .../include/swift_hal.h | 0 .../include/swift_hal_extras.h | 3 - .../include/swift_hal_native.h | 138 +++++ .../include/swift_uart_native.h | 34 ++ .../swift_fs_native.c} | 20 +- .../swift_platform_hwcycle.c} | 101 +--- Sources/CLinuxHalSwiftIO/swift_uart_native.c | 172 ++++++ Sources/LinuxHalSwiftIO/Counter.swift | 85 +++ .../{swift_pwm.c => Exports.swift} | 31 +- Sources/LinuxHalSwiftIO/FS.swift | 294 ++++++++++ Sources/LinuxHalSwiftIO/GPIO.swift | 351 ++++++++++++ Sources/LinuxHalSwiftIO/I2C.swift | 163 ++++++ Sources/LinuxHalSwiftIO/OS.swift | 404 ++++++++++++++ Sources/LinuxHalSwiftIO/Platform.swift | 111 ++++ Sources/LinuxHalSwiftIO/SPI.swift | 222 ++++++++ Sources/LinuxHalSwiftIO/Stubs.swift | 96 ++++ Sources/LinuxHalSwiftIO/Timer.swift | 125 +++++ Sources/LinuxHalSwiftIO/UART.swift | 352 ++++++++++++ Sources/LinuxHalSwiftIO/swift_counter.c | 97 ---- Sources/LinuxHalSwiftIO/swift_fs.c | 283 ---------- Sources/LinuxHalSwiftIO/swift_gpio.c | 444 --------------- Sources/LinuxHalSwiftIO/swift_hal_internal.h | 133 ----- Sources/LinuxHalSwiftIO/swift_i2c.c | 193 ------- Sources/LinuxHalSwiftIO/swift_i2s.c | 63 --- Sources/LinuxHalSwiftIO/swift_lcd.c | 51 -- Sources/LinuxHalSwiftIO/swift_os.c | 428 --------------- Sources/LinuxHalSwiftIO/swift_spi.c | 257 --------- Sources/LinuxHalSwiftIO/swift_timer.c | 138 ----- Sources/LinuxHalSwiftIO/swift_uart.c | 512 ------------------ Sources/LinuxHalSwiftIO/swift_wifi.c | 43 -- .../LinuxHalSwiftIOTests/HALNativeTests.swift | 63 +++ .../LinuxHalSwiftIOTests.swift | 5 +- 35 files changed, 2659 insertions(+), 2805 deletions(-) rename Sources/{LinuxHalSwiftIO/swift_eth.c => CLinuxHalSwiftIO/include/swift_gpio_native.h} (53%) rename Sources/{LinuxHalSwiftIO => CLinuxHalSwiftIO}/include/swift_hal.h (100%) rename Sources/{LinuxHalSwiftIO => CLinuxHalSwiftIO}/include/swift_hal_extras.h (90%) create mode 100644 Sources/CLinuxHalSwiftIO/include/swift_hal_native.h create mode 100644 Sources/CLinuxHalSwiftIO/include/swift_uart_native.h rename Sources/{LinuxHalSwiftIO/swift_adc.c => CLinuxHalSwiftIO/swift_fs_native.c} (57%) rename Sources/{LinuxHalSwiftIO/swift_platform.c => CLinuxHalSwiftIO/swift_platform_hwcycle.c} (55%) create mode 100644 Sources/CLinuxHalSwiftIO/swift_uart_native.c create mode 100644 Sources/LinuxHalSwiftIO/Counter.swift rename Sources/LinuxHalSwiftIO/{swift_pwm.c => Exports.swift} (50%) create mode 100644 Sources/LinuxHalSwiftIO/FS.swift create mode 100644 Sources/LinuxHalSwiftIO/GPIO.swift create mode 100644 Sources/LinuxHalSwiftIO/I2C.swift create mode 100644 Sources/LinuxHalSwiftIO/OS.swift create mode 100644 Sources/LinuxHalSwiftIO/Platform.swift create mode 100644 Sources/LinuxHalSwiftIO/SPI.swift create mode 100644 Sources/LinuxHalSwiftIO/Stubs.swift create mode 100644 Sources/LinuxHalSwiftIO/Timer.swift create mode 100644 Sources/LinuxHalSwiftIO/UART.swift delete mode 100644 Sources/LinuxHalSwiftIO/swift_counter.c delete mode 100644 Sources/LinuxHalSwiftIO/swift_fs.c delete mode 100644 Sources/LinuxHalSwiftIO/swift_gpio.c delete mode 100644 Sources/LinuxHalSwiftIO/swift_hal_internal.h delete mode 100644 Sources/LinuxHalSwiftIO/swift_i2c.c delete mode 100644 Sources/LinuxHalSwiftIO/swift_i2s.c delete mode 100644 Sources/LinuxHalSwiftIO/swift_lcd.c delete mode 100644 Sources/LinuxHalSwiftIO/swift_os.c delete mode 100644 Sources/LinuxHalSwiftIO/swift_spi.c delete mode 100644 Sources/LinuxHalSwiftIO/swift_timer.c delete mode 100644 Sources/LinuxHalSwiftIO/swift_uart.c delete mode 100644 Sources/LinuxHalSwiftIO/swift_wifi.c create mode 100644 Tests/LinuxHalSwiftIOTests/HALNativeTests.swift diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index f40fe1f..6132085 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -8,7 +8,7 @@ on: jobs: linux: runs-on: ubuntu-24.04 - container: swift:6.2 + container: swift:6.3 steps: - name: Packages run: | diff --git a/Package.swift b/Package.swift index 8beb148..c8c38b1 100644 --- a/Package.swift +++ b/Package.swift @@ -1,5 +1,6 @@ -// swift-tools-version: 5.7 -// The swift-tools-version declares the minimum version of Swift required to build this package. +// swift-tools-version: 6.3 +// The @c attribute used by the native-Swift HAL requires the Swift 6.3 compiler; +// gate older toolchains here rather than failing mid-build with cryptic errors. import Foundation import PackageDescription @@ -51,8 +52,11 @@ let package = Package( .package(url: "https://github.com/apple/swift-system", from: "1.0.0"), ], targets: [ + // Residual C implementation: peripherals not yet migrated to Swift, plus the + // inline-assembly hwcycle functions and the ioctl/mq_open C shims that the + // Swift layer calls. Also exports the public HAL headers. .target( - name: "LinuxHalSwiftIO", + name: "CLinuxHalSwiftIO", dependencies: [ .product(name: "CSwiftIO", package: "SwiftIO"), ], @@ -60,6 +64,16 @@ let package = Package( .linkedLibrary("gpiod", .when(platforms: [.linux])), ] ), + // Native-Swift HAL implementation. Exports the same C ABI via @c and + // re-exports the C module so `import LinuxHalSwiftIO` still surfaces the + // HAL declarations to consumers (AsyncSwiftIO, SwiftIO). + .target( + name: "LinuxHalSwiftIO", + dependencies: [ + "CLinuxHalSwiftIO", + .product(name: "CSwiftIO", package: "SwiftIO"), + ] + ), .target( name: "AsyncSwiftIO", dependencies: [ @@ -98,5 +112,7 @@ let package = Package( .target(name: "AsyncSwiftIO"), ] ), - ] + ], + // Keep the Swift 5 language mode the package built with under tools 5.7. + swiftLanguageModes: [.v5] ) diff --git a/Sources/LinuxHalSwiftIO/swift_eth.c b/Sources/CLinuxHalSwiftIO/include/swift_gpio_native.h similarity index 53% rename from Sources/LinuxHalSwiftIO/swift_eth.c rename to Sources/CLinuxHalSwiftIO/include/swift_gpio_native.h index 4ed218f..2f2c99e 100644 --- a/Sources/LinuxHalSwiftIO/swift_eth.c +++ b/Sources/CLinuxHalSwiftIO/include/swift_gpio_native.h @@ -14,22 +14,16 @@ // limitations under the License. // -#include -#include -#include -#include +// Exposes libgpiod to the native-Swift GPIO implementation (GPIO.swift). The +// request-type/flag values are enum constants (not bare macros) and the structs +// import cleanly, so no wrappers are needed; Swift calls gpiod directly. -#include "swift_hal_internal.h" +#pragma once -int swift_eth_setup_mac(const uint8_t *mac) { return -ENOSYS; } +#ifdef __linux__ +#include +#endif -int swift_eth_tx_register(int (*send)(const uint8_t *, int)) { return -ENOSYS; } - -int swift_eth_rx(uint8_t *data, uint16_t len) { return -ENOSYS; } - -int swift_eth_event_send(int32_t event_id, - void *event_data, - ssize_t event_data_size, - ssize_t ticks_to_wait) { - return -ENOSYS; -} +// The libgpiod consumer label, as a permanent C string literal (infallible, +// unlike a Swift String or strdup()). +static inline const char *swifthal_gpio__consumer(void) { return "SwiftIO"; } diff --git a/Sources/LinuxHalSwiftIO/include/swift_hal.h b/Sources/CLinuxHalSwiftIO/include/swift_hal.h similarity index 100% rename from Sources/LinuxHalSwiftIO/include/swift_hal.h rename to Sources/CLinuxHalSwiftIO/include/swift_hal.h diff --git a/Sources/LinuxHalSwiftIO/include/swift_hal_extras.h b/Sources/CLinuxHalSwiftIO/include/swift_hal_extras.h similarity index 90% rename from Sources/LinuxHalSwiftIO/include/swift_hal_extras.h rename to Sources/CLinuxHalSwiftIO/include/swift_hal_extras.h index cab56af..4cad702 100644 --- a/Sources/LinuxHalSwiftIO/include/swift_hal_extras.h +++ b/Sources/CLinuxHalSwiftIO/include/swift_hal_extras.h @@ -22,9 +22,6 @@ int swifthal_gpio_get_fd(const void *_Nonnull gpio); -int swifthal_gpio_interrupt_callback_install_block( - void *_Nonnull gpio, void (^_Nonnull)(uint8_t, struct timespec)); - /// /// I2C /// diff --git a/Sources/CLinuxHalSwiftIO/include/swift_hal_native.h b/Sources/CLinuxHalSwiftIO/include/swift_hal_native.h new file mode 100644 index 0000000..36f20aa --- /dev/null +++ b/Sources/CLinuxHalSwiftIO/include/swift_hal_native.h @@ -0,0 +1,138 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// C helpers for the native-Swift HAL implementation (Sources/LinuxHalSwiftIO/). +// +// Two jobs: +// +// 1. Expose system headers whose declarations the Swift code needs but which +// the Glibc module map does not surface (mqueue, sys/random, sys/sysinfo, +// semaphore, pthread, sched). Once included here they become visible to +// Swift via `import CLinuxHalSwiftIO`. +// +// 2. Wrap the two things Swift's C interop cannot call directly: +// - function-like ioctl request-number macros (SPI_IOC_MESSAGE(n), the +// _IOR/_IOW-expanding SPI_IOC_* constants); +// - the variadic mq_open(), whose trailing attr pointer Swift's importer +// turns into an un-passable `Any`/CVarArg. +// (Plain object-like constants such as SPI_CPOL and `struct +// spi_ioc_transfer` import into Swift natively and need no wrapper.) + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +// syslog() is variadic and thus not callable from Swift; wrap a preformatted +// message. Callers do their own interpolation and pass a plain string. +static inline void swifthal__syslog(int priority, const char *msg) { + syslog(priority, "%s", msg); +} + +#ifdef __linux__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#ifdef __linux__ + +/// +/// SPI mode bits: the SPI_* macros expand via _BITUL(), which Swift's importer +/// reports as "structure not supported", so re-expose them as typed constants. +/// + +static const uint8_t swifthal_spi__cpha = SPI_CPHA; +static const uint8_t swifthal_spi__cpol = SPI_CPOL; +static const uint8_t swifthal_spi__loop = SPI_LOOP; +static const uint8_t swifthal_spi__lsb_first = SPI_LSB_FIRST; + +/// +/// Message queue (variadic mq_open cannot be called from Swift) +/// + +static inline mqd_t swifthal_os__mq_open(const char *name, + int oflag, + mode_t mode, + struct mq_attr *attr) { + return mq_open(name, oflag, mode, attr); +} + +/// +/// SPI ioctls (request numbers are function-like macros) +/// + +static inline int swifthal_spi__rd_max_speed(int fd, uint32_t *speed) { + return ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, speed); +} + +static inline int swifthal_spi__wr_max_speed(int fd, const uint32_t *speed) { + return ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, speed); +} + +static inline int swifthal_spi__rd_mode(int fd, uint8_t *mode) { + return ioctl(fd, SPI_IOC_RD_MODE, mode); +} + +static inline int swifthal_spi__wr_mode(int fd, const uint8_t *mode) { + return ioctl(fd, SPI_IOC_WR_MODE, mode); +} + +static inline int swifthal_spi__rd_lsb_first(int fd, uint8_t *lsb) { + return ioctl(fd, SPI_IOC_RD_LSB_FIRST, lsb); +} + +static inline int swifthal_spi__rd_bits_per_word(int fd, uint8_t *bits) { + return ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, bits); +} + +static inline int swifthal_spi__wr_bits_per_word(int fd, const uint8_t *bits) { + return ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, bits); +} + +// Issue a two-transfer full-duplex SPI message. +static inline int swifthal_spi__message2(int fd, + struct spi_ioc_transfer *xfer) { + return ioctl(fd, SPI_IOC_MESSAGE(2), xfer); +} + +/// +/// I2C ioctls (I2C_SLAVE takes a scalar; I2C_RDWR takes a pointer — neither is +/// callable through Swift's variadic ioctl import). +/// + +static inline int swifthal_i2c__set_slave(int fd, unsigned long addr) { + return ioctl(fd, I2C_SLAVE, addr); +} + +static inline int swifthal_i2c__rdwr(int fd, + struct i2c_rdwr_ioctl_data *data) { + return ioctl(fd, I2C_RDWR, data); +} + +#endif // __linux__ diff --git a/Sources/CLinuxHalSwiftIO/include/swift_uart_native.h b/Sources/CLinuxHalSwiftIO/include/swift_uart_native.h new file mode 100644 index 0000000..66aecb8 --- /dev/null +++ b/Sources/CLinuxHalSwiftIO/include/swift_uart_native.h @@ -0,0 +1,34 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Plain prototypes for the UART termios2 helpers implemented in +// swift_uart_native.c. Deliberately does NOT include so it can +// be part of the module Swift imports without colliding with Glibc's +// (both define `struct termios`). + +#pragma once + +#include + +int swifthal_uart__set_baud(int fd, uint32_t baud); +int swifthal_uart__set_parity(int fd, int parity); // 0 none, 1 odd, 2 even +int swifthal_uart__set_stop_bits(int fd, int two); +int swifthal_uart__set_data_bits_8(int fd); +int swifthal_uart__set_raw(int fd); +int swifthal_uart__get_config(int fd, + uint32_t *baud, + int *parity, + int *stop2); diff --git a/Sources/LinuxHalSwiftIO/swift_adc.c b/Sources/CLinuxHalSwiftIO/swift_fs_native.c similarity index 57% rename from Sources/LinuxHalSwiftIO/swift_adc.c rename to Sources/CLinuxHalSwiftIO/swift_fs_native.c index 22555a1..e9df70e 100644 --- a/Sources/LinuxHalSwiftIO/swift_adc.c +++ b/Sources/CLinuxHalSwiftIO/swift_fs_native.c @@ -14,20 +14,8 @@ // limitations under the License. // -#include -#include -#include -#include -#include +// The filesystem HAL is implemented in Swift (FS.swift); this one entry stays +// in C because it must return a permanent C string. A string literal here is +// infallible, whereas a Swift String has no stable char* and strdup() can fail. -#include "swift_hal_internal.h" - -void *swifthal_adc_open(int id) { return NULL; } - -int swifthal_adc_close(void *adc) { return -ENOSYS; } - -int swifthal_adc_read(void *adc, uint16_t *sample_buffer) { return -ENOSYS; } - -int swifthal_adc_info_get(void *adc, swift_adc_info_t *info) { return -ENOSYS; } - -int swifthal_adc_dev_number_get(void) { return 0; } +char *swifthal_mount_point_get(void) { return "/"; } diff --git a/Sources/LinuxHalSwiftIO/swift_platform.c b/Sources/CLinuxHalSwiftIO/swift_platform_hwcycle.c similarity index 55% rename from Sources/LinuxHalSwiftIO/swift_platform.c rename to Sources/CLinuxHalSwiftIO/swift_platform_hwcycle.c index 39ecd4b..4acc963 100644 --- a/Sources/LinuxHalSwiftIO/swift_platform.c +++ b/Sources/CLinuxHalSwiftIO/swift_platform_hwcycle.c @@ -14,49 +14,14 @@ // limitations under the License. // -#include -#include -#include -#include -#include +// The rest of the platform HAL (sleep/wait/uptime/random) is implemented in +// native Swift (Sources/LinuxHalSwiftIO/Platform.swift). The two hardware-cycle +// functions remain in C because they require inline assembly to read the +// per-architecture cycle counter, which Swift cannot express. + #include #include #include -#include -#ifdef __linux__ -#include -#endif - -#include "swift_hal_internal.h" - -void swifthal_ms_sleep(ssize_t ms) { - struct timespec ts, rem; - - if (ms <= 0) - return; - - // nanosleep avoids usleep's useconds_t (32-bit) truncation for large delays - // and correctly handles interruption by a signal. - ts.tv_sec = ms / 1000; - ts.tv_nsec = (ms % 1000) * 1000000L; - - while (nanosleep(&ts, &rem) < 0 && errno == EINTR) - ts = rem; -} - -void swifthal_us_wait(uint32_t us) { usleep(us); } - -int64_t swifthal_uptime_get(void) { -#ifdef __linux__ - struct sysinfo info; - if (sysinfo(&info) < 0) - return -errno; - - return (int64_t)info.uptime * 1000; -#else - return 0; -#endif -} uint32_t swifthal_hwcycle_get(void) { #if __x86_64__ @@ -127,59 +92,3 @@ uint32_t swifthal_hwcycle_to_ns(unsigned int cycles) { #error implement swifthal_hwcycle_to_ns() for your platform #endif } - -#if defined(__linux__) -static int swifthal__urandom_fill(uint8_t *buf, ssize_t length) { - ssize_t total = 0; - int fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC); - - if (fd < 0) - return -1; - - while (total < length) { - ssize_t n = read(fd, buf + total, (size_t)(length - total)); - if (n < 0) { - if (errno == EINTR) - continue; - break; - } - if (n == 0) - break; - total += n; - } - - close(fd); - return total == length ? 0 : -1; -} -#endif - -void swiftHal_randomGet(uint8_t *buf, ssize_t length) { -#if defined(__linux__) - ssize_t total = 0; - - if (length < 0) - return; - - // getrandom() may return short or fail with EINTR before the entropy pool is - // initialized; ignoring that leaves the buffer partially/fully uninitialized - // while callers treat it as random (key/nonce) material. Fill completely. - while (total < length) { - ssize_t n = getrandom(buf + total, (size_t)(length - total), 0); - if (n < 0) { - if (errno == EINTR) - continue; - // getrandom() may be unavailable (pre-3.17 kernel, or blocked by a - // seccomp policy, returning ENOSYS/EFAULT). Fall back to /dev/urandom - // rather than crashing the process or handing back non-random bytes. - if (swifthal__urandom_fill(buf + total, length - total) == 0) - return; - abort(); // no entropy source available: fail closed - } - total += n; - } -#elif defined(__APPLE__) - arc4random_buf(buf, length); -#else -#error define swiftHal_randomGet for your platform -#endif -} diff --git a/Sources/CLinuxHalSwiftIO/swift_uart_native.c b/Sources/CLinuxHalSwiftIO/swift_uart_native.c new file mode 100644 index 0000000..4fb93ba --- /dev/null +++ b/Sources/CLinuxHalSwiftIO/swift_uart_native.c @@ -0,0 +1,172 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// termios2 line-configuration helpers for the native-Swift UART (UART.swift). +// termios2 lives in , which redefines `struct termios` and so +// cannot be exposed to Swift alongside Glibc's . Keeping it in this +// isolated .c (declared via plain prototypes in swift_hal_native.h) confines that +// conflict to C while Swift owns the open/buffering/poll logic. + +#include +#include +#ifdef __linux__ +#include +#include +#endif + +#include "swift_uart_native.h" + +int swifthal_uart__set_baud(int fd, uint32_t baud) { +#ifdef __linux__ + struct termios2 tty; + + if (baud == 0) + return -EINVAL; + if (ioctl(fd, TCGETS2, &tty) < 0) + return -errno; + + tty.c_cflag &= ~(CBAUD); + tty.c_cflag |= BOTHER; + tty.c_ospeed = baud; + + tty.c_cflag &= ~(CBAUD << IBSHIFT); + tty.c_cflag |= BOTHER << IBSHIFT; + tty.c_ispeed = baud; + + if (ioctl(fd, TCSETS2, &tty) < 0) + return -errno; + return 0; +#else + return -ENOSYS; +#endif +} + +int swifthal_uart__set_parity(int fd, int parity) { +#ifdef __linux__ + struct termios2 tty; + + if (ioctl(fd, TCGETS2, &tty) < 0) + return -errno; + + switch (parity) { + case 0: // none + tty.c_cflag &= ~(PARENB | PARODD); + break; + case 1: // odd + tty.c_cflag |= PARENB | PARODD; + break; + case 2: // even + tty.c_cflag |= PARENB; + tty.c_cflag &= ~(PARODD); + break; + default: + return -EINVAL; + } + + if (ioctl(fd, TCSETS2, &tty) < 0) + return -errno; + return 0; +#else + return -ENOSYS; +#endif +} + +int swifthal_uart__set_stop_bits(int fd, int two) { +#ifdef __linux__ + struct termios2 tty; + + if (ioctl(fd, TCGETS2, &tty) < 0) + return -errno; + + if (two) + tty.c_cflag |= CSTOPB; + else + tty.c_cflag &= ~(CSTOPB); + + if (ioctl(fd, TCSETS2, &tty) < 0) + return -errno; + return 0; +#else + return -ENOSYS; +#endif +} + +int swifthal_uart__set_data_bits_8(int fd) { +#ifdef __linux__ + struct termios2 tty; + + if (ioctl(fd, TCGETS2, &tty) < 0) + return -errno; + + tty.c_cflag |= CS8; + + if (ioctl(fd, TCSETS2, &tty) < 0) + return -errno; + return 0; +#else + return -ENOSYS; +#endif +} + +int swifthal_uart__set_raw(int fd) { +#ifdef __linux__ + struct termios2 tty; + + if (ioctl(fd, TCGETS2, &tty) < 0) + return -errno; + + tty.c_iflag &= + ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); + tty.c_oflag &= ~OPOST; + tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + + if (ioctl(fd, TCSETS2, &tty) < 0) + return -errno; + return 0; +#else + return -ENOSYS; +#endif +} + +// Output: *baud, *parity (0/1/2), *stop2 (0/1). Returns -EINVAL if the data +// width is not 8 bits (the only supported value), matching the original. +int swifthal_uart__get_config(int fd, + uint32_t *baud, + int *parity, + int *stop2) { +#ifdef __linux__ + struct termios2 tty; + + if (ioctl(fd, TCGETS2, &tty) < 0) + return -errno; + + *baud = tty.c_ispeed < tty.c_ospeed ? tty.c_ispeed : tty.c_ospeed; + + if (tty.c_cflag & PARENB) + *parity = (tty.c_cflag & PARODD) ? 1 : 2; + else + *parity = 0; + + *stop2 = (tty.c_cflag & CSTOPB) ? 1 : 0; + + if ((tty.c_cflag & CSIZE) != CS8) + return -EINVAL; + + return 0; +#else + return -ENOSYS; +#endif +} diff --git a/Sources/LinuxHalSwiftIO/Counter.swift b/Sources/LinuxHalSwiftIO/Counter.swift new file mode 100644 index 0000000..8bc21f9 --- /dev/null +++ b/Sources/LinuxHalSwiftIO/Counter.swift @@ -0,0 +1,85 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Native-Swift port of the counter HAL (previously swift_counter.c): a thin +// wrapper over a POSIX clock. Most of the alarm/frequency surface is +// unimplemented on Linux, matching the original. + +#if canImport(Glibc) +import Glibc +#elseif canImport(Darwin) +import Darwin +#endif +import CLinuxHalSwiftIO + +private struct CounterHandle { + var clockid: clockid_t +} + +@c +func swifthal_counter_open(_ id: CInt) -> UnsafeMutableRawPointer? { + // Darwin imports clockid_t as an enum; reject ids that do not map to one. + #if os(Linux) + let clockid = clockid_t(id) + #else + guard let clockid = clockid_t(rawValue: UInt32(bitPattern: id)) else { return nil } + #endif + let c = UnsafeMutablePointer.allocate(capacity: 1) + c.initialize(to: CounterHandle(clockid: clockid)) + return UnsafeMutableRawPointer(c) +} + +@c +func swifthal_counter_close(_ arg: UnsafeMutableRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let c = arg.assumingMemoryBound(to: CounterHandle.self) + c.deinitialize(count: 1) + c.deallocate() + return 0 +} + +@c +func swifthal_counter_read(_ arg: UnsafeMutableRawPointer?, _ ticks: UnsafeMutablePointer?) -> CInt { + ticks?.pointee = 0 + guard let arg else { return -EINVAL } + let c = arg.assumingMemoryBound(to: CounterHandle.self) + + var tp = timespec() + if clock_gettime(c.pointee.clockid, &tp) < 0 { return -errno } + + let us = UInt64(tp.tv_sec) * 1_000_000 + UInt64(tp.tv_nsec) / 1000 + ticks?.pointee = swifthal_counter_us_to_ticks(arg, us) + return 0 +} + +@c +func swifthal_counter_add_callback( + _ arg: UnsafeMutableRawPointer?, + _ userData: UnsafeRawPointer?, + _ callback: (@convention(c) (UInt32, UnsafeRawPointer?) -> Void)? +) -> CInt { + -ENOSYS +} + +@c func swifthal_counter_freq(_ arg: UnsafeMutableRawPointer?) -> UInt32 { 0 } +@c func swifthal_counter_ticks_to_us(_ arg: UnsafeMutableRawPointer?, _ ticks: UInt32) -> UInt64 { 0 } +@c func swifthal_counter_us_to_ticks(_ arg: UnsafeMutableRawPointer?, _ us: UInt64) -> UInt32 { 0 } +@c func swifthal_counter_get_max_top_value(_ arg: UnsafeMutableRawPointer?) -> UInt32 { UInt32.max } +@c func swifthal_counter_set_channel_alarm(_ arg: UnsafeMutableRawPointer?, _ ticks: UInt32) -> CInt { -ENOSYS } +@c func swifthal_counter_cancel_channel_alarm(_ arg: UnsafeMutableRawPointer?) -> CInt { -ENOSYS } +@c func swifthal_counter_start(_ arg: UnsafeMutableRawPointer?) -> CInt { -ENOSYS } +@c func swifthal_counter_stop(_ arg: UnsafeMutableRawPointer?) -> CInt { -ENOSYS } +@c func swifthal_counter_dev_number_get() -> CInt { 0 } diff --git a/Sources/LinuxHalSwiftIO/swift_pwm.c b/Sources/LinuxHalSwiftIO/Exports.swift similarity index 50% rename from Sources/LinuxHalSwiftIO/swift_pwm.c rename to Sources/LinuxHalSwiftIO/Exports.swift index 574ee38..5db9f5d 100644 --- a/Sources/LinuxHalSwiftIO/swift_pwm.c +++ b/Sources/LinuxHalSwiftIO/Exports.swift @@ -14,29 +14,8 @@ // limitations under the License. // -#include -#include -#include -#include -#include - -#include "swift_hal_internal.h" - -void *swifthal_pwm_open(int id) { return NULL; } - -int swifthal_pwm_close(void *pwm) { return -ENOSYS; } - -int swifthal_pwm_set(void *pwm, ssize_t period, ssize_t pulse) { - return -ENOSYS; -} - -int swifthal_pwm_suspend(void *pwm) { return -ENOSYS; } - -int swifthal_pwm_resume(void *pwm) { return -ENOSYS; } - -int swifthal_pwm_info_get(void *pwm, swift_pwm_info_t *info) { - memset(info, 0, sizeof(*info)); - return -ENOSYS; -} - -int swifthal_pwm_dev_number_get(void) { return 0; } +// Re-export the C module so existing consumers that `import LinuxHalSwiftIO` +// (e.g. AsyncSwiftIO, SwiftIO) continue to see the HAL C declarations, the +// public headers (swift_hal.h / swift_hal_extras.h) and native-support +// declarations, unchanged. +@_exported import CLinuxHalSwiftIO diff --git a/Sources/LinuxHalSwiftIO/FS.swift b/Sources/LinuxHalSwiftIO/FS.swift new file mode 100644 index 0000000..1a266d3 --- /dev/null +++ b/Sources/LinuxHalSwiftIO/FS.swift @@ -0,0 +1,294 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Native-Swift port of the filesystem HAL (previously swift_fs.c), mapping the +// HAL onto POSIX stdio / dirent / stat / statfs. + +#if canImport(Glibc) +import Glibc +#elseif canImport(Darwin) +import Darwin +#endif +import CLinuxHalSwiftIO + +// swifthal_mount_point_get() stays in C (swift_fs_native.c): it must return a +// permanent C string, and a string literal there is infallible — unlike a +// Swift String (no stable char* without a scoped withCString) or strdup (can +// fail with nil). + +@c +func swifthal_fs_open(_ fp: UnsafeMutablePointer?, _ path: UnsafePointer?, _ flags: UInt8) -> CInt { + fp?.pointee = nil + + let f = Int32(flags) + let modeBits = f & SWIFT_FS_O_MODE_MASK + let flagBits = f & SWIFT_FS_O_FLAGS_MASK + let mode: String + + if modeBits == SWIFT_FS_O_READ, flagBits == 0 { + mode = "r" + } else if modeBits == SWIFT_FS_O_WRITE, flagBits == SWIFT_FS_O_CREATE { + mode = "w" + } else if modeBits == SWIFT_FS_O_WRITE, flagBits == (SWIFT_FS_O_CREATE | SWIFT_FS_O_APPEND) { + mode = "a" + } else if modeBits == SWIFT_FS_O_RDWR, flagBits == 0 { + mode = "r+" + } else if modeBits == SWIFT_FS_O_RDWR, flagBits == SWIFT_FS_O_CREATE { + mode = "w+" + } else if modeBits == SWIFT_FS_O_READ, flagBits == (SWIFT_FS_O_CREATE | SWIFT_FS_O_APPEND) { + mode = "a+" + } else { + return -EINVAL + } + + guard let handle = fopen(path, mode) else { return -errno } + fp?.pointee = UnsafeMutableRawPointer(handle) + return 0 +} + +@c +func swifthal_fs_close(_ fp: UnsafeMutableRawPointer?) -> CInt { + guard let fp else { return -EINVAL } + if fclose(fp.assumingMemoryBound(to: FILE.self)) != 0 { return -errno } + return 0 +} + +@c +func swifthal_fs_remove(_ path: UnsafePointer?) -> CInt { + guard let path else { return -EINVAL } + if unlink(path) != 0 { return -errno } + return 0 +} + +@c +func swifthal_fs_rename(_ from: UnsafePointer?, _ to: UnsafeMutablePointer?) -> CInt { + guard let from, let to else { return -EINVAL } + if rename(from, to) != 0 { return -errno } + return 0 +} + +@c +func swifthal_fs_write(_ fp: UnsafeMutableRawPointer?, _ buf: UnsafeRawPointer?, _ size: Int) -> CInt { + if size < 0 { return -EINVAL } + if size == 0 { return 0 } // a zero-byte write is not an error + guard let fp else { return -EINVAL } + let file = fp.assumingMemoryBound(to: FILE.self) + + let nbytes = fwrite(buf, 1, size, file) + if nbytes == 0 { + if ferror(file) != 0 { return -errno } + return -EIO + } + return CInt(truncatingIfNeeded: nbytes) +} + +@c +func swifthal_fs_read(_ fp: UnsafeMutableRawPointer?, _ buf: UnsafeMutableRawPointer?, _ size: Int) -> CInt { + if size < 0 { return -EINVAL } + if size == 0 { return 0 } // a zero-byte read is not an error + guard let fp else { return -EINVAL } + let file = fp.assumingMemoryBound(to: FILE.self) + + let nbytes = fread(buf, 1, size, file) + if nbytes == 0 { + if ferror(file) != 0 { return -errno } + if feof(file) != 0 { return 0 } + return -EIO + } + return CInt(truncatingIfNeeded: nbytes) +} + +@c +func swifthal_fs_seek(_ fp: UnsafeMutableRawPointer?, _ offset: Int, _ whence: CInt) -> CInt { + guard let fp else { return -EINVAL } + let file = fp.assumingMemoryBound(to: FILE.self) + + var fwhence: Int32 = 0 + switch whence { + case SWIFT_FS_SEEK_SET: fwhence = Int32(SEEK_SET) + case SWIFT_FS_SEEK_CUR: fwhence = Int32(SEEK_CUR) + case SWIFT_FS_SEEK_END: fwhence = Int32(SEEK_END) + default: break + } + + // fseeko/ftello use off_t so the seek itself is correct for >2GiB offsets. + if fseeko(file, off_t(offset), fwhence) != 0 { return -errno } + return 0 +} + +@c +func swifthal_fs_tell(_ fp: UnsafeMutableRawPointer?) -> CInt { + guard let fp else { return -EINVAL } + let offset = ftello(fp.assumingMemoryBound(to: FILE.self)) + if offset == -1 { return -errno } + return CInt(truncatingIfNeeded: offset) +} + +@c +func swifthal_fs_truncate(_ fp: UnsafeMutableRawPointer?, _ length: Int) -> CInt { + guard let fp else { return -EINVAL } + if ftruncate(fileno(fp.assumingMemoryBound(to: FILE.self)), off_t(length)) < 0 { return -errno } + return 0 +} + +@c +func swifthal_fs_sync(_ fp: UnsafeMutableRawPointer?) -> CInt { + guard let fp else { return -EINVAL } + if fsync(fileno(fp.assumingMemoryBound(to: FILE.self))) < 0 { return -errno } + return 0 +} + +@c +func swifthal_fs_mkdir(_ path: UnsafePointer?) -> CInt { + guard let path else { return -EINVAL } + if mkdir(path, 0o777) < 0 { return -errno } + return 0 +} + +@c +func swifthal_fs_opendir(_ dp: UnsafeMutablePointer?, _ path: UnsafePointer?) -> CInt { + guard let path else { return -EINVAL } + guard let handle = opendir(path) else { + dp?.pointee = nil + return -errno + } + dp?.pointee = UnsafeMutableRawPointer(handle) + return 0 +} + +// Copy a NUL-terminated C name into the `char[256]` dirent name field, +// truncating like strncpy(dst, src, 255) with an explicit NUL terminator. +private func setDirentName(_ entry: UnsafeMutablePointer, _ name: UnsafePointer) { + withUnsafeMutablePointer(to: &entry.pointee.name) { + $0.withMemoryRebound(to: CChar.self, capacity: 256) { dst in + _ = strncpy(dst, name, 255) + dst[255] = 0 + } + } +} + +// Fill `entry` from one dirent: returns 0, a negative errno, or nil to skip. +// d_name stays raw bytes: a String round-trip would mangle non-UTF-8 names. +private func fillDirent( + _ dir: OpaquePointer, + _ dentry: UnsafeMutablePointer, + _ entry: UnsafeMutablePointer +) -> CInt? { + withUnsafePointer(to: &dentry.pointee.d_name) { + $0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: dentry.pointee.d_name)) { name -> CInt? in + var dType = dentry.pointee.d_type + + // Some filesystems always report DT_UNKNOWN; stat to classify the entry. + if dType == DT_UNKNOWN { + var sb = stat() + if fstatat(dirfd(dir), name, &sb, 0) < 0 { + // Skip entries lost to an unlink race (or dangling symlinks); surface + // any other failure rather than silently omitting entries. + return errno == ENOENT ? nil : -errno + } + if sb.st_mode & S_IFMT == S_IFDIR { + dType = UInt8(DT_DIR) + } else if sb.st_mode & S_IFMT == S_IFREG { + dType = UInt8(DT_REG) + } + } + + if dType == UInt8(DT_DIR) { + entry.pointee.type = SWIFT_FS_DIR_ENTRY_DIR + setDirentName(entry, name) + entry.pointee.size = 0 + return 0 + } else if dType == UInt8(DT_REG) { + var sb = stat() + if fstatat(dirfd(dir), name, &sb, 0) < 0 { + // As above: tolerate losing a race with unlink, surface real errors. + return errno == ENOENT ? nil : -errno + } + entry.pointee.type = SWIFT_FS_DIR_ENTRY_FILE + setDirentName(entry, name) + entry.pointee.size = Int(sb.st_size) + return 0 + } + return nil // neither directory nor regular file: skip + } + } +} + +@c +func swifthal_fs_readdir(_ dp: UnsafeMutableRawPointer?, _ entry: UnsafeMutablePointer?) -> CInt { + guard let dp, let entry else { return -EINVAL } + let dir = OpaquePointer(dp) + + while true { + // readdir() returns NULL both at end-of-directory and on error; per POSIX, + // errno must be cleared first to tell them apart. + errno = 0 + guard let dentry = readdir(dir) else { + if errno != 0 { return -errno } + entry.pointee.name.0 = 0 + return 0 + } + + if let rc = fillDirent(dir, dentry, entry) { return rc } + } +} + +@c +func swifthal_fs_closedir(_ dp: UnsafeMutableRawPointer?) -> CInt { + guard let dp else { return -EINVAL } + if closedir(OpaquePointer(dp)) < 0 { return -errno } + return 0 +} + +@c +func swifthal_fs_stat(_ path: UnsafePointer?, _ entry: UnsafeMutablePointer?) -> CInt { + guard let path, let entry else { return -EINVAL } + var sb = stat() + if stat(path, &sb) < 0 { return -errno } + + if sb.st_mode & S_IFMT == S_IFDIR { + entry.pointee.type = SWIFT_FS_DIR_ENTRY_DIR + entry.pointee.size = 0 + } else if sb.st_mode & S_IFMT == S_IFREG { + entry.pointee.type = SWIFT_FS_DIR_ENTRY_FILE + entry.pointee.size = Int(sb.st_size) + } else { + return -ENOENT + } + + // The name is not derivable from a path; make it a defined empty string. + entry.pointee.name.0 = 0 + return 0 +} + +@c +func swifthal_fs_statfs(_ path: UnsafePointer?, _ stat: UnsafeMutablePointer?) -> CInt { + guard let path, let stat else { return -EINVAL } + var buf = statfs() + if statfs(path, &buf) < 0 { return -errno } + + stat.pointee = swift_fs_statvfs_t() + #if os(Linux) + stat.pointee.f_bsize = UInt(bitPattern: buf.f_bsize) + stat.pointee.f_frsize = UInt(bitPattern: buf.f_frsize) + #else + // Darwin's struct statfs has no f_frsize; leave it zero as the old C did. + stat.pointee.f_bsize = UInt(buf.f_bsize) + #endif + stat.pointee.f_blocks = UInt(buf.f_blocks) + stat.pointee.f_bfree = UInt(buf.f_bfree) + return 0 +} diff --git a/Sources/LinuxHalSwiftIO/GPIO.swift b/Sources/LinuxHalSwiftIO/GPIO.swift new file mode 100644 index 0000000..7704fa0 --- /dev/null +++ b/Sources/LinuxHalSwiftIO/GPIO.swift @@ -0,0 +1,351 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Native-Swift port of the GPIO HAL (previously swift_gpio.c), driving libgpiod +// with a Swift Dispatch read source for edge interrupts. The original used an +// Objective-C block for the interrupt callback; because that entry point is +// consumed only by AsyncSwiftIO, it is now a native Swift closure API +// (swifthal_gpio_interrupt_callback_install_block, below) and no Blocks runtime +// is involved. + +#if canImport(Glibc) +import Glibc +#elseif canImport(Darwin) +import Darwin +#endif +import Dispatch +import CLinuxHalSwiftIO + +#if os(Linux) +private let gpioChip = "gpiochip0" // FIXME: make configurable + +private final class GPIO { + var chip: OpaquePointer? // gpiod_chip * + var line: OpaquePointer? // gpiod_line * + var direction = SWIFT_GPIO_DIRECTION_OUT + var ioMode = SWIFT_GPIO_MODE_PULL_NONE + var intMode = SWIFT_GPIO_INT_MODE_RISING_EDGE + let queue = DispatchQueue.global() + var source: (any DispatchSourceProtocol)? + var sourceSuspended = false + var callback: ((UInt8, timespec) -> Void)? +} + +// Cancel and release the interrupt dispatch source, bringing its suspend count +// to zero first. Safe to call with no source installed. +private func releaseSource(_ gpio: GPIO) { + guard let source = gpio.source else { return } + source.cancel() + if gpio.sourceSuspended { + source.resume() + gpio.sourceSuspended = false + } + gpio.source = nil +} + +private func ioModeFlags(_ ioMode: swift_gpio_mode_t) -> CInt? { + switch ioMode { + case SWIFT_GPIO_MODE_PULL_UP: return CInt(GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP) + case SWIFT_GPIO_MODE_PULL_DOWN: return CInt(GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN) + case SWIFT_GPIO_MODE_PULL_NONE: return CInt(GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE) + case SWIFT_GPIO_MODE_OPEN_DRAIN: return CInt(GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN) + default: return nil + } +} + +// Takes ownership of `chip`, wires up the line, and returns the retained opaque +// handle (or nil, having closed the chip, on failure). +private func chip2gpio( + _ id: CInt, + _ chip: OpaquePointer, + _ direction: swift_gpio_direction_t, + _ ioMode: swift_gpio_mode_t +) -> UnsafeMutableRawPointer? { + let gpio = GPIO() + gpio.chip = chip + + gpio.line = gpiod_chip_get_line(chip, UInt32(bitPattern: id)) + if gpio.line == nil { + gpiod_chip_close(chip) + return nil + } + + let opaque = Unmanaged.passRetained(gpio).toOpaque() + if swifthal_gpio_config(opaque, direction, ioMode) < 0 { + _ = swifthal_gpio_close(opaque) + return nil + } + return opaque +} +#endif + +@c +func swifthal_gpio_open(_ id: CInt, _ direction: swift_gpio_direction_t, _ ioMode: swift_gpio_mode_t) -> UnsafeMutableRawPointer? { + #if os(Linux) + // http://git.munts.com/muntsos/doc/AppNote11-link-gpiochip.pdf + guard let iter = gpiod_chip_iter_new() else { return nil } + + var chip = gpiod_chip_iter_next(iter) + while let c = chip { + if let label = gpiod_chip_label(c) { + let name = String(cString: label) + if name == "pinctrl-rp1" || name == "pinctrl-bcm2835" || name == "pinctrl-bcm2711" { + let gpio = chip2gpio(id, c, direction, ioMode) + gpiod_chip_iter_free_noclose(iter) + return gpio + } + } + chip = gpiod_chip_iter_next(iter) + } + + gpiod_chip_iter_free(iter) + #endif + return nil +} + +@c +func swifthal_gpio_close(_ arg: UnsafeMutableRawPointer?) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + + // Release the dispatch source before closing the chip: gpiod_chip_close() + // drops the line's event fd, and the cancel path traps on EBADF if it's gone. + releaseSource(gpio) + if let chip = gpio.chip { + gpiod_chip_close(chip) + } + Unmanaged.fromOpaque(arg).release() + return 0 + #else + return -EINVAL + #endif +} + +@c +func swifthal_gpio_config(_ arg: UnsafeMutableRawPointer?, _ direction: swift_gpio_direction_t, _ ioMode: swift_gpio_mode_t) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + + releaseSource(gpio) + + var lrc = gpiod_line_request_config() + lrc.consumer = swifthal_gpio__consumer() + lrc.flags = 0 + + switch direction { + case SWIFT_GPIO_DIRECTION_OUT: lrc.request_type = CInt(GPIOD_LINE_REQUEST_DIRECTION_OUTPUT) + case SWIFT_GPIO_DIRECTION_IN: lrc.request_type = CInt(GPIOD_LINE_REQUEST_DIRECTION_INPUT) + default: return -EINVAL + } + + guard let flags = ioModeFlags(ioMode) else { return -EINVAL } + lrc.flags = flags + + if gpiod_line_request(gpio.line, &lrc, 0) < 0 { return -errno } + + gpio.direction = direction + gpio.ioMode = ioMode + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_gpio_set(_ arg: UnsafeMutableRawPointer?, _ level: CInt) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + if gpiod_line_set_value(gpio.line, level) < 0 { return -errno } + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_gpio_get(_ arg: UnsafeMutableRawPointer?) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let value = gpiod_line_get_value(gpio.line) + if value < 0 { return -errno } + return value + #else + return -ENOSYS + #endif +} + +@c +func swifthal_gpio_interrupt_config(_ arg: UnsafeMutableRawPointer?, _ intMode: swift_gpio_int_mode_t) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + + // Release any prior source so reconfiguring the edge mode does not leak it. + releaseSource(gpio) + + var lrc = gpiod_line_request_config() + lrc.consumer = swifthal_gpio__consumer() + lrc.flags = 0 + + switch intMode { + case SWIFT_GPIO_INT_MODE_RISING_EDGE: lrc.request_type = CInt(GPIOD_LINE_REQUEST_EVENT_RISING_EDGE) + case SWIFT_GPIO_INT_MODE_FALLING_EDGE: lrc.request_type = CInt(GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE) + case SWIFT_GPIO_INT_MODE_BOTH_EDGE: lrc.request_type = CInt(GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES) + default: return -EINVAL + } + + guard let flags = ioModeFlags(gpio.ioMode) else { return -EINVAL } + lrc.flags = flags + + if gpiod_line_is_requested(gpio.line) { + gpiod_line_release(gpio.line) + } + + if gpiod_line_request(gpio.line, &lrc, 0) < 0 { return -errno } + + let fd = gpiod_line_event_get_fd(gpio.line) + if fd < 0 { return -errno } + + let source: any DispatchSourceProtocol = gpio.direction == SWIFT_GPIO_DIRECTION_OUT + ? DispatchSource.makeWriteSource(fileDescriptor: fd, queue: gpio.queue) + : DispatchSource.makeReadSource(fileDescriptor: fd, queue: gpio.queue) + gpio.source = source + // dispatch sources are created inactive; interrupt_enable resumes. + gpio.sourceSuspended = true + gpio.intMode = intMode + return 0 + #else + return -ENOSYS + #endif +} + +// Native-Swift replacement for the former Objective-C block entry point; wires +// the Swift closure to the dispatch source's event handler. Consumed only by +// AsyncSwiftIO. +public func swifthal_gpio_interrupt_callback_install_block( + _ arg: UnsafeMutableRawPointer, + _ callback: @escaping (UInt8, timespec) -> Void +) -> CInt { + #if os(Linux) + let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + guard let source = gpio.source else { return -EINVAL } + + gpio.callback = callback + source.setEventHandler { [weak gpio] in + guard let gpio, let source = gpio.source else { return } + let fd = CInt(source.handle) + var event = gpiod_line_event() + if gpiod_line_event_read_fd(fd, &event) < 0 { + source.cancel() + return + } + gpio.callback?(event.event_type == CInt(GPIOD_LINE_EVENT_RISING_EDGE) ? 1 : 0, event.ts) + } + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_gpio_interrupt_callback_install( + _ arg: UnsafeMutableRawPointer?, + _ param: UnsafeRawPointer?, + _ callback: (@convention(c) (UnsafeRawPointer?) -> Void)? +) -> CInt { + guard let arg else { return -EINVAL } + return swifthal_gpio_interrupt_callback_install_block(arg) { _, _ in + callback?(param) + } +} + +@c +func swifthal_gpio_interrupt_callback_uninstall(_ arg: UnsafeMutableRawPointer?) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + // Fully tear down (cancel + rebalance + clear) rather than only cancel: cancel + // is permanent, and resuming a dead source would silently drop all events. + releaseSource(gpio) + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_gpio_interrupt_enable(_ arg: UnsafeMutableRawPointer?) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + guard let source = gpio.source else { return -EINVAL } + // Idempotent: resume only when currently suspended. + if gpio.sourceSuspended { + source.resume() + gpio.sourceSuspended = false + } + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_gpio_interrupt_disable(_ arg: UnsafeMutableRawPointer?) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + // With no source (e.g. after callback_uninstall) the interrupt is already + // effectively disabled; a no-op, as SwiftIO's teardown paths expect success. + guard let source = gpio.source else { return 0 } + // Idempotent: suspend only when currently running. + if !gpio.sourceSuspended { + source.suspend() + gpio.sourceSuspended = true + } + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_gpio_dev_number_get() -> CInt { + #if os(Linux) + guard let chip = gpiod_chip_open_by_name(gpioChip) else { return 0 } + defer { gpiod_chip_close(chip) } + var bulk = gpiod_line_bulk() + gpiod_chip_get_all_lines(chip, &bulk) + return CInt(bulk.num_lines) + #else + return 0 + #endif +} + +@c +func swifthal_gpio_get_fd(_ arg: UnsafeRawPointer?) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + let gpio = Unmanaged.fromOpaque(UnsafeMutableRawPointer(mutating: arg)).takeUnretainedValue() + return gpiod_line_event_get_fd(gpio.line) + #else + return -ENOSYS + #endif +} diff --git a/Sources/LinuxHalSwiftIO/I2C.swift b/Sources/LinuxHalSwiftIO/I2C.swift new file mode 100644 index 0000000..9424379 --- /dev/null +++ b/Sources/LinuxHalSwiftIO/I2C.swift @@ -0,0 +1,163 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Native-Swift port of the I2C HAL (previously swift_i2c.c), driving Linux +// i2c-dev. The I2C_SLAVE / I2C_RDWR ioctls go through the swifthal_i2c__* +// wrappers in swift_hal_native.h. + +#if canImport(Glibc) +import Glibc +#elseif canImport(Darwin) +import Darwin +#endif +import CLinuxHalSwiftIO + +private struct I2CHandle { + var id: CInt + var fd: CInt +} + +@c +func swifthal_i2c_open(_ id: CInt) -> UnsafeMutableRawPointer? { + let i2c = UnsafeMutablePointer.allocate(capacity: 1) + i2c.initialize(to: I2CHandle(id: id, fd: -1)) + + let device = "/dev/i2c-\(id)" + let fd = device.withCString { open($0, O_RDWR) } + if fd < 0 { + _ = swifthal_i2c_close(UnsafeMutableRawPointer(i2c)) + return nil + } + i2c.pointee.fd = fd + return UnsafeMutableRawPointer(i2c) +} + +@c +func swifthal_i2c_close(_ arg: UnsafeMutableRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let i2c = arg.assumingMemoryBound(to: I2CHandle.self) + if i2c.pointee.fd != -1 { + close(i2c.pointee.fd) + } + i2c.deinitialize(count: 1) + i2c.deallocate() + return 0 +} + +@c +func swifthal_i2c_config(_ arg: UnsafeMutableRawPointer?, _ speed: UInt32) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + let i2c = arg.assumingMemoryBound(to: I2CHandle.self) + swifthal__syslog( + LOG_INFO, + "LinuxHalSwiftIO: I2C speed can only be configured by driver or device tree (device \(i2c.pointee.id))") + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_i2c_write(_ arg: UnsafeMutableRawPointer?, _ address: UInt8, _ buf: UnsafePointer?, _ length: Int) -> CInt { + #if os(Linux) + guard let arg, length >= 0 else { return -EINVAL } + let i2c = arg.assumingMemoryBound(to: I2CHandle.self) + if swifthal_i2c__set_slave(i2c.pointee.fd, UInt(address)) < 0 || write(i2c.pointee.fd, buf, length) < 0 { + return -errno + } + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_i2c_read(_ arg: UnsafeMutableRawPointer?, _ address: UInt8, _ buf: UnsafeMutablePointer?, _ length: Int) -> CInt { + #if os(Linux) + guard let arg, length >= 0 else { return -EINVAL } + let i2c = arg.assumingMemoryBound(to: I2CHandle.self) + if swifthal_i2c__set_slave(i2c.pointee.fd, UInt(address)) < 0 || read(i2c.pointee.fd, buf, length) < 0 { + return -errno + } + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_i2c_write_read( + _ arg: UnsafeMutableRawPointer?, + _ addr: UInt8, + _ writeBuf: UnsafeRawPointer?, + _ numWrite: Int, + _ readBuf: UnsafeMutableRawPointer?, + _ numRead: Int +) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + guard numWrite >= 0, numRead >= 0, numWrite <= Int(UInt16.max), numRead <= Int(UInt16.max) else { + return -EINVAL + } + let i2c = arg.assumingMemoryBound(to: I2CHandle.self) + + var messages = [i2c_msg](repeating: i2c_msg(), count: 2) + messages[0].addr = UInt16(addr) + messages[0].flags = 0 + messages[0].len = UInt16(numWrite) + messages[0].buf = UnsafeMutablePointer(mutating: writeBuf?.assumingMemoryBound(to: UInt8.self)) + + // I2C_M_RD alone: the kernel emits a repeated START and addr+R before the read + // segment. I2C_M_NOSTART would suppress that (and is rejected by most + // adapters), breaking the combined write-then-read transaction. + messages[1].addr = UInt16(addr) + messages[1].flags = UInt16(I2C_M_RD) + messages[1].len = UInt16(numRead) + messages[1].buf = readBuf?.assumingMemoryBound(to: UInt8.self) + + let rc = messages.withUnsafeMutableBufferPointer { msgs -> CInt in + var data = i2c_rdwr_ioctl_data(msgs: msgs.baseAddress, nmsgs: 2) + return swifthal_i2c__rdwr(i2c.pointee.fd, &data) + } + if rc < 0 { return -errno } + return 0 + #else + return -ENOSYS + #endif +} + +// FIXME: implement this by /sys/class/i2c-dev +@c func swifthal_i2c_dev_number_get() -> CInt { 0 } + +@c +func swifthal_i2c_get_fd(_ arg: UnsafeRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let i2c = arg.assumingMemoryBound(to: I2CHandle.self) + return i2c.pointee.fd +} + +@c +func swifthal_i2c_set_addr(_ arg: UnsafeRawPointer?, _ addr: UInt8) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + let i2c = arg.assumingMemoryBound(to: I2CHandle.self) + if swifthal_i2c__set_slave(i2c.pointee.fd, UInt(addr)) < 0 { return -errno } + return 0 + #else + return -ENOSYS + #endif +} diff --git a/Sources/LinuxHalSwiftIO/OS.swift b/Sources/LinuxHalSwiftIO/OS.swift new file mode 100644 index 0000000..5bcdc15 --- /dev/null +++ b/Sources/LinuxHalSwiftIO/OS.swift @@ -0,0 +1,404 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Native-Swift reimplementation of the OS-primitives HAL (previously +// swift_os.c): threads, POSIX message queues, mutexes and counting semaphores. + +#if canImport(Glibc) +import Glibc +#elseif canImport(Darwin) +import Darwin +#endif +import Synchronization +import CLinuxHalSwiftIO + +@c +func swifthal_os_task_yield() { + _ = sched_yield() +} + +private typealias TaskFn = @convention(c) ( + UnsafeMutableRawPointer?, UnsafeMutableRawPointer?, UnsafeMutableRawPointer? +) -> Void + +private struct TaskParam { + var fn: TaskFn + var p1: UnsafeMutableRawPointer? + var p2: UnsafeMutableRawPointer? + var p3: UnsafeMutableRawPointer? +} + +private func taskMain(_ arg: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? { + let param = arg.assumingMemoryBound(to: TaskParam.self) + let p = param.pointee + p.fn(p.p1, p.p2, p.p3) + param.deinitialize(count: 1) + param.deallocate() + return nil +} + +// pthread_create's start routine takes a nullable argument on Linux but a +// non-nullable one on Darwin. +#if os(Linux) +private func taskStart(_ arg: UnsafeMutableRawPointer?) -> UnsafeMutableRawPointer? { + guard let arg else { return nil } + return taskMain(arg) +} +#else +private func taskStart(_ arg: UnsafeMutableRawPointer) -> UnsafeMutableRawPointer? { + taskMain(arg) +} +#endif + +@c +func swifthal_os_task_create( + _ name: UnsafeMutablePointer?, + _ fn: swifthal_task?, + _ p1: UnsafeMutableRawPointer?, + _ p2: UnsafeMutableRawPointer?, + _ p3: UnsafeMutableRawPointer?, + _ prio: CInt, + _ stackSize: CInt +) -> UnsafeMutableRawPointer? { + guard let fn else { return nil } + + let param = UnsafeMutablePointer.allocate(capacity: 1) + param.initialize(to: TaskParam(fn: fn, p1: p1, p2: p2, p3: p3)) + + var attr = pthread_attr_t() + var err = pthread_attr_init(&attr) + if err == 0 { err = pthread_attr_setdetachstate(&attr, CInt(PTHREAD_CREATE_DETACHED)) } + var sched = sched_param() + if err == 0 { err = pthread_attr_getschedparam(&attr, &sched) } + if err == 0 { + sched.sched_priority = prio + err = pthread_attr_setschedparam(&attr, &sched) + } + if err == 0 { err = pthread_attr_setstacksize(&attr, Int(stackSize)) } + + #if os(Linux) + var thread = pthread_t() + #else + var thread: pthread_t? + #endif + if err == 0 { err = pthread_create(&thread, &attr, taskStart, param) } + + pthread_attr_destroy(&attr) + + if err != 0 { + param.deinitialize(count: 1) + param.deallocate() + return nil + } + + // The opaque handle is the pthread_t itself: an integer on Linux, an opaque + // pointer on Darwin. + #if os(Linux) + return UnsafeMutableRawPointer(bitPattern: UInt(thread)) + #else + return thread.map { UnsafeMutableRawPointer($0) } + #endif +} + +#if os(Linux) +private func timeoutToTimespec(_ timeout: CInt) -> timespec { + var ts = timespec() + clock_gettime(CLOCK_REALTIME, &ts) + ts.tv_sec += Int(timeout) / 1000 + ts.tv_nsec += (Int(timeout) % 1000) * 1_000_000 + if ts.tv_nsec >= 1_000_000_000 { + ts.tv_sec += ts.tv_nsec / 1_000_000_000 + ts.tv_nsec %= 1_000_000_000 + } + return ts +} + +private struct MQueue { + var mq: mqd_t + var size: Int + var name: String +} + +private let mqID = Atomic(0) +#endif + +@c +func swifthal_os_mq_create(_ mqSize: Int, _ mqNum: Int) -> UnsafeRawPointer? { + #if os(Linux) + var attr = mq_attr() + attr.mq_maxmsg = mqNum + attr.mq_msgsize = mqSize + + let id = mqID.wrappingAdd(1, ordering: .relaxed).oldValue + let name = "/LinuxHalSwiftIO.\(getpid()).\(id)" + + let mqd = name.withCString { + swifthal_os__mq_open($0, O_RDWR | O_CREAT, 0o600, &attr) + } + if mqd == -1 { return nil } + + let p = UnsafeMutablePointer.allocate(capacity: 1) + p.initialize(to: MQueue(mq: mqd, size: mqSize, name: name)) + return UnsafeRawPointer(p) + #else + return nil + #endif +} + +@c +func swifthal_os_mq_destroy(_ mp: UnsafeRawPointer?) -> CInt { + #if os(Linux) + guard let mp else { return -EINVAL } + let p = UnsafeMutableRawPointer(mutating: mp).assumingMemoryBound(to: MQueue.self) + + // Attempt both operations and always free: short-circuiting on the first + // failure would leak the descriptor, the heap struct, and/or leave the named + // queue behind in /dev/mqueue. + var err: CInt = 0 + if mq_close(p.pointee.mq) != 0 { err = -errno } + if p.pointee.name.withCString({ mq_unlink($0) }) != 0, err == 0 { err = -errno } + p.deinitialize(count: 1) + p.deallocate() + return err + #else + return -ENOSYS + #endif +} + +@c +func swifthal_os_mq_send(_ mp: UnsafeRawPointer?, _ data: UnsafeRawPointer?, _ timeout: CInt) -> CInt { + #if os(Linux) + guard let mp, let data else { return -EINVAL } + let p = mp.assumingMemoryBound(to: MQueue.self) + let msg = data.assumingMemoryBound(to: CChar.self) + + if timeout == -1 { + if mq_send(p.pointee.mq, msg, p.pointee.size, 0) != 0 { return -errno } + } else { + var ts = timeoutToTimespec(timeout) + if mq_timedsend(p.pointee.mq, msg, p.pointee.size, 0, &ts) != 0 { return -errno } + } + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_os_mq_recv(_ mp: UnsafeRawPointer?, _ data: UnsafeMutableRawPointer?, _ timeout: CInt) -> CInt { + #if os(Linux) + guard let mp, let data else { return -EINVAL } + let p = mp.assumingMemoryBound(to: MQueue.self) + let buf = data.assumingMemoryBound(to: CChar.self) + var prio: UInt32 = 0 + + // NOTE: the original C (swift_os.c) tested `!= 0` here, but mq_receive returns + // the number of bytes received on success (non-zero for any real message), so + // it returned -errno on every successful receive. Corrected to `< 0`. + if timeout == -1 { + if mq_receive(p.pointee.mq, buf, p.pointee.size, &prio) < 0 { return -errno } + } else { + var ts = timeoutToTimespec(timeout) + if mq_timedreceive(p.pointee.mq, buf, p.pointee.size, &prio, &ts) < 0 { return -errno } + } + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_os_mq_purge(_ mp: UnsafeRawPointer?) -> CInt { + #if os(Linux) + guard let mp else { return -EINVAL } + let p = mp.assumingMemoryBound(to: MQueue.self) + var prio: UInt32 = 0 + + // Drain with a non-blocking receive (absolute deadline in the past) until the + // queue reports empty. Trusting mq_curmsgs and then blocking in mq_receive + // would hang if another consumer drained the queue in between, and a + // caller-controlled alloca(mq_size) risks a stack overflow. + let data = UnsafeMutableRawPointer.allocate(byteCount: max(p.pointee.size, 1), alignment: 1) + defer { data.deallocate() } + + while true { + var ts = timespec(tv_sec: 0, tv_nsec: 0) + if mq_timedreceive(p.pointee.mq, data.assumingMemoryBound(to: CChar.self), + p.pointee.size, &prio, &ts) < 0 { + if errno == ETIMEDOUT || errno == EAGAIN { break } // queue empty + return -errno + } + } + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_os_mutex_create() -> UnsafeRawPointer? { + let m = UnsafeMutablePointer.allocate(capacity: 1) + m.initialize(to: pthread_mutex_t()) + if pthread_mutex_init(m, nil) != 0 { + m.deinitialize(count: 1) + m.deallocate() + return nil + } + return UnsafeRawPointer(m) +} + +@c +func swifthal_os_mutex_destroy(_ arg: UnsafeRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let m = UnsafeMutableRawPointer(mutating: arg).assumingMemoryBound(to: pthread_mutex_t.self) + let err = pthread_mutex_destroy(m) + if err != 0 { return -err } + m.deinitialize(count: 1) + m.deallocate() + return 0 +} + +@c +func swifthal_os_mutex_lock(_ arg: UnsafeRawPointer?, _ timeout: CInt) -> CInt { + guard let arg else { return -EINVAL } + let m = UnsafeMutableRawPointer(mutating: arg).assumingMemoryBound(to: pthread_mutex_t.self) + + if timeout == -1 { + let err = pthread_mutex_lock(m) + if err != 0 { return -err } + } else { + #if os(Linux) + var ts = timeoutToTimespec(timeout) + let err = pthread_mutex_timedlock(m, &ts) + if err != 0 { return -err } + #else + return -ENOSYS + #endif + } + return 0 +} + +@c +func swifthal_os_mutex_unlock(_ arg: UnsafeRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let m = UnsafeMutableRawPointer(mutating: arg).assumingMemoryBound(to: pthread_mutex_t.self) + let err = pthread_mutex_unlock(m) + if err != 0 { return -err } + return 0 +} + +private struct Semaphore { + var sem = sem_t() + var initCnt: CInt = 0 + var limit: CInt = 0 + var giveLock = pthread_mutex_t() +} + +@c +func swifthal_os_sem_create(_ initCnt: UInt32, _ limit: UInt32) -> UnsafeRawPointer? { + guard initCnt <= UInt32(CInt.max), limit <= UInt32(CInt.max) else { return nil } + + let s = UnsafeMutablePointer.allocate(capacity: 1) + s.initialize(to: Semaphore()) + if sem_init(&s.pointee.sem, 0, initCnt) != 0 { + s.deinitialize(count: 1) + s.deallocate() + return nil + } + if pthread_mutex_init(&s.pointee.giveLock, nil) != 0 { + sem_destroy(&s.pointee.sem) + s.deinitialize(count: 1) + s.deallocate() + return nil + } + s.pointee.initCnt = CInt(initCnt) + s.pointee.limit = CInt(limit) + return UnsafeRawPointer(s) +} + +@c +func swifthal_os_sem_destroy(_ arg: UnsafeRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let s = UnsafeMutableRawPointer(mutating: arg).assumingMemoryBound(to: Semaphore.self) + + // Always destroy the mutex and free, even if sem_destroy fails, so the + // give_lock and heap struct are not leaked. + var err: CInt = 0 + if sem_destroy(&s.pointee.sem) != 0 { err = -errno } + pthread_mutex_destroy(&s.pointee.giveLock) + s.deinitialize(count: 1) + s.deallocate() + return err +} + +@c +func swifthal_os_sem_take(_ arg: UnsafeRawPointer?, _ timeout: CInt) -> CInt { + guard let arg else { return -EINVAL } + let s = UnsafeMutableRawPointer(mutating: arg).assumingMemoryBound(to: Semaphore.self) + + if timeout == -1 { + if sem_wait(&s.pointee.sem) != 0 { return -errno } + } else { + #if os(Linux) + var ts = timeoutToTimespec(timeout) + if sem_timedwait(&s.pointee.sem, &ts) != 0 { return -errno } + #else + return -ENOSYS + #endif + } + return 0 +} + +@c +func swifthal_os_sem_give(_ arg: UnsafeRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let s = UnsafeMutableRawPointer(mutating: arg).assumingMemoryBound(to: Semaphore.self) + var value: CInt = 0 + + // Enforce the counting-semaphore limit (POSIX sem_post would happily count up + // to SEM_VALUE_MAX). Serialize give against itself so the getvalue/post pair is + // atomic w.r.t. other givers. pthread_mutex_lock returns the error directly and + // does not set errno. + let lockErr = pthread_mutex_lock(&s.pointee.giveLock) + if lockErr != 0 { return -lockErr } + + var err: CInt = 0 + if sem_getvalue(&s.pointee.sem, &value) != 0 { + err = -errno + } else if s.pointee.limit == 0 || value < s.pointee.limit { + if sem_post(&s.pointee.sem) != 0 { err = -errno } // limit == 0 is uncapped + } + + pthread_mutex_unlock(&s.pointee.giveLock) + return err +} + +@c +func swifthal_os_sem_reset(_ arg: UnsafeRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let s = UnsafeMutableRawPointer(mutating: arg).assumingMemoryBound(to: Semaphore.self) + var value: CInt = 0 + + if sem_getvalue(&s.pointee.sem, &value) != 0 { return -errno } + while value > s.pointee.initCnt { + if sem_trywait(&s.pointee.sem) != 0 { + if errno == EAGAIN { break } + return -errno + } + if sem_getvalue(&s.pointee.sem, &value) != 0 { return -errno } + } + return 0 +} diff --git a/Sources/LinuxHalSwiftIO/Platform.swift b/Sources/LinuxHalSwiftIO/Platform.swift new file mode 100644 index 0000000..0f61e09 --- /dev/null +++ b/Sources/LinuxHalSwiftIO/Platform.swift @@ -0,0 +1,111 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Native-Swift reimplementation of the platform HAL (previously swift_platform.c). +// swifthal_hwcycle_get / swifthal_hwcycle_to_ns remain in C +// (swift_platform_hwcycle.c) because they require inline assembly. + +#if canImport(Glibc) +import Glibc +#elseif canImport(Darwin) +import Darwin +#endif +import CLinuxHalSwiftIO + +@c +func swifthal_ms_sleep(_ ms: Int) { + guard ms > 0 else { return } + + // nanosleep avoids usleep's useconds_t (32-bit) truncation for large delays + // and correctly handles interruption by a signal. + var ts = timespec(tv_sec: ms / 1000, tv_nsec: (ms % 1000) * 1_000_000) + var rem = timespec() + + while nanosleep(&ts, &rem) < 0, errno == EINTR { + ts = rem + } +} + +@c +func swifthal_us_wait(_ us: UInt32) { + usleep(us) +} + +@c +func swifthal_uptime_get() -> Int64 { + #if os(Linux) + var info = sysinfo() + guard sysinfo(&info) == 0 else { return -Int64(errno) } + return Int64(info.uptime) * 1000 + #else + // No sysinfo() on Darwin; CLOCK_MONOTONIC (ms since boot, excluding sleep) + // is the closest equivalent. The old C just returned 0 here. + var ts = timespec() + guard clock_gettime(CLOCK_MONOTONIC, &ts) == 0 else { return -Int64(errno) } + return Int64(ts.tv_sec) * 1000 + Int64(ts.tv_nsec) / 1_000_000 + #endif +} + +#if os(Linux) +private func urandomFill(_ buf: UnsafeMutablePointer, _ length: Int) -> Int { + let fd = open("/dev/urandom", O_RDONLY | O_CLOEXEC) + if fd < 0 { return -1 } + defer { close(fd) } + + var total = 0 + while total < length { + let n = read(fd, buf + total, length - total) + if n < 0 { + if errno == EINTR { continue } + break + } + if n == 0 { break } + total += n + } + + return total == length ? 0 : -1 +} +#endif + +// NOTE: named to match the HAL header declaration `swifthal_random_get`. The +// original C defined `swiftHal_randomGet`, a symbol that did not match the +// prototype, so it could never be linked against — a pre-existing bug, fixed +// here by adopting the declared name. +@c +func swifthal_random_get(_ buf: UnsafeMutablePointer?, _ length: Int) { + guard let buf, length > 0 else { return } + + #if os(Linux) + var total = 0 + // getrandom() may return short or fail with EINTR before the entropy pool is + // initialized; ignoring that leaves the buffer partially/fully uninitialized + // while callers treat it as random (key/nonce) material. Fill completely. + while total < length { + let n = getrandom(buf + total, length - total, 0) + if n < 0 { + if errno == EINTR { continue } + // getrandom() may be unavailable (pre-3.17 kernel, or blocked by a seccomp + // policy, returning ENOSYS/EFAULT). Fall back to /dev/urandom rather than + // crashing the process or handing back non-random bytes. + if urandomFill(buf + total, length - total) == 0 { return } + abort() // no entropy source available: fail closed + } + total += n + } + #elseif canImport(Darwin) + arc4random_buf(buf, length) + #endif +} diff --git a/Sources/LinuxHalSwiftIO/SPI.swift b/Sources/LinuxHalSwiftIO/SPI.swift new file mode 100644 index 0000000..7554be0 --- /dev/null +++ b/Sources/LinuxHalSwiftIO/SPI.swift @@ -0,0 +1,222 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Native-Swift reimplementation of the SPI HAL (previously swift_spi.c), driving +// Linux spidev. The ioctl request numbers are function-like C macros, so the +// actual ioctl() calls go through the `swifthal_spi__*` wrappers declared in +// swift_hal_native.h. + +#if canImport(Glibc) +import Glibc +#elseif canImport(Darwin) +import Darwin +#endif +import CLinuxHalSwiftIO + +private struct SPIHandle { + var fd: CInt = -1 + var speedOld: UInt32 = 0 + var operationOld: UInt16 = 0 +} + +#if os(Linux) +private func spiReadConfig(_ spi: UnsafeMutablePointer) -> CInt { + let fd = spi.pointee.fd + var speed: UInt32 = 0 + var operation: Int32 = 0 + var tmp: UInt8 = 0 + + if swifthal_spi__rd_max_speed(fd, &speed) < 0 { return -errno } + + if swifthal_spi__rd_mode(fd, &tmp) < 0 { return -errno } + if tmp & swifthal_spi__cpol != 0 { operation |= SWIFT_SPI_MODE_CPOL } + if tmp & swifthal_spi__cpha != 0 { operation |= SWIFT_SPI_MODE_CPHA } + if tmp & swifthal_spi__loop != 0 { operation |= SWIFT_SPI_MODE_LOOP } + + if swifthal_spi__rd_lsb_first(fd, &tmp) < 0 { return -errno } + operation |= (tmp != 0) ? SWIFT_SPI_TRANSFER_LSB : SWIFT_SPI_TRANSFER_MSB + + if swifthal_spi__rd_bits_per_word(fd, &tmp) < 0 { return -errno } + switch tmp { + case 8: + operation |= SWIFT_SPI_TRANSFER_8_BITS + default: + return -EINVAL + } + + spi.pointee.speedOld = speed + spi.pointee.operationOld = UInt16(truncatingIfNeeded: operation) + return 0 +} +#endif + +@c +func swifthal_spi_open( + _ id: CInt, + _ speed: Int, + _ operation: UInt16, + _ wNotify: (@convention(c) (UnsafeMutableRawPointer?) -> Void)?, + _ rNotify: (@convention(c) (UnsafeMutableRawPointer?) -> Void)? +) -> UnsafeMutableRawPointer? { + let spi = UnsafeMutablePointer.allocate(capacity: 1) + spi.initialize(to: SPIHandle()) + + // FIXME: how to integrate with CS from calling library + let device = "/dev/spidev\(id).0" + let fd = device.withCString { open($0, O_RDWR) } + if fd < 0 { + _ = swifthal_spi_close(UnsafeMutableRawPointer(spi)) + return nil + } + spi.pointee.fd = fd + + #if os(Linux) + // save previous configuration so we can restore it + if spiReadConfig(spi) < 0 + || swifthal_spi_config(UnsafeMutableRawPointer(spi), speed, operation) < 0 { + _ = swifthal_spi_close(UnsafeMutableRawPointer(spi)) + return nil + } + #endif + + return UnsafeMutableRawPointer(spi) +} + +@c +func swifthal_spi_close(_ arg: UnsafeMutableRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let spi = arg.assumingMemoryBound(to: SPIHandle.self) + + if spi.pointee.speedOld != 0 { + _ = swifthal_spi_config(arg, Int(spi.pointee.speedOld), spi.pointee.operationOld) + } + if spi.pointee.fd != -1 { + close(spi.pointee.fd) + } + spi.deinitialize(count: 1) + spi.deallocate() + return 0 +} + +@c +func swifthal_spi_config(_ arg: UnsafeMutableRawPointer?, _ speed: Int, _ operation: UInt16) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + let spi = arg.assumingMemoryBound(to: SPIHandle.self) + let fd = spi.pointee.fd + let op = Int32(operation) + var mode: UInt8 = 0 + + if op & SWIFT_SPI_MODE_CPOL != 0 { mode |= swifthal_spi__cpol } + if op & SWIFT_SPI_MODE_CPHA != 0 { mode |= swifthal_spi__cpha } + if op & SWIFT_SPI_MODE_LOOP != 0 { mode |= swifthal_spi__loop } + if op & SWIFT_SPI_TRANSFER_LSB != 0 { mode |= swifthal_spi__lsb_first } + + if swifthal_spi__wr_mode(fd, &mode) < 0 { return -errno } + + if op & SWIFT_SPI_TRANSFER_8_BITS != 0 { + var bpw: UInt8 = 8 + if swifthal_spi__wr_bits_per_word(fd, &bpw) < 0 { return -errno } + } + + if speed != 0 { + var freq = UInt32(truncatingIfNeeded: speed) + if swifthal_spi__wr_max_speed(fd, &freq) < 0 { return -errno } + } + + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_spi_write(_ arg: UnsafeMutableRawPointer?, _ buf: UnsafePointer?, _ length: Int) -> CInt { + guard let arg else { return -EINVAL } + let spi = arg.assumingMemoryBound(to: SPIHandle.self) + + let nbytes = write(spi.pointee.fd, buf, length) + if nbytes < 0 { return -errno } + return CInt(truncatingIfNeeded: nbytes) +} + +@c +func swifthal_spi_read(_ arg: UnsafeMutableRawPointer?, _ buf: UnsafeMutablePointer?, _ length: Int) -> CInt { + guard let arg else { return -EINVAL } + let spi = arg.assumingMemoryBound(to: SPIHandle.self) + + let nbytes = read(spi.pointee.fd, buf, length) + if nbytes < 0 { return -errno } + return CInt(truncatingIfNeeded: nbytes) +} + +@c +func swifthal_spi_transceive( + _ arg: UnsafeMutableRawPointer?, + _ wBuf: UnsafePointer?, + _ wLength: Int, + _ rBuf: UnsafeMutablePointer?, + _ rLength: Int +) -> CInt { + #if os(Linux) + guard let arg, let wBuf, let rBuf else { return -EINVAL } + + // Guard against a negative length: it would convert to a huge size_t and + // overflow the memset / spi_ioc_transfer len field. + guard wLength >= 0, rLength >= 0 else { return -EINVAL } + + let spi = arg.assumingMemoryBound(to: SPIHandle.self) + + rBuf.update(repeating: 0, count: rLength) + + var xfer = [spi_ioc_transfer](repeating: spi_ioc_transfer(), count: 2) + xfer[0].tx_buf = UInt64(UInt(bitPattern: UnsafeRawPointer(wBuf))) + xfer[0].len = UInt32(truncatingIfNeeded: wLength) + xfer[1].rx_buf = UInt64(UInt(bitPattern: UnsafeMutableRawPointer(rBuf))) + xfer[1].len = UInt32(truncatingIfNeeded: rLength) + + let rc = xfer.withUnsafeMutableBufferPointer { + swifthal_spi__message2(spi.pointee.fd, $0.baseAddress) + } + if rc < 0 { return -errno } + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_spi_async_write(_ arg: UnsafeMutableRawPointer?, _ buf: UnsafePointer?, _ length: Int) -> CInt { + -ENOSYS +} + +@c +func swifthal_spi_async_read(_ arg: UnsafeMutableRawPointer?, _ buf: UnsafeMutablePointer?, _ length: Int) -> CInt { + -ENOSYS +} + +// FIXME: implement this by interrogating /sys/class/spi_master +@c +func swifthal_spi_dev_number_get() -> CInt { + 0 +} + +@c +func swifthal_spi_get_fd(_ arg: UnsafeRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let spi = arg.assumingMemoryBound(to: SPIHandle.self) + return spi.pointee.fd +} diff --git a/Sources/LinuxHalSwiftIO/Stubs.swift b/Sources/LinuxHalSwiftIO/Stubs.swift new file mode 100644 index 0000000..fdbe987 --- /dev/null +++ b/Sources/LinuxHalSwiftIO/Stubs.swift @@ -0,0 +1,96 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Native-Swift ports of the unimplemented HAL subsystems (ADC, Ethernet, I2S, +// LCD, PWM, WiFi). These have no Linux backing and return the same NULL / +// -ENOSYS / 0 sentinels as the original C stubs. + +#if canImport(Glibc) +import Glibc +#elseif canImport(Darwin) +import Darwin +#endif +import CLinuxHalSwiftIO + +// MARK: - ADC + +@c func swifthal_adc_open(_ id: CInt) -> UnsafeMutableRawPointer? { nil } +@c func swifthal_adc_close(_ adc: UnsafeMutableRawPointer?) -> CInt { -ENOSYS } +@c func swifthal_adc_read(_ adc: UnsafeMutableRawPointer?, _ sampleBuffer: UnsafeMutablePointer?) -> CInt { -ENOSYS } +@c func swifthal_adc_info_get(_ adc: UnsafeMutableRawPointer?, _ info: UnsafeMutablePointer?) -> CInt { -ENOSYS } +@c func swifthal_adc_dev_number_get() -> CInt { 0 } + +// MARK: - Ethernet + +@c func swift_eth_setup_mac(_ mac: UnsafePointer?) -> CInt { -ENOSYS } +@c func swift_eth_tx_register(_ send: (@convention(c) (UnsafePointer?, CInt) -> CInt)?) -> CInt { -ENOSYS } +@c func swift_eth_rx(_ buffer: UnsafeMutablePointer?, _ len: UInt16) -> CInt { -ENOSYS } +@c func swift_eth_event_send(_ eventID: Int32, _ eventData: UnsafeMutableRawPointer?, _ eventDataSize: Int, _ ticksToWait: Int) -> CInt { -ENOSYS } + +// MARK: - I2S + +@c func swifthal_i2s_open(_ id: CInt) -> UnsafeMutableRawPointer? { nil } +@c func swifthal_i2s_handle_get(_ id: CInt) -> UnsafeMutableRawPointer? { nil } +@c func swifthal_i2s_id_get(_ i2s: UnsafeMutableRawPointer?) -> CInt { -1 } +@c func swifthal_i2s_close(_ i2s: UnsafeMutableRawPointer?) -> CInt { -ENOSYS } +@c func swifthal_i2s_config_set(_ i2s: UnsafeMutableRawPointer?, _ dir: swift_i2s_dir_t, _ cfg: UnsafePointer?) -> CInt { -ENOSYS } +@c func swifthal_i2s_config_get(_ i2s: UnsafeMutableRawPointer?, _ dir: swift_i2s_dir_t, _ cfg: UnsafeMutablePointer?) -> CInt { -ENOSYS } +@c func swifthal_i2s_trigger(_ i2s: UnsafeMutableRawPointer?, _ dir: swift_i2s_dir_t, _ cmd: i2s_trigger_cmd_t) -> CInt { -ENOSYS } +@c func swifthal_i2s_status_get(_ i2s: UnsafeMutableRawPointer?, _ dir: swift_i2s_dir_t) -> CInt { -ENOSYS } +@c func swifthal_i2s_write(_ i2s: UnsafeMutableRawPointer?, _ buf: UnsafePointer?, _ length: Int) -> CInt { -ENOSYS } +@c func swifthal_i2s_read(_ i2s: UnsafeMutableRawPointer?, _ buf: UnsafeMutablePointer?, _ length: Int) -> CInt { -ENOSYS } +@c func swifthal_i2s_dev_number_get() -> CInt { 0 } + +// MARK: - LCD + +@c func swifthal_lcd_open(_ param: UnsafePointer?) -> UnsafeMutableRawPointer? { nil } +@c func swifthal_lcd_close(_ lcd: UnsafeMutableRawPointer?) -> CInt { -ENOSYS } +@c func swifthal_lcd_start(_ lcd: UnsafeMutableRawPointer?, _ buf: UnsafeMutableRawPointer?, _ size: UInt32) -> CInt { -ENOSYS } +@c func swifthal_lcd_stop(_ lcd: UnsafeMutableRawPointer?) -> CInt { -ENOSYS } +@c func swifthal_lcd_fb_update(_ lcd: UnsafeMutableRawPointer?, _ buf: UnsafeMutableRawPointer?, _ size: UInt32) -> CInt { -ENOSYS } +@c func swifthal_lcd_screen_param_get(_ lcd: UnsafeMutableRawPointer?, _ width: UnsafeMutablePointer?, _ height: UnsafeMutablePointer?, _ format: UnsafeMutablePointer?, _ bpp: UnsafeMutablePointer?) -> CInt { + // The original C stub zeroed all out-params before returning; preserve that so + // a caller reading them after -ENOSYS sees defined zeros, not stack garbage. + width?.pointee = 0 + height?.pointee = 0 + format.map { _ = memset($0, 0, MemoryLayout.size) } + bpp?.pointee = 0 + return -ENOSYS +} +@c func swifthal_lcd_refresh_rate_get(_ lcd: UnsafeMutableRawPointer?) -> CInt { -ENOSYS } + +// MARK: - PWM + +@c func swifthal_pwm_open(_ id: CInt) -> UnsafeMutableRawPointer? { nil } +@c func swifthal_pwm_close(_ pwm: UnsafeMutableRawPointer?) -> CInt { -ENOSYS } +@c func swifthal_pwm_set(_ pwm: UnsafeMutableRawPointer?, _ period: Int, _ pulse: Int) -> CInt { -ENOSYS } +@c func swifthal_pwm_suspend(_ pwm: UnsafeMutableRawPointer?) -> CInt { -ENOSYS } +@c func swifthal_pwm_resume(_ pwm: UnsafeMutableRawPointer?) -> CInt { -ENOSYS } +@c func swifthal_pwm_info_get(_ pwm: UnsafeMutableRawPointer?, _ info: UnsafeMutablePointer?) -> CInt { -ENOSYS } +@c func swifthal_pwm_dev_number_get() -> CInt { 0 } + +// MARK: - WiFi + +@c func swift_wifi_scan(_ results: UnsafeMutablePointer?, _ num: CInt) -> CInt { -ENOSYS } +@c func swift_wifi_connect(_ ssid: UnsafeMutablePointer?, _ ssidLength: CInt, _ psk: UnsafeMutablePointer?, _ pskLength: CInt) -> CInt { -ENOSYS } +@c func swift_wifi_disconnect() -> CInt { -ENOSYS } +@c func swift_wifi_ap_mode_set(_ enable: CInt, _ ssid: UnsafeMutablePointer?, _ ssidLength: CInt, _ psk: UnsafeMutablePointer?, _ pskLength: CInt) -> CInt { -ENOSYS } +@c func swift_wifi_status_get(_ status: UnsafeMutablePointer?) -> CInt { + // The original C stub memset the status struct before returning; preserve that + // so a caller reading it after -ENOSYS sees a defined all-zero status. + status?.pointee = swift_wifi_status_t() + return -ENOSYS +} diff --git a/Sources/LinuxHalSwiftIO/Timer.swift b/Sources/LinuxHalSwiftIO/Timer.swift new file mode 100644 index 0000000..5905897 --- /dev/null +++ b/Sources/LinuxHalSwiftIO/Timer.swift @@ -0,0 +1,125 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Native-Swift port of the timer HAL (previously swift_timer.c). The original +// used libdispatch timer sources with an Objective-C block handler; this uses +// the Swift Dispatch overlay and a Swift closure, so no Blocks runtime is +// involved. The public callback is a plain C function pointer. + +#if canImport(Glibc) +import Glibc +#elseif canImport(Darwin) +import Darwin +#endif +import Dispatch +import Synchronization +import CLinuxHalSwiftIO + +private final class Timer { + let source: DispatchSourceTimer + var type: swift_timer_type_t = SWIFT_TIMER_TYPE_ONESHOT + let status = Atomic(0) + var suspended = true // dispatch sources start inactive + var callback: (@convention(c) (UnsafeRawPointer?) -> Void)? + var param: UnsafeRawPointer? + + init() { + source = DispatchSource.makeTimerSource(queue: .global()) + } +} + +@c +func swifthal_timer_open() -> UnsafeMutableRawPointer? { + Unmanaged.passRetained(Timer()).toOpaque() +} + +@c +func swifthal_timer_close(_ arg: UnsafeMutableRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let timer = Unmanaged.fromOpaque(arg).takeUnretainedValue() + + timer.source.cancel() + // A suspended source traps when its last reference is dropped; balance the + // suspend count back to zero before releasing. + if timer.suspended { + timer.source.resume() + timer.suspended = false + } + Unmanaged.fromOpaque(arg).release() + return 0 +} + +@c +func swifthal_timer_start(_ arg: UnsafeMutableRawPointer?, _ type: swift_timer_type_t, _ period: Int) -> CInt { + guard let arg else { return -EINVAL } + let timer = Unmanaged.fromOpaque(arg).takeUnretainedValue() + + timer.type = type + let interval = DispatchTimeInterval.milliseconds(Int(period)) + if type == SWIFT_TIMER_TYPE_ONESHOT { + timer.source.schedule(deadline: .now() + interval) + } else { + timer.source.schedule(deadline: .now() + interval, repeating: interval) + } + // Resume only if suspended so re-arming a running timer does not over-resume. + if timer.suspended { + timer.source.resume() + timer.suspended = false + } + return 0 +} + +@c +func swifthal_timer_stop(_ arg: UnsafeMutableRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let timer = Unmanaged.fromOpaque(arg).takeUnretainedValue() + + // Suspend rather than cancel: cancellation is permanent, so a cancel here + // would leave a subsequent timer_start unable to re-arm the timer. + if !timer.suspended { + timer.source.suspend() + timer.suspended = true + } + return 0 +} + +@c +func swifthal_timer_add_callback( + _ arg: UnsafeMutableRawPointer?, + _ param: UnsafeRawPointer?, + _ callback: (@convention(c) (UnsafeRawPointer?) -> Void)? +) -> CInt { + guard let arg else { return -EINVAL } + let timer = Unmanaged.fromOpaque(arg).takeUnretainedValue() + + timer.callback = callback + timer.param = param + // weak capture avoids a source<->timer retain cycle; the timer is kept alive + // by the retained opaque handle until timer_close releases it. + timer.source.setEventHandler { [weak timer] in + guard let timer else { return } + timer.status.wrappingAdd(1, ordering: .relaxed) + timer.callback?(timer.param) + } + return 0 +} + +@c +func swifthal_timer_status_get(_ arg: UnsafeMutableRawPointer?) -> UInt32 { + guard let arg else { return 0 } + let timer = Unmanaged.fromOpaque(arg).takeUnretainedValue() + return timer.status.exchange(0, ordering: .relaxed) +} diff --git a/Sources/LinuxHalSwiftIO/UART.swift b/Sources/LinuxHalSwiftIO/UART.swift new file mode 100644 index 0000000..fec1143 --- /dev/null +++ b/Sources/LinuxHalSwiftIO/UART.swift @@ -0,0 +1,352 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Native-Swift port of the UART HAL (previously swift_uart.c): serial-port I/O +// with a buffered, timeout-aware read path (poll-based). The termios2 line +// configuration lives in swift_uart_native.c (see swift_uart_native.h) because +// cannot be exposed to Swift. + +#if canImport(Glibc) +import Glibc +#elseif canImport(Darwin) +import Darwin +#endif +import CLinuxHalSwiftIO + +private final class UART { + var fd: CInt = -1 + let readBuf: UnsafeMutablePointer + let readBufLen: Int + var readBufOffset = 0 + var readBufConsumed = 0 + + init(bufLen: Int) { + readBufLen = bufLen + let n = max(bufLen, 1) + readBuf = UnsafeMutablePointer.allocate(capacity: n) + readBuf.initialize(repeating: 0, count: n) + } + + deinit { + readBuf.deinitialize(count: max(readBufLen, 1)) + readBuf.deallocate() + } +} + +private func handle(_ arg: UnsafeMutableRawPointer) -> UART { + Unmanaged.fromOpaque(arg).takeUnretainedValue() +} + +#if os(Linux) +private func enableNbio(_ fd: CInt) -> CInt { + let flags = fcntl(fd, F_GETFL, 0) + if flags & O_NONBLOCK == 0 { + if fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0 { + swifthal__syslog(LOG_INFO, "LinuxHalSwiftIO: failed to enable non-blocking I/O on UART fd \(fd)") + return -errno + } + } + return 0 +} + +private func parityCode(_ parity: swift_uart_parity_t) -> CInt? { + switch parity { + case SWIFT_UART_PARITY_NONE: return 0 + case SWIFT_UART_PARITY_ODD: return 1 + case SWIFT_UART_PARITY_EVEN: return 2 + default: return nil + } +} +#endif + +@c +func swifthal_uart_open(_ id: CInt, _ cfg: UnsafePointer?) -> UnsafeMutableRawPointer? { + guard let cfg else { return nil } + let uart = UART(bufLen: Int(cfg.pointee.read_buf_len)) + let opaque = Unmanaged.passRetained(uart).toOpaque() + + // FIXME: use this or /dev/serial? + let device = "/dev/ttyAMA\(id)" + let fd = device.withCString { open($0, O_RDWR) } + if fd < 0 { + _ = swifthal_uart_close(opaque) + return nil + } + uart.fd = fd + + #if os(Linux) + var err = enableNbio(fd) + if err == 0 { err = swifthal_uart__set_raw(fd) } + if err == 0 { err = swifthal_uart_baudrate_set(opaque, cfg.pointee.baudrate) } + if err == 0 { err = swifthal_uart_parity_set(opaque, cfg.pointee.parity) } + if err == 0 { err = swifthal_uart_stop_bits_set(opaque, cfg.pointee.stop_bits) } + if err == 0 { + // Call the helper directly rather than the public data_bits_set: the latter + // is also declared _Nonnull in swift_hal_extras.h, so the name is ambiguous + // at an internal call site. + switch cfg.pointee.data_bits { + case SWIFT_UART_DATA_BITS_8: err = swifthal_uart__set_data_bits_8(fd) + default: err = -EINVAL + } + } + if err != 0 { + _ = swifthal_uart_close(opaque) + return nil + } + #endif + + _ = swifthal_uart_buffer_clear(opaque) + return opaque +} + +@c +func swifthal_uart_close(_ arg: UnsafeMutableRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let uart = handle(arg) + if uart.fd != -1 { + close(uart.fd) + } + Unmanaged.fromOpaque(arg).release() + return 0 +} + +@c +func swifthal_uart_baudrate_set(_ arg: UnsafeMutableRawPointer?, _ baudrate: Int) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + guard baudrate != 0 else { return -EINVAL } + return swifthal_uart__set_baud(handle(arg).fd, UInt32(truncatingIfNeeded: baudrate)) + #else + return -ENOSYS + #endif +} + +@c +func swifthal_uart_parity_set(_ arg: UnsafeMutableRawPointer?, _ parity: swift_uart_parity_t) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + guard let code = parityCode(parity) else { return -EINVAL } + return swifthal_uart__set_parity(handle(arg).fd, code) + #else + return -ENOSYS + #endif +} + +@c +func swifthal_uart_stop_bits_set(_ arg: UnsafeMutableRawPointer?, _ stopBits: swift_uart_stop_bits_t) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + let two: CInt + switch stopBits { + case SWIFT_UART_STOP_BITS_2: two = 1 + case SWIFT_UART_STOP_BITS_1: two = 0 + default: return -EINVAL + } + return swifthal_uart__set_stop_bits(handle(arg).fd, two) + #else + return -ENOSYS + #endif +} + +@c +func swifthal_uart_data_bits_set(_ arg: UnsafeRawPointer?, _ dataBits: swift_uart_data_bits_t) -> CInt { + #if os(Linux) + guard let arg else { return -EINVAL } + switch dataBits { + case SWIFT_UART_DATA_BITS_8: + return swifthal_uart__set_data_bits_8(handle(UnsafeMutableRawPointer(mutating: arg)).fd) + default: + return -EINVAL + } + #else + return -ENOSYS + #endif +} + +@c +func swifthal_uart_config_get(_ arg: UnsafeMutableRawPointer?, _ cfg: UnsafeMutablePointer?) -> CInt { + #if os(Linux) + guard let cfg else { return -EINVAL } + cfg.pointee = swift_uart_cfg_t() + guard let arg else { return -EINVAL } + let uart = handle(arg) + + // Set read_buf_len before interrogating the line: AsyncUART reads it without + // checking the return code (the old C also filled it before the CSIZE check). + cfg.pointee.read_buf_len = uart.readBufLen + + var baud: UInt32 = 0 + var parity: CInt = 0 + var stop2: CInt = 0 + let rc = swifthal_uart__get_config(uart.fd, &baud, &parity, &stop2) + if rc < 0 { return rc } + + cfg.pointee.baudrate = Int(baud) + cfg.pointee.parity = parity == 1 ? SWIFT_UART_PARITY_ODD : (parity == 2 ? SWIFT_UART_PARITY_EVEN : SWIFT_UART_PARITY_NONE) + cfg.pointee.stop_bits = stop2 == 1 ? SWIFT_UART_STOP_BITS_2 : SWIFT_UART_STOP_BITS_1 + cfg.pointee.data_bits = SWIFT_UART_DATA_BITS_8 + return 0 + #else + return -ENOSYS + #endif +} + +@c +func swifthal_uart_char_put(_ arg: UnsafeMutableRawPointer?, _ c: UInt8) -> CInt { + var ch = c + return swifthal_uart_write(arg, &ch, 1) +} + +@c +func swifthal_uart_char_get(_ arg: UnsafeMutableRawPointer?, _ c: UnsafeMutablePointer?, _ timeout: CInt) -> CInt { + swifthal_uart_read(arg, c, 1, timeout) +} + +@c +func swifthal_uart_write(_ arg: UnsafeMutableRawPointer?, _ buf: UnsafePointer?, _ length: Int) -> CInt { + guard let arg else { return -EINVAL } + let uart = handle(arg) + + var pfd = pollfd(fd: uart.fd, events: Int16(POLLOUT), revents: 0) + var bufp = buf + var nremain = length + + while nremain > 0 { + let err = poll(&pfd, 1, -1) + if err < 0 { return -errno } + + if pfd.revents & Int16(POLLERR | POLLHUP | POLLNVAL) != 0 { return -EIO } + + if pfd.revents == Int16(POLLOUT) { + let nbytes = write(uart.fd, bufp, nremain) + if nbytes < 0 { return -errno } + nremain -= nbytes + bufp = bufp?.advanced(by: nbytes) + } + } + return 0 +} + +private func tv2ms(_ tv: timeval) -> Int64 { + Int64(tv.tv_sec) * 1000 + Int64(tv.tv_usec) / 1000 +} + +// Buffer whatever data arrives within `timeout` ms (nil = block indefinitely; +// expired = non-blocking poll), decrementing it in place by the elapsed wait. +// Returns the number of bytes still unfilled in the buffer, or a negative errno. +private func readBuffer(_ uart: UART, _ timeout: inout Int64?) -> Int { + var pfd = pollfd(fd: uart.fd, events: 0, revents: 0) + + while uart.readBufOffset < uart.readBufLen { + pfd.events = Int16(POLLIN) + pfd.revents = 0 + + var start = timeval() + gettimeofday(&start, nil) + + let err = poll(&pfd, 1, timeout.map { CInt(truncatingIfNeeded: max($0, 0)) } ?? -1) + + if timeout != nil { + var now = timeval() + gettimeofday(&now, nil) + timeout! -= tv2ms(now) - tv2ms(start) + } + + if err < 0 { return -Int(errno) } + if err == 0 { break } // timed out + + // Drain readable data first: a hangup (POLLHUP) can be reported together + // with POLLIN while bytes are still buffered, and those must not be lost. + if pfd.revents & Int16(POLLIN) != 0 { + let nbytes = read(uart.fd, uart.readBuf + uart.readBufOffset, uart.readBufLen - uart.readBufOffset) + if nbytes < 0 { return -Int(errno) } + if nbytes == 0 { break } // EOF: return the bytes already buffered + uart.readBufOffset += nbytes + break // hand the data back rather than waiting for the buffer to fill + } + + if pfd.revents & Int16(POLLERR | POLLHUP | POLLNVAL) != 0 { return -Int(EIO) } + // spurious wakeup with nothing readable: poll again with the reduced timeout + } + + return uart.readBufLen - uart.readBufOffset +} + +@c +func swifthal_uart_read(_ arg: UnsafeMutableRawPointer?, _ buf: UnsafeMutablePointer?, _ length: Int, _ timeout: CInt) -> CInt { + if length < 0 { return -EINVAL } + if length == 0 { return 0 } // a zero-byte read is not an error + guard let arg, let buf else { return -EINVAL } + let uart = handle(arg) + + var timeout64: Int64? = timeout == -1 ? nil : Int64(timeout) + var out = buf + var nremain = length + + while nremain > 0 { + // hand back any already-buffered, not-yet-consumed bytes + if uart.readBufConsumed < uart.readBufOffset { + let nconsume = min(uart.readBufOffset - uart.readBufConsumed, nremain) + memcpy(out, uart.readBuf + uart.readBufConsumed, nconsume) + uart.readBufConsumed += nconsume + out = out.advanced(by: nconsume) + nremain -= nconsume + continue + } + + // reset the buffer once fully consumed + if uart.readBufOffset == uart.readBufLen { + _ = swifthal_uart_buffer_clear(arg) + } + + // A zero-length internal buffer can never deliver data. + if uart.readBufLen == 0 { break } + + let filled = uart.readBufOffset + let res = readBuffer(uart, &timeout64) + if res < 0 { return CInt(truncatingIfNeeded: res) } + + // No new data: timeout expired, or EOF/hangup with nothing buffered — stop + // rather than re-poll (a blocking re-poll at EOF would spin forever). + if uart.readBufOffset == filled { break } + } + + return CInt(truncatingIfNeeded: nremain) +} + +@c +func swifthal_uart_remainder_get(_ arg: UnsafeMutableRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let uart = handle(arg) + return CInt(uart.readBufOffset - uart.readBufConsumed) +} + +@c +func swifthal_uart_buffer_clear(_ arg: UnsafeMutableRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + let uart = handle(arg) + uart.readBufOffset = 0 + uart.readBufConsumed = 0 + return 0 +} + +@c func swifthal_uart_dev_number_get() -> CInt { 0 } + +@c +func swifthal_uart_get_fd(_ arg: UnsafeRawPointer?) -> CInt { + guard let arg else { return -EINVAL } + return handle(UnsafeMutableRawPointer(mutating: arg)).fd +} diff --git a/Sources/LinuxHalSwiftIO/swift_counter.c b/Sources/LinuxHalSwiftIO/swift_counter.c deleted file mode 100644 index 1e61a9a..0000000 --- a/Sources/LinuxHalSwiftIO/swift_counter.c +++ /dev/null @@ -1,97 +0,0 @@ -// -// Copyright (c) 2023 PADL Software Pty Ltd -// -// Licensed under the Apache License, Version 2.0 (the License); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an 'AS IS' BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include -#include -#include -#include -#include -#include - -#include "swift_hal_internal.h" - -struct swifthal_counter { - clockid_t clockid; -}; - -void *swifthal_counter_open(int id) { - struct swifthal_counter *counter; - - counter = calloc(1, sizeof(*counter)); - if (counter == NULL) - return counter; - - counter->clockid = id; - - return counter; -} - -int swifthal_counter_close(void *arg) { - struct swifthal_counter *counter = (struct swifthal_counter *)arg; - - if (counter) { - free(counter); - return 0; - } - - return -EINVAL; -} - -int swifthal_counter_read(void *arg, uint32_t *ticks) { - struct swifthal_counter *counter = arg; - struct timespec tp; - unsigned long us; - - *ticks = 0; - - if (counter == NULL) - return -EINVAL; - - if (clock_gettime(counter->clockid, &tp) < 0) - return -errno; - - us = (unsigned long)tp.tv_sec * USEC_PER_SEC; - us += tp.tv_nsec / NSEC_PER_USEC; - - *ticks = swifthal_counter_us_to_ticks(counter, us); - return 0; -} - -int swifthal_counter_add_callback(void *arg, - const void *user_data, - void (*callback)(uint32_t, const void *)) { - return -ENOSYS; -} - -uint32_t swifthal_counter_freq(void *arg) { return 0; } - -uint64_t swifthal_counter_ticks_to_us(void *arg, uint32_t ticks) { return 0; } - -uint32_t swifthal_counter_us_to_ticks(void *arg, uint64_t us) { return 0; } - -uint32_t swifthal_counter_get_max_top_value(void *arg) { return UINT_MAX; } - -int swifthal_counter_set_channel_alarm(void *arg, uint32_t ticks) { - return -ENOSYS; -} - -int swifthal_counter_cancel_channel_alarm(void *arg) { return -ENOSYS; } - -int swifthal_counter_start(void *arg) { return -ENOSYS; } - -int swifthal_counter_stop(void *arg) { return -ENOSYS; } - -int swifthal_counter_dev_number_get(void) { return 0; } diff --git a/Sources/LinuxHalSwiftIO/swift_fs.c b/Sources/LinuxHalSwiftIO/swift_fs.c deleted file mode 100644 index b0222af..0000000 --- a/Sources/LinuxHalSwiftIO/swift_fs.c +++ /dev/null @@ -1,283 +0,0 @@ -// -// Copyright (c) 2023 PADL Software Pty Ltd -// -// Licensed under the Apache License, Version 2.0 (the License); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an 'AS IS' BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef __linux__ -#include -#elif defined(__APPLE__) -#include -#endif - -#include "swift_hal_internal.h" - -char *swifthal_mount_point_get(void) { return "/"; } - -int swifthal_fs_open(void **fp, const char *path, uint8_t flags) { - int oflags = 0; - const char *mode = NULL; - - *fp = NULL; - - if ((flags & SWIFT_FS_O_MODE_MASK) == SWIFT_FS_O_READ && - (flags & SWIFT_FS_O_FLAGS_MASK) == 0) { - mode = "r"; - } else if ((flags & SWIFT_FS_O_MODE_MASK) == SWIFT_FS_O_WRITE && - (flags & SWIFT_FS_O_FLAGS_MASK) == SWIFT_FS_O_CREATE) { - mode = "w"; - } else if ((flags & SWIFT_FS_O_MODE_MASK) == SWIFT_FS_O_WRITE && - (flags & SWIFT_FS_O_FLAGS_MASK) == - (SWIFT_FS_O_CREATE | SWIFT_FS_O_APPEND)) { - mode = "a"; - } else if ((flags & SWIFT_FS_O_MODE_MASK) == SWIFT_FS_O_RDWR && - (flags & SWIFT_FS_O_FLAGS_MASK) == 0) { - mode = "r+"; - } else if ((flags & SWIFT_FS_O_MODE_MASK) == SWIFT_FS_O_RDWR && - (flags & SWIFT_FS_O_FLAGS_MASK) == SWIFT_FS_O_CREATE) { - mode = "w+"; - } else if ((flags & SWIFT_FS_O_MODE_MASK) == SWIFT_FS_O_READ && - (flags & SWIFT_FS_O_FLAGS_MASK) == - (SWIFT_FS_O_CREATE | SWIFT_FS_O_APPEND)) { - mode = "a+"; - } else { - return -EINVAL; - } - - *fp = fopen(path, mode); - if (*fp == NULL) - return -errno; - - return 0; -} - -int swifthal_fs_close(void *fp) { - if (fclose(fp) != 0) - return -errno; - return 0; -} - -int swifthal_fs_remove(const char *path) { - if (unlink(path) != 0) - return -errno; - return 0; -} - -int swifthal_fs_rename(const char *from, char *to) { - if (rename(from, to) != 0) - return -errno; - return 0; -} - -int swifthal_fs_write(void *fp, const void *buf, ssize_t size) { - size_t nbytes; - - if (size < 0) - return -EINVAL; - if (size == 0) - return 0; // a zero-byte write is not an error - - nbytes = fwrite(buf, 1, size, fp); - if (nbytes == 0) { - if (ferror(fp)) - return -errno; - return -EIO; - } - - return (int)nbytes; -} - -int swifthal_fs_read(void *fp, void *buf, ssize_t size) { - size_t nbytes; - - if (size < 0) - return -EINVAL; - if (size == 0) - return 0; // a zero-byte read is not an error - - nbytes = fread(buf, 1, size, fp); - if (nbytes == 0) { - if (ferror(fp)) - return -errno; - if (feof(fp)) - return 0; - return -EIO; - } - - return (int)nbytes; -} - -int swifthal_fs_seek(void *fp, ssize_t offset, int whence) { - int fwhence = 0; - - switch (whence) { - case SWIFT_FS_SEEK_SET: - fwhence = SEEK_SET; - break; - case SWIFT_FS_SEEK_CUR: - fwhence = SEEK_CUR; - break; - case SWIFT_FS_SEEK_END: - fwhence = SEEK_END; - break; - } - - // fseeko/ftello use off_t so the seek itself is correct for >2GiB offsets - // (the int return type still bounds what tell can report back). - if (fseeko(fp, offset, fwhence) != 0) - return -errno; - return 0; -} - -int swifthal_fs_tell(void *fp) { - off_t offset = ftello(fp); - if (offset == -1) - return -errno; - return (int)offset; -} - -int swifthal_fs_truncate(void *fp, ssize_t length) { - if (ftruncate(fileno(fp), length) < 0) - return -errno; - return 0; -} - -int swifthal_fs_sync(void *fp) { - if (fsync(fileno(fp)) < 0) - return -errno; - return 0; -} - -int swifthal_fs_mkdir(const char *path) { - if (mkdir(path, 0777) < 0) - return -errno; - return 0; -} - -int swifthal_fs_opendir(void **dp, const char *path) { - *dp = opendir(path); - if (*dp == NULL) - return -errno; - return 0; -} - -int swifthal_fs_readdir(void *dp, swift_fs_dirent_t *entry) { - struct dirent *dentry; - -next: - // readdir() returns NULL both at end-of-directory and on error; per POSIX, - // errno must be cleared first to tell them apart. - errno = 0; - dentry = readdir(dp); - if (dentry == NULL) { - if (errno != 0) - return -errno; - else { - entry->name[0] = '\0'; - return 0; - } - } - - { - unsigned char d_type = dentry->d_type; - - // Some filesystems always report DT_UNKNOWN; stat to classify the entry. - if (d_type == DT_UNKNOWN) { - struct stat sb; - - // The entry may have been unlinked between readdir and here, or be a - // dangling symlink; skip it rather than aborting the whole enumeration. - if (fstatat(dirfd(dp), dentry->d_name, &sb, 0) < 0) - goto next; - if (S_ISDIR(sb.st_mode)) - d_type = DT_DIR; - else if (S_ISREG(sb.st_mode)) - d_type = DT_REG; - } - - if (d_type == DT_DIR) { - entry->type = SWIFT_FS_DIR_ENTRY_DIR; - strncpy(entry->name, dentry->d_name, sizeof(entry->name) - 1); - entry->name[sizeof(entry->name) - 1] = '\0'; - entry->size = 0; - } else if (d_type == DT_REG) { - struct stat statbuf; - - entry->type = SWIFT_FS_DIR_ENTRY_FILE; - strncpy(entry->name, dentry->d_name, sizeof(entry->name) - 1); - entry->name[sizeof(entry->name) - 1] = '\0'; - if (fstatat(dirfd(dp), dentry->d_name, &statbuf, 0) < 0) - return -errno; - entry->size = statbuf.st_size; - } else - goto next; - } - - return 0; -} - -int swifthal_fs_closedir(void *dp) { - if (closedir(dp) < 0) - return -errno; - return 0; -} - -int swifthal_fs_stat(const char *path, swift_fs_dirent_t *entry) { - struct stat statbuf; - - if (stat(path, &statbuf) < 0) - return -errno; - - if (S_ISDIR(statbuf.st_mode)) { - entry->type = SWIFT_FS_DIR_ENTRY_DIR; - entry->size = 0; - } else if (S_ISREG(statbuf.st_mode)) { - entry->type = SWIFT_FS_DIR_ENTRY_FILE; - entry->size = statbuf.st_size; - } else { - return -ENOENT; - } - - // The name field is not derivable here (the caller passed a path); make it a - // defined empty string rather than leaving caller stack garbage. - entry->name[0] = '\0'; - - return 0; -} - -int swifthal_fs_statfs(const char *path, swift_fs_statvfs_t *stat) { - struct statfs statfsbuf; - - if (statfs(path, &statfsbuf) < 0) - return -errno; - - memset(stat, 0, sizeof(*stat)); - stat->f_bsize = statfsbuf.f_bsize; -#ifdef __linux__ - stat->f_frsize = statfsbuf.f_frsize; -#endif - stat->f_blocks = statfsbuf.f_blocks; - stat->f_bfree = statfsbuf.f_bfree; - - return 0; -} diff --git a/Sources/LinuxHalSwiftIO/swift_gpio.c b/Sources/LinuxHalSwiftIO/swift_gpio.c deleted file mode 100644 index 693e135..0000000 --- a/Sources/LinuxHalSwiftIO/swift_gpio.c +++ /dev/null @@ -1,444 +0,0 @@ -// -// Copyright (c) 2023 PADL Software Pty Ltd -// -// Licensed under the Apache License, Version 2.0 (the License); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an 'AS IS' BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include -#include -#include -#include -#include -#ifdef __linux__ -#include -#endif -#include "swift_hal_internal.h" - -#ifndef __linux__ -struct gpiod_chip; -#endif - -struct swifthal_gpio { - swift_gpio_direction_t direction; - swift_gpio_mode_t io_mode; - swift_gpio_int_mode_t int_mode; - void (^block)(uint8_t, struct timespec); -#ifdef __linux__ - struct gpiod_chip *chip; - struct gpiod_line *line; -#endif - dispatch_queue_t queue; - dispatch_source_t source; - bool source_suspended; -}; - -// Cancel and release the interrupt dispatch source, bringing its suspend count -// to exactly zero first. libdispatch traps on releasing a suspended source and -// on over-resuming an active one, so track whether we currently hold it -// suspended and resume only when we do. Safe to call with no source installed. -static void swifthal_gpio__release_source(struct swifthal_gpio *gpio) { - if (gpio->source == NULL) - return; - - dispatch_source_cancel(gpio->source); - if (gpio->source_suspended) { - dispatch_resume(gpio->source); - gpio->source_suspended = false; - } - dispatch_release(gpio->source); - gpio->source = NULL; -} - -static struct swifthal_gpio * -swifthal_gpio__chip2gpio(int id, - struct gpiod_chip **chip, - swift_gpio_direction_t direction, - swift_gpio_mode_t io_mode) { - struct swifthal_gpio *gpio; - - gpio = calloc(1, sizeof(*gpio)); - if (gpio == NULL) - return NULL; - -#ifdef __linux__ - gpio->chip = *chip; - *chip = NULL; - - gpio->line = gpiod_chip_get_line(gpio->chip, id); - if (gpio->line == NULL) { - swifthal_gpio_close(gpio); - return NULL; - } - - if (swifthal_gpio_config(gpio, direction, io_mode) < 0) { - swifthal_gpio_close(gpio); - return NULL; - } -#endif - - gpio->queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); - - return gpio; -} - -void *swifthal_gpio_open(int id, - swift_gpio_direction_t direction, - swift_gpio_mode_t io_mode) { -#ifdef __linux__ - struct swifthal_gpio *gpio = NULL; - struct gpiod_chip *chip; - struct gpiod_chip_iter *iter = gpiod_chip_iter_new(); - - // http://git.munts.com/muntsos/doc/AppNote11-link-gpiochip.pdf - - gpiod_foreach_chip(iter, chip) { - const char *label = gpiod_chip_label(chip); - - if (strcmp(label, "pinctrl-rp1") == 0 || - strcmp(label, "pinctrl-bcm2835") == 0 || - strcmp(label, "pinctrl-bcm2711") == 0) { - gpio = swifthal_gpio__chip2gpio(id, &chip, direction, io_mode); - gpiod_chip_iter_free_noclose(iter); - return gpio; - } - } - - gpiod_chip_iter_free(iter); -#endif - - return NULL; -} - -void *swifthal_gpio__open(int id, - const char *name, - swift_gpio_direction_t direction, - swift_gpio_mode_t io_mode) { -#ifdef __linux__ - struct gpiod_chip *chip; - - chip = gpiod_chip_open_by_name(name); - if (chip == NULL) - return NULL; - - return swifthal_gpio__chip2gpio(id, &chip, direction, io_mode); -#endif - - return NULL; -} - -int swifthal_gpio_close(void *arg) { - struct swifthal_gpio *gpio = (struct swifthal_gpio *)arg; - - if (gpio) { - // Cancel and release the dispatch source before closing the chip: - // gpiod_chip_close() drops the line's event fd, and on - // swift-corelibs-libdispatch the cancel path will EPOLL_CTL_DEL the fd - // and trap on EBADF if it's already gone. - swifthal_gpio__release_source(gpio); -#ifdef __linux__ - if (gpio->chip) - gpiod_chip_close(gpio->chip); -#endif - if (gpio->block) - _Block_release(gpio->block); - free(gpio); - return 0; - } - - return -EINVAL; -} - -static int swifthal_gpio__io_mode_flags(swift_gpio_mode_t io_mode, int *flags) { -#ifdef __linux__ - switch (io_mode) { - case SWIFT_GPIO_MODE_PULL_UP: - *flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP; - break; - case SWIFT_GPIO_MODE_PULL_DOWN: - *flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_DOWN; - break; - case SWIFT_GPIO_MODE_PULL_NONE: - *flags |= GPIOD_LINE_REQUEST_FLAG_BIAS_DISABLE; - break; - case SWIFT_GPIO_MODE_OPEN_DRAIN: - *flags |= GPIOD_LINE_REQUEST_FLAG_OPEN_DRAIN; - break; - default: - *flags = 0; - return -EINVAL; - } - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_gpio_config(void *arg, - swift_gpio_direction_t direction, - swift_gpio_mode_t io_mode) { -#ifdef __linux__ - int err; - struct swifthal_gpio *gpio = (struct swifthal_gpio *)arg; - struct gpiod_line_request_config lrc; - - if (gpio == NULL) - return -EINVAL; - - lrc.consumer = SWIFTHAL_GPIO_CONSUMER; - lrc.flags = 0; - - swifthal_gpio__release_source(gpio); - - switch (direction) { - case SWIFT_GPIO_DIRECTION_OUT: - lrc.request_type = GPIOD_LINE_REQUEST_DIRECTION_OUTPUT; - break; - case SWIFT_GPIO_DIRECTION_IN: - lrc.request_type = GPIOD_LINE_REQUEST_DIRECTION_INPUT; - break; - default: - return -EINVAL; - } - - err = swifthal_gpio__io_mode_flags(io_mode, &lrc.flags); - if (err) - return err; - - if (gpiod_line_request(gpio->line, &lrc, 0) < 0) - return -errno; - - gpio->direction = direction; - gpio->io_mode = io_mode; - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_gpio_set(void *arg, int level) { - const struct swifthal_gpio *gpio = arg; - - if (gpio == NULL) - return -EINVAL; - -#ifdef __linux__ - if (gpiod_line_set_value(gpio->line, level) < 0) - return -errno; - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_gpio_get(void *arg) { - const struct swifthal_gpio *gpio = arg; - int value; - - if (gpio == NULL) - return -EINVAL; - -#ifdef __linux__ - value = gpiod_line_get_value(gpio->line); - if (value < 0) - return -errno; - - return value; -#else - return -ENOSYS; -#endif -} - -int swifthal_gpio_interrupt_config(void *arg, swift_gpio_int_mode_t int_mode) { -#ifdef __linux__ - int err; - struct swifthal_gpio *gpio = (struct swifthal_gpio *)arg; - struct gpiod_line_request_config lrc; - int fd; - - if (gpio == NULL) - return -EINVAL; - - // Release any source from a previous interrupt_config so reconfiguring the - // edge mode does not leak the old dispatch source (which would keep watching - // the now-released line's stale event fd). - swifthal_gpio__release_source(gpio); - - lrc.consumer = SWIFTHAL_GPIO_CONSUMER; - lrc.flags = 0; - - switch (int_mode) { - case SWIFT_GPIO_INT_MODE_RISING_EDGE: - lrc.request_type = GPIOD_LINE_REQUEST_EVENT_RISING_EDGE; - break; - case SWIFT_GPIO_INT_MODE_FALLING_EDGE: - lrc.request_type = GPIOD_LINE_REQUEST_EVENT_FALLING_EDGE; - break; - case SWIFT_GPIO_INT_MODE_BOTH_EDGE: - lrc.request_type = GPIOD_LINE_REQUEST_EVENT_BOTH_EDGES; - break; - case SWIFT_GPIO_INT_MODE_HIGH_LEVEL: - case SWIFT_GPIO_INT_MODE_LOW_LEVEL: - default: - return -EINVAL; - } - - err = swifthal_gpio__io_mode_flags(gpio->io_mode, &lrc.flags); - if (err) - return err; - - if (gpiod_line_is_requested(gpio->line)) - gpiod_line_release(gpio->line); - - if (gpiod_line_request(gpio->line, &lrc, 0) < 0) - return -errno; - - fd = gpiod_line_event_get_fd(gpio->line); - if (fd < 0) - return -errno; - - gpio->source = dispatch_source_create( - gpio->direction == SWIFT_GPIO_DIRECTION_OUT ? DISPATCH_SOURCE_TYPE_WRITE - : DISPATCH_SOURCE_TYPE_READ, - fd, 0, gpio->queue); - if (gpio->source == NULL) - return -ENOMEM; - - // dispatch sources are created suspended; interrupt_enable resumes. - gpio->source_suspended = true; - gpio->int_mode = int_mode; - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_gpio_interrupt_callback_install_block( - void *arg, void (^block)(uint8_t risingEdge, struct timespec ts)) { -#ifdef __linux__ - struct swifthal_gpio *gpio = arg; - - if (gpio == NULL || gpio->source == NULL) - return -EINVAL; - - if (gpio->block) - _Block_release(gpio->block); - gpio->block = _Block_copy(block); - - dispatch_source_set_event_handler(gpio->source, ^{ - int fd = dispatch_source_get_handle(gpio->source); - struct gpiod_line_event event; - - memset(&event, 0, sizeof(event)); - - if (gpiod_line_event_read_fd(fd, &event) < 0) { - dispatch_source_cancel(gpio->source); - return; - } - - gpio->block(event.event_type == GPIOD_LINE_EVENT_RISING_EDGE, event.ts); - }); - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_gpio_interrupt_callback_install(void *arg, - const void *param, - void (*callback)(const void *)) { - return swifthal_gpio_interrupt_callback_install_block( - arg, ^(uint8_t risingEdge, struct timespec ts) { - callback(param); - }); -} - -int swifthal_gpio_interrupt_callback_uninstall(void *arg) { - struct swifthal_gpio *gpio = arg; - - if (gpio == NULL) - return -EINVAL; - - if (gpio->source) - dispatch_source_cancel(gpio->source); - - return 0; -} - -int swifthal_gpio_interrupt_enable(void *arg) { - struct swifthal_gpio *gpio = arg; - - if (gpio == NULL || gpio->source == NULL) - return -EINVAL; - - // Idempotent: resume only when currently suspended, otherwise a second - // enable (or an enable after open) over-resumes the source and traps. - if (gpio->source_suspended) { - dispatch_resume(gpio->source); - gpio->source_suspended = false; - } - - return 0; -} - -int swifthal_gpio_interrupt_disable(void *arg) { - struct swifthal_gpio *gpio = arg; - - if (gpio == NULL || gpio->source == NULL) - return -EINVAL; - - // Idempotent: suspend only when currently running, otherwise a second - // disable over-suspends and later traps on release. - if (!gpio->source_suspended) { - dispatch_suspend(gpio->source); - gpio->source_suspended = true; - } - - return 0; -} - -int swifthal_gpio_dev_number_get(void) { -#ifdef __linux__ - struct gpiod_chip *chip; - struct gpiod_line_bulk bulk; - - chip = gpiod_chip_open_by_name(SWIFTHAL_GPIOCHIP); - if (chip == NULL) { - return 0; - } - - memset(&bulk, 0, sizeof(bulk)); - gpiod_chip_get_all_lines(chip, &bulk); - gpiod_chip_close(chip); - - return bulk.num_lines; -#else - return 0; -#endif -} - -int swifthal_gpio_get_fd(const void *arg) { - const struct swifthal_gpio *gpio = arg; - - if (gpio == NULL) - return -EINVAL; - -#if __linux__ - return gpiod_line_event_get_fd(gpio->line); -#else - return -ENOSYS; -#endif -} diff --git a/Sources/LinuxHalSwiftIO/swift_hal_internal.h b/Sources/LinuxHalSwiftIO/swift_hal_internal.h deleted file mode 100644 index 85d0478..0000000 --- a/Sources/LinuxHalSwiftIO/swift_hal_internal.h +++ /dev/null @@ -1,133 +0,0 @@ -// -// Copyright (c) 2023 PADL Software Pty Ltd -// -// Licensed under the Apache License, Version 2.0 (the License); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an 'AS IS' BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#pragma once - -#include -#include -#include - -#include - -#if __has_include() -#include -#elif __has_include() -#include -#else -void *_Block_copy(const void *); -void _Block_release(const void *); -#endif - -#if __has_include() -#include -#else -// on Linux dispatch/dispatch.h is only available with unsafe Swift flags, -// which preclude the use of this package as a versioned dependency -struct dispatch_source_type_s { -} __attribute__((aligned(sizeof(uintptr_t)))); - -typedef struct dispatch_source_s *dispatch_source_t; -typedef struct dispatch_queue_s *dispatch_queue_t; -typedef const struct dispatch_source_type_s *dispatch_source_type_t; - -typedef void (^dispatch_block_t)(void); - -extern const struct dispatch_source_type_s _dispatch_source_type_read; -extern const struct dispatch_source_type_s _dispatch_source_type_write; -extern const struct dispatch_source_type_s _dispatch_source_type_timer; - -void dispatch_release(void *object); -void dispatch_resume(void *object); -void dispatch_suspend(void *object); - -void *dispatch_get_context(void *object); -void dispatch_set_context(void *object, void *context); - -void dispatch_source_cancel(void *object); -void dispatch_source_set_event_handler(dispatch_source_t source, - dispatch_block_t handler); -void dispatch_source_set_cancel_handler(dispatch_source_t source, - dispatch_block_t handler); - -typedef uint64_t dispatch_time_t; - -uintptr_t dispatch_source_get_handle(dispatch_source_t source); - -dispatch_queue_t dispatch_get_global_queue(intptr_t identifier, - uintptr_t flags); -dispatch_source_t dispatch_source_create(dispatch_source_type_t type, - uintptr_t handle, - uintptr_t mask, - dispatch_queue_t queue); -void dispatch_source_set_timer(dispatch_source_t source, - dispatch_time_t start, - uint64_t interval, - uint64_t leeway); -dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta); - -#define dispatch_cancel dispatch_source_cancel -#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 -#define DISPATCH_SOURCE_TYPE_READ (&_dispatch_source_type_read) -#define DISPATCH_SOURCE_TYPE_WRITE (&_dispatch_source_type_write) -#define DISPATCH_SOURCE_TYPE_TIMER (&_dispatch_source_type_timer) -#define DISPATCH_TIME_NOW (0ull) -#define DISPATCH_TIME_FOREVER (~0ull) -#endif - -#include - -#include "swift_hal.h" -#include "swift_hal_extras.h" - -#define SWIFTHAL_GPIO_CONSUMER "SwiftIO" -#define SWIFTHAL_GPIOCHIP "gpiochip0" // FIXME: make configurable - -#ifndef SWIFT_SPI_TRANSFER_8_BITS -#define SWIFT_SPI_TRANSFER_8_BITS (1 << 5) // undocumented -#endif - -#ifndef MSEC_PER_SEC -#define MSEC_PER_SEC (1000) -#endif - -#ifndef USEC_PER_MSEC -#define USEC_PER_MSEC (1000) -#endif - -#ifndef NSEC_PER_USEC -#define NSEC_PER_USEC (1000) -#endif - -#ifndef NSEC_PER_MSEC -#define NSEC_PER_MSEC (NSEC_PER_USEC * USEC_PER_MSEC) -#endif - -#ifndef USEC_PER_SEC -#define USEC_PER_SEC (USEC_PER_MSEC * MSEC_PER_SEC) -#endif - -struct swifthal_counter; -struct swifthal_gpio; -struct swifthal_i2c; -struct swifthal_os_task; -struct swifthal_spi; -struct swifthal_timer; -struct swifthal_uart; - -void *swifthal_gpio__open(int id, - const char *chip, - swift_gpio_direction_t direction, - swift_gpio_mode_t io_mode); diff --git a/Sources/LinuxHalSwiftIO/swift_i2c.c b/Sources/LinuxHalSwiftIO/swift_i2c.c deleted file mode 100644 index 2ed96b6..0000000 --- a/Sources/LinuxHalSwiftIO/swift_i2c.c +++ /dev/null @@ -1,193 +0,0 @@ -// -// Copyright (c) 2023 PADL Software Pty Ltd -// -// Licensed under the Apache License, Version 2.0 (the License); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an 'AS IS' BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef __linux__ -#include -#include -#endif - -#include "swift_hal_internal.h" - -struct swifthal_i2c { - int id; - int fd; -}; - -void *swifthal_i2c_open(int id) { - struct swifthal_i2c *i2c; - char device[PATH_MAX + 1]; - - i2c = calloc(1, sizeof(*i2c)); - if (i2c == NULL) - return NULL; - - snprintf(device, sizeof(device), "/dev/i2c-%d", id); - - i2c->id = id; - i2c->fd = open(device, O_RDWR); - if (i2c->fd < 0) { - swifthal_i2c_close(i2c); - return NULL; - } - - return i2c; -} - -int swifthal_i2c_close(void *arg) { - struct swifthal_i2c *i2c = (struct swifthal_i2c *)arg; - - if (i2c) { - if (i2c->fd != -1) - close(i2c->fd); - free(i2c); - return 0; - } - - return -EINVAL; -} - -int swifthal_i2c_config(void *arg, unsigned int speed) { -#ifdef __linux__ - struct swifthal_i2c *i2c = (struct swifthal_i2c *)arg; - - if (i2c == NULL) - return -EINVAL; - - syslog(LOG_INFO, - "LinuxHalSwiftIO: I2C speed can only be configured " - "by driver or device tree (device %d)", - i2c->id); - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_i2c_write(void *arg, - uint8_t address, - const uint8_t *buf, - ssize_t length) { -#ifdef __linux__ - const struct swifthal_i2c *i2c = arg; - - if (i2c == NULL || length < 0) - return -EINVAL; - - if (ioctl(i2c->fd, I2C_SLAVE, address) < 0 || write(i2c->fd, buf, length) < 0) - return -errno; - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_i2c_read(void *arg, - uint8_t address, - uint8_t *buf, - ssize_t length) { -#ifdef __linux__ - const struct swifthal_i2c *i2c = arg; - - if (i2c == NULL || length < 0) - return -EINVAL; - - if (ioctl(i2c->fd, I2C_SLAVE, address) < 0 || read(i2c->fd, buf, length) < 0) - return -errno; - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_i2c_write_read(void *arg, - uint8_t addr, - const void *write_buf, - ssize_t num_write, - void *read_buf, - ssize_t num_read) { -#ifdef __linux__ - const struct swifthal_i2c *i2c = arg; - struct i2c_msg messages[2]; - struct i2c_rdwr_ioctl_data ioctl_data = {messages, sizeof(messages) / - sizeof(messages[0])}; - - if (i2c == NULL) - return -EINVAL; - - if (num_write < 0 || num_read < 0 || num_write > UINT16_MAX || - num_read > UINT16_MAX) - return -EINVAL; - - messages[0].addr = addr; - messages[0].flags = 0; - messages[0].len = num_write; - messages[0].buf = (uint8_t *)write_buf; - - // I2C_M_RD alone: the kernel emits a repeated START and addr+R before the - // read segment. I2C_M_NOSTART would suppress that (and is rejected by most - // adapters), breaking the combined write-then-read transaction. - messages[1].addr = addr; - messages[1].flags = I2C_M_RD; - messages[1].len = num_read; - messages[1].buf = read_buf; - - if (ioctl(i2c->fd, I2C_RDWR, &ioctl_data) < 0) - return -errno; - - return 0; -#else - return -ENOSYS; -#endif -} - -// FIXME: implement this by /sys/class/i2c-dev -int swifthal_i2c_dev_number_get(void) { return 0; } - -int swifthal_i2c_get_fd(const void *arg) { - const struct swifthal_i2c *i2c = arg; - - if (i2c == NULL) - return -EINVAL; - - return i2c->fd; -} - -int swifthal_i2c_set_addr(const void *arg, uint8_t addr) { - const struct swifthal_i2c *i2c = arg; - - if (i2c == NULL) - return -EINVAL; - -#ifdef __linux__ - if (ioctl(i2c->fd, I2C_SLAVE, addr) < 0) - return -errno; - - return 0; -#else - return -ENOSYS; -#endif -} diff --git a/Sources/LinuxHalSwiftIO/swift_i2s.c b/Sources/LinuxHalSwiftIO/swift_i2s.c deleted file mode 100644 index 258e7f2..0000000 --- a/Sources/LinuxHalSwiftIO/swift_i2s.c +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright (c) 2023 PADL Software Pty Ltd -// -// Licensed under the Apache License, Version 2.0 (the License); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an 'AS IS' BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include -#include -#include -#include -#include - -#include "swift_hal_internal.h" - -void *swifthal_i2s_open(int id) { return NULL; } - -void *swifthal_i2s_handle_get(int id) { return NULL; } - -int swifthal_i2s_id_get(void *i2s) { return -1; } - -int swifthal_i2s_close(void *i2s) { return -ENOSYS; } - -int swifthal_i2s_config_set(void *i2s, - const swift_i2s_dir_t dir, - const swift_i2s_cfg_t *cfg) { - return -ENOSYS; -} - -int swifthal_i2s_config_get(void *i2s, - const swift_i2s_dir_t dir, - swift_i2s_cfg_t *cfg) { - return -ENOSYS; -} - -int swifthal_i2s_trigger(void *i2s, - const swift_i2s_dir_t dir, - const i2s_trigger_cmd_t cmd) { - return -ENOSYS; -} - -int swifthal_i2s_status_get(void *i2s, const swift_i2s_dir_t dir) { - return -ENOSYS; -} - -int swifthal_i2s_write(void *i2s, const uint8_t *buf, ssize_t length) { - return -ENOSYS; -} - -int swifthal_i2s_read(void *i2s, uint8_t *buf, ssize_t length) { - return -ENOSYS; -} - -int swifthal_i2s_dev_number_get(void) { return 0; } diff --git a/Sources/LinuxHalSwiftIO/swift_lcd.c b/Sources/LinuxHalSwiftIO/swift_lcd.c deleted file mode 100644 index 51e3c3a..0000000 --- a/Sources/LinuxHalSwiftIO/swift_lcd.c +++ /dev/null @@ -1,51 +0,0 @@ -// -// Copyright (c) 2023 PADL Software Pty Ltd -// -// Licensed under the Apache License, Version 2.0 (the License); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an 'AS IS' BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include -#include -#include -#include -#include - -#include "swift_hal_internal.h" - -void *swifthal_lcd_open(const swift_lcd_panel_param_t *param) { return NULL; } - -int swifthal_lcd_close(void *lcd) { return -ENOSYS; } - -int swifthal_lcd_start(void *lcd, void *buf, unsigned int size) { - return -ENOSYS; -} - -int swifthal_lcd_stop(void *lcd) { return -ENOSYS; } - -int swifthal_lcd_fb_update(void *lcd, void *buf, unsigned int size) { - return -ENOSYS; -} - -int swifthal_lcd_screen_param_get(void *lcd, - int *width, - int *height, - swift_lcd_pixel_format_t *format, - int *bpp) { - *width = 0; - *height = 0; - memset(format, 0, sizeof(*format)); - *bpp = 0; - return -ENOSYS; -} - -int swifthal_lcd_refresh_rate_get(void *lcd) { return -ENOSYS; } diff --git a/Sources/LinuxHalSwiftIO/swift_os.c b/Sources/LinuxHalSwiftIO/swift_os.c deleted file mode 100644 index 02eeaa5..0000000 --- a/Sources/LinuxHalSwiftIO/swift_os.c +++ /dev/null @@ -1,428 +0,0 @@ -// -// Copyright (c) 2023 PADL Software Pty Ltd -// -// Licensed under the Apache License, Version 2.0 (the License); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an 'AS IS' BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef __linux__ -#include -#endif -#include - -#include "swift_hal_internal.h" - -void swifthal_os_task_yield(void) { sched_yield(); } - -struct swifthal_os_task { - swifthal_task fn; - void *p1, *p2, *p3; -}; - -static void *swifthal_os_task__start(void *arg) { - struct swifthal_os_task *param = arg; - param->fn(param->p1, param->p2, param->p3); - free(param); - return NULL; -} - -void *swifthal_os_task_create(char *name, - swifthal_task fn, - void *p1, - void *p2, - void *p3, - int prio, - int stack_size) { - pthread_attr_t attr; - struct sched_param param; - struct swifthal_os_task *task; - pthread_t thread; - int err; - - task = malloc(sizeof(*task)); - if (task == NULL) - return NULL; - - task->fn = fn; - task->p1 = p1; - task->p2 = p2; - task->p3 = p3; - - err = pthread_attr_init(&attr); - if (err == 0) - err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - if (err == 0) - err = pthread_attr_getschedparam(&attr, ¶m); - if (err == 0) { - param.sched_priority = prio; - err = pthread_attr_setschedparam(&attr, ¶m); - } - if (err == 0) - err = pthread_attr_setstacksize(&attr, stack_size); - if (err == 0) - err = pthread_create(&thread, &attr, swifthal_os_task__start, task); - - pthread_attr_destroy(&attr); - - if (err) { - free(task); - return NULL; - } - - return (void *)((intptr_t)thread); -} - -#ifdef __linux__ -struct swifthal_os_task__mqueue { - mqd_t mq; - int mq_size; - char name[NAME_MAX]; -}; - -static _Atomic(uintptr_t) mq_id = 0; -#endif - -const void *swifthal_os_mq_create(ssize_t mq_size, ssize_t mq_num) { -#ifdef __linux__ - struct swifthal_os_task__mqueue *mq = calloc(1, sizeof(*mq)); - struct mq_attr attr; - - if (mq == NULL) - return NULL; - - memset(&attr, 0, sizeof(attr)); - attr.mq_maxmsg = mq_num; - attr.mq_msgsize = mq_size; - - snprintf(mq->name, sizeof(mq->name), "/LinuxHalSwiftIO.%d.%lu", getpid(), - atomic_fetch_add(&mq_id, 1)); - - mq->mq = mq_open(mq->name, O_RDWR | O_CREAT, 0600, &attr); - if (mq->mq == -1) { - free(mq); - return NULL; - } - - mq->mq_size = mq_size; - - return mq; -#else - return NULL; -#endif -} - -int swifthal_os_mq_destroy(const void *mp) { -#ifdef __linux__ - struct swifthal_os_task__mqueue *mq = (struct swifthal_os_task__mqueue *)mp; - - if (mq) { - int err = 0; - - // Attempt both operations and always free: short-circuiting on the first - // failure would leak the descriptor, the heap struct, and/or leave the - // named queue behind in /dev/mqueue. - if (mq_close(mq->mq) != 0) - err = -errno; - if (mq_unlink(mq->name) != 0 && err == 0) - err = -errno; - free(mq); - return err; - } - - return -EINVAL; -#else - return -ENOSYS; -#endif -} - -static struct timespec *swifthal_timeout_to_timespec(int timeout, - struct timespec *ts) { - if (timeout == -1) - return NULL; - - clock_gettime(CLOCK_REALTIME, ts); - ts->tv_sec += timeout / 1000; - ts->tv_nsec += (timeout % 1000) * NSEC_PER_MSEC; - if (ts->tv_nsec >= 1000000000L) { - ts->tv_sec += ts->tv_nsec / 1000000000L; - ts->tv_nsec %= 1000000000L; - } - - return ts; -} - -int swifthal_os_mq_send(const void *mp, const void *data, int timeout) { -#ifdef __linux__ - const struct swifthal_os_task__mqueue *mq = mp; - - if (timeout == -1) { - if (mq_send(mq->mq, data, mq->mq_size, 0) != 0) - return -errno; - } else { - struct timespec ts; - - swifthal_timeout_to_timespec(timeout, &ts); - - if (mq_timedsend(mq->mq, data, mq->mq_size, 0, &ts) != 0) - return -errno; - } - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_os_mq_recv(const void *mp, void *data, int timeout) { -#ifdef __linux__ - const struct swifthal_os_task__mqueue *mq = mp; - unsigned int prio; - - if (timeout == -1) { - if (mq_receive(mq->mq, data, mq->mq_size, &prio) != 0) - return -errno; - } else { - struct timespec ts; - - swifthal_timeout_to_timespec(timeout, &ts); - - if (mq_timedreceive(mq->mq, data, mq->mq_size, &prio, &ts) != 0) - return -errno; - } - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_os_mq_purge(const void *mp) { -#ifdef __linux__ - const struct swifthal_os_task__mqueue *mq = mp; - unsigned int prio; - void *data; - - // Drain with a non-blocking receive (absolute deadline in the past) until the - // queue reports empty. Trusting mq_curmsgs and then blocking in mq_receive - // would hang if another consumer drained the queue in between, and a - // caller-controlled alloca(mq_size) risks a stack overflow. - data = malloc(mq->mq_size); - if (data == NULL) - return -ENOMEM; - - for (;;) { - struct timespec ts = {.tv_sec = 0, .tv_nsec = 0}; - - if (mq_timedreceive(mq->mq, data, mq->mq_size, &prio, &ts) < 0) { - if (errno == ETIMEDOUT || errno == EAGAIN) - break; // queue empty - free(data); - return -errno; - } - } - - free(data); - return 0; -#else - return -ENOSYS; -#endif -} - -const void *swifthal_os_mutex_create(void) { - pthread_mutex_t *mutex = calloc(1, sizeof(*mutex)); - if (mutex == NULL) - return NULL; - if (pthread_mutex_init(mutex, NULL) != 0) { - free(mutex); - return NULL; - } - - return mutex; -} - -int swifthal_os_mutex_destroy(const void *arg) { - pthread_mutex_t *mutex = (pthread_mutex_t *)arg; - int err; - - if (mutex) { - err = pthread_mutex_destroy(mutex); - if (err) - return -err; - free(mutex); - return 0; - } - - return -EINVAL; -} - -int swifthal_os_mutex_lock(const void *mutex, int timeout) { - int err; - - if (timeout == -1) { - err = pthread_mutex_lock((pthread_mutex_t *)mutex); - if (err) - return -err; - } else { -#ifdef __linux__ - struct timespec ts; - - swifthal_timeout_to_timespec(timeout, &ts); - - err = pthread_mutex_timedlock((pthread_mutex_t *)mutex, &ts); - if (err) - return -err; -#else - return -ENOSYS; -#endif - } - - return 0; -} - -int swifthal_os_mutex_unlock(const void *mutex) { - int err; - - err = pthread_mutex_unlock((pthread_mutex_t *)mutex); - if (err) - return -err; - - return 0; -} - -struct swifthal_os_task__semaphore { - sem_t sem; - int init_cnt; - int limit; - pthread_mutex_t give_lock; -}; - -const void *swifthal_os_sem_create(uint32_t init_cnt, uint32_t limit) { - struct swifthal_os_task__semaphore *sem; - - if (init_cnt > INT_MAX || limit > INT_MAX) - return NULL; - - sem = calloc(1, sizeof(*sem)); - if (sem == NULL) - return NULL; - if (sem_init(&sem->sem, 0, init_cnt) != 0) { - free(sem); - return NULL; - } - if (pthread_mutex_init(&sem->give_lock, NULL) != 0) { - sem_destroy(&sem->sem); - free(sem); - return NULL; - } - sem->init_cnt = (int)init_cnt; - sem->limit = (int)limit; - - return sem; -} - -int swifthal_os_sem_destroy(const void *arg) { - struct swifthal_os_task__semaphore *sem = - (struct swifthal_os_task__semaphore *)arg; - - if (sem) { - int err = 0; - - // Always destroy the mutex and free, even if sem_destroy fails, so the - // give_lock and heap struct are not leaked. - if (sem_destroy(&sem->sem) != 0) - err = -errno; - pthread_mutex_destroy(&sem->give_lock); - free(sem); - return err; - } - - return -EINVAL; -} - -int swifthal_os_sem_take(const void *arg, int timeout) { - struct swifthal_os_task__semaphore *sem = - (struct swifthal_os_task__semaphore *)arg; - - if (timeout == -1) { - if (sem_wait(&sem->sem) != 0) - return -errno; - } else { -#ifdef __linux__ - struct timespec ts; - - swifthal_timeout_to_timespec(timeout, &ts); - - if (sem_timedwait(&sem->sem, &ts) != 0) - return -errno; -#else - return -ENOSYS; -#endif - } - - return 0; -} - -int swifthal_os_sem_give(const void *arg) { - struct swifthal_os_task__semaphore *sem = - (struct swifthal_os_task__semaphore *)arg; - int value = 0; - int err; - - // Enforce the counting-semaphore limit (POSIX sem_post would happily count - // up to SEM_VALUE_MAX). Serialize give against itself so the getvalue/post - // pair is atomic w.r.t. other givers. pthread_mutex_lock returns the error - // directly and does not set errno. - err = pthread_mutex_lock(&sem->give_lock); - if (err != 0) - return -err; - - err = 0; - if (sem_getvalue(&sem->sem, &value) != 0) - err = -errno; - else if ((sem->limit == 0 || value < sem->limit) && sem_post(&sem->sem) != 0) - err = -errno; // limit == 0 is treated as uncapped - - pthread_mutex_unlock(&sem->give_lock); - - return err; -} - -int swifthal_os_sem_reset(const void *arg) { - struct swifthal_os_task__semaphore *sem = - (struct swifthal_os_task__semaphore *)arg; - int value; - - if (sem_getvalue(&sem->sem, &value) != 0) - return -errno; - while (value > sem->init_cnt) { - if (sem_trywait(&sem->sem) != 0) { - if (errno == EAGAIN) - break; - return -errno; - } - if (sem_getvalue(&sem->sem, &value) != 0) - return -errno; - } - - return 0; -} diff --git a/Sources/LinuxHalSwiftIO/swift_spi.c b/Sources/LinuxHalSwiftIO/swift_spi.c deleted file mode 100644 index e52761d..0000000 --- a/Sources/LinuxHalSwiftIO/swift_spi.c +++ /dev/null @@ -1,257 +0,0 @@ -// -// Copyright (c) 2023 PADL Software Pty Ltd -// -// Licensed under the Apache License, Version 2.0 (the License); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an 'AS IS' BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef __linux__ -#include -#endif - -#include "swift_hal.h" - -struct swifthal_spi { - int fd; - uint32_t speed_old; - uint16_t operation_old; -}; - -static int swifthal_spi__read_config(const struct swifthal_spi *spi, - uint32_t *speed, - uint16_t *operation) { -#ifdef __linux__ - uint8_t tmp; - - *speed = 0; - *operation = 0; - - if (ioctl(spi->fd, SPI_IOC_RD_MAX_SPEED_HZ, speed) < 0) - return -errno; - - if (ioctl(spi->fd, SPI_IOC_RD_MODE, &tmp) < 0) - return -errno; - - if (tmp & SPI_CPOL) - *operation |= SWIFT_SPI_MODE_CPOL; - if (tmp & SPI_CPHA) - *operation |= SWIFT_SPI_MODE_CPHA; - if (tmp & SPI_LOOP) - *operation |= SWIFT_SPI_MODE_LOOP; - - if (ioctl(spi->fd, SPI_IOC_RD_LSB_FIRST, &tmp) < 0) - return -errno; - - *operation |= tmp ? SWIFT_SPI_TRANSFER_LSB : SWIFT_SPI_TRANSFER_MSB; - - if (ioctl(spi->fd, SPI_IOC_RD_BITS_PER_WORD, &tmp) < 0) - return -errno; - - switch (tmp) { - case 8: - *operation |= SWIFT_SPI_TRANSFER_8_BITS; - break; - default: - return -EINVAL; - } - - return 0; -#else - return -ENOSYS; -#endif -} - -void *swifthal_spi_open(int id, - ssize_t speed, - uint16_t operation, - void (*w_notify)(void *), - void (*r_notify)(void *)) { - struct swifthal_spi *spi; - char device[PATH_MAX + 1]; - int err; - - spi = calloc(1, sizeof(*spi)); - if (spi == NULL) - return NULL; - - // FIXME: how to integrate with CS from calling library - snprintf(device, sizeof(device), "/dev/spidev%d.0", id); - - spi->fd = open(device, O_RDWR); - if (spi->fd < 0) { - swifthal_spi_close(spi); - return NULL; - } - - // save previous configuration so we can restore it - if (swifthal_spi__read_config(spi, &spi->speed_old, &spi->operation_old) < - 0 || - swifthal_spi_config(spi, speed, operation) < 0) { - swifthal_spi_close(spi); - return NULL; - } - - return spi; -} - -int swifthal_spi_close(void *arg) { - struct swifthal_spi *spi = (struct swifthal_spi *)arg; - - if (spi) { - if (spi->speed_old) - (void)swifthal_spi_config(spi, spi->speed_old, spi->operation_old); - if (spi->fd != -1) - close(spi->fd); - free(spi); - return 0; - } - - return -EINVAL; -} - -int swifthal_spi_config(void *arg, ssize_t speed, uint16_t operation) { -#ifdef __linux__ - const struct swifthal_spi *spi = arg; - uint8_t mode = 0; - uint8_t bpw; - uint32_t freq = speed; - - if (spi == NULL) - return -EINVAL; - - if (operation & SWIFT_SPI_MODE_CPOL) { - mode |= SPI_CPOL; - } - if (operation & SWIFT_SPI_MODE_CPHA) { - mode |= SPI_CPHA; - } - if (operation & SWIFT_SPI_MODE_LOOP) { - mode |= SPI_LOOP; - } - if (operation & SWIFT_SPI_TRANSFER_LSB) { - mode |= SPI_LSB_FIRST; - } - - if (ioctl(spi->fd, SPI_IOC_WR_MODE, &mode) < 0) - return -errno; - - if (operation & SWIFT_SPI_TRANSFER_8_BITS) { - bpw = 8; - } else { - bpw = 0; - } - if (bpw) { - if (ioctl(spi->fd, SPI_IOC_WR_BITS_PER_WORD, &bpw) < 0) - return -errno; - } - - if (freq) { - if (ioctl(spi->fd, SPI_IOC_WR_MAX_SPEED_HZ, &freq) < 0) - return -errno; - } -#else - return -ENOSYS; -#endif - return 0; -} - -int swifthal_spi_write(void *arg, const uint8_t *buf, ssize_t length) { - const struct swifthal_spi *spi = arg; - - if (spi) { - ssize_t nbytes = write(spi->fd, buf, length); - if (nbytes < 0) - return -errno; - else - return (int)nbytes; - } - - return -EINVAL; -} - -int swifthal_spi_read(void *arg, uint8_t *buf, ssize_t length) { - const struct swifthal_spi *spi = arg; - - if (spi) { - ssize_t nbytes = read(spi->fd, buf, length); - if (nbytes < 0) - return -errno; - else - return (int)nbytes; - } - - return -EINVAL; -} - -int swifthal_spi_transceive(void *arg, - const uint8_t *w_buf, - ssize_t w_length, - uint8_t *r_buf, - ssize_t r_length) { -#ifdef __linux__ - const struct swifthal_spi *spi = arg; - struct spi_ioc_transfer xfer[2]; - - if (spi == NULL || w_buf == NULL || r_buf == NULL) - return -EINVAL; - - // Guard against a negative length: it would convert to a huge size_t and - // overflow the memset / spi_ioc_transfer len field. - if (w_length < 0 || r_length < 0) - return -EINVAL; - - memset(xfer, 0, sizeof(xfer)); - memset(r_buf, 0, r_length); - - xfer[0].tx_buf = (uintptr_t)w_buf; - xfer[0].len = w_length; - - xfer[1].rx_buf = (uintptr_t)r_buf; - xfer[1].len = r_length; - - if (ioctl(spi->fd, SPI_IOC_MESSAGE(2), xfer) < 0) - return -errno; - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_spi_async_write(void *arg, const uint8_t *buf, ssize_t length) { - return -ENOSYS; -} - -int swifthal_spi_async_read(void *arg, uint8_t *buf, ssize_t length) { - return -ENOSYS; -} - -// FIXME: implement this by interrogating /sys/class/spi_master -int swifthal_spi_dev_number_get(void) { return 0; } - -int swifthal_spi_get_fd(const void *arg) { - const struct swifthal_spi *spi = arg; - - if (spi == NULL) - return -EINVAL; - - return spi->fd; -} diff --git a/Sources/LinuxHalSwiftIO/swift_timer.c b/Sources/LinuxHalSwiftIO/swift_timer.c deleted file mode 100644 index ea6a304..0000000 --- a/Sources/LinuxHalSwiftIO/swift_timer.c +++ /dev/null @@ -1,138 +0,0 @@ -// -// Copyright (c) 2023 PADL Software Pty Ltd -// -// Licensed under the Apache License, Version 2.0 (the License); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an 'AS IS' BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include -#include -#include -#include -#include -#include -#include - -#include "swift_hal_internal.h" - -struct swifthal_timer { - swift_timer_type_t type; - dispatch_source_t source; - _Atomic(unsigned int) status; - bool suspended; -}; - -void *swifthal_timer_open(void) { - struct swifthal_timer *timer = calloc(1, sizeof(*timer)); - - if (timer == NULL) - return NULL; - - timer->source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, - dispatch_get_global_queue(0, 0)); - if (timer->source == NULL) { - free(timer); - return NULL; - } - - timer->type = SWIFT_TIMER_TYPE_ONESHOT; - timer->suspended = true; // dispatch sources are created suspended - - return timer; -} - -int swifthal_timer_close(void *arg) { - struct swifthal_timer *timer = (struct swifthal_timer *)arg; - - if (timer) { - dispatch_source_cancel(timer->source); - // Only resume if currently suspended: releasing a suspended source traps, - // and over-resuming an already-running source traps. Bring the suspend - // count to exactly zero before releasing. - if (timer->suspended) - dispatch_resume(timer->source); - dispatch_release(timer->source); - free(timer); - return 0; - } - - return -EINVAL; -} - -int swifthal_timer_start(void *arg, swift_timer_type_t type, ssize_t period) { - struct swifthal_timer *timer = arg; - - if (timer) { - uint64_t interval = (uint64_t)period * NSEC_PER_MSEC; - - timer->type = type; - dispatch_source_set_timer( - timer->source, dispatch_time(DISPATCH_TIME_NOW, interval), - (type == SWIFT_TIMER_TYPE_ONESHOT) ? DISPATCH_TIME_FOREVER : interval, - 0); - // Resume only if suspended so re-arming an already-running timer does not - // over-resume the source. - if (timer->suspended) { - dispatch_resume(timer->source); - timer->suspended = false; - } - return 0; - } - - return -EINVAL; -} - -int swifthal_timer_stop(void *arg) { - struct swifthal_timer *timer = arg; - - if (timer) { - // Suspend rather than cancel: cancellation is permanent on a dispatch - // source, so a cancel here would make a subsequent timer_start unable to - // re-arm the timer. Suspending pauses it and lets start() resume. - if (!timer->suspended) { - dispatch_suspend(timer->source); - timer->suspended = true; - } - return 0; - } - return -EINVAL; -} - -int swifthal_timer_add_callback(void *arg, - const void *param, - void (*callback)(const void *)) { - struct swifthal_timer *timer = (struct swifthal_timer *)arg; - - if (timer) { - dispatch_source_set_event_handler(timer->source, ^{ - // A one-shot uses a DISPATCH_TIME_FOREVER interval, so it already fires - // only once; do not cancel it here, or timer_start could never re-arm it - // (cancellation is permanent). timer_stop suspends instead. - atomic_fetch_add(&timer->status, 1); - callback(param); - }); - return 0; - } - - return -EINVAL; -} - -uint32_t swifthal_timer_status_get(void *arg) { - struct swifthal_timer *timer = (struct swifthal_timer *)arg; - - if (timer) { - unsigned int status = atomic_exchange(&timer->status, 0); - return status; - } - - return 0; -} diff --git a/Sources/LinuxHalSwiftIO/swift_uart.c b/Sources/LinuxHalSwiftIO/swift_uart.c deleted file mode 100644 index cb1aba5..0000000 --- a/Sources/LinuxHalSwiftIO/swift_uart.c +++ /dev/null @@ -1,512 +0,0 @@ -// -// Copyright (c) 2023 PADL Software Pty Ltd -// -// Licensed under the Apache License, Version 2.0 (the License); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an 'AS IS' BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef __linux__ -#include -#elif defined(__APPLE__) -#include -#endif -#include -#include - -#include "swift_hal_internal.h" - -struct swifthal_uart { - int fd; - size_t read_buf_len; - off_t read_buf_offset; - size_t read_buf_consumed; - uint8_t read_buf[0]; -}; - -#define SWIFTHAL_UART_REMAIN(uart) \ - ((uart)->read_buf_len - (uart)->read_buf_offset) - -#define SWIFTHAL_UART_UNCONSUMED(uart) \ - ((uart)->read_buf_offset - (uart)->read_buf_consumed) - -static int swifthal_uart__enable_nbio(const struct swifthal_uart *uart) { - int flags = fcntl(uart->fd, F_GETFL, 0); - if ((flags & O_NONBLOCK) == 0) { - if (fcntl(uart->fd, F_SETFL, flags | O_NONBLOCK) < 0) { - syslog(LOG_INFO, - "LinuxHalSwiftIO: failed to enable non-blocking I/O on UART fd " - "%d: %m\n", - uart->fd); - return -errno; - } - } - return 0; -} - -static int swifthal_uart__raw_set(void *arg); - -void *swifthal_uart_open(int id, const swift_uart_cfg_t *cfg) { - struct swifthal_uart *uart; - char device[PATH_MAX + 1]; - int err; - - uart = calloc(1, sizeof(*uart) + cfg->read_buf_len); - if (uart == NULL) - return NULL; - - // FIXME: use this or /dev/serial? - snprintf(device, sizeof(device), "/dev/ttyAMA%d", id); - - uart->fd = open(device, O_RDWR); - if (uart->fd < 0) { - swifthal_uart_close(uart); - return NULL; - } - - uart->read_buf_len = (int)cfg->read_buf_len; - - err = swifthal_uart__enable_nbio(uart); - if (err == 0) - err = swifthal_uart__raw_set(uart); - if (err == 0) - err = swifthal_uart_baudrate_set(uart, cfg->baudrate); - if (err == 0) - err = swifthal_uart_parity_set(uart, cfg->parity); - if (err == 0) - err = swifthal_uart_stop_bits_set(uart, cfg->stop_bits); - if (err == 0) - err = swifthal_uart_data_bits_set(uart, cfg->data_bits); - - if (err) { - swifthal_uart_close(uart); - return NULL; - } - - swifthal_uart_buffer_clear(uart); - - return uart; -} - -int swifthal_uart_close(void *arg) { - struct swifthal_uart *uart = (struct swifthal_uart *)arg; - - if (uart) { - if (uart->fd != -1) - close(uart->fd); - - free(uart); - return 0; - } - - return -EINVAL; -} - -int swifthal_uart_baudrate_set(void *arg, ssize_t baudrate) { -#ifdef __linux__ - const struct swifthal_uart *uart = arg; - struct termios2 tty; - - if (uart == NULL || baudrate == 0) - return -EINVAL; - - if (ioctl(uart->fd, TCGETS2, &tty) < 0) - return -errno; - - tty.c_cflag &= ~(CBAUD); - tty.c_cflag |= BOTHER; - tty.c_ospeed = baudrate; - - tty.c_cflag &= ~(CBAUD << IBSHIFT); - tty.c_cflag |= BOTHER << IBSHIFT; - tty.c_ispeed = baudrate; - - if (ioctl(uart->fd, TCSETS2, &tty) < 0) - return -errno; - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_uart_parity_set(void *arg, swift_uart_parity_t parity) { -#ifdef __linux__ - const struct swifthal_uart *uart = arg; - struct termios2 tty; - - if (uart == NULL) - return -EINVAL; - - if (ioctl(uart->fd, TCGETS2, &tty) < 0) - return -errno; - - switch (parity) { - case SWIFT_UART_PARITY_NONE: - tty.c_cflag &= ~(PARENB | PARODD); - break; - case SWIFT_UART_PARITY_ODD: - tty.c_cflag |= PARENB | PARODD; - break; - case SWIFT_UART_PARITY_EVEN: - tty.c_cflag |= PARENB; - tty.c_cflag &= ~(PARODD); - break; - default: - return -EINVAL; - } - - if (ioctl(uart->fd, TCSETS2, &tty) < 0) - return -errno; - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_uart_stop_bits_set(void *arg, swift_uart_stop_bits_t stop_bits) { -#ifdef __linux__ - const struct swifthal_uart *uart = arg; - struct termios2 tty; - - if (uart == NULL) - return -EINVAL; - - if (ioctl(uart->fd, TCGETS2, &tty) < 0) - return -errno; - - switch (stop_bits) { - case SWIFT_UART_STOP_BITS_2: - tty.c_cflag |= CSTOPB; - break; - case SWIFT_UART_STOP_BITS_1: - tty.c_cflag &= ~(CSTOPB); - break; - default: - return -EINVAL; - } - - if (ioctl(uart->fd, TCSETS2, &tty) < 0) - return -errno; - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_uart_data_bits_set(const void *arg, - swift_uart_data_bits_t data_bits) { -#ifdef __linux__ - const struct swifthal_uart *uart = arg; - struct termios2 tty; - - if (uart == NULL) - return -EINVAL; - - if (ioctl(uart->fd, TCGETS2, &tty) < 0) - return -errno; - - switch (data_bits) { - case SWIFT_UART_DATA_BITS_8: - tty.c_cflag |= CS8; - break; - default: - return -EINVAL; - } - - if (ioctl(uart->fd, TCSETS2, &tty) < 0) - return -errno; - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_uart_config_get(void *arg, swift_uart_cfg_t *cfg) { -#ifdef __linux__ - const struct swifthal_uart *uart = arg; - struct termios2 tty; - - memset(cfg, 0, sizeof(*cfg)); - - if (uart == NULL) - return -EINVAL; - - if (ioctl(uart->fd, TCGETS2, &tty) < 0) - return -errno; - - cfg->baudrate = MIN(tty.c_ispeed, tty.c_ospeed); - - if (tty.c_cflag & PARENB) { - if (tty.c_cflag & PARODD) - cfg->parity = SWIFT_UART_PARITY_ODD; - else - cfg->parity = SWIFT_UART_PARITY_EVEN; - } else { - cfg->parity = SWIFT_UART_PARITY_NONE; - } - - if (tty.c_cflag & CSTOPB) { - cfg->stop_bits = SWIFT_UART_STOP_BITS_2; - } else { - cfg->stop_bits = SWIFT_UART_STOP_BITS_1; - } - - cfg->read_buf_len = uart->read_buf_len; - - switch (tty.c_cflag & CSIZE) { - case CS8: - cfg->data_bits = SWIFT_UART_DATA_BITS_8; - break; - default: - return -EINVAL; - } - - return 0; -#else - return -ENOSYS; -#endif -} - -int swifthal_uart_char_put(void *arg, uint8_t c) { - struct swifthal_uart *uart = arg; - - return swifthal_uart_write(uart, &c, 1); -} - -int swifthal_uart_char_get(void *arg, uint8_t *c, int timeout) { - struct swifthal_uart *uart = arg; - - return swifthal_uart_read(uart, c, 1, timeout); -} - -int swifthal_uart_write(void *arg, const uint8_t *buf, ssize_t length) { - const struct swifthal_uart *uart = arg; - struct pollfd pollfd; - int err; - const uint8_t *bufp; - size_t nremain; - - if (uart == NULL) - return -EINVAL; - - pollfd.fd = uart->fd; - pollfd.events = POLLOUT; - pollfd.revents = 0; - - for (bufp = buf, nremain = (size_t)length; nremain;) { - err = poll(&pollfd, 1, -1); - if (err < 0) - return -errno; - - if (pollfd.revents & (POLLERR | POLLHUP | POLLNVAL)) - return -EIO; - - if (pollfd.revents == POLLOUT) { - ssize_t nbytes = write(uart->fd, bufp, nremain); - if (nbytes < 0) - return -errno; - - nremain -= nbytes; - bufp += nbytes; - } - } - - return 0; -} - -static inline int64_t swifthal_uart__tv2ms(struct timeval *tv) { - uint64_t ms; - - ms = (uint64_t)tv->tv_sec * 1000; - ms += tv->tv_usec / 1000; - - return ms; -} - -// read a block of data, returning number of bytes remaining in the -// block if the timeout was reached, or a negative value indicating -// a non-recoverable error -// -// pass NULL for timeout if blocking indefinitely is desired -// -// on return timeout is updated to reflect the number of seconds -// remaining, in case a complete buffer was read within the timeout -static ssize_t swifthal_uart__read_buffer(struct swifthal_uart *uart, - int64_t *timeout) { - int err; - struct pollfd pollfd; - struct timeval now = {.tv_sec = 0, .tv_usec = 0}; - - pollfd.fd = uart->fd; - - while (uart->read_buf_offset < uart->read_buf_len) { - ssize_t nbytes; - - if (timeout) { - struct timeval prev = now; - - gettimeofday(&now, NULL); - - if (prev.tv_sec) { - int64_t diff = swifthal_uart__tv2ms(&now) - swifthal_uart__tv2ms(&prev); - *timeout -= diff; - } - - if (*timeout < 0) - break; - } - - pollfd.events = POLLIN; - pollfd.revents = 0; - - err = poll(&pollfd, 1, timeout ? *timeout : -1); - if (err < 0) - return -errno; - else if (err == 0) - break; // timed out - - // Drain readable data first: a hangup (POLLHUP) can be reported together - // with POLLIN while bytes are still buffered in the device, and those must - // not be discarded. Only treat an error/hangup as fatal once there is - // nothing left to read. - if (pollfd.revents & POLLIN) { - nbytes = read(pollfd.fd, &uart->read_buf[uart->read_buf_offset], - SWIFTHAL_UART_REMAIN(uart)); - if (nbytes < 0) - return -errno; - if (nbytes == 0) - break; // EOF: return the bytes already buffered - uart->read_buf_offset += nbytes; - assert(uart->read_buf_offset <= uart->read_buf_len); - continue; - } - - if (pollfd.revents & (POLLERR | POLLHUP | POLLNVAL)) - return -EIO; - } - - return SWIFTHAL_UART_REMAIN(uart); -} - -int swifthal_uart_read(void *arg, uint8_t *buf, ssize_t length, int timeout) { - struct swifthal_uart *uart = (struct swifthal_uart *)arg; - int64_t timeout64 = (int64_t)timeout; - size_t nremain = (size_t)length; - ssize_t res; - - if (uart == NULL) - return -EINVAL; - - assert(length > 0); - assert(buf); - - while (nremain) { - // check for consumed bytes to return - if (uart->read_buf_consumed < uart->read_buf_offset) { - size_t nconsume = MIN(SWIFTHAL_UART_UNCONSUMED(uart), nremain); - - memcpy(buf, &uart->read_buf[uart->read_buf_consumed], nconsume); - uart->read_buf_consumed += nconsume; - assert(uart->read_buf_consumed <= uart->read_buf_offset); - buf += nconsume; - nremain -= nconsume; - } - - if (nremain == 0) - break; // all data read - - // reset buffer if it has been completely read - if (uart->read_buf_offset == uart->read_buf_len) { - assert(uart->read_buf_consumed == uart->read_buf_offset); - swifthal_uart_buffer_clear(uart); - } - - // attempt to read a complete buffer (subject to timeout) - res = swifthal_uart__read_buffer(uart, timeout == -1 ? NULL : &timeout64); - if (res < 0) - return res; - - if (timeout64 < 0) - break; // we timed out - } - - return (int)nremain; -} - -int swifthal_uart_remainder_get(void *arg) { - const struct swifthal_uart *uart = arg; - - return SWIFTHAL_UART_UNCONSUMED(uart); -} - -int swifthal_uart_buffer_clear(void *arg) { - struct swifthal_uart *uart = (struct swifthal_uart *)arg; - - if (uart == NULL) - return -EINVAL; - - uart->read_buf_offset = 0; - uart->read_buf_consumed = 0; - -#ifndef NDEBUG - memset(uart->read_buf, 0xff, uart->read_buf_len); -#endif - - return 0; -} - -int swifthal_uart_dev_number_get(void) { return 0; } - -int swifthal_uart_get_fd(const void *arg) { - const struct swifthal_uart *uart = arg; - - if (uart == NULL) - return -EINVAL; - - return uart->fd; -} - -static int swifthal_uart__raw_set(void *arg) { -#ifdef __linux__ - const struct swifthal_uart *uart = arg; - struct termios2 tty; - - if (uart == NULL) - return -EINVAL; - - if (ioctl(uart->fd, TCGETS2, &tty) < 0) - return -errno; - - tty.c_iflag &= - ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); - tty.c_oflag &= ~OPOST; - tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); - - if (ioctl(uart->fd, TCSETS2, &tty) < 0) - return -errno; - - return 0; -#else - return -ENOSYS; -#endif -} diff --git a/Sources/LinuxHalSwiftIO/swift_wifi.c b/Sources/LinuxHalSwiftIO/swift_wifi.c deleted file mode 100644 index f9bb024..0000000 --- a/Sources/LinuxHalSwiftIO/swift_wifi.c +++ /dev/null @@ -1,43 +0,0 @@ -// -// Copyright (c) 2023 PADL Software Pty Ltd -// -// Licensed under the Apache License, Version 2.0 (the License); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an 'AS IS' BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#include -#include -#include -#include -#include - -#include "swift_hal_internal.h" - -int swift_wifi_scan(swift_wifi_scan_result_t *results, int num) { - return -ENOSYS; -} - -int swift_wifi_connect(char *ssid, int ssid_length, char *psk, int psk_length) { - return -ENOSYS; -} - -int swift_wifi_disconnect(void) { return -ENOSYS; } - -int swift_wifi_ap_mode_set( - int enable, char *ssid, int ssid_length, char *psk, int psk_length) { - return -ENOSYS; -} - -int swift_wifi_status_get(swift_wifi_status_t *status) { - memset(status, 0, sizeof(*status)); - return -ENOSYS; -} diff --git a/Tests/LinuxHalSwiftIOTests/HALNativeTests.swift b/Tests/LinuxHalSwiftIOTests/HALNativeTests.swift new file mode 100644 index 0000000..9264211 --- /dev/null +++ b/Tests/LinuxHalSwiftIOTests/HALNativeTests.swift @@ -0,0 +1,63 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +@testable import LinuxHalSwiftIO +import XCTest + +// Exercises the native-Swift HAL primitives (OS + platform) that do not require +// peripheral hardware, complementing the SPI loopback test. +final class HALNativeTests: XCTestCase { + func testSemaphoreLimitEnforced() throws { + // init_cnt = 0, limit = 2: give() must cap the count at 2 even when called + // more often, and exactly two takes should then succeed. + guard let sem = swifthal_os_sem_create(0, 2) else { + return XCTFail("sem_create failed") + } + defer { XCTAssertEqual(swifthal_os_sem_destroy(sem), 0) } + + XCTAssertEqual(swifthal_os_sem_give(sem), 0) + XCTAssertEqual(swifthal_os_sem_give(sem), 0) + XCTAssertEqual(swifthal_os_sem_give(sem), 0) // over the limit: no-op, no error + + XCTAssertEqual(swifthal_os_sem_take(sem, 10), 0) + XCTAssertEqual(swifthal_os_sem_take(sem, 10), 0) + // Third take must time out: the limit held the count at 2. + XCTAssertLessThan(swifthal_os_sem_take(sem, 10), 0) + } + + func testMutexLockUnlock() throws { + guard let mutex = swifthal_os_mutex_create() else { + return XCTFail("mutex_create failed") + } + defer { XCTAssertEqual(swifthal_os_mutex_destroy(mutex), 0) } + + XCTAssertEqual(swifthal_os_mutex_lock(mutex, -1), 0) + XCTAssertEqual(swifthal_os_mutex_unlock(mutex), 0) + } + + func testRandomGetFillsBuffer() throws { + var buf = [UInt8](repeating: 0, count: 64) + buf.withUnsafeMutableBufferPointer { + swifthal_random_get($0.baseAddress, $0.count) + } + // A 64-byte all-zero result from a working RNG is astronomically unlikely. + XCTAssertTrue(buf.contains { $0 != 0 }) + } + + func testUptimeIsPositive() throws { + XCTAssertGreaterThan(swifthal_uptime_get(), 0) + } +} diff --git a/Tests/LinuxHalSwiftIOTests/LinuxHalSwiftIOTests.swift b/Tests/LinuxHalSwiftIOTests/LinuxHalSwiftIOTests.swift index d10b987..a46f35f 100644 --- a/Tests/LinuxHalSwiftIOTests/LinuxHalSwiftIOTests.swift +++ b/Tests/LinuxHalSwiftIOTests/LinuxHalSwiftIOTests.swift @@ -6,7 +6,10 @@ import XCTest // assumes MISO connected to MOSI for loopback final class LinuxHalSwiftIOTests: XCTestCase { - let spi = SPI(Id(rawValue: 1), speed: 1000, bitOrder: .MSB) + // Constructed lazily: SPI.init asserts when /dev/spidev1.0 is absent, so an + // eager stored property would crash test discovery on any machine without SPI + // hardware (and take the rest of the bundle's tests down with it). + lazy var spi = SPI(Id(rawValue: 1), speed: 1000, bitOrder: .MSB) func testSpiLoopbackTransceive() throws { let writeBuffer: [UInt8] = [1, 2, 3, 4, 10, 11, 12, 13, 0xFF, 0] From 1be9c83ae9ae9179ad4a9c99d262e413cfe21203 Mon Sep 17 00:00:00 2001 From: Luke Howard Date: Mon, 20 Jul 2026 11:20:11 +1000 Subject: [PATCH 2/2] Address remaining review cleanups in the native-Swift HAL - Factor the dispatch-source suspend/resume/teardown patterns shared by GPIO and Timer into a DispatchSourceProtocol extension, and add handle() unwrappers instead of repeated Unmanaged boilerplate - GPIO: release the source fully in the event-handler error path rather than leaving a cancelled-but-installed source behind - FS: factor the dirent type/size fill shared by readdir and fs_stat; DT_UNKNOWN entries are now stat'ed once instead of twice - Counter: implement tick conversions as microseconds (1 MHz) instead of stubbing them to zero, making counter_read meaningful - Stubs: replace the memset spelling of LCD format zeroing with a plain pointee assignment - Correct stale comments (swift_uart_native.h prototypes, CLinuxHalSwiftIO target contents) and document the x86 TSC calibration cost Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_015rhut2AwGnktQ4msgGo6z1 --- Package.swift | 5 +- .../CLinuxHalSwiftIO/swift_platform_hwcycle.c | 4 +- Sources/CLinuxHalSwiftIO/swift_uart_native.c | 2 +- Sources/LinuxHalSwiftIO/Counter.swift | 12 ++-- .../LinuxHalSwiftIO/DispatchSource+HAL.swift | 44 +++++++++++++ Sources/LinuxHalSwiftIO/FS.swift | 65 +++++++++---------- Sources/LinuxHalSwiftIO/GPIO.swift | 46 ++++++------- Sources/LinuxHalSwiftIO/Stubs.swift | 2 +- Sources/LinuxHalSwiftIO/Timer.swift | 33 ++++------ 9 files changed, 119 insertions(+), 94 deletions(-) create mode 100644 Sources/LinuxHalSwiftIO/DispatchSource+HAL.swift diff --git a/Package.swift b/Package.swift index c8c38b1..e058d49 100644 --- a/Package.swift +++ b/Package.swift @@ -52,9 +52,8 @@ let package = Package( .package(url: "https://github.com/apple/swift-system", from: "1.0.0"), ], targets: [ - // Residual C implementation: peripherals not yet migrated to Swift, plus the - // inline-assembly hwcycle functions and the ioctl/mq_open C shims that the - // Swift layer calls. Also exports the public HAL headers. + // Residual C the Swift HAL calls (inline-assembly hwcycle, termios2 UART + // helpers, fs mount point, ioctl/mq/syslog shims) plus the public HAL headers. .target( name: "CLinuxHalSwiftIO", dependencies: [ diff --git a/Sources/CLinuxHalSwiftIO/swift_platform_hwcycle.c b/Sources/CLinuxHalSwiftIO/swift_platform_hwcycle.c index 4acc963..b513e3f 100644 --- a/Sources/CLinuxHalSwiftIO/swift_platform_hwcycle.c +++ b/Sources/CLinuxHalSwiftIO/swift_platform_hwcycle.c @@ -58,8 +58,8 @@ uint32_t swifthal_hwcycle_to_ns(unsigned int cycles) { return 0; return (uint32_t)((uint64_t)cycles * 1000000000ULL / freq); #elif defined(__x86_64__) - // The TSC frequency is not architecturally discoverable; calibrate it once - // against CLOCK_MONOTONIC and cache the result. + // The TSC frequency is not architecturally discoverable: calibrate against + // CLOCK_MONOTONIC (first call blocks ~10ms; racing callers store ~equal hz). static _Atomic(uint64_t) tsc_hz = 0; uint64_t hz = atomic_load_explicit(&tsc_hz, memory_order_relaxed); diff --git a/Sources/CLinuxHalSwiftIO/swift_uart_native.c b/Sources/CLinuxHalSwiftIO/swift_uart_native.c index 4fb93ba..fc850e9 100644 --- a/Sources/CLinuxHalSwiftIO/swift_uart_native.c +++ b/Sources/CLinuxHalSwiftIO/swift_uart_native.c @@ -17,7 +17,7 @@ // termios2 line-configuration helpers for the native-Swift UART (UART.swift). // termios2 lives in , which redefines `struct termios` and so // cannot be exposed to Swift alongside Glibc's . Keeping it in this -// isolated .c (declared via plain prototypes in swift_hal_native.h) confines that +// isolated .c (declared via plain prototypes in swift_uart_native.h) confines that // conflict to C while Swift owns the open/buffering/poll logic. #include diff --git a/Sources/LinuxHalSwiftIO/Counter.swift b/Sources/LinuxHalSwiftIO/Counter.swift index 8bc21f9..37341ae 100644 --- a/Sources/LinuxHalSwiftIO/Counter.swift +++ b/Sources/LinuxHalSwiftIO/Counter.swift @@ -15,8 +15,8 @@ // // Native-Swift port of the counter HAL (previously swift_counter.c): a thin -// wrapper over a POSIX clock. Most of the alarm/frequency surface is -// unimplemented on Linux, matching the original. +// wrapper over a POSIX clock, counting microsecond ticks. Alarms and callbacks +// are unimplemented on Linux, matching the original. #if canImport(Glibc) import Glibc @@ -74,9 +74,11 @@ func swifthal_counter_add_callback( -ENOSYS } -@c func swifthal_counter_freq(_ arg: UnsafeMutableRawPointer?) -> UInt32 { 0 } -@c func swifthal_counter_ticks_to_us(_ arg: UnsafeMutableRawPointer?, _ ticks: UInt32) -> UInt64 { 0 } -@c func swifthal_counter_us_to_ticks(_ arg: UnsafeMutableRawPointer?, _ us: UInt64) -> UInt32 { 0 } +// One tick == one microsecond (the old C stubbed these to 0, which made +// counter_read discard its clock_gettime result and always report 0 ticks). +@c func swifthal_counter_freq(_ arg: UnsafeMutableRawPointer?) -> UInt32 { 1_000_000 } +@c func swifthal_counter_ticks_to_us(_ arg: UnsafeMutableRawPointer?, _ ticks: UInt32) -> UInt64 { UInt64(ticks) } +@c func swifthal_counter_us_to_ticks(_ arg: UnsafeMutableRawPointer?, _ us: UInt64) -> UInt32 { UInt32(truncatingIfNeeded: us) } @c func swifthal_counter_get_max_top_value(_ arg: UnsafeMutableRawPointer?) -> UInt32 { UInt32.max } @c func swifthal_counter_set_channel_alarm(_ arg: UnsafeMutableRawPointer?, _ ticks: UInt32) -> CInt { -ENOSYS } @c func swifthal_counter_cancel_channel_alarm(_ arg: UnsafeMutableRawPointer?) -> CInt { -ENOSYS } diff --git a/Sources/LinuxHalSwiftIO/DispatchSource+HAL.swift b/Sources/LinuxHalSwiftIO/DispatchSource+HAL.swift new file mode 100644 index 0000000..695e835 --- /dev/null +++ b/Sources/LinuxHalSwiftIO/DispatchSource+HAL.swift @@ -0,0 +1,44 @@ +// +// Copyright (c) 2026 PADL Software Pty Ltd +// +// Licensed under the Apache License, Version 2.0 (the License); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an 'AS IS' BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Dispatch + +// Shared idempotent suspend/resume/teardown helpers for the dispatch sources +// backing the GPIO and timer HALs, which track suspension in an adjacent Bool. +extension DispatchSourceProtocol { + // Resume only when suspended, so repeated enables do not over-resume. + func resumeIfSuspended(_ suspended: inout Bool) { + if suspended { + resume() + suspended = false + } + } + + // Suspend only when running, so repeated disables do not over-suspend. + func suspendIfRunning(_ suspended: inout Bool) { + if !suspended { + suspend() + suspended = true + } + } + + // Cancel and bring the suspend count back to zero: a suspended source traps + // when its last reference is dropped. + func cancelForRelease(_ suspended: inout Bool) { + cancel() + resumeIfSuspended(&suspended) + } +} diff --git a/Sources/LinuxHalSwiftIO/FS.swift b/Sources/LinuxHalSwiftIO/FS.swift index 1a266d3..4358b95 100644 --- a/Sources/LinuxHalSwiftIO/FS.swift +++ b/Sources/LinuxHalSwiftIO/FS.swift @@ -180,6 +180,22 @@ private func setDirentName(_ entry: UnsafeMutablePointer, _ n } } +// Fill entry type/size from a stat result; false for types the HAL does not +// surface (neither a regular file nor a directory). +private func setDirentTypeAndSize(_ entry: UnsafeMutablePointer, _ sb: stat) -> Bool { + switch sb.st_mode & S_IFMT { + case S_IFDIR: + entry.pointee.type = SWIFT_FS_DIR_ENTRY_DIR + entry.pointee.size = 0 + case S_IFREG: + entry.pointee.type = SWIFT_FS_DIR_ENTRY_FILE + entry.pointee.size = Int(sb.st_size) + default: + return false + } + return true +} + // Fill `entry` from one dirent: returns 0, a negative errno, or nil to skip. // d_name stays raw bytes: a String round-trip would mangle non-UTF-8 names. private func fillDirent( @@ -189,40 +205,28 @@ private func fillDirent( ) -> CInt? { withUnsafePointer(to: &dentry.pointee.d_name) { $0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: dentry.pointee.d_name)) { name -> CInt? in - var dType = dentry.pointee.d_type - - // Some filesystems always report DT_UNKNOWN; stat to classify the entry. - if dType == DT_UNKNOWN { - var sb = stat() - if fstatat(dirfd(dir), name, &sb, 0) < 0 { - // Skip entries lost to an unlink race (or dangling symlinks); surface - // any other failure rather than silently omitting entries. - return errno == ENOENT ? nil : -errno - } - if sb.st_mode & S_IFMT == S_IFDIR { - dType = UInt8(DT_DIR) - } else if sb.st_mode & S_IFMT == S_IFREG { - dType = UInt8(DT_REG) - } - } + let dType = dentry.pointee.d_type if dType == UInt8(DT_DIR) { + // No stat needed: a directory entry's size is reported as 0. entry.pointee.type = SWIFT_FS_DIR_ENTRY_DIR - setDirentName(entry, name) entry.pointee.size = 0 - return 0 - } else if dType == UInt8(DT_REG) { + } else if dType == UInt8(DT_REG) || dType == DT_UNKNOWN { + // DT_REG needs a stat for the size, and DT_UNKNOWN (filesystems that + // never fill d_type) for classification too. var sb = stat() if fstatat(dirfd(dir), name, &sb, 0) < 0 { - // As above: tolerate losing a race with unlink, surface real errors. + // Skip entries lost to an unlink race (or dangling symlinks); surface + // any other failure rather than silently omitting entries. return errno == ENOENT ? nil : -errno } - entry.pointee.type = SWIFT_FS_DIR_ENTRY_FILE - setDirentName(entry, name) - entry.pointee.size = Int(sb.st_size) - return 0 + if !setDirentTypeAndSize(entry, sb) { return nil } + } else { + return nil // other types (symlink, socket, ...): skip } - return nil // neither directory nor regular file: skip + + setDirentName(entry, name) + return 0 } } } @@ -258,16 +262,7 @@ func swifthal_fs_stat(_ path: UnsafePointer?, _ entry: UnsafeMutablePoint guard let path, let entry else { return -EINVAL } var sb = stat() if stat(path, &sb) < 0 { return -errno } - - if sb.st_mode & S_IFMT == S_IFDIR { - entry.pointee.type = SWIFT_FS_DIR_ENTRY_DIR - entry.pointee.size = 0 - } else if sb.st_mode & S_IFMT == S_IFREG { - entry.pointee.type = SWIFT_FS_DIR_ENTRY_FILE - entry.pointee.size = Int(sb.st_size) - } else { - return -ENOENT - } + if !setDirentTypeAndSize(entry, sb) { return -ENOENT } // The name is not derivable from a path; make it a defined empty string. entry.pointee.name.0 = 0 diff --git a/Sources/LinuxHalSwiftIO/GPIO.swift b/Sources/LinuxHalSwiftIO/GPIO.swift index 7704fa0..4f922a0 100644 --- a/Sources/LinuxHalSwiftIO/GPIO.swift +++ b/Sources/LinuxHalSwiftIO/GPIO.swift @@ -44,15 +44,15 @@ private final class GPIO { var callback: ((UInt8, timespec) -> Void)? } +private func handle(_ arg: UnsafeMutableRawPointer) -> GPIO { + Unmanaged.fromOpaque(arg).takeUnretainedValue() +} + // Cancel and release the interrupt dispatch source, bringing its suspend count // to zero first. Safe to call with no source installed. private func releaseSource(_ gpio: GPIO) { guard let source = gpio.source else { return } - source.cancel() - if gpio.sourceSuspended { - source.resume() - gpio.sourceSuspended = false - } + source.cancelForRelease(&gpio.sourceSuspended) gpio.source = nil } @@ -120,7 +120,7 @@ func swifthal_gpio_open(_ id: CInt, _ direction: swift_gpio_direction_t, _ ioMod func swifthal_gpio_close(_ arg: UnsafeMutableRawPointer?) -> CInt { #if os(Linux) guard let arg else { return -EINVAL } - let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let gpio = handle(arg) // Release the dispatch source before closing the chip: gpiod_chip_close() // drops the line's event fd, and the cancel path traps on EBADF if it's gone. @@ -139,7 +139,7 @@ func swifthal_gpio_close(_ arg: UnsafeMutableRawPointer?) -> CInt { func swifthal_gpio_config(_ arg: UnsafeMutableRawPointer?, _ direction: swift_gpio_direction_t, _ ioMode: swift_gpio_mode_t) -> CInt { #if os(Linux) guard let arg else { return -EINVAL } - let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let gpio = handle(arg) releaseSource(gpio) @@ -170,7 +170,7 @@ func swifthal_gpio_config(_ arg: UnsafeMutableRawPointer?, _ direction: swift_gp func swifthal_gpio_set(_ arg: UnsafeMutableRawPointer?, _ level: CInt) -> CInt { #if os(Linux) guard let arg else { return -EINVAL } - let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let gpio = handle(arg) if gpiod_line_set_value(gpio.line, level) < 0 { return -errno } return 0 #else @@ -182,7 +182,7 @@ func swifthal_gpio_set(_ arg: UnsafeMutableRawPointer?, _ level: CInt) -> CInt { func swifthal_gpio_get(_ arg: UnsafeMutableRawPointer?) -> CInt { #if os(Linux) guard let arg else { return -EINVAL } - let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let gpio = handle(arg) let value = gpiod_line_get_value(gpio.line) if value < 0 { return -errno } return value @@ -195,7 +195,7 @@ func swifthal_gpio_get(_ arg: UnsafeMutableRawPointer?) -> CInt { func swifthal_gpio_interrupt_config(_ arg: UnsafeMutableRawPointer?, _ intMode: swift_gpio_int_mode_t) -> CInt { #if os(Linux) guard let arg else { return -EINVAL } - let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let gpio = handle(arg) // Release any prior source so reconfiguring the edge mode does not leak it. releaseSource(gpio) @@ -244,7 +244,7 @@ public func swifthal_gpio_interrupt_callback_install_block( _ callback: @escaping (UInt8, timespec) -> Void ) -> CInt { #if os(Linux) - let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let gpio = handle(arg) guard let source = gpio.source else { return -EINVAL } gpio.callback = callback @@ -253,7 +253,9 @@ public func swifthal_gpio_interrupt_callback_install_block( let fd = CInt(source.handle) var event = gpiod_line_event() if gpiod_line_event_read_fd(fd, &event) < 0 { - source.cancel() + // Tear down fully: a cancelled-but-installed source would strand later + // disable/enable calls on a dead source (see callback_uninstall). + releaseSource(gpio) return } gpio.callback?(event.event_type == CInt(GPIOD_LINE_EVENT_RISING_EDGE) ? 1 : 0, event.ts) @@ -280,7 +282,7 @@ func swifthal_gpio_interrupt_callback_install( func swifthal_gpio_interrupt_callback_uninstall(_ arg: UnsafeMutableRawPointer?) -> CInt { #if os(Linux) guard let arg else { return -EINVAL } - let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let gpio = handle(arg) // Fully tear down (cancel + rebalance + clear) rather than only cancel: cancel // is permanent, and resuming a dead source would silently drop all events. releaseSource(gpio) @@ -294,13 +296,9 @@ func swifthal_gpio_interrupt_callback_uninstall(_ arg: UnsafeMutableRawPointer?) func swifthal_gpio_interrupt_enable(_ arg: UnsafeMutableRawPointer?) -> CInt { #if os(Linux) guard let arg else { return -EINVAL } - let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let gpio = handle(arg) guard let source = gpio.source else { return -EINVAL } - // Idempotent: resume only when currently suspended. - if gpio.sourceSuspended { - source.resume() - gpio.sourceSuspended = false - } + source.resumeIfSuspended(&gpio.sourceSuspended) return 0 #else return -ENOSYS @@ -311,15 +309,11 @@ func swifthal_gpio_interrupt_enable(_ arg: UnsafeMutableRawPointer?) -> CInt { func swifthal_gpio_interrupt_disable(_ arg: UnsafeMutableRawPointer?) -> CInt { #if os(Linux) guard let arg else { return -EINVAL } - let gpio = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let gpio = handle(arg) // With no source (e.g. after callback_uninstall) the interrupt is already // effectively disabled; a no-op, as SwiftIO's teardown paths expect success. guard let source = gpio.source else { return 0 } - // Idempotent: suspend only when currently running. - if !gpio.sourceSuspended { - source.suspend() - gpio.sourceSuspended = true - } + source.suspendIfRunning(&gpio.sourceSuspended) return 0 #else return -ENOSYS @@ -343,7 +337,7 @@ func swifthal_gpio_dev_number_get() -> CInt { func swifthal_gpio_get_fd(_ arg: UnsafeRawPointer?) -> CInt { #if os(Linux) guard let arg else { return -EINVAL } - let gpio = Unmanaged.fromOpaque(UnsafeMutableRawPointer(mutating: arg)).takeUnretainedValue() + let gpio = handle(UnsafeMutableRawPointer(mutating: arg)) return gpiod_line_event_get_fd(gpio.line) #else return -ENOSYS diff --git a/Sources/LinuxHalSwiftIO/Stubs.swift b/Sources/LinuxHalSwiftIO/Stubs.swift index fdbe987..02df122 100644 --- a/Sources/LinuxHalSwiftIO/Stubs.swift +++ b/Sources/LinuxHalSwiftIO/Stubs.swift @@ -66,7 +66,7 @@ import CLinuxHalSwiftIO // a caller reading them after -ENOSYS sees defined zeros, not stack garbage. width?.pointee = 0 height?.pointee = 0 - format.map { _ = memset($0, 0, MemoryLayout.size) } + format?.pointee = swift_lcd_pixel_format_t(0) bpp?.pointee = 0 return -ENOSYS } diff --git a/Sources/LinuxHalSwiftIO/Timer.swift b/Sources/LinuxHalSwiftIO/Timer.swift index 5905897..4f9c91a 100644 --- a/Sources/LinuxHalSwiftIO/Timer.swift +++ b/Sources/LinuxHalSwiftIO/Timer.swift @@ -41,6 +41,10 @@ private final class Timer { } } +private func handle(_ arg: UnsafeMutableRawPointer) -> Timer { + Unmanaged.fromOpaque(arg).takeUnretainedValue() +} + @c func swifthal_timer_open() -> UnsafeMutableRawPointer? { Unmanaged.passRetained(Timer()).toOpaque() @@ -49,15 +53,9 @@ func swifthal_timer_open() -> UnsafeMutableRawPointer? { @c func swifthal_timer_close(_ arg: UnsafeMutableRawPointer?) -> CInt { guard let arg else { return -EINVAL } - let timer = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let timer = handle(arg) - timer.source.cancel() - // A suspended source traps when its last reference is dropped; balance the - // suspend count back to zero before releasing. - if timer.suspended { - timer.source.resume() - timer.suspended = false - } + timer.source.cancelForRelease(&timer.suspended) Unmanaged.fromOpaque(arg).release() return 0 } @@ -65,7 +63,7 @@ func swifthal_timer_close(_ arg: UnsafeMutableRawPointer?) -> CInt { @c func swifthal_timer_start(_ arg: UnsafeMutableRawPointer?, _ type: swift_timer_type_t, _ period: Int) -> CInt { guard let arg else { return -EINVAL } - let timer = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let timer = handle(arg) timer.type = type let interval = DispatchTimeInterval.milliseconds(Int(period)) @@ -74,25 +72,18 @@ func swifthal_timer_start(_ arg: UnsafeMutableRawPointer?, _ type: swift_timer_t } else { timer.source.schedule(deadline: .now() + interval, repeating: interval) } - // Resume only if suspended so re-arming a running timer does not over-resume. - if timer.suspended { - timer.source.resume() - timer.suspended = false - } + timer.source.resumeIfSuspended(&timer.suspended) return 0 } @c func swifthal_timer_stop(_ arg: UnsafeMutableRawPointer?) -> CInt { guard let arg else { return -EINVAL } - let timer = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let timer = handle(arg) // Suspend rather than cancel: cancellation is permanent, so a cancel here // would leave a subsequent timer_start unable to re-arm the timer. - if !timer.suspended { - timer.source.suspend() - timer.suspended = true - } + timer.source.suspendIfRunning(&timer.suspended) return 0 } @@ -103,7 +94,7 @@ func swifthal_timer_add_callback( _ callback: (@convention(c) (UnsafeRawPointer?) -> Void)? ) -> CInt { guard let arg else { return -EINVAL } - let timer = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let timer = handle(arg) timer.callback = callback timer.param = param @@ -120,6 +111,6 @@ func swifthal_timer_add_callback( @c func swifthal_timer_status_get(_ arg: UnsafeMutableRawPointer?) -> UInt32 { guard let arg else { return 0 } - let timer = Unmanaged.fromOpaque(arg).takeUnretainedValue() + let timer = handle(arg) return timer.status.exchange(0, ordering: .relaxed) }