diff --git a/debian/control.top.in b/debian/control.top.in index 1016d940fc9..54317ce6102 100644 --- a/debian/control.top.in +++ b/debian/control.top.in @@ -28,6 +28,7 @@ Build-Depends: libgl-dev | libgl1-mesa-dev, libglu1-mesa-dev, libgtk-3-dev, + libcap-dev, libmodbus-dev (>= 3.0), libgpiod-dev, @LIBREADLINE_DEV@, diff --git a/debian/extras/lib/udev/rules.d/99-linuxcnc-hm2-pci.rules b/debian/extras/lib/udev/rules.d/99-linuxcnc-hm2-pci.rules new file mode 100644 index 00000000000..5bf292b4beb --- /dev/null +++ b/debian/extras/lib/udev/rules.d/99-linuxcnc-hm2-pci.rules @@ -0,0 +1,51 @@ +# LinuxCNC - grant Mesa HostMot2 PCI cards to the plugdev group so +# rtapi_app can map their config space and BARs without CAP_DAC_OVERRIDE. +# Needed only for rootless (file-capabilities) builds; setuid-root builds +# bypass DAC regardless. +# +# Add or remove Mesa subsystem IDs below to match new boards. SSIDs come +# from src/hal/drivers/mesa-hostmot2/hm2_pci.h. + +ACTION!="add|change", GOTO="linuxcnc_hm2_end" +SUBSYSTEM!="pci", GOTO="linuxcnc_hm2_end" + +# Direct-Mesa cards (5i24, 5i25, 5i25T, 6i25, 6i25T): single vendor match. +ATTR{vendor}=="0x2718", GOTO="linuxcnc_hm2_chmod" + +# PLX-bridged Mesa cards share vendor 0x10B5 with generic PLX bridges, +# so match Mesa subsystem_device ids one at a time. +ATTR{vendor}!="0x10b5", GOTO="linuxcnc_hm2_end" +# 5i20 +ATTRS{subsystem_device}=="0x3131", GOTO="linuxcnc_hm2_chmod" +# 4i65 +ATTRS{subsystem_device}=="0x3132", GOTO="linuxcnc_hm2_chmod" +# 4i68 (old SSID) +ATTRS{subsystem_device}=="0x3133", GOTO="linuxcnc_hm2_chmod" +# 4i68 (new SSID) +ATTRS{subsystem_device}=="0x3311", GOTO="linuxcnc_hm2_chmod" +# 5i21 +ATTRS{subsystem_device}=="0x3312", GOTO="linuxcnc_hm2_chmod" +# 5i22-1.5M +ATTRS{subsystem_device}=="0x3313", GOTO="linuxcnc_hm2_chmod" +# 5i22-1.0M +ATTRS{subsystem_device}=="0x3314", GOTO="linuxcnc_hm2_chmod" +# 5i23 +ATTRS{subsystem_device}=="0x3315", GOTO="linuxcnc_hm2_chmod" +# 3x20-10 +ATTRS{subsystem_device}=="0x3427", GOTO="linuxcnc_hm2_chmod" +# 3x20-15 +ATTRS{subsystem_device}=="0x3428", GOTO="linuxcnc_hm2_chmod" +# 3x20-20 +ATTRS{subsystem_device}=="0x3429", GOTO="linuxcnc_hm2_chmod" +# 4i69-16 +ATTRS{subsystem_device}=="0x3472", GOTO="linuxcnc_hm2_chmod" +# 4i69-25 +ATTRS{subsystem_device}=="0x3473", GOTO="linuxcnc_hm2_chmod" +GOTO="linuxcnc_hm2_end" + +LABEL="linuxcnc_hm2_chmod" +# Fork a helper; sysfs files may not exist until the device is fully +# sized, so the chmod failures are ignored. +RUN+="/bin/sh -c 'chgrp plugdev /sys%p/config /sys%p/resource* 2>/dev/null; chmod g+rw /sys%p/config /sys%p/resource* 2>/dev/null; exit 0'" + +LABEL="linuxcnc_hm2_end" diff --git a/debian/extras/lib/udev/rules.d/99-linuxcnc-realtime.rules b/debian/extras/lib/udev/rules.d/99-linuxcnc-realtime.rules new file mode 100644 index 00000000000..fa3b057e192 --- /dev/null +++ b/debian/extras/lib/udev/rules.d/99-linuxcnc-realtime.rules @@ -0,0 +1,8 @@ +# LinuxCNC - expose realtime tuning knobs to the plugdev group so that +# rtapi_app can tune latency without CAP_DAC_OVERRIDE when running under +# file capabilities. setuid-root builds do not need this rule. + +# /dev/cpu_dma_latency: harden_rt() opens this to pin CPU idle states at +# C0, cutting wake-up jitter on AC-powered machines. Default is 0600 +# root:root, so an unprivileged rtapi_app would fail to open it. +KERNEL=="cpu_dma_latency", MODE="0660", GROUP="plugdev" diff --git a/docs/src/man/man9/hm2_eth.9.adoc b/docs/src/man/man9/hm2_eth.9.adoc index 7cf1b3015a9..cdb29cedddd 100644 --- a/docs/src/man/man9/hm2_eth.9.adoc +++ b/docs/src/man/man9/hm2_eth.9.adoc @@ -7,7 +7,7 @@ IO boards, with HostMot2 firmware. == SYNOPSIS -*loadrt hm2_eth* [**config=**"__str__[,__str__...]"] [**board_ip=**__ip__[,__ip__...] ] [**board_mac=**__mac__[,__mac__...] ] +*loadrt hm2_eth* [**config=**"__str__[,__str__...]"] [**board_ip=**__ip__[,__ip__...] ] [**board_mac=**__mac__[,__mac__...] ] [**no_iptables=**__0|1__] ____ *config* [default: ""]:: @@ -15,6 +15,16 @@ ____ *board_ip* [default: ""]:: The IP address of the board(s), separated by commas. As shipped, the board address is 192.168.1.121. +*no_iptables* [default: 0]:: + Explicit override that disables all iptables interaction. By default + hm2_eth installs *iptables* and *ip6tables* rules itself; rtapi_app + raises *cap_net_admin* into its ambient capability set at startup so + the calls succeed under both setuid-root and rootless (file-cap) + installs. If the cap is not held the probe fails and rule + installation is skipped with a warning; in that case configure the + rules manually using the recipe in the NOTES section below. Set + *no_iptables=1* when iptables is reachable but you prefer to manage + the firewall externally (nftables, firewalld, systemd units). ____ == DESCRIPTION @@ -146,6 +156,53 @@ At (normal) exit, hm2_eth will remove the rules. After a crash, you can manually clear the rules with *sudo iptables -F hm2-eth-rules-output*; the rules are also removed by a reboot. +=== Manual iptables configuration + +When LinuxCNC is installed without *cap_net_admin* on rtapi_app +(typically because *sudo make setcap* was not run after the build), +hm2_eth cannot install its rules and prints a warning. Set up the +chain manually as root. Adjust the IP addresses, UDP destination port, +and interface name to match your install: + +---- +HOST_IP=192.168.1.1 +BOARD_IP=192.168.1.121 +BOARD_DPORT=27181 +IFACE=eth1 + +iptables -N hm2-eth-rules-output +iptables -I OUTPUT 1 -j hm2-eth-rules-output +iptables -A hm2-eth-rules-output \ + -p udp -m udp -d $BOARD_IP --dport $BOARD_DPORT \ + -s $HOST_IP -j ACCEPT +iptables -A hm2-eth-rules-output -o $IFACE -p icmp -j DROP +iptables -A hm2-eth-rules-output -o $IFACE \ + -j REJECT --reject-with icmp-admin-prohibited +ip6tables -N hm2-eth-rules-output +ip6tables -I OUTPUT 1 -j hm2-eth-rules-output +ip6tables -A hm2-eth-rules-output -o $IFACE -j DROP +---- + +For full IPv6 quiescence (no router solicitations or neighbor discovery +on the dedicated interface), additionally add this line to +`/etc/sysctl.d/99-hm2-eth.conf` and reboot: + +---- +net.ipv6.conf.IFACE.disable_ipv6 = 1 +---- + +(The default ip6tables rule above only drops outbound IPv6; the kernel +still generates the packets.) Tear down the runtime rules with: + +---- +iptables -F hm2-eth-rules-output +iptables -D OUTPUT -j hm2-eth-rules-output +iptables -X hm2-eth-rules-output +ip6tables -F hm2-eth-rules-output +ip6tables -D OUTPUT -j hm2-eth-rules-output +ip6tables -X hm2-eth-rules-output +---- + "hardware-irq-coalesce-rx-usecs" decreases time waiting to receive a packet on most systems, but on at least some Marvel-chipset NICs it is harmful. If the line does not improve system performance, then remove it. diff --git a/src/Makefile b/src/Makefile index 15c9000d13c..5eec56cdfc7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -57,7 +57,7 @@ endif ifeq ($(MAKECMDGOALS),) TRIVIAL_BUILD=no else -ifeq ($(filter-out docclean clean setuid install tags swish,$(MAKECMDGOALS)),) +ifeq ($(filter-out docclean clean setuid setcap install tags swish,$(MAKECMDGOALS)),) TRIVIAL_BUILD=yes else TRIVIAL_BUILD=no @@ -140,7 +140,13 @@ ifeq ($(RUN_IN_PLACE),yes) ifneq ($(BUILD_SYS),uspace) @if [ -f ../bin/linuxcnc_module_helper ]; then if ! [ `id -u` = 0 -a -O ../bin/linuxcnc_module_helper -a -u ../bin/linuxcnc_module_helper ]; then $(VECHO) "You now need to run 'sudo make setuid' in order to run in place."; fi; fi else - @if [ -f ../bin/rtapi_app ]; then if ! [ `id -u` = 0 -a -O ../bin/rtapi_app -a -u ../bin/rtapi_app ]; then $(VECHO) "You now need to run 'sudo make setuid' in order to run in place with access to hardware."; fi; fi + @if [ -f ../bin/rtapi_app ]; then \ + if [ `id -u` = 0 -a -O ../bin/rtapi_app -a -u ../bin/rtapi_app ]; then :; \ + elif PATH="/sbin:/usr/sbin:$$PATH" command -v getcap >/dev/null 2>&1 \ + && [ -n "`PATH=/sbin:/usr/sbin:$$PATH getcap ../bin/rtapi_app 2>/dev/null`" ]; then :; \ + else $(VECHO) "You now need to run 'sudo make setuid' or 'sudo make setcap' in order to run in place with access to hardware."; \ + fi; \ + fi endif endif @@ -568,6 +574,25 @@ endif chown root ../bin/linuxcnc_module_helper chmod 4750 ../bin/linuxcnc_module_helper +# File capabilities alternative to setuid (uspace only). +# Grants rtapi_app the kernel privileges it needs without running as root: +# cap_ipc_lock - mlock() for realtime memory +# cap_net_admin - raw socket access for hm2_eth / iptables management +# cap_sys_rawio - iopl() and /dev/mem for parallel port and PCI I/O +# cap_sys_nice - SCHED_FIFO scheduling and CPU affinity +# Linux capabilities are not inherited across exec(), so /sbin/iptables +# launched from rtapi_app would run unprivileged. rtapi_app raises +# cap_net_admin into its ambient set at startup so it survives execve(). +# Clears any setuid bit left by a prior 'make setuid' so the two paths don't +# silently stack. +setcap: +ifeq ($(BUILD_SYS),uspace) + chmod u-s ../bin/rtapi_app + setcap cap_ipc_lock,cap_net_admin,cap_sys_rawio,cap_sys_nice+ep ../bin/rtapi_app +else + @echo "setcap target is only supported for uspace builds" >&2; exit 1 +endif + # These rules allows a header file from this directory to be installed into # ../include. A pair of rules like these will exist in the Submakefile # of each file that contains headers. diff --git a/src/hal/drivers/hal_evoreg.c b/src/hal/drivers/hal_evoreg.c index 5bc2ee34287..cf33a78a11f 100644 --- a/src/hal/drivers/hal_evoreg.c +++ b/src/hal/drivers/hal_evoreg.c @@ -66,20 +66,9 @@ #include /* isspace() */ #include /* RTAPI realtime OS API */ #include /* RTAPI realtime module decls */ +#include /* rtapi_inb(), rtapi_outb() */ #include /* HAL public API decls */ -/* If FASTIO is defined, uses outb() and inb() from , - instead of rtapi_outb() and rtapi_inb() - the ones - are inlined, and save a microsecond or two (on my 233MHz box) -*/ -#define FASTIO - -#ifdef FASTIO -#define rtapi_inb inb -#define rtapi_outb outb -#include -#endif - /* module information */ MODULE_AUTHOR("Martin Kuhnle"); MODULE_DESCRIPTION("SIEMENS-EVOREG Driver for EMC HAL"); diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.c b/src/hal/drivers/mesa-hostmot2/hm2_eth.c index 8cb23fe40b7..5d78d85cf7e 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -45,7 +46,6 @@ #include "hostmot2-lowlevel.h" #include "hostmot2.h" #include "hm2_eth.h" -#include "eshellf.h" struct kvlist { struct rtapi_list_head list; @@ -97,6 +97,9 @@ RTAPI_MP_ARRAY_STRING(config, MAX_ETH_BOARDS, "config string for the AnyIO board int debug = 0; RTAPI_MP_INT(debug, "Developer/debug use only! Enable debug logging."); +static int no_iptables = 0; +RTAPI_MP_INT(no_iptables, "Skip automatic iptables rule installation; firewall must be configured externally."); + static int boards_count = 0; int comm_active = 0; @@ -463,38 +466,115 @@ static hm2_eth_t boards[MAX_ETH_BOARDS]; static int eth_socket_send(int sockfd, const void *buffer, int len, int flags); static int eth_socket_recv(int sockfd, void *buffer, int len, int flags); -#define IPTABLES "env \"PATH=/usr/sbin:/sbin:${PATH}\" iptables" +// hm2_eth installs iptables/ip6tables rules to isolate the dedicated Mesa +// interface from non-realtime traffic. rtapi_app raises CAP_NET_ADMIN +// into its ambient set at startup (see uspace_rtapi_main.cc), so the +// caps survive execve() into the iptables binaries even when we run +// under file caps instead of setuid root. +#define IPTABLES_BIN "/sbin/iptables" +#define IP6TABLES_BIN "/sbin/ip6tables" #define CHAIN "hm2-eth-rules-output" -static bool chain_exists() { - int result = - shell(IPTABLES" -n -L "CHAIN" > /dev/null 2>&1"); - return result == EXIT_SUCCESS; +// run_iptables(): fork+exec the named iptables binary with a +// NULL-terminated argv list (sentinel == NULL, do not omit). Returns +// the child exit status, or -1 on spawn/wait failure. When quiet, the +// child's stdout+stderr are suppressed so probe-style "is this rule +// present?" calls do not spam the log on the (expected) failure path. +static int run_iptables(const char *bin, int quiet, ...) { + char *argv[24]; + int n = 0; + // argv[0] is the program name iptables expects, not the path. + argv[n++] = (char *)(strrchr(bin, '/') ? strrchr(bin, '/') + 1 : bin); + + va_list ap; + va_start(ap, quiet); + while(n < (int)(sizeof(argv)/sizeof(argv[0])) - 1) { + char *a = va_arg(ap, char *); + if(!a) break; + argv[n++] = a; + } + argv[n] = NULL; + va_end(ap); + + posix_spawn_file_actions_t fa, *pfa = NULL; + if(quiet && posix_spawn_file_actions_init(&fa) == 0) { + if(posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, + "/dev/null", O_WRONLY, 0) == 0 + && posix_spawn_file_actions_adddup2(&fa, STDOUT_FILENO, + STDERR_FILENO) == 0) { + pfa = &fa; + } + } + + pid_t pid; + int r = posix_spawn(&pid, bin, pfa, NULL, argv, environ); + if(pfa) posix_spawn_file_actions_destroy(&fa); + + if(r != 0) return -1; + int status; + if(waitpid(pid, &status, 0) < 0) return -1; + if(WIFEXITED(status)) return WEXITSTATUS(status); + return -1; } +#define IPT(quiet, ...) run_iptables(IPTABLES_BIN, (quiet), __VA_ARGS__, NULL) +#define IP6T(quiet, ...) run_iptables(IP6TABLES_BIN, (quiet), __VA_ARGS__, NULL) + static int iptables_state = -1; static bool use_iptables() { if(iptables_state == -1) { - if(!chain_exists()) { - int res = shell(IPTABLES " -N " CHAIN); - if(res != EXIT_SUCCESS) { - LL_PRINT("ERROR: Failed to create iptables chain "CHAIN); + if(no_iptables) { + LL_PRINT("Skipping iptables setup (no_iptables=1); " + "configure firewall externally.\n"); + return (iptables_state = 0); + } + // Read-only probe: list the INPUT chain. Fails when the + // process lacks CAP_NET_ADMIN (rootless without setcap, or + // setcap applied but ambient raise failed in rtapi_app). + if(IPT(1, "-n", "-L", "INPUT") != 0) { + LL_PRINT("iptables not available (missing CAP_NET_ADMIN?); " + "automatic firewall setup skipped. See hm2_eth(9) " + "NOTES for the manual rule recipe.\n"); + return (iptables_state = 0); + } + // Create chain only if absent; insert OUTPUT jump only if absent. + if(IPT(1, "-n", "-L", CHAIN) != 0) { + if(IPT(0, "-N", CHAIN) != 0) { + LL_PRINT("ERROR: failed to create iptables chain " CHAIN "\n"); return (iptables_state = 0); } } - // now add a jump to our chain at the start of the OUTPUT chain if it isn't in the chain already - int res = shell(IPTABLES "-C OUTPUT -j " CHAIN " 2>/dev/null || /sbin/iptables -I OUTPUT 1 -j " CHAIN); - if(res != EXIT_SUCCESS) { - LL_PRINT("ERROR: Failed to insert rule in OUTPUT chain"); - return (iptables_state = 0); + if(IPT(1, "-C", "OUTPUT", "-j", CHAIN) != 0) { + if(IPT(0, "-I", "OUTPUT", "1", "-j", CHAIN) != 0) { + LL_PRINT("ERROR: failed to insert OUTPUT jump\n"); + return (iptables_state = 0); + } } + // Mirror the chain for ip6tables so IPv6 isolation can hang off + // it. Best-effort: kernels without IPv6 support cause this to + // fail silently and the IPv6 rules are simply absent. + if(IP6T(1, "-n", "-L", CHAIN) != 0) + IP6T(1, "-N", CHAIN); + if(IP6T(1, "-C", "OUTPUT", "-j", CHAIN) != 0) + IP6T(1, "-I", "OUTPUT", "1", "-j", CHAIN); + return (iptables_state = 1); } return iptables_state; } static void clear_iptables() { - shell(IPTABLES" -F "CHAIN" > /dev/null 2>&1"); + IPT(1, "-F", CHAIN); + IP6T(1, "-F", CHAIN); +} + +static void cleanup_iptables() { + IPT(1, "-F", CHAIN); + IPT(1, "-D", "OUTPUT", "-j", CHAIN); + IPT(1, "-X", CHAIN); + IP6T(1, "-F", CHAIN); + IP6T(1, "-D", "OUTPUT", "-j", CHAIN); + IP6T(1, "-X", CHAIN); } static char* inet_ntoa_buf(struct in_addr in, char *buf, size_t n) { @@ -530,46 +610,10 @@ static char* fetch_ifname(int sockfd, char *buf, size_t n) { return NULL; } -static char *vseprintf(char *buf, char *ebuf, const char *fmt, va_list ap) { - int result = vsnprintf(buf, ebuf-buf, fmt, ap); - if(result < 0) return ebuf; - else if(buf + result > ebuf) return ebuf; - else return buf + result; -} - -static char *seprintf(char *buf, char *ebuf, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - char *result = vseprintf(buf, ebuf, fmt, ap); - va_end(ap); - return result; -} - -static int install_iptables_rule(const char *fmt, ...) { - char commandbuf[1024], *ptr = commandbuf, - *ebuf = commandbuf + sizeof(commandbuf); - ptr = seprintf(ptr, ebuf, IPTABLES" -A "CHAIN" "); - va_list ap; - va_start(ap, fmt); - ptr = vseprintf(ptr, ebuf, fmt, ap); - va_end(ap); - - if(ptr == ebuf) - { - LL_PRINT("ERROR: commandbuf too small\n"); - return -ENOSPC; - } - - int res = shell(commandbuf); - if(res == EXIT_SUCCESS) return 0; - - LL_PRINT("ERROR: Failed to execute '%s'\n", commandbuf); - return -EINVAL; -} - static int install_iptables_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]; socklen_t addrlen = sizeof(srcaddr); int res = getsockname(sockfd, &srcaddr, &addrlen); @@ -579,33 +623,48 @@ static int install_iptables_board(int sockfd) { res = getpeername(sockfd, &dstaddr, &addrlen); if(res < 0) return -errno; - res = install_iptables_rule( - "-p udp -m udp -d %s --dport %d -s %s --sport %d -j ACCEPT", - inet_ntoa_buf(dstaddr.sin_addr, dsthost, sizeof(dsthost)), - ntohs(dstaddr.sin_port), - inet_ntoa_buf(srcaddr.sin_addr, srchost, sizeof(srchost)), - ntohs(srcaddr.sin_port)); - return res; + if(!use_iptables()) return 0; + + inet_ntoa_buf(srcaddr.sin_addr, srchost, sizeof(srchost)); + inet_ntoa_buf(dstaddr.sin_addr, dsthost, sizeof(dsthost)); + snprintf(dport_s, sizeof(dport_s), "%d", ntohs(dstaddr.sin_port)); + snprintf(sport_s, sizeof(sport_s), "%d", ntohs(srcaddr.sin_port)); + + // --sport is safe here: cleanup_iptables() removes the chain on exit, + // so a stale rule from a previous run with a different ephemeral port + // cannot block the second invocation. + if(IPT(0, "-A", CHAIN, + "-p", "udp", "-m", "udp", + "-d", dsthost, "--dport", dport_s, + "-s", srchost, "--sport", sport_s, + "-j", "ACCEPT") != 0) + return -EINVAL; + return 0; } static int install_iptables_perinterface(const char *ifbuf) { - // without this rule, 'ping' spews a lot of messages like - // From 192.168.1.1 icmp_seq=5 Packet filtered - // many times for each ping packet sent. With this rule, - // ping prints 'ping: sendmsg: Operation not permitted' once - // per second. - int res = install_iptables_rule( - "-o %s -p icmp -j DROP", - ifbuf); - if(res < 0) return res; - - res = install_iptables_rule( - "-o %s -j REJECT --reject-with icmp-admin-prohibited", - ifbuf); - if(res < 0) return res; - - res = eshellf(HM2_LLIO_NAME, "/sbin/sysctl -q net.ipv6.conf.%s.disable_ipv6=1", ifbuf); - if(res < 0) return res; + // Without these rules, 'ping' spews a lot of "Packet filtered" + // messages. With them, ping prints 'ping: sendmsg: Operation not + // permitted' once per second. + // + // Outbound IPv6 on the dedicated interface is dropped via ip6tables + // rather than disable_ipv6 sysctl: writing the sysctl needs + // CAP_DAC_OVERRIDE (file is mode 644 root:root) and we'd rather not + // grant it to rtapi_app. Users who want full IPv6 quiescence (no + // router solicitations etc.) can additionally set + // 'net.ipv6.conf..disable_ipv6=1' in /etc/sysctl.conf. + if(!use_iptables()) return 0; + + if(IPT(0, "-A", CHAIN, "-o", (char *)ifbuf, "-p", "icmp", "-j", "DROP") != 0) + return -EINVAL; + if(IPT(0, "-A", CHAIN, "-o", (char *)ifbuf, + "-j", "REJECT", "--reject-with", "icmp-admin-prohibited") != 0) + return -EINVAL; + + // ip6tables is best-effort: kernel may not have IPv6 support + // compiled in, in which case the chain creation in use_iptables() + // already failed and this rule is simply absent. + IP6T(1, "-A", CHAIN, "-o", (char *)ifbuf, "-j", "DROP"); return 0; } @@ -709,11 +768,11 @@ static int init_board(hm2_eth_t *board, const char *board_ip) { return -errno; } - if(use_iptables()) - { - ret = install_iptables_board(board->sockfd); - if(ret < 0) return ret; - } + // install_iptables_board() is a no-op when iptables is unavailable + // (rootless install without setcap on hm2_eth_iptables, or + // no_iptables=1), so it is safe to call unconditionally. + ret = install_iptables_board(board->sockfd); + if(ret < 0) return ret; board->write_packet_ptr = board->write_packet; board->read_packet_ptr = board->read_packet; @@ -1619,7 +1678,7 @@ void rtapi_app_exit(void) { for(i = 0; i= 0) { + break; + } + + retval = seteuid(0); + if (retval != 0) { + eno = errno; + break; + } + + memfd = open("/dev/mem", O_RDWR); + retval = memfd >= 0 ? 0 : memfd; + eno = errno; + + if (seteuid(getuid()) != 0) { + errmsg(__func__, "unable to drop root privileges"); + /* Don't continue past this point, because following code may + * execute with unexpected privileges + */ + _exit(99); + } + } while (0); + + if (retval == 0) { + /* increment reference count */ + memaccess++; + } else { + errmsg(__func__,"error: %s", strerror(eno)); + } + + return retval; } static void decr_mem_usage ( void ) diff --git a/src/rtapi/Submakefile b/src/rtapi/Submakefile index a51231dc1af..b69918baa80 100644 --- a/src/rtapi/Submakefile +++ b/src/rtapi/Submakefile @@ -32,6 +32,7 @@ $(patsubst ./rtapi/%,../include/%,$(RTAPIINCS)): ../include/%.h: ./rtapi/%.h ifeq ($(BUILD_SYS),uspace) RTAPI_APP_SRCS := \ + rtapi/uspace_rtapi_main.cc \ rtapi/uspace_rtapi_app.cc \ rtapi/uspace_rtapi_parport.cc \ rtapi/uspace_rtapi_string.c \ @@ -43,39 +44,48 @@ $(call TOOBJSDEPS, $(RTAPI_APP_SRCS)): EXTRAFLAGS += -DSIM \ -UULAPI -DRTAPI -pthread ../bin/rtapi_app: $(call TOOBJS, $(RTAPI_APP_SRCS)) $(ECHO) Linking $(notdir $@) - $(Q)$(CXX) -rdynamic -o $@ $^ $(LIBDL) -pthread -lrt $(LIBUDEV_LIBS) -ldl $(LDFLAGS) + $(Q)$(CXX) -rdynamic -o $@ $^ $(LIBDL) -pthread -lrt -lfmt $(LIBUDEV_LIBS) -ldl -lcap $(LDFLAGS) TARGETS += ../bin/rtapi_app endif +USPACE_POSIX_SRCS := rtapi/uspace_posix.cc +USERSRCS += $(USPACE_POSIX_SRCS) +$(call TOOBJSDEPS, $(USPACE_POSIX_SRCS)): EXTRAFLAGS += -pthread -fPIC +../lib/liblinuxcnc-uspace-posix.so.0: $(call TOOBJS, $(USPACE_POSIX_SRCS)) + $(ECHO) Linking $(notdir $@) + $(Q)$(CXX) -shared $(LDFLAGS) -o $@ $^ -Wl,-soname,$(notdir $@) +TARGETS += ../lib/liblinuxcnc-uspace-posix.so.0 +TARGETS += ../lib/liblinuxcnc-uspace-posix.so + ifeq ($(CONFIG_USPACE_RTAI),y) USPACE_RTAI_SRCS := rtapi/uspace_rtai.cc USERSRCS += $(USPACE_RTAI_SRCS) $(call TOOBJSDEPS, $(USPACE_RTAI_SRCS)): EXTRAFLAGS += -pthread -fPIC $(filter-out -Wstrict-prototypes,$(RTAI_LXRT_CFLAGS)) -../lib/libuspace-rtai.so.0: $(call TOOBJS, $(USPACE_RTAI_SRCS)) +../lib/liblinuxcnc-uspace-rtai.so.0: $(call TOOBJS, $(USPACE_RTAI_SRCS)) $(ECHO) Linking $(notdir $@) $(Q)$(CXX) -shared $(LDFLAGS) -o $@ $^ $(RTAI_LXRT_LDFLAGS) -Wl,-soname,$(notdir $@) -TARGETS += ../lib/libuspace-rtai.so.0 -TARGETS += ../lib/libuspace-rtai.so +TARGETS += ../lib/liblinuxcnc-uspace-rtai.so.0 +TARGETS += ../lib/liblinuxcnc-uspace-rtai.so endif ifeq ($(CONFIG_USPACE_XENOMAI),y) USPACE_XENOMAI_SRCS := rtapi/uspace_xenomai.cc USERSRCS += $(USPACE_XENOMAI_SRCS) $(call TOOBJSDEPS, $(USPACE_XENOMAI_SRCS)): EXTRAFLAGS += -fPIC $(XENOMAI_CFLAGS) -../lib/libuspace-xenomai.so.0: $(call TOOBJS, $(USPACE_XENOMAI_SRCS)) +../lib/liblinuxcnc-uspace-xenomai.so.0: $(call TOOBJS, $(USPACE_XENOMAI_SRCS)) $(ECHO) Linking $(notdir $@) $(Q)$(CXX) -shared $(LDFLAGS) -o $@ $^ $(XENOMAI_LDFLAGS) -Wl,-soname,$(notdir $@) -TARGETS += ../lib/libuspace-xenomai.so.0 -TARGETS += ../lib/libuspace-xenomai.so +TARGETS += ../lib/liblinuxcnc-uspace-xenomai.so.0 +TARGETS += ../lib/liblinuxcnc-uspace-xenomai.so endif ifeq ($(CONFIG_USPACE_XENOMAI_EVL),y) USPACE_XENOMAI_EVL_SRCS := rtapi/uspace_xenomai_evl.cc USERSRCS += $(USPACE_XENOMAI_EVL_SRCS) $(call TOOBJSDEPS, $(USPACE_XENOMAI_EVL_SRCS)): EXTRAFLAGS += -fPIC $(XENOMAI_EVL_CFLAGS) -../lib/libuspace-xenomai-evl.so.0: $(call TOOBJS, $(USPACE_XENOMAI_EVL_SRCS)) +../lib/liblinuxcnc-uspace-xenomai-evl.so.0: $(call TOOBJS, $(USPACE_XENOMAI_EVL_SRCS)) $(ECHO) Linking $(notdir $@) $(Q)$(CXX) -shared $(LDFLAGS) -o $@ $^ $(XENOMAI_EVL_LDFLAGS) -Wl,-soname,$(notdir $@) -TARGETS += ../lib/libuspace-xenomai-evl.so.0 -TARGETS += ../lib/libuspace-xenomai-evl.so +TARGETS += ../lib/liblinuxcnc-uspace-xenomai-evl.so.0 +TARGETS += ../lib/liblinuxcnc-uspace-xenomai-evl.so endif diff --git a/src/rtapi/rtapi_pci.cc b/src/rtapi/rtapi_pci.cc index f3e71844515..cfbf05ca885 100644 --- a/src/rtapi/rtapi_pci.cc +++ b/src/rtapi/rtapi_pci.cc @@ -34,7 +34,7 @@ #include #include #include -#include "rtapi_uspace.hh" +#include "uspace_rtapi_app.hh" #include #include diff --git a/src/rtapi/uspace_common.h b/src/rtapi/uspace_common.h index 9c82c3a397e..a7981164b37 100644 --- a/src/rtapi/uspace_common.h +++ b/src/rtapi/uspace_common.h @@ -21,11 +21,14 @@ #include #include #include +#include #include #include #include #include #include +#include +#include #include #include @@ -39,7 +42,7 @@ static msg_level_t msg_level = RTAPI_MSG_ERR; /* message printing level */ #include "config.h" #ifdef RTAPI -#include "rtapi_uspace.hh" +#include "uspace_rtapi_app.hh" #endif typedef struct { @@ -115,7 +118,7 @@ int rtapi_shmem_new(int key, int module_id, unsigned long int size) */ /* ensure the segment is owned by user, not root */ if(geteuid() == 0) { - stat.shm_perm.uid = ruid; + stat.shm_perm.uid = WithRoot::getRuid(); res = shmctl(shmem->id, IPC_SET, &stat); if(res < 0) perror("shmctl IPC_SET"); } @@ -350,27 +353,38 @@ int rtapi_exit(int module_id) } int rtapi_is_kernelspace() { return 0; } -static int _rtapi_is_realtime = -1; + #ifdef __linux__ -static int detect_preempt_rt() { +// detect_preempt_rt() inspects uname for the PREEMPT_RT marker. Used only +// for diagnostic warning at startup; callers must not gate behavior on +// the kernel string, since SCHED_FIFO on a PREEMPT_DYNAMIC kernel is still +// useful (better than SCHED_OTHER, worse than PREEMPT_RT). +static inline int detect_preempt_rt() { struct utsname u; - int crit1 = 0; - - uname(&u); - crit1 = strcasestr (u.version, "PREEMPT RT") != 0; - - //"PREEMPT_RT" is used in the version string instead of "PREEMPT RT" starting with kernel version 5.4 - crit1 = crit1 || (strcasestr(u.version, "PREEMPT_RT") != 0); - - return crit1; + if(uname(&u) < 0) return 0; + return strcasestr(u.version, "PREEMPT RT") != 0 + || strcasestr(u.version, "PREEMPT_RT") != 0; } #else -static int detect_preempt_rt() { +static inline int detect_preempt_rt() { return 0; } #endif + +// FIXME: detect_rtai/detect_xenomai/detect_xenomai_evl currently gate on +// setuid because the RTAI/Xenomai backends still need root for iopl() +// (RTAI) or RTDM device access (Xenomai/EVL). Long-term these should +// probe the actual capability the way can_set_sched_fifo() does, paired +// with udev rules + a 'xenomai'/'evl' group; @hdiethelm has a follow-up +// planned. Until then, an unprivileged user on a Xenomai kernel cannot +// claim the Xenomai backend, and falls back to the SCHED_FIFO probe. +static inline int has_setuid_root() { + return geteuid() == 0; +} + #ifdef USPACE_RTAI static int detect_rtai() { + if(!has_setuid_root()) return 0; struct utsname u; uname(&u); return strcasestr (u.release, "-rtai") != 0; @@ -382,6 +396,7 @@ static int detect_rtai() { #endif #ifdef USPACE_XENOMAI static int detect_xenomai() { + if(!has_setuid_root()) return 0; struct stat sb; //Running xenomai has /proc/xenomai return stat("/proc/xenomai", &sb) == 0; @@ -393,6 +408,7 @@ static int detect_xenomai() { #endif #ifdef USPACE_XENOMAI_EVL static int detect_xenomai_evl() { + if(!has_setuid_root()) return 0; struct stat sb; //Running xenomai evl has /dev/evl but no /proc/xenomai return stat("/dev/evl", &sb) == 0; @@ -403,22 +419,64 @@ static int detect_xenomai_evl() { } #endif -static int detect_env_override() { - char *p = getenv("LINUXCNC_FORCE_REALTIME"); - return p != NULL && atoi(p) != 0; -} +// errno from the most recent sched_setscheduler(SCHED_FIFO) probe. Zero +// when the probe succeeded or has not run yet. Read via +// rtapi_sched_fifo_errno() from diagnostic code. +static int rtapi_sched_fifo_last_errno = 0; + +// Success-probe for realtime scheduling: briefly try to set SCHED_FIFO on +// the calling thread and restore the previous policy. Succeeds when the +// process holds CAP_SYS_NICE (file caps or setuid root) or has a matching +// RLIMIT_RTPRIO. Works on any kernel, so the probe also covers the +// PREEMPT_RT-vs-stock distinction implicitly: if we can actually get +// SCHED_FIFO, the platform can deliver realtime, regardless of how. +static int can_set_sched_fifo(void) { + struct sched_param old_param, probe_param; + int old_policy = sched_getscheduler(0); + if(old_policy < 0) { + rtapi_sched_fifo_last_errno = errno; + return 0; + } + if(sched_getparam(0, &old_param) < 0) { + rtapi_sched_fifo_last_errno = errno; + return 0; + } -static int detect_realtime() { - struct stat st; - if ((stat(EMC2_BIN_DIR "/rtapi_app", &st) < 0) - || st.st_uid != 0 || !(st.st_mode & S_ISUID)) + memset(&probe_param, 0, sizeof(probe_param)); + probe_param.sched_priority = sched_get_priority_min(SCHED_FIFO); + if(sched_setscheduler(0, SCHED_FIFO, &probe_param) < 0) { + rtapi_sched_fifo_last_errno = errno; return 0; - return detect_env_override() || detect_preempt_rt() || detect_rtai() || detect_xenomai() || detect_xenomai_evl(); + } + + // Best-effort restore; if this fails we are still on SCHED_FIFO at + // minimum priority, which is no worse than where we started. + sched_setscheduler(0, old_policy, &old_param); + rtapi_sched_fifo_last_errno = 0; + return 1; } +static inline int rtapi_sched_fifo_errno(void) { return rtapi_sched_fifo_last_errno; } + +// rtapi_is_realtime() reports whether this process can actually run +// realtime code. This matches the convention used by JACK, PipeWire, +// rtkit, Xenomai, and Klipper: surface the observed capability, not +// kernel metadata. The old setuid-root stat check has been removed; it +// stat()ed EMC2_BIN_DIR/rtapi_app rather than the running binary (breaking +// wrapper-based installs like NixOS /run/wrappers) and silently masked +// LINUXCNC_FORCE_REALTIME (see issue #3928). int rtapi_is_realtime() { - if(_rtapi_is_realtime == -1) _rtapi_is_realtime = detect_realtime(); - return _rtapi_is_realtime; + static int cached = -1; + if(cached != -1) return cached; + + const char *force = getenv("LINUXCNC_FORCE_REALTIME"); + if(force != NULL && atoi(force) != 0) + return (cached = 1); + + if(detect_rtai() || detect_xenomai() || detect_xenomai_evl()) + return (cached = 1); + + return (cached = can_set_sched_fifo()); } /* Like clock_nanosleep, except that an optional 'estimate of now' parameter may @@ -432,7 +490,7 @@ static int rtapi_clock_nanosleep(clockid_t clock_id, int flags, { (void)pnow; #if defined(HAVE_CLOCK_NANOSLEEP) - return clock_nanosleep(clock_id, flags, prequest, remain); + return TEMP_FAILURE_RETRY(clock_nanosleep(clock_id, flags, prequest, remain)); #else if(flags == 0) return nanosleep(prequest, remain); diff --git a/src/rtapi/uspace_posix.cc b/src/rtapi/uspace_posix.cc new file mode 100644 index 00000000000..a6490fdcf22 --- /dev/null +++ b/src/rtapi/uspace_posix.cc @@ -0,0 +1,246 @@ +/* Copyright (C) 2006-2014 Jeff Epler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "rtapi.h" +#include "uspace_rtapi_app.hh" +#include +#include +#include +#include +#ifdef HAVE_SYS_IO_H +#include +#endif + +namespace { +struct PosixTask : RtapiTask { + PosixTask() : RtapiTask{}, thr{} { + } + + pthread_t thr; /* thread's context */ +}; + +struct PosixApp : RtapiApp { + PosixApp(int policy = SCHED_FIFO) : RtapiApp(policy), do_thread_lock(policy != SCHED_FIFO) { + pthread_once(&key_once, init_key); + if (do_thread_lock) { + pthread_once(&lock_once, init_lock); + } + } + + RtapiTask *do_task_new() { + return new PosixTask; + } + + int task_delete(int id) { + auto task = ::rtapi_get_task(id); + if (!task) + return -EINVAL; + + pthread_cancel(task->thr); + pthread_join(task->thr, 0); + task->magic = 0; + task_array[id] = 0; + delete task; + return 0; + } + + int task_start(int task_id, unsigned long period_nsec) { + auto task = ::rtapi_get_task(task_id); + if (!task) + return -EINVAL; + + task->period = period_nsec; + struct sched_param param; + memset(¶m, 0, sizeof(param)); + param.sched_priority = task->prio; + + // limit PLL correction values to +/-1% of cycle time + task->pll_correction_limit = period_nsec / 100; + task->pll_correction = 0; + + int nprocs = sysconf(_SC_NPROCESSORS_ONLN); + + pthread_attr_t attr; + int ret; + if ((ret = pthread_attr_init(&attr)) != 0) + return -ret; + if ((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) + return -ret; + if ((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) + return -ret; + if ((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) + return -ret; + if ((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) + return -ret; + if (nprocs > 1) { + const static int rt_cpu_number = find_rt_cpu_number(); + rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); + if (rt_cpu_number != -1) { +#ifdef __FreeBSD__ + cpuset_t cpuset; +#else + cpu_set_t cpuset; +#endif + CPU_ZERO(&cpuset); + CPU_SET(rt_cpu_number, &cpuset); + if ((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) + return -ret; + } + } + if (do_thread_lock) + pthread_mutex_lock(&thread_lock); + if ((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) + return -ret; + + return 0; + } + + static void *wrapper(void *arg) { + auto task = reinterpret_cast(arg); + + pthread_setspecific(key, arg); + set_namef("rtapi_app:T#%d", task->id); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + rtapi_timespec_advance(task->nextstart, now, task->period + task->pll_correction); + + /* call the task function with the task argument */ + (task->taskcode)(task->arg); + + rtapi_print("ERROR: reached end of wrapper for task %d\n", task->id); + return NULL; + } + + int task_pause(int task_id) { + (void)task_id; + return -ENOSYS; + } + + int task_resume(int task_id) { + (void)task_id; + return -ENOSYS; + } + + long long task_pll_get_reference(void) { + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return 0; + return task->nextstart.tv_sec * 1000000000LL + task->nextstart.tv_nsec; + } + + int task_pll_set_correction(long value) { + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; + if (value > task->pll_correction_limit) + value = task->pll_correction_limit; + if (value < -(task->pll_correction_limit)) + value = -(task->pll_correction_limit); + task->pll_correction = value; + return 0; + } + + void wait() { + if (do_thread_lock) + pthread_mutex_unlock(&thread_lock); + pthread_testcancel(); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); + rtapi_timespec_advance(task->nextstart, task->nextstart, task->period + task->pll_correction); + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + if (rtapi_timespec_less(task->nextstart, now)) { + if (policy == SCHED_FIFO) + unexpected_realtime_delay(task); + } else { + int res = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &task->nextstart, nullptr); + if (res < 0) + perror("clock_nanosleep"); + } + if (do_thread_lock) + pthread_mutex_lock(&thread_lock); + } + + unsigned char do_inb(unsigned int port) { +#ifdef HAVE_SYS_IO_H + return inb(port); +#else + (void)port; + return 0; +#endif + } + + void do_outb(unsigned char val, unsigned int port) { +#ifdef HAVE_SYS_IO_H + return outb(val, port); +#else + (void)val; + (void)port; +#endif + } + + int task_self() { + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; + return task->id; + } + + bool do_thread_lock; + + static pthread_once_t key_once; + static pthread_key_t key; + static void init_key(void) { + pthread_key_create(&key, NULL); + } + + static pthread_once_t lock_once; + static pthread_mutex_t thread_lock; + static void init_lock(void) { + pthread_mutex_init(&thread_lock, NULL); + } + + long long do_get_time() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000000000LL + ts.tv_nsec; + } + + void do_delay(long ns) { + struct timespec ts = {0, ns}; + clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, nullptr); + } +}; + +pthread_once_t PosixApp::key_once = PTHREAD_ONCE_INIT; +pthread_once_t PosixApp::lock_once = PTHREAD_ONCE_INIT; +pthread_key_t PosixApp::key; +pthread_mutex_t PosixApp::thread_lock; + +} // namespace + +extern "C" RtapiApp *make(int policy); + +RtapiApp *make(int policy) { + if (policy == SCHED_OTHER) { + rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using POSIX non-realtime\n"); + } else { + rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using POSIX realtime\n"); + } + return new PosixApp(policy); +} diff --git a/src/rtapi/uspace_rtai.cc b/src/rtapi/uspace_rtai.cc index dd6549e8441..ad3adf2dd21 100644 --- a/src/rtapi/uspace_rtai.cc +++ b/src/rtapi/uspace_rtai.cc @@ -1,28 +1,44 @@ +/* Copyright (C) 2016 Jeff Epler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ #include "config.h" #include "rtapi.h" -#include "rtapi_uspace.hh" +#include "uspace_rtapi_app.hh" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnarrowing" #include #pragma GCC diagnostic pop +#include #ifdef HAVE_SYS_IO_H #include #endif -namespace -{ +namespace { RtapiApp *app; -struct RtaiTask : rtapi_task { - RtaiTask() : rtapi_task{}, cancel{}, rt_task{}, thr{} {} +struct RtaiTask : RtapiTask { + RtaiTask() : RtapiTask{}, cancel{}, rt_task{}, thr{} { + } std::atomic_int cancel; RT_TASK *rt_task; pthread_t thr; }; -template -T *get_task(int task_id) { - return static_cast(RtapiApp::get_task(task_id)); +template T *get_task(int task_id) { + return static_cast(RtapiApp::get_task(task_id)); } @@ -31,13 +47,14 @@ struct RtaiApp : RtapiApp { pthread_once(&key_once, init_key); } - RtaiTask *do_task_new() { + RtapiTask *do_task_new() { return new RtaiTask; } int task_delete(int id) { auto task = ::get_task(id); - if(!task) return -EINVAL; + if (!task) + return -EINVAL; task->cancel = 1; pthread_join(task->thr, nullptr); @@ -49,7 +66,8 @@ struct RtaiApp : RtapiApp { int task_start(int task_id, unsigned long period_nsec) { auto task = ::get_task(task_id); - if(!task) return -EINVAL; + if (!task) + return -EINVAL; task->period = period_nsec; struct sched_param param; @@ -62,30 +80,30 @@ struct RtaiApp : RtapiApp { int ret; pthread_attr_t attr; - if((ret = pthread_attr_init(&attr)) != 0) + if ((ret = pthread_attr_init(&attr)) != 0) return -ret; - if((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) + if ((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) return -ret; - if((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) + if ((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) return -ret; - if((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) + if ((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) return -ret; - if((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) + if ((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) return -ret; - if((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) + if ((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) return -ret; return 0; } static void *wrapper(void *arg) { - auto task = reinterpret_cast(arg); + auto task = reinterpret_cast(arg); pthread_setspecific(key, arg); - - int nprocs = sysconf( _SC_NPROCESSORS_ONLN ); - int cpus_allowed = 1 << (nprocs-1); //Use last CPU as default + + int nprocs = sysconf(_SC_NPROCESSORS_ONLN); + int cpus_allowed = 1 << (nprocs - 1); //Use last CPU as default const static int rt_cpu_number = find_rt_cpu_number(); - if(rt_cpu_number != -1) { + if (rt_cpu_number != -1) { rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); cpus_allowed = 1 << rt_cpu_number; } @@ -96,7 +114,7 @@ struct RtaiApp : RtapiApp { rt_task_use_fpu(task->rt_task, 1); rt_make_hard_real_time(); rt_task_make_periodic_relative_ns(task->rt_task, task->period, task->period); - (task->taskcode) (task->arg); + (task->taskcode)(task->arg); rtapi_print("ERROR: reached end of wrapper for task %d\n", task->id); rt_make_soft_real_time(); @@ -105,13 +123,15 @@ struct RtaiApp : RtapiApp { int task_pause(int task_id) { auto task = ::get_task(task_id); - if(!task) return -EINVAL; + if (!task) + return -EINVAL; return rt_task_suspend(task->rt_task); } int task_resume(int task_id) { auto task = ::get_task(task_id); - if(!task) return -EINVAL; + if (!task) + return -EINVAL; return rt_task_resume(task->rt_task); } @@ -129,11 +149,12 @@ struct RtaiApp : RtapiApp { void wait() { int task_id = task_self(); auto task = ::get_task(task_id); - if(task->cancel) { + if (task->cancel) { rt_make_soft_real_time(); pthread_exit(nullptr); } - if(rt_task_wait_period() < 0) unexpected_realtime_delay(task); + if (rt_task_wait_period() < 0) + unexpected_realtime_delay(task); } unsigned char do_inb(unsigned int port) { @@ -149,19 +170,15 @@ struct RtaiApp : RtapiApp { #ifdef HAVE_SYS_IO_H return outb(val, port); #else + (void)val; (void)port; - return 0; #endif } - int run_threads(int fd, int (*callback)(int fd)) { - while(callback(fd)) { /* nothing */ } - return 0; - } - int task_self() { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; return task->id; } @@ -190,11 +207,14 @@ struct RtaiApp : RtapiApp { pthread_once_t RtaiApp::key_once; pthread_key_t RtaiApp::key; -} +} // namespace -extern "C" RtapiApp *make(); +extern "C" RtapiApp *make(int policy); -RtapiApp *make() { +RtapiApp *make(int policy) { + if (policy != SCHED_FIFO) { + throw std::invalid_argument("Only SCHED_FIFO allowed"); + } rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using LXRT realtime\n"); - return app = new RtaiApp; + return app = new RtaiApp(); } diff --git a/src/rtapi/uspace_rtapi_app.cc b/src/rtapi/uspace_rtapi_app.cc index bda630254d0..8e3746e0af8 100644 --- a/src/rtapi/uspace_rtapi_app.cc +++ b/src/rtapi/uspace_rtapi_app.cc @@ -15,56 +15,18 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "config.h" -#include +#include "rtapi.h" +#include "uspace_rtapi_app.hh" -#ifdef __linux__ -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#ifdef HAVE_SYS_IO_H -#include -#endif -#include -#include -#ifdef __linux__ -#include -#include -#endif -#ifdef __FreeBSD__ -#include -#endif - -#include - -#include "rtapi.h" -#include -#include "hal/hal_priv.h" -#include "rtapi_uspace.hh" +#include +#include std::atomic_int WithRoot::level; -static uid_t euid, ruid; - -#include "rtapi/uspace_common.h" +uid_t WithRoot::ruid, WithRoot::euid; WithRoot::WithRoot() { - if(!level++) { + if (!level++) { #ifdef __linux__ setfsuid(euid); #endif @@ -72,848 +34,46 @@ WithRoot::WithRoot() { } WithRoot::~WithRoot() { - if(!--level) { + if (!--level) { #ifdef __linux__ setfsuid(ruid); #endif } } -namespace -{ -RtapiApp &App(); - -struct message_t { - msg_level_t level; - char msg[1024-sizeof(level)]; -}; - -boost::lockfree::queue> -rtapi_msg_queue; - -static void set_namef(const char *fmt, ...) { - char *buf = NULL; - va_list ap; - - va_start(ap, fmt); - if (vasprintf(&buf, fmt, ap) < 0) { - va_end(ap); - return; - } - va_end(ap); - - int res = pthread_setname_np(pthread_self(), buf); - if (res) { - fprintf(stderr, "pthread_setname_np() failed for %s: %d\n", buf, res); - } - free(buf); -} - -pthread_t queue_thread; -void *queue_function(void * /*arg*/) { - set_namef("rtapi_app:mesg"); - // note: can't use anything in this function that requires App() to exist - // but it's OK to use functions that aren't safe for realtime (that's the - // point of running this in a thread) - while(1) { - pthread_testcancel(); - pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, nullptr); - rtapi_msg_queue.consume_all([](const message_t &m) { - fputs(m.msg, m.level == RTAPI_MSG_ALL ? stdout : stderr); - }); - pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr); - struct timespec ts = {0, 10000000}; - rtapi_clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL, NULL); - } - return nullptr; -} -} - -static int sim_rtapi_run_threads(int fd, int (*callback)(int fd)); - -using namespace std; - -template T DLSYM(void *handle, const string &name) { - return (T)(dlsym(handle, name.c_str())); -} - -template T DLSYM(void *handle, const char *name) { - return (T)(dlsym(handle, name)); -} - -static std::map modules; - -static int instance_count = 0; -static int force_exit = 0; - -static int do_newinst_cmd(const string& type, const string& name, const string& arg) { - void *module = modules["hal_lib"]; - if(!module) { - rtapi_print_msg(RTAPI_MSG_ERR, - "newinst: hal_lib is required, but not loaded\n"); - return -1; - } - - hal_comp_t *(*find_comp_by_name)(char*) = - DLSYM(module, "halpr_find_comp_by_name"); - if(!find_comp_by_name) { - rtapi_print_msg(RTAPI_MSG_ERR, - "newinst: halpr_find_comp_by_name not found\n"); - return -1; - } - - hal_comp_t *comp = find_comp_by_name((char*)type.c_str()); - if(!comp) { - rtapi_print_msg(RTAPI_MSG_ERR, - "newinst: component %s not found\n", type.c_str()); - return -1; - } - - return comp->make((char*)name.c_str(), (char*)arg.c_str()); -} - -static int do_one_item(char item_type_char, const string ¶m_name, const string ¶m_value, void *vitem, int idx=0) { - char *endp; - switch(item_type_char) { - case 'l': { - long *litem = *(long**) vitem; - litem[idx] = strtol(param_value.c_str(), &endp, 0); - if(*endp) { - rtapi_print_msg(RTAPI_MSG_ERR, - "`%s' invalid for parameter `%s'", - param_value.c_str(), param_name.c_str()); - return -1; - } - return 0; - } - case 'i': { - int *iitem = *(int**) vitem; - iitem[idx] = strtol(param_value.c_str(), &endp, 0); - if(*endp) { - rtapi_print_msg(RTAPI_MSG_ERR, - "`%s' invalid for parameter `%s'", - param_value.c_str(), param_name.c_str()); - return -1; - } - return 0; - } - case 's': { - char **sitem = *(char***) vitem; - sitem[idx] = strdup(param_value.c_str()); - return 0; - } - default: - rtapi_print_msg(RTAPI_MSG_ERR, - "%s: Invalid type character `%c'\n", - param_name.c_str(), item_type_char); - return -1; - } - return 0; -} - -void remove_quotes(string &s) { - s.erase(remove_copy(s.begin(), s.end(), s.begin(), '"'), s.end()); -} - -static int do_comp_args(void *module, vector args) { - for(unsigned i=1; i < args.size(); i++) { - string &s = args[i]; - remove_quotes(s); - size_t idx = s.find('='); - if(idx == string::npos) { - rtapi_print_msg(RTAPI_MSG_ERR, "Invalid parameter `%s'\n", - s.c_str()); - return -1; - } - string param_name(s, 0, idx); - string param_value(s, idx+1); - void *item=DLSYM(module, "rtapi_info_address_" + param_name); - if(!item) { - rtapi_print_msg(RTAPI_MSG_ERR, - "Unknown parameter `%s'\n", s.c_str()); - return -1; - } - char **item_type=DLSYM(module, "rtapi_info_type_" + param_name); - if(!item_type || !*item_type) { - rtapi_print_msg(RTAPI_MSG_ERR, - "Unknown parameter `%s' (type information missing)\n", - s.c_str()); - return -1; - } - - int*max_size_ptr=DLSYM(module, "rtapi_info_size_" + param_name); - - char item_type_char = **item_type; - if(max_size_ptr) { - int max_size = *max_size_ptr; - size_t idx = 0; - int i = 0; - while(idx != string::npos) { - if(i == max_size) { - rtapi_print_msg(RTAPI_MSG_ERR, - "%s: can only take %d arguments\n", - s.c_str(), max_size); - return -1; - } - size_t idx1 = param_value.find(",", idx); - string substr(param_value, idx, idx1 - idx); - int result = do_one_item(item_type_char, s, substr, item, i); - if(result != 0) return result; - i++; - idx = idx1 == string::npos ? idx1 : idx1 + 1; - } - } else { - int result = do_one_item(item_type_char, s, param_value, item); - if(result != 0) return result; - } - } - return 0; -} - -static int do_load_cmd(const string& name, const vector& args) { - void *w = modules[name]; - if(w == NULL) { - char what[LINELEN+1]; - snprintf(what, LINELEN, "%s/%s.so", EMC2_RTLIB_DIR, name.c_str()); - void *module = modules[name] = dlopen(what, RTLD_GLOBAL | RTLD_NOW); - if(!module) { - rtapi_print_msg(RTAPI_MSG_ERR, "%s: dlopen: %s\n", name.c_str(), dlerror()); - modules.erase(name); - return -1; - } - /// XXX handle arguments - int (*start)(void) = DLSYM(module, "rtapi_app_main"); - if(!start) { - rtapi_print_msg(RTAPI_MSG_ERR, "%s: dlsym: %s\n", name.c_str(), dlerror()); - dlclose(module); - modules.erase(name); - return -1; - } - if(!DLSYM(module, "rtapi_app_exit")) { - rtapi_print_msg(RTAPI_MSG_ERR, "%s: component is missing rtapi_app_exit\n", name.c_str()); - dlclose(module); - modules.erase(name); - return -1; - } - int result; - - result = do_comp_args(module, args); - if(result < 0) { - dlclose(module); - modules.erase(name); - return -1; - } - - if ((result=start()) < 0) { - rtapi_print_msg(RTAPI_MSG_ERR, "%s: rtapi_app_main: %s (%d)\n", - name.c_str(), strerror(-result), result); - dlclose(module); - modules.erase(name); - return result; - } else { - instance_count ++; - return 0; - } - } else { - rtapi_print_msg(RTAPI_MSG_ERR, "%s: already exists\n", name.c_str()); - return -1; - } -} - -static int do_unload_cmd(const string& name) { - void *w = modules[name]; - if(w == NULL) { - rtapi_print_msg(RTAPI_MSG_ERR, "%s: not loaded\n", name.c_str()); - return -1; - } else { - void (*stop)(void) = DLSYM(w, "rtapi_app_exit"); - if(stop) stop(); - modules.erase(modules.find(name)); - dlclose(w); - instance_count --; - } - return 0; -} - -static int do_debug_cmd(const string& value) { - try{ - int new_level = stoi(value); - if (new_level < 0 || new_level > 5){ - rtapi_print_msg(RTAPI_MSG_ERR, "Debug level must be >=0 and <= 5\n"); - return -EINVAL; - } - return rtapi_set_msg_level(new_level); - }catch(invalid_argument &e){ - //stoi will throw an exception if parsing is not possible - rtapi_print_msg(RTAPI_MSG_ERR, "Debug level is not a number\n"); - return -EINVAL; - } -} - -struct ReadError : std::exception {}; -struct WriteError : std::exception {}; - -static int read_number(int fd) { - int r = 0, neg=1; - char ch; - - while(1) { - int res = read(fd, &ch, 1); - if(res != 1) return -1; - if(ch == '-') neg = -1; - else if(ch == ' ') return r * neg; - else r = 10 * r + ch - '0'; - } -} - -static string read_string(int fd) { - int len = read_number(fd); - if(len < 0) - throw ReadError(); - if(!len) - return string(); - string str(len, 0); - if(read(fd, str.data(), len) != len) - throw ReadError(); - return str; -} - -static vector read_strings(int fd) { - vector result; - int count = read_number(fd); - if(count < 0) - return result; - for(int i=0; i& strings) { - string buf; - write_number(buf, strings.size()); - for(unsigned int i=0; i args) { - if(args.size() == 0) { return 0; } - if(args.size() == 1 && args[0] == "exit") { - force_exit = 1; - return 0; - } else if(args.size() >= 2 && args[0] == "load") { - string name = args[1]; - args.erase(args.begin()); - return do_load_cmd(name, args); - } else if(args.size() == 2 && args[0] == "unload") { - return do_unload_cmd(args[1]); - } else if(args.size() == 3 && args[0] == "newinst") { - return do_newinst_cmd(args[1], args[2], ""); - } else if(args.size() == 4 && args[0] == "newinst") { - return do_newinst_cmd(args[1], args[2], args[3]); - } else if(args.size() == 2 && args[0] == "debug") { - return do_debug_cmd(args[1]); - } else { - rtapi_print_msg(RTAPI_MSG_ERR, - "Unrecognized command starting with %s\n", - args[0].c_str()); - return -1; - } -} - -static int slave(int fd, const vector& args) { - try { - write_strings(fd, args); - } - catch (WriteError &e) { - rtapi_print_msg(RTAPI_MSG_ERR, - "rtapi_app: failed to write to master: %s\n", strerror(errno)); - } - - int result = read_number(fd); - return result; -} - -static int callback(int fd) -{ - struct sockaddr_un client_addr; - memset(&client_addr, 0, sizeof(client_addr)); - socklen_t len = sizeof(client_addr); - int fd1 = accept(fd, (sockaddr*)&client_addr, &len); - if(fd1 < 0) { - rtapi_print_msg(RTAPI_MSG_ERR, - "rtapi_app: failed to accept connection from slave: %s\n", strerror(errno)); - return -1; - } else { - int result; - try { - result = handle_command(read_strings(fd1)); - } catch (ReadError &e) { - rtapi_print_msg(RTAPI_MSG_ERR, - "rtapi_app: failed to read from slave: %s\n", strerror(errno)); - close(fd1); - return -1; - } - string buf; - write_number(buf, result); - if(write(fd1, buf.data(), buf.size()) != (ssize_t)buf.size()) { - rtapi_print_msg(RTAPI_MSG_ERR, - "rtapi_app: failed to write to slave: %s\n", strerror(errno)); - }; - close(fd1); - } - return !force_exit && instance_count > 0; -} - -static pthread_t main_thread{}; - -static int master(int fd, const vector& args) { - main_thread = pthread_self(); - int result; - if((result = pthread_create(&queue_thread, nullptr, &queue_function, nullptr)) != 0) { - errno = result; - perror("pthread_create (queue function)"); - return -1; - } - do_load_cmd("hal_lib", vector()); - instance_count = 0; - App(); // force rtapi_app to be created - if(args.size()) { - result = handle_command(args); - if(result != 0) goto out; - if(force_exit || instance_count == 0) goto out; - } - sim_rtapi_run_threads(fd, callback); -out: - pthread_cancel(queue_thread); - pthread_join(queue_thread, nullptr); - rtapi_msg_queue.consume_all([](const message_t &m) { - fputs(m.msg, m.level == RTAPI_MSG_ALL ? stdout : stderr); - }); - return result; -} - -static std::string -_get_fifo_path() { - std::string s; - if(getenv("RTAPI_FIFO_PATH")) - s = getenv("RTAPI_FIFO_PATH"); - else if(getenv("HOME")) - s = std::string(getenv("HOME")) + "/.rtapi_fifo"; - else { - rtapi_print_msg(RTAPI_MSG_ERR, - "rtapi_app: RTAPI_FIFO_PATH and HOME are unset. rtapi fifo creation is unsafe."); - return NULL; - } - if(s.size() + 1 > sizeof(sockaddr_un::sun_path)) { - rtapi_print_msg(RTAPI_MSG_ERR, - "rtapi_app: rtapi fifo path is too long (arch limit %zd): %s", - sizeof(sockaddr_un::sun_path), s.c_str()); - return NULL; - } - return s; -} - -static const char * -get_fifo_path() { - static std::string path = _get_fifo_path(); - return path.c_str(); -} - -static int -get_fifo_path(char *buf, size_t bufsize) { - int len; - const char *s = get_fifo_path(); - if(!s) return -1; - len=snprintf(buf+1, bufsize-1, "%s", s); - return len; -} - -int main(int argc, char **argv) { - if(getuid() == 0) { - char *fallback_uid_str = getenv("RTAPI_UID"); - int fallback_uid = fallback_uid_str ? atoi(fallback_uid_str) : 0; - if(fallback_uid == 0) - { - // Cppcheck cannot see EMC2_BIN_DIR when RTAPI is defined, but that - // doesn't happen in uspace. - fprintf(stderr, - "Refusing to run as root without fallback UID specified\n" - "To run under a debugger with I/O, use e.g.,\n" - // cppcheck-suppress unknownMacro - " sudo env RTAPI_UID=`id -u` RTAPI_FIFO_PATH=$HOME/.rtapi_fifo gdb " EMC2_BIN_DIR "/rtapi_app\n"); - exit(1); - } - if (setreuid(fallback_uid, 0) != 0) { perror("setreuid"); abort(); } - fprintf(stderr, - "Running with fallback_uid. getuid()=%d geteuid()=%d\n", - getuid(), geteuid()); - } - ruid = getuid(); - euid = geteuid(); - if (setresuid(euid, euid, ruid) != 0) { perror("setresuid"); abort(); } -#ifdef __linux__ - setfsuid(ruid); -#endif - vector args; - for(int i=1; i(malloc(PRE_ALLOC_SIZE)); - if (buf == NULL) { - rtapi_print_msg(RTAPI_MSG_WARN, "malloc(PRE_ALLOC_SIZE) failed\n"); - return; - } - long pagesize = sysconf(_SC_PAGESIZE); - /* Touch each page in this piece of memory to get it mapped into RAM */ - for (size_t i = 0; i < PRE_ALLOC_SIZE; i += pagesize) { - /* Each write to this buffer will generate a pagefault. - * Once the pagefault is handled a page will be locked in - * memory and never given back to the system. */ - buf[i] = 0; - } - free((void *)buf); -} - -static int harden_rt() -{ - if(!rtapi_is_realtime()) return -EINVAL; - - WITH_ROOT; -#if defined(__linux__) && (defined(__x86_64__) || defined(__i386__)) - if (iopl(3) < 0) { - rtapi_print_msg(RTAPI_MSG_ERR, - "iopl() failed: %s\n" - "cannot gain I/O privileges - " - "forgot 'sudo make setuid' or using secure boot? -" - "parallel port access is not allowed\n", - strerror(errno)); - } -#endif - - struct sigaction sig_act = {}; -#ifdef __linux__ - // enable realtime - if (setrlimit(RLIMIT_RTPRIO, &unlimited) < 0) - { - rtapi_print_msg(RTAPI_MSG_WARN, - "setrlimit(RTLIMIT_RTPRIO): %s\n", - strerror(errno)); - return -errno; - } - - // enable core dumps - if (setrlimit(RLIMIT_CORE, &unlimited) < 0) - rtapi_print_msg(RTAPI_MSG_WARN, - "setrlimit: %s - core dumps may be truncated or non-existent\n", - strerror(errno)); - - // even when setuid root - if (prctl(PR_SET_DUMPABLE, 1) < 0) - rtapi_print_msg(RTAPI_MSG_WARN, - "prctl(PR_SET_DUMPABLE) failed: no core dumps will be created - %d - %s\n", - errno, strerror(errno)); -#endif /* __linux__ */ - - configure_memory(); - - sigemptyset( &sig_act.sa_mask ); - sig_act.sa_handler = SIG_IGN; - sig_act.sa_sigaction = NULL; - - // prevent stopping of RT threads by ^Z - sigaction(SIGTSTP, &sig_act, (struct sigaction *) NULL); - - sig_act.sa_sigaction = signal_handler; - sig_act.sa_flags = SA_SIGINFO; - - sigaction(SIGSEGV, &sig_act, (struct sigaction *) NULL); - sigaction(SIGILL, &sig_act, (struct sigaction *) NULL); - sigaction(SIGFPE, &sig_act, (struct sigaction *) NULL); - sigaction(SIGTERM, &sig_act, (struct sigaction *) NULL); - sigaction(SIGINT, &sig_act, (struct sigaction *) NULL); - -#ifdef __linux__ - int fd = open("/dev/cpu_dma_latency", O_WRONLY | O_CLOEXEC); - if (fd < 0) { - rtapi_print_msg(RTAPI_MSG_WARN, "failed to open /dev/cpu_dma_latency: %s\n", strerror(errno)); - } else { - int r; - r = write(fd, "\0\0\0\0", 4); - if (r != 4) { - rtapi_print_msg(RTAPI_MSG_WARN, "failed to write to /dev/cpu_dma_latency: %s\n", strerror(errno)); - } - // deliberately leak fd until program exit - } -#endif /* __linux__ */ - return 0; +void WithRoot::init(uid_t ruid_ini, uid_t euid_ini) { + ruid = ruid_ini; + euid = euid_ini; } +RtapiTask::RtapiTask() + : magic{}, id{}, owner{}, uses_fp{}, stacksize{}, prio{}, period{}, nextstart{}, pll_correction{}, + pll_correction_limit{}, arg{}, taskcode{} -static RtapiApp *makeApp() -{ - if(euid != 0 || harden_rt() < 0) - { - rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using POSIX non-realtime\n"); - return new Posix(SCHED_OTHER); - } - WithRoot r; - void *dll = nullptr; - if(detect_xenomai_evl()) { - dll = dlopen(EMC2_HOME "/lib/libuspace-xenomai-evl.so.0", RTLD_NOW); - if(!dll) fprintf(stderr, "dlopen: %s\n", dlerror()); - }else if(detect_xenomai()) { - dll = dlopen(EMC2_HOME "/lib/libuspace-xenomai.so.0", RTLD_NOW); - if(!dll) fprintf(stderr, "dlopen: %s\n", dlerror()); - } else if(detect_rtai()) { - dll = dlopen(EMC2_HOME "/lib/libuspace-rtai.so.0", RTLD_NOW); - if(!dll) fprintf(stderr, "dlopen: %s\n", dlerror()); - } - if(dll) - { - auto fn = reinterpret_cast(dlsym(dll, "make")); - if(!fn) fprintf(stderr, "dlopen: %s\n", dlerror()); - auto result = fn ? fn() : nullptr; - if(result) { - return result; - } - } - rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using POSIX realtime\n"); - return new Posix(SCHED_FIFO); -} -RtapiApp &App() { - static RtapiApp *app = makeApp(); - return *app; } -} -/* data for all tasks */ -struct rtapi_task *task_array[MAX_TASKS]; +RtapiTask *RtapiApp::task_array[MAX_TASKS]; /* Priority functions. Uspace uses POSIX task priorities. */ -int RtapiApp::prio_highest() const -{ +int RtapiApp::prio_highest() const { return sched_get_priority_max(policy); } -int RtapiApp::prio_lowest() const -{ - return sched_get_priority_min(policy); +int RtapiApp::prio_lowest() const { + return sched_get_priority_min(policy); } int RtapiApp::prio_higher_delta() const { - if(rtapi_prio_highest() > rtapi_prio_lowest()) { + if (rtapi_prio_highest() > rtapi_prio_lowest()) { return 1; } return -1; } int RtapiApp::prio_bound(int prio) const { - if(rtapi_prio_highest() > rtapi_prio_lowest()) { + if (rtapi_prio_highest() > rtapi_prio_lowest()) { if (prio >= rtapi_prio_highest()) return rtapi_prio_highest(); if (prio < rtapi_prio_lowest()) @@ -928,112 +88,115 @@ int RtapiApp::prio_bound(int prio) const { } bool RtapiApp::prio_check(int prio) const { - if(rtapi_prio_highest() > rtapi_prio_lowest()) { + if (rtapi_prio_highest() > rtapi_prio_lowest()) { return (prio <= rtapi_prio_highest()) && (prio >= rtapi_prio_lowest()); } else { return (prio <= rtapi_prio_lowest()) && (prio >= rtapi_prio_highest()); } } -int RtapiApp::prio_next_higher(int prio) const -{ +int RtapiApp::prio_next_higher(int prio) const { prio = prio_bound(prio); - if(prio != rtapi_prio_highest()) + if (prio != rtapi_prio_highest()) return prio + prio_higher_delta(); return prio; } -int RtapiApp::prio_next_lower(int prio) const -{ +int RtapiApp::prio_next_lower(int prio) const { prio = prio_bound(prio); - if(prio != rtapi_prio_lowest()) + if (prio != rtapi_prio_lowest()) return prio - prio_higher_delta(); return prio; } -int RtapiApp::allocate_task_id() -{ - for(int n=0; nid = n; - task->owner = owner; - /* uses_fp is deprecated and ignored; always save FPU state */ - task->uses_fp = 1; - task->arg = arg; - task->stacksize = stacksize; - task->taskcode = taskcode; - task->prio = prio; - task->magic = TASK_MAGIC; - task_array[n] = task; + RtapiTask *task = do_task_new(); + if (stacksize < (1024 * 1024)) + stacksize = (1024 * 1024); + task->id = n; + task->owner = owner; + /* uses_fp is deprecated and ignored; always save FPU state */ + task->uses_fp = 1; + task->arg = arg; + task->stacksize = stacksize; + task->taskcode = taskcode; + task->prio = prio; + task->magic = TASK_MAGIC; + task_array[n] = task; - /* and return handle to the caller */ + /* and return handle to the caller */ - return n; + return n; } -rtapi_task *RtapiApp::get_task(int task_id) { - if(task_id < 0 || task_id >= MAX_TASKS) return NULL; +RtapiTask *RtapiApp::get_task(int task_id) { + if (task_id < 0 || task_id >= MAX_TASKS) + return NULL; /* validate task handle */ - rtapi_task *task = task_array[task_id]; - if(!task || task == TASK_MAGIC_INIT || task->magic != TASK_MAGIC) + RtapiTask *task = task_array[task_id]; + if (!task || task == TASK_MAGIC_INIT || task->magic != TASK_MAGIC) return NULL; return task; } -void RtapiApp::unexpected_realtime_delay(rtapi_task *task, int /*nperiod*/) { +void RtapiApp::unexpected_realtime_delay(RtapiTask *task, int /*nperiod*/) { static int printed = 0; - if(!printed) - { - rtapi_print_msg(RTAPI_MSG_ERR, - "Unexpected realtime delay on task %d with period %ld\n" - "This Message will only display once per session.\n" - "Run the Latency Test and resolve before continuing.\n", - task->id, task->period); + if (!printed) { + rtapi_print_msg( + RTAPI_MSG_ERR, + "Unexpected realtime delay on task %d with period %ld\n" + "This Message will only display once per session.\n" + "Run the Latency Test and resolve before continuing.\n", + task->id, + task->period + ); printed = 1; } } -int Posix::task_delete(int id) -{ - auto task = ::rtapi_get_task(id); - if(!task) return -EINVAL; - - pthread_cancel(task->thr); - pthread_join(task->thr, 0); - task->magic = 0; - task_array[id] = 0; - delete task; - return 0; +long RtapiApp::clock_set_period(long nsecs) { + if (nsecs == 0) + return period; + if (period != 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "attempt to set period twice\n"); + return -EINVAL; + } + period = nsecs; + return period; } //parse_cpu_list from https://gitlab.com/Xenomai/xenomai4/libevl/-/blob/11e6a1fb183a315ae861762e7650fd5e10d83ff5/tests/helpers.c //License: MIT -static void parse_cpu_list(const char *path, cpu_set_t *cpuset) -{ +static void parse_cpu_list(const char *path, cpu_set_t *cpuset) { char *p, *range, *range_p = NULL, *id, *id_r; int start, end, cpu; char buf[BUFSIZ]; @@ -1071,30 +234,34 @@ static void parse_cpu_list(const char *path, cpu_set_t *cpuset) fclose(fp); } -int find_rt_cpu_number() { - if(getenv("RTAPI_CPU_NUMBER")) return atoi(getenv("RTAPI_CPU_NUMBER")); +int RtapiApp::find_rt_cpu_number() { + if (getenv("RTAPI_CPU_NUMBER")) + return atoi(getenv("RTAPI_CPU_NUMBER")); #ifdef __linux__ - const char* isolated_file="/sys/devices/system/cpu/isolated"; + const char *isolated_file = "/sys/devices/system/cpu/isolated"; cpu_set_t cpuset; parse_cpu_list(isolated_file, &cpuset); //Print list rtapi_print_msg(RTAPI_MSG_INFO, "cpuset isolated "); - for(int i=0; i(task_id); - if(!task) return -EINVAL; - - if(period_nsec < (unsigned long)period) period_nsec = (unsigned long)period; - task->period = period_nsec; - task->ratio = period_nsec / period; - - struct sched_param param; - memset(¶m, 0, sizeof(param)); - param.sched_priority = task->prio; - - // limit PLL correction values to +/-1% of cycle time - task->pll_correction_limit = period_nsec / 100; - task->pll_correction = 0; - - int nprocs = sysconf( _SC_NPROCESSORS_ONLN ); - - pthread_attr_t attr; - int ret; - if((ret = pthread_attr_init(&attr)) != 0) - return -ret; - if((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) - return -ret; - if((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) - return -ret; - if((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) - return -ret; - if((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) - return -ret; - if(nprocs > 1) { - const static int rt_cpu_number = find_rt_cpu_number(); - rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); - if(rt_cpu_number != -1) { -#ifdef __FreeBSD__ - cpuset_t cpuset; -#else - cpu_set_t cpuset; -#endif - CPU_ZERO(&cpuset); - CPU_SET(rt_cpu_number, &cpuset); - if((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) - return -ret; - } - } - if((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) - return -ret; - - return 0; -} - -#define RTAPI_CLOCK (CLOCK_MONOTONIC) - -pthread_once_t Posix::key_once = PTHREAD_ONCE_INIT; -pthread_once_t Posix::lock_once = PTHREAD_ONCE_INIT; -pthread_key_t Posix::key; -pthread_mutex_t Posix::thread_lock; - -void *Posix::wrapper(void *arg) -{ - struct rtapi_task *task; - - /* use the argument to point to the task data */ - task = (struct rtapi_task*)arg; - long int period = App().period; - if(task->period < period) task->period = period; - task->ratio = task->period / period; - task->period = task->ratio * period; - rtapi_print_msg(RTAPI_MSG_INFO, "task %p period = %lu ratio=%u\n", - task, task->period, task->ratio); - - pthread_setspecific(key, arg); - set_namef("rtapi_app:T#%d", task->id); - - Posix &papp = reinterpret_cast(App()); - if(papp.do_thread_lock) - pthread_mutex_lock(&papp.thread_lock); - - struct timespec now; - clock_gettime(RTAPI_CLOCK, &now); - rtapi_timespec_advance(task->nextstart, now, task->period + task->pll_correction); - - /* call the task function with the task argument */ - (task->taskcode) (task->arg); - - rtapi_print("ERROR: reached end of wrapper for task %d\n", task->id); - return NULL; -} - -long long Posix::task_pll_get_reference(void) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return 0; - return task->nextstart.tv_sec * 1000000000LL + task->nextstart.tv_nsec; -} - -int Posix::task_pll_set_correction(long value) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; - if (value > task->pll_correction_limit) value = task->pll_correction_limit; - if (value < -(task->pll_correction_limit)) value = -(task->pll_correction_limit); - task->pll_correction = value; - return 0; -} - -int Posix::task_pause(int) { - return -ENOSYS; -} - -int Posix::task_resume(int) { - return -ENOSYS; -} - -int Posix::task_self() { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; - return task->id; -} - -void Posix::wait() { - if(do_thread_lock) - pthread_mutex_unlock(&thread_lock); - pthread_testcancel(); - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - rtapi_timespec_advance(task->nextstart, task->nextstart, task->period + task->pll_correction); - struct timespec now; - clock_gettime(RTAPI_CLOCK, &now); - if(rtapi_timespec_less(task->nextstart, now)) - { - if(policy == SCHED_FIFO) - unexpected_realtime_delay(task); - } - else - { - int res = rtapi_clock_nanosleep(RTAPI_CLOCK, TIMER_ABSTIME, &task->nextstart, nullptr, &now); - if(res < 0) perror("clock_nanosleep"); - } - if(do_thread_lock) - pthread_mutex_lock(&thread_lock); -} - -unsigned char Posix::do_inb(unsigned int port) -{ -#ifdef HAVE_SYS_IO_H - return inb(port); -#else - (void)port; - return 0; -#endif -} - -void Posix::do_outb(unsigned char val, unsigned int port) -{ -#ifdef HAVE_SYS_IO_H - return outb(val, port); -#else - (void)val; - (void)port; -#endif -} - -void Posix::do_delay(long ns) { - struct timespec ts = {0, ns}; - rtapi_clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL, NULL); -} -int rtapi_prio_highest(void) -{ - return App().prio_highest(); -} - -int rtapi_prio_lowest(void) -{ - return App().prio_lowest(); -} - -int rtapi_prio_next_higher(int prio) -{ - return App().prio_next_higher(prio); -} - -int rtapi_prio_next_lower(int prio) -{ - return App().prio_next_lower(prio); -} - -long rtapi_clock_set_period(long nsecs) -{ - return App().clock_set_period(nsecs); -} - -long RtapiApp::clock_set_period(long nsecs) -{ - if(nsecs == 0) return period; - if(period != 0) { - rtapi_print_msg(RTAPI_MSG_ERR, "attempt to set period twice\n"); - return -EINVAL; - } - period = nsecs; - return period; -} - - -int rtapi_task_new(void (*taskcode) (void*), void *arg, - int prio, int owner, unsigned long int stacksize, int uses_fp) { - return App().task_new(taskcode, arg, prio, owner, stacksize, uses_fp); -} - -int rtapi_task_delete(int id) { - return App().task_delete(id); -} - -int rtapi_task_start(int task_id, unsigned long period_nsec) -{ - int ret = App().task_start(task_id, period_nsec); - if(ret != 0) { - errno = -ret; - perror("rtapi_task_start()"); - } - return ret; -} - -int rtapi_task_pause(int task_id) -{ - return App().task_pause(task_id); -} - -int rtapi_task_resume(int task_id) -{ - return App().task_resume(task_id); -} - -int rtapi_task_self() -{ - return App().task_self(); -} - -long long rtapi_task_pll_get_reference(void) -{ - return App().task_pll_get_reference(); -} - -int rtapi_task_pll_set_correction(long value) -{ - return App().task_pll_set_correction(value); -} - -void rtapi_wait(void) -{ - App().wait(); -} - -void rtapi_outb(unsigned char byte, unsigned int port) -{ - App().do_outb(byte, port); -} - -unsigned char rtapi_inb(unsigned int port) -{ - return App().do_inb(port); -} - -long int simple_strtol(const char *nptr, char **endptr, int base) { - return strtol(nptr, endptr, base); -} - -int Posix::run_threads(int fd, int(*callback)(int fd)) { - while(callback(fd)) { /* nothing */ } - return 0; -} - -int sim_rtapi_run_threads(int fd, int (*callback)(int fd)) { - return App().run_threads(fd, callback); -} - -long long rtapi_get_time() { - return App().do_get_time(); -} +void RtapiApp::set_namef(const char *fmt, ...) { + char *buf = NULL; + va_list ap; -void default_rtapi_msg_handler(msg_level_t level, const char *fmt, va_list ap) { - if(main_thread && pthread_self() != main_thread) { - message_t m; - m.level = level; - vsnprintf(m.msg, sizeof(m.msg), fmt, ap); - rtapi_msg_queue.push(m); - } else { - vfprintf(level == RTAPI_MSG_ALL ? stdout : stderr, fmt, ap); + va_start(ap, fmt); + if (vasprintf(&buf, fmt, ap) < 0) { + va_end(ap); + return; } -} - -long int rtapi_delay_max() { return 10000; } - -void rtapi_delay(long ns) { - if(ns > rtapi_delay_max()) ns = rtapi_delay_max(); - App().do_delay(ns); -} + va_end(ap); -const unsigned long ONE_SEC_IN_NS = 1000000000; -void rtapi_timespec_advance(struct timespec &result, const struct timespec &src, unsigned long nsec) -{ - time_t sec = src.tv_sec; - while(nsec >= ONE_SEC_IN_NS) - { - ++sec; - nsec -= ONE_SEC_IN_NS; - } - nsec += src.tv_nsec; - if(nsec >= ONE_SEC_IN_NS) - { - ++sec; - nsec -= ONE_SEC_IN_NS; + int res = pthread_setname_np(pthread_self(), buf); + if (res) { + fprintf(stderr, "pthread_setname_np() failed for %s: %d\n", buf, res); } - result.tv_sec = sec; - result.tv_nsec = nsec; -} - -int rtapi_open_as_root(const char *filename, int mode) { - WITH_ROOT; - int r = open(filename, mode); - if(r < 0) return -errno; - return r; -} - -int rtapi_spawn_as_root(pid_t *pid, const char *path, - const posix_spawn_file_actions_t *file_actions, - const posix_spawnattr_t *attrp, - char *const argv[], char *const envp[]) -{ - return posix_spawn(pid, path, file_actions, attrp, argv, envp); -} - -int rtapi_spawnp_as_root(pid_t *pid, const char *path, - const posix_spawn_file_actions_t *file_actions, - const posix_spawnattr_t *attrp, - char *const argv[], char *const envp[]) -{ - return posix_spawnp(pid, path, file_actions, attrp, argv, envp); -} + free(buf); +} \ No newline at end of file diff --git a/src/rtapi/rtapi_uspace.hh b/src/rtapi/uspace_rtapi_app.hh similarity index 66% rename from src/rtapi/rtapi_uspace.hh rename to src/rtapi/uspace_rtapi_app.hh index bc589c12536..5a33423a689 100644 --- a/src/rtapi/rtapi_uspace.hh +++ b/src/rtapi/uspace_rtapi_app.hh @@ -34,42 +34,56 @@ static inline void rtapi_timespec_add(timespec &result, const timespec &ta, cons } static inline bool rtapi_timespec_less(const struct timespec &ta, const struct timespec &tb) { - if(ta.tv_sec < tb.tv_sec) return 1; - if(ta.tv_sec > tb.tv_sec) return 0; + if (ta.tv_sec < tb.tv_sec) + return 1; + if (ta.tv_sec > tb.tv_sec) + return 0; return ta.tv_nsec < tb.tv_nsec; } void rtapi_timespec_advance(struct timespec &result, const struct timespec &src, unsigned long nsec); -struct WithRoot -{ +struct WithRoot { WithRoot(); ~WithRoot(); + static void init(uid_t ruid_ini, uid_t euid_ini); + static uid_t getRuid() { + return ruid; + } + static uid_t getEuid() { + return euid; + } + + private: static std::atomic_int level; + static uid_t ruid, euid; }; -struct rtapi_task { - rtapi_task(); +struct RtapiTask { + RtapiTask(); - int magic; /* to check for valid handle */ - int id; - int owner; - int uses_fp; - size_t stacksize; - int prio; - long period; - struct timespec nextstart; - unsigned ratio; - long pll_correction; - long pll_correction_limit; - void *arg; - void (*taskcode) (void*); /* pointer to task function */ + int magic; /* to check for valid handle */ + int id; + int owner; + int uses_fp; + size_t stacksize; + int prio; + long period; + struct timespec nextstart; + long pll_correction; + long pll_correction_limit; + void *arg; + void (*taskcode)(void *); /* pointer to task function */ }; -struct RtapiApp -{ +#define MAX_TASKS 64 +#define TASK_MAGIC 21979 /* random numbers used as signatures */ +#define TASK_MAGIC_INIT ((RtapiTask *)(-1)) - RtapiApp(int policy = SCHED_OTHER) : policy(policy), period(0) {} +struct RtapiApp { + + RtapiApp(int policy = SCHED_OTHER) : policy(policy), period(0) { + } virtual int prio_highest() const; virtual int prio_lowest() const; @@ -79,12 +93,11 @@ struct RtapiApp int prio_next_higher(int prio) const; int prio_next_lower(int prio) const; long clock_set_period(long int period_nsec); - int task_new(void (*taskcode)(void*), void *arg, - int prio, int owner, unsigned long int stacksize, int uses_fp); - virtual rtapi_task *do_task_new() = 0; + int task_new(void (*taskcode)(void *), void *arg, int prio, int owner, unsigned long int stacksize, int uses_fp); + virtual RtapiTask *do_task_new() = 0; static int allocate_task_id(); - static struct rtapi_task *get_task(int task_id); - void unexpected_realtime_delay(rtapi_task *task, int nperiod=1); + static RtapiTask *get_task(int task_id); + void unexpected_realtime_delay(RtapiTask *task, int nperiod = 1); virtual int task_delete(int id) = 0; virtual int task_start(int task_id, unsigned long period_nsec) = 0; virtual int task_pause(int task_id) = 0; @@ -95,25 +108,18 @@ struct RtapiApp virtual void wait() = 0; virtual unsigned char do_inb(unsigned int port) = 0; virtual void do_outb(unsigned char value, unsigned int port) = 0; - virtual int run_threads(int fd, int (*callback)(int fd)) = 0; virtual long long do_get_time(void) = 0; virtual void do_delay(long ns) = 0; + static int find_rt_cpu_number(); + static void set_namef(const char *fmt, ...); int policy; long period; + static RtapiTask *task_array[MAX_TASKS]; }; -template -T *rtapi_get_task(int task_id) { - return static_cast(RtapiApp::get_task(task_id)); +template T *rtapi_get_task(int task_id) { + return static_cast(RtapiApp::get_task(task_id)); } -int find_rt_cpu_number(); - -#define MAX_TASKS 64 -#define TASK_MAGIC 21979 /* random numbers used as signatures */ -#define TASK_MAGIC_INIT ((rtapi_task*)(-1)) - -extern struct rtapi_task *task_array[MAX_TASKS]; - #define WITH_ROOT WithRoot root #endif diff --git a/src/rtapi/uspace_rtapi_main.cc b/src/rtapi/uspace_rtapi_main.cc new file mode 100644 index 00000000000..15798f4784a --- /dev/null +++ b/src/rtapi/uspace_rtapi_main.cc @@ -0,0 +1,1232 @@ +/* Copyright (C) 2006-2014 Jeff Epler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include + +#ifdef __linux__ +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_IO_H +#include +#endif +#include +#include +#ifdef __linux__ +#include +#include +#include +#endif +#ifdef __FreeBSD__ +#include +#endif + +#include +#include + +#include "rtapi.h" +#include +#include "hal/hal_priv.h" +#include "uspace_common.h" + +static RtapiApp &App(); + +struct message_t { + msg_level_t level; + char msg[1024 - sizeof(level)]; +}; + +static boost::lockfree::queue> rtapi_msg_queue; + +static pthread_t queue_thread; +static void *queue_function(void * /*arg*/) { + RtapiApp::set_namef("rtapi_app:mesg"); + // note: can't use anything in this function that requires App() to exist + // but it's OK to use functions that aren't safe for realtime (that's the + // point of running this in a thread) + while (1) { + pthread_testcancel(); + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, nullptr); + rtapi_msg_queue.consume_all([](const message_t &m) { + fputs(m.msg, m.level == RTAPI_MSG_ALL ? stdout : stderr); + }); + pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, nullptr); + struct timespec ts = {0, 10000000}; + rtapi_clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL, NULL); + } + return nullptr; +} + +template T DLSYM(void *handle, const std::string &name) { + return (T)(dlsym(handle, name.c_str())); +} + +template T DLSYM(void *handle, const char *name) { + return (T)(dlsym(handle, name)); +} + +static std::map modules; + +static int instance_count = 0; +static int force_exit = 0; + +static int do_newinst_cmd(const std::string &type, const std::string &name, const std::string &arg) { + void *module = modules["hal_lib"]; + if (!module) { + rtapi_print_msg(RTAPI_MSG_ERR, "newinst: hal_lib is required, but not loaded\n"); + return -1; + } + + hal_comp_t *(*find_comp_by_name)(char *) = DLSYM(module, "halpr_find_comp_by_name"); + if (!find_comp_by_name) { + rtapi_print_msg(RTAPI_MSG_ERR, "newinst: halpr_find_comp_by_name not found\n"); + return -1; + } + + hal_comp_t *comp = find_comp_by_name((char *)type.c_str()); + if (!comp) { + rtapi_print_msg(RTAPI_MSG_ERR, "newinst: component %s not found\n", type.c_str()); + return -1; + } + + return comp->make((char *)name.c_str(), (char *)arg.c_str()); +} + +static int do_one_item( + char item_type_char, const std::string ¶m_name, const std::string ¶m_value, void *vitem, int idx = 0 +) { + char *endp; + switch (item_type_char) { + case 'l': { + long *litem = *(long **)vitem; + litem[idx] = strtol(param_value.c_str(), &endp, 0); + if (*endp) { + rtapi_print_msg( + RTAPI_MSG_ERR, "`%s' invalid for parameter `%s'\n", param_value.c_str(), param_name.c_str() + ); + return -1; + } + return 0; + } + case 'i': { + int *iitem = *(int **)vitem; + iitem[idx] = strtol(param_value.c_str(), &endp, 0); + if (*endp) { + rtapi_print_msg( + RTAPI_MSG_ERR, "`%s' invalid for parameter `%s'\n", param_value.c_str(), param_name.c_str() + ); + return -1; + } + return 0; + } + case 's': { + char **sitem = *(char ***)vitem; + sitem[idx] = strdup(param_value.c_str()); + return 0; + } + default: + rtapi_print_msg(RTAPI_MSG_ERR, "%s: Invalid type character `%c'\n", param_name.c_str(), item_type_char); + return -1; + } + return 0; +} + +static void remove_quotes(std::string &s) { + s.erase(remove_copy(s.begin(), s.end(), s.begin(), '"'), s.end()); +} + +static int do_comp_args(void *module, std::vector args) { + for (unsigned i = 1; i < args.size(); i++) { + std::string &s = args[i]; + remove_quotes(s); + size_t idx = s.find('='); + if (idx == std::string::npos) { + rtapi_print_msg(RTAPI_MSG_ERR, "Invalid parameter `%s'\n", s.c_str()); + return -1; + } + std::string param_name(s, 0, idx); + std::string param_value(s, idx + 1); + void *item = DLSYM(module, "rtapi_info_address_" + param_name); + if (!item) { + rtapi_print_msg(RTAPI_MSG_ERR, "Unknown parameter `%s'\n", s.c_str()); + return -1; + } + char **item_type = DLSYM(module, "rtapi_info_type_" + param_name); + if (!item_type || !*item_type) { + rtapi_print_msg(RTAPI_MSG_ERR, "Unknown parameter `%s' (type information missing)\n", s.c_str()); + return -1; + } + + int *max_size_ptr = DLSYM(module, "rtapi_info_size_" + param_name); + + char item_type_char = **item_type; + if (max_size_ptr) { + int max_size = *max_size_ptr; + size_t idx = 0; + int i = 0; + while (idx != std::string::npos) { + if (i == max_size) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: can only take %d arguments\n", s.c_str(), max_size); + return -1; + } + size_t idx1 = param_value.find(",", idx); + std::string substr(param_value, idx, idx1 - idx); + int result = do_one_item(item_type_char, s, substr, item, i); + if (result != 0) + return result; + i++; + idx = idx1 == std::string::npos ? idx1 : idx1 + 1; + } + } else { + int result = do_one_item(item_type_char, s, param_value, item); + if (result != 0) + return result; + } + } + return 0; +} + +static int do_load_cmd(const std::string &name, const std::vector &args) { + void *w = modules[name]; + if (w == NULL) { + std::string what; + what = fmt::format("{}/{}.so", EMC2_RTLIB_DIR, name); + void *module = modules[name] = dlopen(what.c_str(), RTLD_GLOBAL | RTLD_NOW); + if (!module) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: dlopen: %s\n", name.c_str(), dlerror()); + modules.erase(name); + return -1; + } + /// XXX handle arguments + int (*start)(void) = DLSYM(module, "rtapi_app_main"); + if (!start) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: dlsym: %s\n", name.c_str(), dlerror()); + dlclose(module); + modules.erase(name); + return -1; + } + if(!DLSYM(module, "rtapi_app_exit")) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: component is missing rtapi_app_exit\n", name.c_str()); + dlclose(module); + modules.erase(name); + return -1; + } + int result; + + result = do_comp_args(module, args); + if (result < 0) { + dlclose(module); + modules.erase(name); + return -1; + } + + if ((result = start()) < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: rtapi_app_main: %s (%d)\n", name.c_str(), strerror(-result), result); + dlclose(module); + modules.erase(name); + return result; + } else { + instance_count++; + return 0; + } + } else { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: already exists\n", name.c_str()); + return -1; + } +} + +static int do_unload_cmd(const std::string &name) { + void *w = modules[name]; + if (w == NULL) { + rtapi_print_msg(RTAPI_MSG_ERR, "%s: not loaded\n", name.c_str()); + return -1; + } else { + void (*stop)(void) = DLSYM(w, "rtapi_app_exit"); + if (stop) + stop(); + modules.erase(modules.find(name)); + dlclose(w); + instance_count--; + } + return 0; +} + +static int do_debug_cmd(const std::string &value) { + try { + int new_level = stoi(value); + if (new_level < 0 || new_level > 5) { + rtapi_print_msg(RTAPI_MSG_ERR, "Debug level must be >=0 and <= 5\n"); + return -EINVAL; + } + return rtapi_set_msg_level(new_level); + } catch (std::invalid_argument &e) { + //stoi will throw an exception if parsing is not possible + rtapi_print_msg(RTAPI_MSG_ERR, "Debug level is not a number\n"); + return -EINVAL; + } +} + +/* + * Fully checked send/recv + * Will retry on EINTR, so to abort a send_data/recv_data on a + * signal, change to something like while(remaining > 0 && !exit_flag) + * and set exit_flag in signal handler + */ +static int send_data(int fd, const void *buf, size_t n, int flags) { + const uint8_t *ptr = (const uint8_t *)buf; + size_t n_rem = n; + while (n_rem > 0) { + ssize_t n_ret = send(fd, ptr, n_rem, flags); + if (n_ret == -1) { + if (errno == EINTR) { + // Retry + } else { + return -1; // Other error, fail + } + } else if (n_ret == 0) { + return (n - n_rem); // No more data + } else { + ptr += n_ret; + n_rem -= n_ret; + } + } + return n; // All sent +} + +static int recv_data(int fd, void *buf, size_t n, int flags) { + uint8_t *ptr = (uint8_t *)buf; + size_t n_rem = n; + while (n_rem > 0) { + ssize_t n_ret = recv(fd, ptr, n_rem, flags); + if (n_ret == -1) { + if (errno == EINTR) { + // Retry + } else { + return -1; // Other error, fail + } + } else if (n_ret == 0) { + return (n - n_rem); // No more data + } else { + ptr += n_ret; + n_rem -= n_ret; + } + } + return n; // All read +} + +/* + * Protocol: + * + * client->master: std::vector args + * master processes the args and returns result + * master->client: int result + * + * Packing: + * args are serialized as: + * uint16_t size (full package size including the size field) + * uint16_t n_args + * n_args times: + * { + * uint16_t arg_size + * char[arg_size] argument + * } + * + * result is serialized as: + * int + */ + +static bool send_result(int fd, int result) { + ssize_t res = send_data(fd, &result, sizeof(int), 0); + if (res != sizeof(int)) { + if (res == -1) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: send_result failed: %s\n", strerror(errno)); + } else { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: send_result failed, send only %li of %li bytes\n", res, sizeof(int) + ); + } + return false; + } else { + return true; + } +} + +static bool recv_result(int fd, int *result) { + ssize_t res = recv_data(fd, result, sizeof(int), 0); + if (res != sizeof(int)) { + if (res == -1) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: recv_result failed: %s\n", strerror(errno)); + } else { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: recv_result failed, recv only %li of %li bytes\n", res, sizeof(int) + ); + } + return false; + } else { + return true; + } +} + +static void push_uint16(std::vector &buf, uint16_t value) { + buf.push_back(0xff & (value >> 0)); + buf.push_back(0xff & (value >> 8)); +} + +static uint16_t get_uint16(const std::vector &buf, size_t idx) { + //at() will check index and throw std::out_of_range + return ((uint16_t)buf.at(idx) << 0) | ((uint16_t)buf.at(idx + 1) << 8); +} + +static bool recv_args(int fd, std::vector &args) { + //Get size + uint16_t tmp; + ssize_t res = recv_data(fd, &tmp, sizeof(uint16_t), 0); + if (res != sizeof(uint16_t)) { + if (res == -1) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: recv_args 1 failed: %s\n", strerror(errno)); + } else { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: recv_args 1 failed, recv only %li of %li bytes\n", res, sizeof(uint16_t) + ); + } + return false; + } + size_t buff_size = tmp - sizeof(uint16_t); //Size already consumed + + //Get data + std::vector buf(buff_size); + res = recv_data(fd, buf.data(), buff_size, 0); + if (res != (ssize_t)buff_size) { + if (res == -1) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: recv_args 2 failed: %s\n", strerror(errno)); + } else { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: recv_args 2 failed, recv only %li of %li bytes\n", res, buff_size + ); + } + return false; + } + + //Deserialize + try { + size_t idx = 0; + size_t n_args = get_uint16(buf, idx); + args.resize(n_args); + idx += sizeof(uint16_t); + for (size_t i = 0; i < n_args; i++) { + size_t arg_size = get_uint16(buf, idx); + idx += sizeof(uint16_t); + //Bound checked, unpack argument + auto start = buf.begin() + idx; + auto end = start + arg_size; + if (end > buf.end()) { + throw std::out_of_range("recv_args: arg size not in buffer range"); + } + args[i] = std::string(start, end); + idx += arg_size; + } + if (idx != buff_size) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: Bug recv_args: idx %li != buff_size %li\n", idx, buff_size); + return false; + } + } catch (std::out_of_range &e) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: Bug recv_args: %s\n", e.what()); + return false; + } + + return true; +} + +static bool send_args(int fd, const std::vector &args) { + //Calculate size + size_t buff_size = 0; + buff_size += 2 * sizeof(uint16_t); + for (size_t i = 0; i < args.size(); i++) { + buff_size += sizeof(uint16_t); + buff_size += args[i].size(); + } + + //This is the largest value set by set_int16() + if (buff_size > std::numeric_limits::max()) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: send_args: args to big, size = %li!\n", buff_size); + return false; + } + + //Serialize + std::vector buf; + buf.reserve(buff_size); + push_uint16(buf, buff_size); + push_uint16(buf, args.size()); + for (size_t i = 0; i < args.size(); i++) { + push_uint16(buf, args[i].size()); + buf.insert(buf.end(), args[i].begin(), args[i].end()); + } + if (buf.size() != buff_size) { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: Bug send_args: buf.size() %li != buff_size %li\n", buf.size(), buff_size + ); + return false; + } + + //Send + ssize_t res = send_data(fd, buf.data(), buf.size(), 0); + if (res != (ssize_t)buf.size()) { + if (res == -1) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: send_args failed: %s\n", strerror(errno)); + } else { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: send_args failed, sent only %li of %li bytes\n", res, buf.size() + ); + } + return false; + } + return true; +} + +static int handle_command(std::vector args) { + if (args.size() == 0) { + return 0; + } + if (args.size() == 1 && args[0] == "exit") { + force_exit = 1; + return 0; + } else if (args.size() >= 2 && args[0] == "load") { + std::string name = args[1]; + args.erase(args.begin()); + return do_load_cmd(name, args); + } else if (args.size() == 2 && args[0] == "unload") { + return do_unload_cmd(args[1]); + } else if (args.size() == 3 && args[0] == "newinst") { + return do_newinst_cmd(args[1], args[2], ""); + } else if (args.size() == 4 && args[0] == "newinst") { + return do_newinst_cmd(args[1], args[2], args[3]); + } else if (args.size() == 2 && args[0] == "debug") { + return do_debug_cmd(args[1]); + } else { + rtapi_print_msg(RTAPI_MSG_ERR, "Unrecognized command starting with %s\n", args[0].c_str()); + return -1; + } +} + +static int slave(int fd, const std::vector &args) { + if (!send_args(fd, args)) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to write to master\n"); + return -1; + } + + int result = -1; + if (!recv_result(fd, &result)) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to read from master\n"); + return -1; + } else { + return result; + } +} + +//Processes incoming command on socket +//This function blocks on accept until a client connects +//return: true if master should continue +// false if master should exit +static bool master_process_socket_command(int fd) { + struct sockaddr_un client_addr; + memset(&client_addr, 0, sizeof(client_addr)); + socklen_t len = sizeof(client_addr); + int fd1 = accept(fd, (sockaddr *)&client_addr, &len); + if (fd1 < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to accept connection from slave: %s\n", strerror(errno)); + return true; //If there is a socket error, just continue, no need to check errno + } else { + int result; + std::vector args; + + //Set timeout, so master doesn't hang forever + struct timeval timeout; + timeout.tv_sec = 10; + timeout.tv_usec = 0; + if (setsockopt(fd1, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: setsockopt timeout failed: %s\n", strerror(errno)); + close(fd1); + return true; //If there is a socket error, just continue + } + + if (!recv_args(fd1, args)) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to read from slave\n"); + close(fd1); + return true; //If there is a socket error, just continue + } + + result = handle_command(args); + + if (!send_result(fd1, result)) { + rtapi_print_msg(RTAPI_MSG_ERR, "rtapi_app: failed to write to slave\n"); + } + close(fd1); + } + return !force_exit && instance_count > 0; +} + +static pthread_t main_thread{}; + +static int master(int fd, const std::vector &args) { + main_thread = pthread_self(); + int result; + if ((result = pthread_create(&queue_thread, nullptr, &queue_function, nullptr)) != 0) { + errno = result; + perror("pthread_create (queue function)"); + return -1; + } + do_load_cmd("hal_lib", std::vector()); + instance_count = 0; + App(); // force rtapi_app to be created + if (args.size()) { + result = handle_command(args); + if (result != 0) + goto out; + if (force_exit || instance_count == 0) + goto out; + } + //Process commands as long as master should not exit + while(master_process_socket_command(fd)); +out: + pthread_cancel(queue_thread); + pthread_join(queue_thread, nullptr); + rtapi_msg_queue.consume_all([](const message_t &m) { + fputs(m.msg, m.level == RTAPI_MSG_ALL ? stdout : stderr); + }); + return result; +} + +static std::string get_fifo_path() { + std::string s; + if (getenv("RTAPI_FIFO_PATH")) { + s = getenv("RTAPI_FIFO_PATH"); + } else if (getenv("HOME")) { + s = std::string(getenv("HOME")) + "/.rtapi_fifo"; + } else { + rtapi_print_msg( + RTAPI_MSG_ERR, "rtapi_app: RTAPI_FIFO_PATH and HOME are unset. rtapi fifo creation is unsafe.\n" + ); + } + return s; +} + +static bool get_fifo_path_to_addr(struct sockaddr_un *addr) { + const std::string s = get_fifo_path(); + if (s.empty()) { + return false; + } + if (s.size() + 2 > sizeof(addr->sun_path)) { + rtapi_print_msg( + RTAPI_MSG_ERR, + "rtapi_app: rtapi fifo path is too long (arch limit %zd): %s\n", + sizeof(sockaddr_un::sun_path), + s.c_str() + ); + return false; + } + //See: https://www.man7.org/linux/man-pages/man7/unix.7.html abstract + //sun_path[0] is a null byte ('\0') + addr->sun_path[0] = 0; + strncpy(addr->sun_path + 1, s.c_str(), sizeof(addr->sun_path) - 2); + return true; +} + +static double diff_timespec(const struct timespec *time1, const struct timespec *time0) { + return (time1->tv_sec - time0->tv_sec) + (time1->tv_nsec - time0->tv_nsec) / 1000000000.0; +} + +#ifdef __linux__ +static void raise_net_admin_ambient(void); +#endif + +int main(int argc, char **argv) { + if (getuid() == 0) { + char *fallback_uid_str = getenv("RTAPI_UID"); + int fallback_uid = fallback_uid_str ? atoi(fallback_uid_str) : 0; + if (fallback_uid == 0) { + // Cppcheck cannot see EMC2_BIN_DIR when RTAPI is defined, but that + // doesn't happen in uspace. + fprintf( + stderr, + "Refusing to run as root without fallback UID specified\n" + "To run under a debugger with I/O, use e.g.,\n" + // cppcheck-suppress unknownMacro + " sudo env RTAPI_UID=`id -u` RTAPI_FIFO_PATH=$HOME/.rtapi_fifo gdb " EMC2_BIN_DIR "/rtapi_app\n" + ); + exit(1); + } + if (setreuid(fallback_uid, 0) != 0) { + perror("setreuid"); + abort(); + } + fprintf(stderr, "Running with fallback_uid. getuid()=%d geteuid()=%d\n", getuid(), geteuid()); + } + uid_t ruid = getuid(); + uid_t euid = geteuid(); + WithRoot::init(ruid, euid); + if (setresuid(euid, euid, ruid) != 0) { + perror("setresuid"); + abort(); + } +#ifdef __linux__ + setfsuid(ruid); + raise_net_admin_ambient(); +#endif + std::vector args; + for (int i = 1; i < argc; i++) { + args.push_back(std::string(argv[i])); + } + +become_master: + int fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + perror("socket"); + exit(1); + } + + int enable = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); + struct sockaddr_un addr; + memset(&addr, 0x0, sizeof(addr)); + addr.sun_family = AF_UNIX; + if (!get_fifo_path_to_addr(&addr)) + exit(1); + + // plus one because we use the abstract namespace, it will show up in + // /proc/net/unix prefixed with an @ + int result = bind(fd, (sockaddr *)&addr, sizeof(addr)); + + if (result == 0) { + //If called in master mode with exit command, no need to start master + //and exit again + if (args.size() == 1 && args[0] == "exit") { + return 0; + } + int result = listen(fd, 10); + if (result != 0) { + perror("listen"); + exit(1); + } + setsid(); // create a new session if we can... + result = master(fd, args); + return result; + } else if (errno == EADDRINUSE) { + struct timespec start, now; + clock_gettime(CLOCK_MONOTONIC, &start); + clock_gettime(CLOCK_MONOTONIC, &now); + srand48(start.tv_sec ^ start.tv_nsec); + while (diff_timespec(&now, &start) < 3.0) { + result = connect(fd, (sockaddr *)&addr, sizeof(addr)); + if (result == 0) + break; + + usleep(lrand48() % 100000 + 100); //Random sleep min 100us max 100100us + clock_gettime(CLOCK_MONOTONIC, &now); + } + if (result < 0 && errno == ECONNREFUSED) { + fprintf(stderr, "Waited 3 seconds for master. giving up.\n"); + close(fd); + goto become_master; + } + if (result < 0) { + fprintf(stderr, "connect %s: %s", addr.sun_path, strerror(errno)); + exit(1); + } + return slave(fd, args); + } else { + perror("bind"); + exit(1); + } +} + +static inline void write_string(int fd, const char *str) { + (void)!write(fd, str, strlen(str)); +} + +static void signal_handler(int sig, siginfo_t * /*si*/, void * /*uctx*/) { + //Read: https://www.man7.org/linux/man-pages/man7/signal-safety.7.html + bool doAbort = true; + switch (sig) { + case SIGXCPU: + write_string(STDERR_FILENO, "rtapi_app: SIGXCPU - aborting\n"); + break; + case SIGSEGV: + write_string(STDERR_FILENO, "rtapi_app: SIGSEGV - aborting\n"); + break; + case SIGILL: + write_string(STDERR_FILENO, "rtapi_app: SIGILL - aborting\n"); + break; + case SIGFPE: + write_string(STDERR_FILENO, "rtapi_app: SIGFPE - aborting\n"); + break; + case SIGTERM: + write_string(STDERR_FILENO, "rtapi_app: SIGTERM - shutting down\n"); + doAbort = false; //TERM is a user signal, no need for a coredump + break; + case SIGINT: + write_string(STDERR_FILENO, "rtapi_app: SIGINT - shutting down\n"); + doAbort = false; //INT is a user signal, no need for a coredump + break; + default: + write_string(STDERR_FILENO, "rtapi_app: UNKNOWN - aborting\n"); + break; + } + + //Write remaining messages + rtapi_msg_queue.consume_all([](const message_t &m) { + write_string(STDERR_FILENO, m.msg); + }); + + if (doAbort) { + //Call abort to generate a coredump if enabled + //To enable coredumps for setuid applications: + //echo 1 | sudo tee /proc/sys/fs/suid_dumpable #rtapi_app is setuid + //In general: + //ulimit -c unlimited or coredumpctl + abort(); + } + _exit(128 + sig); //128+n: Fatal error signal "n" +} + +const static size_t PRE_ALLOC_SIZE = 1024 * 1024 * 32; +const static struct rlimit unlimited = {RLIM_INFINITY, RLIM_INFINITY}; +static void configure_memory() { + // Best-effort raise of the soft cap to the hard cap. Fails on + // unprivileged processes without CAP_SYS_RESOURCE or without a + // matching setrlimit; CAP_IPC_LOCK alone lets mlockall succeed + // regardless of the rlimit, so ignoring the failure is safe. + struct rlimit limit; + if (getrlimit(RLIMIT_MEMLOCK, &limit) == 0) { + limit.rlim_cur = limit.rlim_max; + if (setrlimit(RLIMIT_MEMLOCK, &limit) < 0) + rtapi_print_msg(RTAPI_MSG_DBG, + "setrlimit(RLIMIT_MEMLOCK) failed: %s\n", strerror(errno)); + } + + int res = mlockall(MCL_CURRENT | MCL_FUTURE); + if (res < 0) + rtapi_print_msg(RTAPI_MSG_WARN, + "mlockall failed: %s. Realtime latency may suffer.\n", + strerror(errno)); + +#ifdef __linux__ + /* Turn off malloc trimming.*/ + if (!mallopt(M_TRIM_THRESHOLD, -1)) { + rtapi_print_msg(RTAPI_MSG_WARN, "mallopt(M_TRIM_THRESHOLD, -1) failed\n"); + } + /* Turn off mmap usage. */ + if (!mallopt(M_MMAP_MAX, 0)) { + rtapi_print_msg(RTAPI_MSG_WARN, "mallopt(M_MMAP_MAX, -1) failed\n"); + } +#endif + /* + * The following code seems pointless, but there is a non-observable effect + * in the allocation and loop. + * + * The malloc() is forced to set brk() because mmap() allocation is + * disabled in a call to mallopt() above. All touched pages become resident + * and locked in the loop because of above mlockall() call (see notes in + * mlockall(2)). The mallopt() trim setting prevents the brk() from being + * reduced after free(), effectively creating an open space for future + * allocations that will not generate page faults. + * + * The qualifier 'volatile' on the buffer pointer is required because newer + * clang would remove the malloc(), for()-loop and free() completely. + * Marking 'buf' volatile ensures that the code will remain in place. + */ + volatile char *buf = static_cast(malloc(PRE_ALLOC_SIZE)); + if (buf == NULL) { + rtapi_print_msg(RTAPI_MSG_WARN, "malloc(PRE_ALLOC_SIZE) failed\n"); + return; + } + long pagesize = sysconf(_SC_PAGESIZE); + /* Touch each page in this piece of memory to get it mapped into RAM */ + for (size_t i = 0; i < PRE_ALLOC_SIZE; i += pagesize) { + /* Each write to this buffer will generate a pagefault. + * Once the pagefault is handled a page will be locked in + * memory and never given back to the system. */ + buf[i] = 0; + } + free((void *)buf); +} + +static int harden_rt() { + if (!rtapi_is_realtime()) + return -EINVAL; + + WITH_ROOT; +#if defined(__linux__) && (defined(__x86_64__) || defined(__i386__)) + if (iopl(3) < 0) { + rtapi_print_msg( + RTAPI_MSG_ERR, + "iopl() failed: %s\n" + "cannot gain I/O privileges - " + "missing CAP_SYS_RAWIO or using secure boot? - " + "parallel port access is not allowed\n", + strerror(errno) + ); + } +#endif + + struct sigaction sig_act = {}; +#ifdef __linux__ + // Best-effort raise of RTPRIO/CORE soft caps. Setting these to + // RLIM_INFINITY requires CAP_SYS_RESOURCE, which neither setuid root + // nor file capabilities grant by default. Without it, threads still + // get SCHED_FIFO via CAP_SYS_NICE; the rlimit just gates how high + // they can go. Don't fail harden_rt() when it can't be raised. + if (setrlimit(RLIMIT_RTPRIO, &unlimited) < 0) + rtapi_print_msg(RTAPI_MSG_DBG, + "setrlimit(RLIMIT_RTPRIO): %s\n", strerror(errno)); + + if (setrlimit(RLIMIT_CORE, &unlimited) < 0) + rtapi_print_msg( + RTAPI_MSG_WARN, "setrlimit: %s - core dumps may be truncated or non-existent\n", strerror(errno) + ); + + // even when running with elevated capabilities + if (prctl(PR_SET_DUMPABLE, 1) < 0) + rtapi_print_msg( + RTAPI_MSG_WARN, + "prctl(PR_SET_DUMPABLE) failed: no core dumps will be created - %d - %s\n", + errno, + strerror(errno) + ); +#endif /* __linux__ */ + + configure_memory(); + + sigemptyset(&sig_act.sa_mask); + sig_act.sa_handler = SIG_IGN; + sig_act.sa_sigaction = NULL; + + // prevent stopping of RT threads by ^Z + sigaction(SIGTSTP, &sig_act, (struct sigaction *)NULL); + + sig_act.sa_sigaction = signal_handler; + sig_act.sa_flags = SA_SIGINFO; + + sigaction(SIGXCPU, &sig_act, (struct sigaction *)NULL); + sigaction(SIGSEGV, &sig_act, (struct sigaction *)NULL); + sigaction(SIGILL, &sig_act, (struct sigaction *)NULL); + sigaction(SIGFPE, &sig_act, (struct sigaction *)NULL); + sigaction(SIGTERM, &sig_act, (struct sigaction *)NULL); + sigaction(SIGINT, &sig_act, (struct sigaction *)NULL); + +#ifdef __linux__ + int fd = open("/dev/cpu_dma_latency", O_WRONLY | O_CLOEXEC); + if (fd < 0) { + rtapi_print_msg(RTAPI_MSG_WARN, "failed to open /dev/cpu_dma_latency: %s\n", strerror(errno)); + } else { + int r; + r = write(fd, "\0\0\0\0", 4); + if (r != 4) { + rtapi_print_msg(RTAPI_MSG_WARN, "failed to write to /dev/cpu_dma_latency: %s\n", strerror(errno)); + } + // deliberately leak fd until program exit + } +#endif /* __linux__ */ + return 0; +} + +static RtapiApp *makeDllApp(const std::string &dllName, int policy) { + void *dll = nullptr; + dll = dlopen(dllName.c_str(), RTLD_NOW); + if (!dll) { + fprintf(stderr, "dlopen: %s\n", dlerror()); + return nullptr; + } + auto fn = reinterpret_cast(dlsym(dll, "make")); + if (!fn) { + fprintf(stderr, "dlsym: %s\n", dlerror()); + return nullptr; + } + auto result = fn(policy); + if (!result) { + fprintf(stderr, "dlsym: %s\n", dlerror()); + return nullptr; + } + return result; +} + +// Diagnostic helper: report cap_effective state for a single capability. +// Returns "yes", "no", or "unknown" if libcap could not introspect. +#ifdef __linux__ +static const char *cap_effective_str(cap_t caps, cap_value_t cap) { + if (!caps) return "unknown"; + cap_flag_value_t v; + if (cap_get_flag(caps, cap, CAP_EFFECTIVE, &v) != 0) return "unknown"; + return v == CAP_SET ? "yes" : "no"; +} + +// Raise CAP_NET_ADMIN into the ambient set so it survives execve() into +// child processes (iptables, ip6tables) launched by HAL drivers like +// hm2_eth. Linux file capabilities on rtapi_app give cap_net_admin in +// the permitted+effective sets but not inheritable/ambient, so without +// this iptables runs cap-less and fails with EPERM. No-op when the cap +// is not held (e.g. running unprivileged). +static void raise_net_admin_ambient(void) { + cap_t caps = cap_get_proc(); + if (!caps) return; + + cap_value_t cap = CAP_NET_ADMIN; + cap_flag_value_t v; + if (cap_get_flag(caps, cap, CAP_PERMITTED, &v) == 0 && v == CAP_SET) { + if (cap_set_flag(caps, CAP_INHERITABLE, 1, &cap, CAP_SET) == 0 + && cap_set_proc(caps) == 0) { + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, + CAP_NET_ADMIN, 0, 0) != 0 + && geteuid() != 0) { + rtapi_print_msg(RTAPI_MSG_WARN, + "rtapi_app: PR_CAP_AMBIENT_RAISE(CAP_NET_ADMIN) " + "failed: %s; iptables-using drivers may not work " + "under file caps.\n", strerror(errno)); + } + } + } + cap_free(caps); +} +#endif + +static RtapiApp *makeApp() { + RtapiApp *app; + bool rt_ok = rtapi_is_realtime(); + if (!rt_ok) { + // Surface the actual reason so the user does not have to guess + // between "no caps", "stock kernel", or "wrong rlimits" (issue + // #3928). errno comes from the SCHED_FIFO probe in + // can_set_sched_fifo(); cap state comes from libcap. + int sched_err = rtapi_sched_fifo_errno(); +#ifdef __linux__ + cap_t caps = cap_get_proc(); + const char *nice_s = cap_effective_str(caps, CAP_SYS_NICE); + const char *lock_s = cap_effective_str(caps, CAP_IPC_LOCK); +#else + const char *nice_s = "unknown"; + const char *lock_s = "unknown"; +#endif + rtapi_print_msg(RTAPI_MSG_ERR, + "Note: realtime scheduling unavailable " + "(sched_setscheduler SCHED_FIFO: %s).\n" + " Process capabilities: cap_sys_nice=%s cap_ipc_lock=%s.\n" + " Falling back to POSIX non-realtime.\n" + " Fix: 'sudo make setcap' (preferred) or 'sudo make setuid' " + "on rtapi_app.\n" + " Override (testing only): set LINUXCNC_FORCE_REALTIME=1.\n", + sched_err ? strerror(sched_err) : "denied", + nice_s, lock_s); +#ifdef __linux__ + if (caps) cap_free(caps); +#endif + } + if (!rt_ok || harden_rt() < 0) { + app = makeDllApp("liblinuxcnc-uspace-posix.so.0", SCHED_OTHER); + } else { + WithRoot r; + if (detect_xenomai_evl()) { + app = makeDllApp("liblinuxcnc-uspace-xenomai-evl.so.0", SCHED_FIFO); + } else if (detect_xenomai()) { + app = makeDllApp("liblinuxcnc-uspace-xenomai.so.0", SCHED_FIFO); + } else if (detect_rtai()) { + app = makeDllApp("liblinuxcnc-uspace-rtai.so.0", SCHED_FIFO); + } else { + // SCHED_FIFO available but no Xenomai/RTAI backend. Warn if the + // kernel is not PREEMPT_RT: SCHED_FIFO still beats SCHED_OTHER, + // but latency on a PREEMPT_DYNAMIC stock kernel can be tens of + // milliseconds, which will surprise users who expect the same + // bounds as a PREEMPT_RT or Xenomai setup. + if (!detect_preempt_rt()) { + rtapi_print_msg(RTAPI_MSG_ERR, + "Note: SCHED_FIFO available but kernel is not PREEMPT_RT. " + "Latency may be unbounded; install a PREEMPT_RT kernel " + "for hard realtime guarantees.\n"); + } + app = makeDllApp("liblinuxcnc-uspace-posix.so.0", SCHED_FIFO); + } + } + + if (!app) { + throw std::invalid_argument("Could not load rtapi dll"); + } else { + return app; + } +} +RtapiApp &App() { + static RtapiApp *app = makeApp(); + return *app; +} + +int rtapi_prio_highest(void) { + return App().prio_highest(); +} + +int rtapi_prio_lowest(void) { + return App().prio_lowest(); +} + +int rtapi_prio_next_higher(int prio) { + return App().prio_next_higher(prio); +} + +int rtapi_prio_next_lower(int prio) { + return App().prio_next_lower(prio); +} + +long rtapi_clock_set_period(long nsecs) { + return App().clock_set_period(nsecs); +} + +int rtapi_task_new(void (*taskcode)(void *), void *arg, int prio, int owner, unsigned long int stacksize, int uses_fp) { + return App().task_new(taskcode, arg, prio, owner, stacksize, uses_fp); +} + +int rtapi_task_delete(int id) { + return App().task_delete(id); +} + +int rtapi_task_start(int task_id, unsigned long period_nsec) { + int ret = App().task_start(task_id, period_nsec); + if (ret != 0) { + errno = -ret; + perror("rtapi_task_start()"); + } + return ret; +} + +int rtapi_task_pause(int task_id) { + return App().task_pause(task_id); +} + +int rtapi_task_resume(int task_id) { + return App().task_resume(task_id); +} + +int rtapi_task_self() { + return App().task_self(); +} + +long long rtapi_task_pll_get_reference(void) { + return App().task_pll_get_reference(); +} + +int rtapi_task_pll_set_correction(long value) { + return App().task_pll_set_correction(value); +} + +void rtapi_wait(void) { + App().wait(); +} + +void rtapi_outb(unsigned char byte, unsigned int port) { + App().do_outb(byte, port); +} + +unsigned char rtapi_inb(unsigned int port) { + return App().do_inb(port); +} + +long int simple_strtol(const char *nptr, char **endptr, int base) { + return strtol(nptr, endptr, base); +} + +long long rtapi_get_time() { + return App().do_get_time(); +} + +void default_rtapi_msg_handler(msg_level_t level, const char *fmt, va_list ap) { + if (main_thread && pthread_self() != main_thread) { + message_t m; + m.level = level; + vsnprintf(m.msg, sizeof(m.msg), fmt, ap); + rtapi_msg_queue.push(m); + } else { + vfprintf(level == RTAPI_MSG_ALL ? stdout : stderr, fmt, ap); + } +} + +long int rtapi_delay_max() { + return 10000; +} + +void rtapi_delay(long ns) { + if (ns > rtapi_delay_max()) + ns = rtapi_delay_max(); + App().do_delay(ns); +} + +const unsigned long ONE_SEC_IN_NS = 1000000000; +void rtapi_timespec_advance(struct timespec &result, const struct timespec &src, unsigned long nsec) { + time_t sec = src.tv_sec; + while (nsec >= ONE_SEC_IN_NS) { + ++sec; + nsec -= ONE_SEC_IN_NS; + } + nsec += src.tv_nsec; + if (nsec >= ONE_SEC_IN_NS) { + ++sec; + nsec -= ONE_SEC_IN_NS; + } + result.tv_sec = sec; + result.tv_nsec = nsec; +} + +int rtapi_open_as_root(const char *filename, int mode) { + WITH_ROOT; + int r = open(filename, mode); + if (r < 0) + return -errno; + return r; +} + +int rtapi_spawn_as_root( + pid_t *pid, + const char *path, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *attrp, + char *const argv[], + char *const envp[] +) { + return posix_spawn(pid, path, file_actions, attrp, argv, envp); +} + +int rtapi_spawnp_as_root( + pid_t *pid, + const char *path, + const posix_spawn_file_actions_t *file_actions, + const posix_spawnattr_t *attrp, + char *const argv[], + char *const envp[] +) { + return posix_spawnp(pid, path, file_actions, attrp, argv, envp); +} diff --git a/src/rtapi/uspace_rtapi_parport.cc b/src/rtapi/uspace_rtapi_parport.cc index f396837c71f..f208181fa97 100644 --- a/src/rtapi/uspace_rtapi_parport.cc +++ b/src/rtapi/uspace_rtapi_parport.cc @@ -19,7 +19,7 @@ #include #include #include -#include "rtapi_uspace.hh" +#include "uspace_rtapi_app.hh" #include #include #include diff --git a/src/rtapi/uspace_xenomai.cc b/src/rtapi/uspace_xenomai.cc index c3c6e432ea5..4c7ff05c5a2 100644 --- a/src/rtapi/uspace_xenomai.cc +++ b/src/rtapi/uspace_xenomai.cc @@ -1,18 +1,35 @@ +/* Copyright (C) 2016 Jeff Epler + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ #include "config.h" #include "rtapi.h" -#include "rtapi_uspace.hh" +#include "uspace_rtapi_app.hh" #include -#include +#include #include #include +#include #ifdef HAVE_SYS_IO_H #include #endif -namespace -{ -struct RtaiTask : rtapi_task { - RtaiTask() : rtapi_task{}, cancel{}, thr{} {} +namespace { +struct XenomaiTask : RtapiTask { + XenomaiTask() : RtapiTask{}, cancel{}, thr{} { + } std::atomic_int cancel; pthread_t thr; }; @@ -23,13 +40,14 @@ struct XenomaiApp : RtapiApp { pthread_once(&key_once, init_key); } - RtaiTask *do_task_new() { - return new RtaiTask; + RtapiTask *do_task_new() { + return new XenomaiTask; } int task_delete(int id) { - auto task = ::rtapi_get_task(id); - if(!task) return -EINVAL; + auto task = ::rtapi_get_task(id); + if (!task) + return -EINVAL; task->cancel = 1; pthread_join(task->thr, nullptr); @@ -40,8 +58,9 @@ struct XenomaiApp : RtapiApp { } int task_start(int task_id, unsigned long period_nsec) { - auto task = ::rtapi_get_task(task_id); - if(!task) return -EINVAL; + auto task = ::rtapi_get_task(task_id); + if (!task) + return -EINVAL; task->period = period_nsec; struct sched_param param; @@ -52,39 +71,39 @@ struct XenomaiApp : RtapiApp { task->pll_correction_limit = period_nsec / 100; task->pll_correction = 0; - int nprocs = sysconf( _SC_NPROCESSORS_ONLN ); + int nprocs = sysconf(_SC_NPROCESSORS_ONLN); - int ret; pthread_attr_t attr; - if((ret = pthread_attr_init(&attr)) != 0) + int ret; + if ((ret = pthread_attr_init(&attr)) != 0) return -ret; - if((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) + if ((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) return -ret; - if((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) + if ((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) return -ret; - if((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) + if ((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) return -ret; - if((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) + if ((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) return -ret; - if(nprocs > 1){ + if (nprocs > 1) { const static int rt_cpu_number = find_rt_cpu_number(); rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); - if(rt_cpu_number != -1) { + if (rt_cpu_number != -1) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(rt_cpu_number, &cpuset); - if((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) + if ((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) return -ret; } } - if((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) + if ((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) return -ret; return 0; } static void *wrapper(void *arg) { - auto task = reinterpret_cast(arg); + auto task = reinterpret_cast(arg); pthread_setspecific(key, arg); struct timespec now; @@ -97,7 +116,7 @@ struct XenomaiApp : RtapiApp { // encountered on: 3.18.20-xenomai-2.6.5 with a 2-thread SMP system rtapi_timespec_advance(task->nextstart, now, task->period + task->pll_correction); - (task->taskcode) (task->arg); + (task->taskcode)(task->arg); rtapi_print("ERROR: reached end of wrapper for task %d\n", task->id); return nullptr; @@ -114,38 +133,40 @@ struct XenomaiApp : RtapiApp { } long long task_pll_get_reference(void) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return 0; + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return 0; return task->nextstart.tv_sec * 1000000000LL + task->nextstart.tv_nsec; } int task_pll_set_correction(long value) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; - if (value > task->pll_correction_limit) value = task->pll_correction_limit; - if (value < -(task->pll_correction_limit)) value = -(task->pll_correction_limit); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; + if (value > task->pll_correction_limit) + value = task->pll_correction_limit; + if (value < -(task->pll_correction_limit)) + value = -(task->pll_correction_limit); task->pll_correction = value; return 0; } void wait() { int task_id = task_self(); - auto task = ::rtapi_get_task(task_id); - if(task->cancel) { + auto task = ::rtapi_get_task(task_id); + if (task->cancel) { pthread_exit(nullptr); } rtapi_timespec_advance(task->nextstart, task->nextstart, task->period + task->pll_correction); struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); - if(rtapi_timespec_less(task->nextstart, now)) - { - if(policy == SCHED_FIFO) + if (rtapi_timespec_less(task->nextstart, now)) { + if (policy == SCHED_FIFO) unexpected_realtime_delay(task); - } - else - { + } else { int res = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &task->nextstart, nullptr); - if(res < 0) perror("clock_nanosleep"); + if (res < 0) + perror("clock_nanosleep"); } } @@ -153,6 +174,7 @@ struct XenomaiApp : RtapiApp { #ifdef HAVE_SYS_IO_H return inb(port); #else + (void)port; return 0; #endif } @@ -161,18 +183,15 @@ struct XenomaiApp : RtapiApp { #ifdef HAVE_SYS_IO_H return outb(val, port); #else - return 0; + (void)val; + (void)port; #endif } - int run_threads(int fd, int (*callback)(int fd)) { - while(callback(fd)) { /* nothing */ } - return 0; - } - int task_self() { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; return task->id; } @@ -196,11 +215,14 @@ struct XenomaiApp : RtapiApp { pthread_once_t XenomaiApp::key_once; pthread_key_t XenomaiApp::key; -} +} // namespace -extern "C" RtapiApp *make(); +extern "C" RtapiApp *make(int policy); -RtapiApp *make() { +RtapiApp *make(int policy) { + if (policy != SCHED_FIFO) { + throw std::invalid_argument("Only SCHED_FIFO allowed"); + } rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using XENOMAI (posix-skin) realtime\n"); - return new XenomaiApp; + return new XenomaiApp(); } diff --git a/src/rtapi/uspace_xenomai_evl.cc b/src/rtapi/uspace_xenomai_evl.cc index b4f65f2f4c2..6c2d46b6142 100644 --- a/src/rtapi/uspace_xenomai_evl.cc +++ b/src/rtapi/uspace_xenomai_evl.cc @@ -1,6 +1,25 @@ +/* Copyright (C) 2016 Jeff Epler + * Copyright (C) 2026 Hannes Diethelm + * Copy uspace_xenomai.cc and adapted to Xenomai4 EVL + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + #include "config.h" #include "rtapi.h" -#include "rtapi_uspace.hh" +#include "uspace_rtapi_app.hh" #include @@ -13,31 +32,33 @@ #include #include #include +#include #ifdef HAVE_SYS_IO_H #include #endif -namespace -{ -struct RtaiTask : rtapi_task { - RtaiTask() : rtapi_task{}, cancel{}, thr{} {} +namespace { +struct EvlTask : RtapiTask { + EvlTask() : RtapiTask{}, cancel{}, thr{} { + } std::atomic_int cancel; pthread_t thr; }; -struct XenomaiApp : RtapiApp { - XenomaiApp() : RtapiApp(SCHED_FIFO) { +struct EvlApp : RtapiApp { + EvlApp() : RtapiApp(SCHED_FIFO) { pthread_once(&key_once, init_key); } - RtaiTask *do_task_new() { - return new RtaiTask; + RtapiTask *do_task_new() { + return new EvlTask; } int task_delete(int id) { - auto task = ::rtapi_get_task(id); - if(!task) return -EINVAL; + auto task = ::rtapi_get_task(id); + if (!task) + return -EINVAL; task->cancel = 1; pthread_join(task->thr, nullptr); @@ -48,8 +69,9 @@ struct XenomaiApp : RtapiApp { } int task_start(int task_id, unsigned long period_nsec) { - auto task = ::rtapi_get_task(task_id); - if(!task) return -EINVAL; + auto task = ::rtapi_get_task(task_id); + if (!task) + return -EINVAL; task->period = period_nsec; struct sched_param param; @@ -60,39 +82,39 @@ struct XenomaiApp : RtapiApp { task->pll_correction_limit = period_nsec / 100; task->pll_correction = 0; - int nprocs = sysconf( _SC_NPROCESSORS_ONLN ); + int nprocs = sysconf(_SC_NPROCESSORS_ONLN); - int ret; pthread_attr_t attr; - if((ret = pthread_attr_init(&attr)) != 0) + int ret; + if ((ret = pthread_attr_init(&attr)) != 0) return -ret; - if((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) + if ((ret = pthread_attr_setstacksize(&attr, task->stacksize)) != 0) return -ret; - if((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) + if ((ret = pthread_attr_setschedpolicy(&attr, policy)) != 0) return -ret; - if((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) + if ((ret = pthread_attr_setschedparam(&attr, ¶m)) != 0) return -ret; - if((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) + if ((ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) != 0) return -ret; - if(nprocs > 1){ + if (nprocs > 1) { const static int rt_cpu_number = find_rt_cpu_number(); rtapi_print_msg(RTAPI_MSG_INFO, "rt_cpu_number = %i\n", rt_cpu_number); - if(rt_cpu_number != -1) { + if (rt_cpu_number != -1) { cpu_set_t cpuset; CPU_ZERO(&cpuset); CPU_SET(rt_cpu_number, &cpuset); - if((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) + if ((ret = pthread_attr_setaffinity_np(&attr, sizeof(cpuset), &cpuset)) != 0) return -ret; } } - if((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) + if ((ret = pthread_create(&task->thr, &attr, &wrapper, reinterpret_cast(task))) != 0) return -ret; return 0; } static void *wrapper(void *arg) { - auto task = reinterpret_cast(arg); + auto task = reinterpret_cast(arg); pthread_setspecific(key, arg); { @@ -100,7 +122,7 @@ struct XenomaiApp : RtapiApp { /* Attach to the core. */ rtapi_print("linuxcnc-task:%d\n", gettid()); int tfd = evl_attach_self("linuxcnc-thread:%d", gettid()); - if (tfd < 0){ + if (tfd < 0) { rtapi_print("evl_attach_self() failed ret %i errno %i\n", tfd, errno); } } @@ -115,7 +137,7 @@ struct XenomaiApp : RtapiApp { // encountered on: 3.18.20-xenomai-2.6.5 with a 2-thread SMP system rtapi_timespec_advance(task->nextstart, now, task->period + task->pll_correction); - (task->taskcode) (task->arg); + (task->taskcode)(task->arg); rtapi_print("ERROR: reached end of wrapper for task %d\n", task->id); return nullptr; @@ -132,38 +154,40 @@ struct XenomaiApp : RtapiApp { } long long task_pll_get_reference(void) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return 0; + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return 0; return task->nextstart.tv_sec * 1000000000LL + task->nextstart.tv_nsec; } int task_pll_set_correction(long value) { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; - if (value > task->pll_correction_limit) value = task->pll_correction_limit; - if (value < -(task->pll_correction_limit)) value = -(task->pll_correction_limit); + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; + if (value > task->pll_correction_limit) + value = task->pll_correction_limit; + if (value < -(task->pll_correction_limit)) + value = -(task->pll_correction_limit); task->pll_correction = value; return 0; } void wait() { int task_id = task_self(); - auto task = ::rtapi_get_task(task_id); - if(task->cancel) { + auto task = ::rtapi_get_task(task_id); + if (task->cancel) { pthread_exit(nullptr); } rtapi_timespec_advance(task->nextstart, task->nextstart, task->period + task->pll_correction); struct timespec now; evl_read_clock(EVL_CLOCK_MONOTONIC, &now); - if(rtapi_timespec_less(task->nextstart, now)) - { - if(policy == SCHED_FIFO) + if (rtapi_timespec_less(task->nextstart, now)) { + if (policy == SCHED_FIFO) unexpected_realtime_delay(task); - } - else - { + } else { int res = evl_sleep_until(EVL_CLOCK_MONOTONIC, &task->nextstart); - if(res < 0) perror("evl_sleep_until"); + if (res < 0) + perror("evl_sleep_until"); } } @@ -171,6 +195,7 @@ struct XenomaiApp : RtapiApp { #ifdef HAVE_SYS_IO_H return inb(port); #else + (void)port; return 0; #endif } @@ -179,18 +204,15 @@ struct XenomaiApp : RtapiApp { #ifdef HAVE_SYS_IO_H return outb(val, port); #else - return 0; + (void)val; + (void)port; #endif } - int run_threads(int fd, int (*callback)(int fd)) { - while(callback(fd)) { /* nothing */ } - return 0; - } - int task_self() { - struct rtapi_task *task = reinterpret_cast(pthread_getspecific(key)); - if(!task) return -EINVAL; + RtapiTask *task = reinterpret_cast(pthread_getspecific(key)); + if (!task) + return -EINVAL; return task->id; } @@ -214,13 +236,16 @@ struct XenomaiApp : RtapiApp { } }; -pthread_once_t XenomaiApp::key_once; -pthread_key_t XenomaiApp::key; -} +pthread_once_t EvlApp::key_once; +pthread_key_t EvlApp::key; +} // namespace -extern "C" RtapiApp *make(); +extern "C" RtapiApp *make(int policy); -RtapiApp *make() { +RtapiApp *make(int policy) { + if (policy != SCHED_FIFO) { + throw std::invalid_argument("Only SCHED_FIFO allowed"); + } rtapi_print_msg(RTAPI_MSG_ERR, "Note: Using XENOMAI4 EVL realtime\n"); - return new XenomaiApp; + return new EvlApp(); }