Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/scripts/install-xenomai.sh
Original file line number Diff line number Diff line change
@@ -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
16 changes: 16 additions & 0 deletions .github/scripts/test-xenomai.sh
Original file line number Diff line number Diff line change
@@ -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
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 4 additions & 7 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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
Expand All @@ -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 $@

Expand Down
12 changes: 12 additions & 0 deletions src/hal/drivers/Submakefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

88 changes: 73 additions & 15 deletions src/hal/drivers/mesa-hostmot2/hm2_eth.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <dlfcn.h>

#include <rtapi_slab.h>
#include <rtapi_ctype.h>
Expand All @@ -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

Expand Down Expand Up @@ -591,7 +592,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;

Expand Down Expand Up @@ -638,7 +639,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:
Expand Down Expand Up @@ -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;

Expand All @@ -706,7 +707,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];
Expand Down Expand Up @@ -751,7 +752,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.
Expand Down Expand Up @@ -795,7 +796,7 @@ 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);
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -843,7 +882,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
Expand Down Expand Up @@ -1723,6 +1779,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);
Expand All @@ -1736,6 +1793,7 @@ void rtapi_app_exit(void) {
for(i = 0; i<MAX_ETH_BOARDS && board_ip[i] && board_ip[i][0]; i++)
close_board(&boards[i]);

clear_firewall();
cleanup_firewall();

kvlist_free(&board_num);
Expand Down
8 changes: 2 additions & 6 deletions src/hal/drivers/mesa-hostmot2/hm2_eth.h
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,7 @@ struct hm2_eth_t {
} *hal;
};

bool use_firewall();
int install_firewall_board(int sockfd);
int install_firewall_perinterface(const char *ifbuf);
void clear_firewall();
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
19 changes: 2 additions & 17 deletions src/hal/drivers/mesa-hostmot2/hm2_eth_net_evl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -82,13 +82,6 @@ int hm2_evl_init_board(hm2_eth_t *board, const char *board_ip) {
LL_PRINT("ERROR: can't SO_BINDTODEVICE socket: %s\n", strerror(errno));
}

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;
Expand All @@ -115,18 +108,12 @@ 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;
}

// 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;

Expand Down Expand Up @@ -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));
Expand Down
Loading