From c727c6a7092fff92fc747e03194f0fe68ceeb01f Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Sun, 6 Sep 2026 11:48:40 +0200 Subject: [PATCH 1/3] hm2_eth: Move firewall to hm2_eth This way, the specific network implementations don't have to know anything about the firewall. --- src/hal/drivers/mesa-hostmot2/hm2_eth.c | 29 +++++++++++++++---- src/hal/drivers/mesa-hostmot2/hm2_eth.h | 4 --- .../drivers/mesa-hostmot2/hm2_eth_net_evl.c | 15 ---------- .../drivers/mesa-hostmot2/hm2_eth_net_posix.c | 15 ---------- 4 files changed, 24 insertions(+), 39 deletions(-) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.c b/src/hal/drivers/mesa-hostmot2/hm2_eth.c index 5dbf3cfae9a..18eb1fb1d37 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.c @@ -591,7 +591,7 @@ static fw_state_t fw_state = FW_UNRESOLVED; // Resolve and bring up the firewall backend on first call, caching the // result. Returns true when a backend is ready, false when isolation // is unavailable or disabled. -bool use_firewall() { +static bool use_firewall() { if(fw_state != FW_UNRESOLVED) return fw_state == FW_READY; @@ -638,7 +638,7 @@ bool use_firewall() { // Drop all rules from our chain/table but keep the chain in place, so a // fresh set can be installed on (re-)init. -void clear_firewall() { +static void clear_firewall() { if(!use_firewall()) return; switch(fw_backend) { case FW_IPTABLES: @@ -706,7 +706,7 @@ char* fetch_ifname(int sockfd, char *buf, size_t n) { return NULL; } -int install_firewall_board(int sockfd) { +static int install_firewall_board(int sockfd) { struct sockaddr_in srcaddr, dstaddr; char srchost[16], dsthost[16]; // enough for 255.255.255.255\0 char dport_s[8], sport_s[8]; @@ -751,7 +751,7 @@ int install_firewall_board(int sockfd) { return 0; } -int install_firewall_perinterface(const char *ifbuf) { +static int install_firewall_perinterface(const char *ifbuf) { // Without these rules, 'ping' spews a lot of "Packet filtered" // messages. With them, ping prints 'ping: sendmsg: Operation not // permitted' once per second. @@ -843,7 +843,24 @@ static int init_board(hm2_eth_t *board, const char *board_ip, const char *board_ return -1; } - return board->init_board(board, board_ip); + int ret; + ret = board->init_board(board, board_ip); + if (ret < 0) return ret; + + if (!use_firewall()) { + LL_PRINT(\ + "WARNING: Unable to restrict other access to the hm2-eth device.\n" + "This means that other software using the same network interface can violate\n" + "realtime guarantees. See hm2_eth(9) for more information.\n"); + } + + // install_firewall_board() is a no-op when no firewall backend is + // available (rootless install without CAP_NET_ADMIN, or + // firewall=none), so it is safe to call unconditionally. + ret = install_firewall_board(board->sockfd); + if (ret < 0) return ret; + + return 0; } /// ethernet io functions mapping @@ -1723,6 +1740,7 @@ int rtapi_app_main(void) { close_board(&boards[i]); // Full teardown: rtapi_app_exit() is not called when rtapi_app_main() // fails, so this is the only chance to remove the chain and jump. + clear_firewall(); cleanup_firewall(); kvlist_free(&board_num); kvlist_free(&ifnames); @@ -1736,6 +1754,7 @@ void rtapi_app_exit(void) { for(i = 0; isockfd); - if (ret < 0) return ret; - board->write_packet_ptr = board->write_packet; board->read_packet_ptr = board->read_packet; @@ -225,8 +212,6 @@ int hm2_evl_close_board(hm2_eth_t *board) { board->llio.reset(&board->llio); - clear_firewall(); - ret = close(board->sockfd); if (ret == -1) LL_PRINT("ERROR: can't close socket: %s\n", strerror(errno)); diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.c b/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.c index 43ae1358ec2..6286c0a59a3 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.c @@ -66,13 +66,6 @@ int hm2_posix_init_board(hm2_eth_t *board, const char *board_ip) { return 0; } - if(!use_firewall()) { - LL_PRINT(\ -"WARNING: Unable to restrict other access to the hm2-eth device.\n" -"This means that other software using the same network interface can violate\n" -"realtime guarantees. See hm2_eth(9) for more information.\n"); - } - struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = RECV_TIMEOUT_US; @@ -118,12 +111,6 @@ int hm2_posix_init_board(hm2_eth_t *board, const char *board_ip) { board->req.arp_flags &= ~ATF_PERM; } - // install_firewall_board() is a no-op when no firewall backend is - // available (rootless install without CAP_NET_ADMIN, or - // firewall=none), so it is safe to call unconditionally. - ret = install_firewall_board(board->sockfd); - if(ret < 0) return ret; - board->write_packet_ptr = board->write_packet; board->read_packet_ptr = board->read_packet; @@ -139,8 +126,6 @@ int hm2_posix_close_board(hm2_eth_t *board) { int ret; board->llio.reset(&board->llio); - clear_firewall(); - if(board->req.arp_flags & ATF_PERM) { ret = ioctl(board->sockfd, SIOCDARP, &board->req); if(ret < 0) perror("ioctl SIOCDARP"); From 9978feee354123124b0716ef4fd6e8949f86a50f Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Mon, 7 Sep 2026 19:56:55 +0200 Subject: [PATCH 2/3] hm2_eth: Use libraries for ethernet backend --- src/Makefile | 11 ++-- src/hal/drivers/Submakefile | 12 ++++ src/hal/drivers/mesa-hostmot2/hm2_eth.c | 59 +++++++++++++++---- src/hal/drivers/mesa-hostmot2/hm2_eth.h | 4 +- .../drivers/mesa-hostmot2/hm2_eth_net_evl.c | 4 +- .../drivers/mesa-hostmot2/hm2_eth_net_posix.c | 4 +- 6 files changed, 71 insertions(+), 23 deletions(-) diff --git a/src/Makefile b/src/Makefile index 2c56a18ed3a..ef7ba15b609 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1080,11 +1080,8 @@ hm2_pci-objs := \ $(MATHSTUB) hm2_eth-objs := \ hal/drivers/mesa-hostmot2/hm2_eth.o \ - hal/drivers/mesa-hostmot2/hm2_eth_net_posix.o -ifeq ($(CONFIG_USPACE_XENOMAI_EVL),y) -hm2_eth-objs += hal/drivers/mesa-hostmot2/hm2_eth_net_evl.o -endif -hm2_eth-objs += $(MATHSTUB) + hal/drivers/mesa-hostmot2/hm2_eth_net_posix.o \ + $(MATHSTUB) hm2_spi-objs := \ hal/drivers/mesa-hostmot2/hm2_spi.o \ hal/drivers/mesa-hostmot2/llio_info.o \ @@ -1329,7 +1326,7 @@ modules: $(patsubst %.o,../rtlib/%.so,$(obj-m)) $(Q)objdump -w -j .rtapi_export -t objects/$*.tmp \ | awk 'BEGIN{print "{ global :"} /rtapi_exported_/{printf("%s;\n", substr($$6,16))} END{print "local : * ; };"}' \ > objects/$*.ver - $(Q)$(CC) -shared -Bsymbolic -Wl,--version-script,objects/$*.ver -o $@ $^ -lm $(XENOMAI_EVL_LDFLAGS) $(LDFLAGS) + $(Q)$(CC) -shared -Bsymbolic -Wl,--version-script,objects/$*.ver -o $@ $^ -lm $(LDFLAGS) $(Q)chmod -x $@ RTFLAGS += -fno-strict-aliasing -fwrapv @@ -1340,7 +1337,7 @@ $(sort $(filter-out objects/rtemc/tp/cruckig/%,$(RTOBJS))) : objects/rt%.o : %.c @rm -f $@ @mkdir -p $(dir $@) $(Q)$(CC) -c $(OPT) $(DEBUG) $(EXTRA_DEBUG) -DRTAPI \ - $(EXTRA_CFLAGS) $(XENOMAI_EVL_CFLAGS) \ + $(EXTRA_CFLAGS) \ -MP -MD -MF "${@:.o=.d}" -MT "$@" \ $< -o $@ diff --git a/src/hal/drivers/Submakefile b/src/hal/drivers/Submakefile index b4ff16113ef..6be58e665db 100644 --- a/src/hal/drivers/Submakefile +++ b/src/hal/drivers/Submakefile @@ -39,3 +39,15 @@ modules: \ hal/drivers/pluto_step_rbf.h endif + +ifeq ($(CONFIG_USPACE_XENOMAI_EVL),y) +NET_EVL_SRCS := hal/drivers/mesa-hostmot2/hm2_eth_net_evl.c +USERSRCS += $(NET_EVL_SRCS) +$(call TOOBJSDEPS, $(NET_EVL_SRCS)): EXTRAFLAGS += -fPIC $(XENOMAI_EVL_CFLAGS) +../lib/liblinuxcnc-hm2_eth_net_evl.so.0: $(call TOOBJS, $(NET_EVL_SRCS)) + $(ECHO) Linking $(notdir $@) + $(Q)$(CXX) -shared $(LDFLAGS) -o $@ $^ $(XENOMAI_EVL_LDFLAGS) -Wl,-soname,$(notdir $@) +TARGETS += ../lib/liblinuxcnc-hm2_eth_net_evl.so.0 +TARGETS += ../lib/liblinuxcnc-hm2_eth_net_evl.so +endif + diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.c b/src/hal/drivers/mesa-hostmot2/hm2_eth.c index 18eb1fb1d37..fbcc1f0c57f 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -50,9 +51,9 @@ #include "hostmot2.h" #include "hm2_eth.h" #include "hm2_eth_net_posix.h" -#ifdef USPACE_XENOMAI_EVL -#include "hm2_eth_net_evl.h" -#endif + +EXPORT_SYMBOL(hm2_eth_fetch_ifname); +EXPORT_SYMBOL(hm2_eth_fetch_hwaddr); #define RECV_TIMEOUT_NON_RT_NS (200 * 1000 * 1000) //200ms for initialisation / non-realtime part @@ -679,7 +680,7 @@ static char* inet_ntoa_buf(struct in_addr in, char *buf, size_t n) { return buf; } -char* fetch_ifname(int sockfd, char *buf, size_t n) { +char* hm2_eth_fetch_ifname(int sockfd, char *buf, size_t n) { struct sockaddr_in srcaddr; struct ifaddrs *ifa, *it; @@ -795,7 +796,7 @@ static int install_firewall_perinterface(const char *ifbuf) { return 0; } -int fetch_hwaddr(hm2_eth_t *board, unsigned char buf[6]) { +int hm2_eth_fetch_hwaddr(hm2_eth_t *board, unsigned char buf[6]) { lbp16_cmd_addr packet; unsigned char response[6]; LBP16_INIT_PACKET4(packet, 0x4983, 0x0002); @@ -815,6 +816,21 @@ int fetch_hwaddr(hm2_eth_t *board, unsigned char buf[6]) { return 0; } +#ifdef USPACE_XENOMAI_EVL +static void *eth_net_evl_lib = NULL; +static bool load_eth_net_evl(void) { + if (eth_net_evl_lib != NULL) { + return true; // Already checked + } + eth_net_evl_lib = dlopen("liblinuxcnc-hm2_eth_net_evl.so", RTLD_LOCAL | RTLD_NOW); + if (!eth_net_evl_lib) { + LL_PRINT("ERROR: EVL support loading library failed: %s\n", dlerror()); + return false; + } + return true; +} +#endif + static int init_board(hm2_eth_t *board, const char *board_ip, const char *board_rtnet){ //Default (NULL) is posix if (board_rtnet == NULL || strcmp(board_rtnet, "posix") == 0) { @@ -829,11 +845,34 @@ static int init_board(hm2_eth_t *board, const char *board_ip, const char *board_ LL_PRINT("ERROR: board_rtnet = %s not available, LinuxCNC not running with Xenomai4 EVL realtime\n", board_rtnet) return -1; } - board->init_board = &hm2_evl_init_board; - board->init_board_realtime = &hm2_evl_init_board_realtime; - board->close_board = &hm2_evl_close_board; - board->eth_socket_send = &hm2_evl_eth_socket_send; - board->eth_socket_recv = &hm2_evl_eth_socket_recv; + if (!load_eth_net_evl()) { + return -1; + } + board->init_board = dlsym(eth_net_evl_lib, "hm2_evl_init_board"); + if (board->init_board == NULL) { + LL_PRINT("ERROR: EVL support dlsym hm2_evl_init_board failed: %s\n", dlerror()); + return -1; + } + board->init_board_realtime = dlsym(eth_net_evl_lib, "hm2_evl_init_board_realtime"); + if (board->init_board_realtime == NULL) { + LL_PRINT("ERROR: EVL support dlsym hm2_evl_init_board_realtime failed: %s\n", dlerror()); + return -1; + } + board->close_board = dlsym(eth_net_evl_lib, "hm2_evl_close_board"); + if (board->close_board == NULL) { + LL_PRINT("ERROR: EVL support dlsym hm2_evl_init_close_boardboard failed: %s\n", dlerror()); + return -1; + } + board->eth_socket_send = dlsym(eth_net_evl_lib, "hm2_evl_eth_socket_send"); + if (board->eth_socket_send == NULL) { + LL_PRINT("ERROR: EVL support dlsym eth_socket_send failed: %s\n", dlerror()); + return -1; + } + board->eth_socket_recv = dlsym(eth_net_evl_lib, "hm2_evl_eth_socket_recv"); + if (board->eth_socket_recv == NULL) { + LL_PRINT("ERROR: EVL support dlsym eth_socket_recv failed: %s\n", dlerror()); + return -1; + } #else LL_PRINT("ERROR: board_rtnet = %s not available, LinuxCNC was built without Xenomai EVL support\n", board_rtnet); return -1; diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.h b/src/hal/drivers/mesa-hostmot2/hm2_eth.h index 3f1ecf7a620..dd6f98592f4 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.h +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.h @@ -94,7 +94,7 @@ struct hm2_eth_t { } *hal; }; -char* fetch_ifname(int sockfd, char *buf, size_t n); -int fetch_hwaddr(hm2_eth_t *board, unsigned char buf[6]); +char* hm2_eth_fetch_ifname(int sockfd, char *buf, size_t n); +int hm2_eth_fetch_hwaddr(hm2_eth_t *board, unsigned char buf[6]); #endif diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth_net_evl.c b/src/hal/drivers/mesa-hostmot2/hm2_eth_net_evl.c index 2dc54cbc098..423ebb8e972 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth_net_evl.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth_net_evl.c @@ -72,7 +72,7 @@ int hm2_evl_init_board(hm2_eth_t *board, const char *board_ip) { } strncpy(board->ip, board_ip, sizeof(board->ip)-1); - char *ifptr = fetch_ifname(board->sockfd, board->ifname, sizeof(board->ifname)); + char *ifptr = hm2_eth_fetch_ifname(board->sockfd, board->ifname, sizeof(board->ifname)); if(!ifptr) { LL_PRINT("failed to retrieve interface name for board\n"); return 0; @@ -108,7 +108,7 @@ int hm2_evl_init_board(hm2_eth_t *board, const char *board_ip) { board->req.arp_ha.sa_family = AF_LOCAL; board->req.arp_flags = ATF_PERM | ATF_COM; - ret = fetch_hwaddr( board, (void*)&board->req.arp_ha.sa_data ); + ret = hm2_eth_fetch_hwaddr( board, (void*)&board->req.arp_ha.sa_data ); if (ret < 0) { LL_PRINT("ERROR: Could not retrieve hardware address (MAC) of %s: %s\n", board_ip, strerror(-ret)); return ret; diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.c b/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.c index 6286c0a59a3..e7f6f699c48 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.c @@ -60,7 +60,7 @@ int hm2_posix_init_board(hm2_eth_t *board, const char *board_ip) { } strncpy(board->ip, board_ip, sizeof(board->ip)-1); - char *ifptr = fetch_ifname(board->sockfd, board->ifname, sizeof(board->ifname)); + char *ifptr = hm2_eth_fetch_ifname(board->sockfd, board->ifname, sizeof(board->ifname)); if(!ifptr) { LL_PRINT("failed to retrieve interface name for board\n"); return 0; @@ -92,7 +92,7 @@ int hm2_posix_init_board(hm2_eth_t *board, const char *board_ip) { board->req.arp_ha.sa_family = AF_LOCAL; board->req.arp_flags = ATF_PERM | ATF_COM; - ret = fetch_hwaddr( board, (void*)&board->req.arp_ha.sa_data ); + ret = hm2_eth_fetch_hwaddr( board, (void*)&board->req.arp_ha.sa_data ); if(ret < 0) { LL_PRINT("ERROR: Could not retrieve hardware address (MAC) of %s: %s\n", board_ip, strerror(-ret)); return ret; From a6c1479ff15be17684338a7ae6afdc07bc099cd0 Mon Sep 17 00:00:00 2001 From: Hannes Diethelm Date: Wed, 9 Sep 2026 13:52:04 +0200 Subject: [PATCH 3/3] CI: Add Xenomai3/4 build --- .github/scripts/install-xenomai.sh | 37 ++++++++++++++++++++++++++++++ .github/scripts/test-xenomai.sh | 16 +++++++++++++ .github/workflows/ci.yml | 32 ++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100755 .github/scripts/install-xenomai.sh create mode 100755 .github/scripts/test-xenomai.sh diff --git a/.github/scripts/install-xenomai.sh b/.github/scripts/install-xenomai.sh new file mode 100755 index 00000000000..a7ec58e811f --- /dev/null +++ b/.github/scripts/install-xenomai.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +set -eu #Needed so CI fails when anything is wrong +set -x + +DIST=$1 + +if false; then + #To install the Xenomai deb's from linuxcnc base (not available right now) + .github/scripts/add-linuxcnc-repository.sh "$DIST" + sudo apt-get --yes install libxenomai1 libxenomai-dev libevl libevl-dev linux-libc-evl-dev +else + CURLOPTS=( --no-progress-meter --retry-all-errors --retry 5 --retry-delay 2 -fLO ) + XENOMAI3_DLD="https://github.com/hdiethelm/xenomai3-linuxcnc/releases/download" + XENOMAI4_DLD="https://github.com/hdiethelm/xenomai4-linuxcnc/releases/download" + #To install the RTAI deb's from NTULINUX git + TMPDIR=$(mktemp -d) + ( + cd "$TMPDIR" + curl "${CURLOPTS[@]}" "${XENOMAI3_DLD}/xenomai-3.3-6/libxenomai1_3.3-6_amd64.deb" + curl "${CURLOPTS[@]}" "${XENOMAI3_DLD}/xenomai-3.3-6/libxenomai-dev_3.3-6_amd64.deb" + curl "${CURLOPTS[@]}" "${XENOMAI4_DLD}/libevl-0.59-3/libevl_0.59-3_amd64.deb" + curl "${CURLOPTS[@]}" "${XENOMAI4_DLD}/libevl-0.59-3/libevl-dev_0.59-3_amd64.deb" + curl "${CURLOPTS[@]}" "${XENOMAI4_DLD}/kernel-6.12.90-cip24-xenomai4-0.59_6.12.90-4/linux-libc-evl-dev_6.12.90-4_amd64.deb" + ) + #dependency + sudo apt-get --quiet update + sudo apt-get --yes install libbpf1 adduser + #packages + sudo dpkg -i \ + "$TMPDIR/libxenomai1_3.3-6_amd64.deb" \ + "$TMPDIR/libxenomai-dev_3.3-6_amd64.deb" \ + "$TMPDIR/libevl_0.59-3_amd64.deb" \ + "$TMPDIR/libevl-dev_0.59-3_amd64.deb" \ + "$TMPDIR/linux-libc-evl-dev_6.12.90-4_amd64.deb" + rm -rf "$TMPDIR" +fi diff --git a/.github/scripts/test-xenomai.sh b/.github/scripts/test-xenomai.sh new file mode 100755 index 00000000000..d63613e3b17 --- /dev/null +++ b/.github/scripts/test-xenomai.sh @@ -0,0 +1,16 @@ +#! /bin/sh + +#We can not really test the xenomai runtime as long as the kernel is not running +#but this would need a VM. +#What we can do is test if the libraries where built. Due to configure autodetect, +#this can silently fail. + +FILES="lib/liblinuxcnc-uspace-xenomai.so.0 lib/liblinuxcnc-uspace-xenomai-evl.so.0" +for FILE in $FILES; do + if [ ! -f "$FILE" ]; then + echo "$FILE not found" + exit 1 + fi +done +echo "Xenomai libraries where built" +exit 0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d04b5052449..10f5ef88a92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,6 +154,38 @@ jobs: run: | .github/scripts/verify-clean-repo.sh + rip-xenomai: + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - name: Dump GitHub context + env: + GITHUB_CONTEXT: ${{ toJson(github) }} + run: | + echo Number of CPUs: $(nproc) + echo "$GITHUB_CONTEXT" + - name: Checkout repository + uses: actions/checkout@v6 + with: + submodules: false + fetch-depth: 1 + - name: Install dependencies + run: | + set -x + .github/scripts/install-xenomai.sh trixie + .github/scripts/install-deps.sh + - name: Build RIP Xenomai + run: | + set -x + .github/scripts/build-rip.sh --with-realtime=uspace + - name: Verify xenomai build + run: | + set -x + .github/scripts/test-xenomai.sh + - name: Verify no untracked or modified files after build + run: | + .github/scripts/verify-clean-repo.sh + rip-and-test-clang: runs-on: ubuntu-24.04 timeout-minutes: 45