diff --git a/docs/src/man/man9/hm2_eth.9.adoc b/docs/src/man/man9/hm2_eth.9.adoc index 0f84dccdde0..0e6e3b43dcf 100644 --- a/docs/src/man/man9/hm2_eth.9.adoc +++ b/docs/src/man/man9/hm2_eth.9.adoc @@ -7,13 +7,17 @@ IO boards, with HostMot2 firmware. == SYNOPSIS -*loadrt hm2_eth* [**config=**"__str__[,__str__...]"] [**board_ip=**__ip__[,__ip__...] ] [**board_mac=**__mac__[,__mac__...] ] [**firewall=**__auto|iptables|nft|none__] +*loadrt hm2_eth* [**config=**"__str__[,__str__...]"] [**board_ip=**__ip__[,__ip__...] ] [**board_rtnet=**__posix|evl__[,__posix|evl__...] ] [**board_mac=**__mac__[,__mac__...] ] [**firewall=**__auto|iptables|nft|none__] *config* [default: ""]:: HostMot2 config strings, described in the hostmot2(9) manpage. *board_ip* [default: ""]:: The IP address of the board(s), separated by commas. As shipped, the board address is 192.168.1.121. +*board_rtnet* [default: posix]:: + The real time ethernet implementation of the board(s), separated by + commas. Use *posix* for PREEMT_RT. Use *evl* if you are running a + Xenomai4 EVL kernel and have a supported network card. *firewall* [default: auto]:: Selects the firewall backend used to isolate the dedicated interface. By default hm2_eth installs the rules itself; rtapi_app raises @@ -124,6 +128,8 @@ hm2_____.____.packet-error-level (s32, out):: The error level is always in the range from 0 to packet-error-limit, inclusive. hm2_____.____.packet-error-exceeded (bit, out):: This pin is TRUE when the current error level is equal to the maximum, and FALSE at other times. +hm2_____.____.packet-error-total (u32, io):: + This pin shows the total package error count. == PARAMETERS @@ -150,6 +156,16 @@ hm2_____.____.packet-read-timeout (s32, rw):: Setting this value too low can cause spurious read errors. Setting it too high can cause realtime delay errors. +== FUNCTIONS + +hm2_eth.realtime-init:: + Realtime initialization function. This function initializes the realtime + ethernet for *evl*. With *posix*, this function is not needed. + It needs to be called once at startup using: +.... +initf hm2_eth.realtime-init servo-thread +.... + == NOTES hm2_eth installs firewall rules to control the network interface while diff --git a/src/Makefile b/src/Makefile index 69952ddeac9..f43362f943b 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1049,7 +1049,11 @@ hm2_pci-objs := \ $(MATHSTUB) hm2_eth-objs := \ hal/drivers/mesa-hostmot2/hm2_eth.o \ - $(MATHSTUB) + hal/drivers/mesa-hostmot2/hm2_eth_net_posix.o +ifeq ($(CONFIG_USPACE_XENOMAI_EVL),y) +hm2_eth-objs += hal/drivers/mesa-hostmot2/hm2_eth_net_evl.o +endif +hm2_eth-objs += $(MATHSTUB) hm2_spi-objs := \ hal/drivers/mesa-hostmot2/hm2_spi.o \ hal/drivers/mesa-hostmot2/llio_info.o \ @@ -1286,7 +1290,7 @@ modules: $(patsubst %.o,../rtlib/%.so,$(obj-m)) $(Q)objdump -w -j .rtapi_export -t objects/$*.tmp \ | awk 'BEGIN{print "{ global :"} /rtapi_exported_/{printf("%s;\n", substr($$6,16))} END{print "local : * ; };"}' \ > objects/$*.ver - $(Q)$(CC) -shared -Bsymbolic -Wl,--version-script,objects/$*.ver -o $@ $^ -lm $(LDFLAGS) + $(Q)$(CC) -shared -Bsymbolic -Wl,--version-script,objects/$*.ver -o $@ $^ -lm $(XENOMAI_EVL_LDFLAGS) $(LDFLAGS) $(Q)chmod -x $@ RTFLAGS += -fno-strict-aliasing -fwrapv @@ -1297,7 +1301,7 @@ $(sort $(filter-out objects/rtemc/tp/cruckig/%,$(RTOBJS))) : objects/rt%.o : %.c @rm -f $@ @mkdir -p $(dir $@) $(Q)$(CC) -c $(OPT) $(DEBUG) $(EXTRA_DEBUG) -DRTAPI \ - $(EXTRA_CFLAGS) \ + $(EXTRA_CFLAGS) $(XENOMAI_EVL_CFLAGS) \ -MP -MD -MF "${@:.o=.d}" -MT "$@" \ $< -o $@ diff --git a/src/configure.ac b/src/configure.ac index 86af93b5162..03bf65e37e1 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -264,7 +264,7 @@ if test "$RTS" = uspace || test -z "$RTS" -a "$RTAI_CONFIG" = "none" ; then CONFIG_USPACE_XENOMAI_EVL=y XENOMAI_EVL_CFLAGS= XENOMAI_EVL_LDFLAGS=-levl - AC_DEFINE([USPACE_XENOMAI_EVL], [], [Define if uspace realtime should optionally support Xenomai-evl]) + AC_DEFINE([USPACE_XENOMAI_EVL], [], [Define if uspace realtime should optionally support Xenomai EVL]) fi AC_SUBST([CONFIG_USPACE_XENOMAI_EVL]) diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth.c b/src/hal/drivers/mesa-hostmot2/hm2_eth.c index 5d89756a97a..6dd41acdb73 100644 --- a/src/hal/drivers/mesa-hostmot2/hm2_eth.c +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth.c @@ -43,9 +43,15 @@ #include +#include "config.h" //For USPACE_XENOMAI_EVL + #include "hostmot2-lowlevel.h" #include "hostmot2.h" #include "hm2_eth.h" +#include "hm2_eth_net_posix.h" +#ifdef USPACE_XENOMAI_EVL +#include "hm2_eth_net_evl.h" +#endif struct kvlist { struct rtapi_list_head list; @@ -89,6 +95,9 @@ MODULE_SUPPORTED_DEVICE("Mesa-AnythingIO-7i80"); static char *board_ip[MAX_ETH_BOARDS]; RTAPI_MP_ARRAY_STRING(board_ip, MAX_ETH_BOARDS, "ip address of ethernet board(s)"); +static char *board_rtnet[MAX_ETH_BOARDS]; +RTAPI_MP_ARRAY_STRING(board_rtnet, MAX_ETH_BOARDS, "realtime network type of ethernet board(s)"); + static char *config[MAX_ETH_BOARDS]; RTAPI_MP_ARRAY_STRING(config, MAX_ETH_BOARDS, "config string for the AnyIO boards (see hostmot2(9) manpage)"); @@ -100,7 +109,7 @@ RTAPI_MP_STRING(firewall, "Firewall backend for traffic isolation: auto (default static int boards_count = 0; -int comm_active = 0; +static int comm_active = 0; static int comp_id; @@ -452,17 +461,14 @@ static const char *hm2_8cSS_pin_names[] = { }; -#define UDP_PORT 27181 -#define SEND_TIMEOUT_US 10 -#define RECV_TIMEOUT_US 10 #define READ_PCK_DELAY_NS 10000 -static hm2_eth_t boards[MAX_ETH_BOARDS]; +static hm2_eth_t boards[MAX_ETH_BOARDS]={}; -/// ethernet io functions +static int eth_socket_send(hm2_eth_t *board, const void *buffer, int len, int flags); +static int eth_socket_recv(hm2_eth_t *board, void *buffer, int len, int flags); -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); +/// firewall functions // hm2_eth installs firewall rules to isolate the dedicated Mesa // interface from non-realtime traffic. rtapi_app raises CAP_NET_ADMIN @@ -584,7 +590,7 @@ static fw_state_t fw_state = FW_UNRESOLVED; // Resolve and bring up the firewall backend on first call, caching the // result. Returns true when a backend is ready, false when isolation // is unavailable or disabled. -static bool use_firewall() { +bool use_firewall() { if(fw_state != FW_UNRESOLVED) return fw_state == FW_READY; @@ -631,7 +637,7 @@ static bool use_firewall() { // Drop all rules from our chain/table but keep the chain in place, so a // fresh set can be installed on (re-)init. -static void clear_firewall() { +void clear_firewall() { if(!use_firewall()) return; switch(fw_backend) { case FW_IPTABLES: @@ -672,7 +678,7 @@ static char* inet_ntoa_buf(struct in_addr in, char *buf, size_t n) { return buf; } -static char* fetch_ifname(int sockfd, char *buf, size_t n) { +char* fetch_ifname(int sockfd, char *buf, size_t n) { struct sockaddr_in srcaddr; struct ifaddrs *ifa, *it; @@ -699,7 +705,7 @@ static char* fetch_ifname(int sockfd, char *buf, size_t n) { return NULL; } -static int install_firewall_board(int sockfd) { +int install_firewall_board(int sockfd) { struct sockaddr_in srcaddr, dstaddr; char srchost[16], dsthost[16]; // enough for 255.255.255.255\0 char dport_s[8], sport_s[8]; @@ -744,7 +750,7 @@ static int install_firewall_board(int sockfd) { return 0; } -static int install_firewall_perinterface(const char *ifbuf) { +int install_firewall_perinterface(const char *ifbuf) { // Without these rules, 'ping' spews a lot of "Packet filtered" // messages. With them, ping prints 'ping: sendmsg: Operation not // permitted' once per second. @@ -788,16 +794,16 @@ static int install_firewall_perinterface(const char *ifbuf) { return 0; } -static int fetch_hwaddr(const char *board_ip, int sockfd, unsigned char buf[6]) { +int fetch_hwaddr(hm2_eth_t *board, unsigned char buf[6]) { lbp16_cmd_addr packet; unsigned char response[6]; LBP16_INIT_PACKET4(packet, 0x4983, 0x0002); - int res = eth_socket_send(sockfd, &packet, sizeof(packet), 0); + int res = eth_socket_send(board, &packet, sizeof(packet), 0); if(res < 0) return -errno; int i=0; do { - res = eth_socket_recv(sockfd, &response, sizeof(response), 0); + res = eth_socket_recv(board, &response, sizeof(response), 0); } while(++i < 10 && res < 0 && errno == EAGAIN); if(res < 0) return -errno; @@ -805,140 +811,64 @@ static int fetch_hwaddr(const char *board_ip, int sockfd, unsigned char buf[6]) for(i=0; i<6; i++) buf[i] = response[5-i]; LL_PRINT("%s: INFO: Hardware address (MAC): %02x:%02x:%02x:%02x:%02x:%02x\n", - board_ip, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); + board->ip, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); return 0; } -int ioctl_siocsarp(void *arg) { - hm2_eth_t *board = (hm2_eth_t *)arg; - return ioctl(board->sockfd, SIOCSARP, &board->req); -} - -int ioctl_siocdarp(void *arg) { - hm2_eth_t *board = (hm2_eth_t *)arg; - return ioctl(board->sockfd, SIOCDARP, &board->req); -} - -static int init_board(hm2_eth_t *board, const char *board_ip) { - int ret; - - board->sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); - if (board->sockfd < 0) { - LL_PRINT("ERROR: can't open socket: %s\n", strerror(errno)); - return -errno; - } - board->server_addr.sin_family = AF_INET; - board->server_addr.sin_port = htons(LBP16_UDP_PORT); - board->server_addr.sin_addr.s_addr = inet_addr(board_ip); - - board->local_addr.sin_family = AF_INET; - board->local_addr.sin_addr.s_addr = INADDR_ANY; - - ret = connect(board->sockfd, (struct sockaddr *) &board->server_addr, sizeof(struct sockaddr_in)); - if (ret < 0) { - LL_PRINT("ERROR: can't connect: %s\n", strerror(errno)); - return -errno; - } - - if(!use_firewall()) { - LL_PRINT(\ -"WARNING: Unable to restrict other access to the hm2-eth device.\n" -"This means that other software using the same network interface can violate\n" -"realtime guarantees. See hm2_eth(9) for more information.\n"); - } - - struct timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = RECV_TIMEOUT_US; - - ret = setsockopt(board->sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); - if (ret < 0) { - LL_PRINT("ERROR: can't set receive timeout socket option: %s\n", strerror(errno)); - return -errno; - } - - timeout.tv_usec = SEND_TIMEOUT_US; - ret = setsockopt(board->sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)); - if (ret < 0) { - LL_PRINT("ERROR: can't set send timeout socket option: %s\n", strerror(errno)); - return -errno; - } - - memset(&board->req, 0, sizeof(board->req)); - struct sockaddr_in *sin; - - sin = (struct sockaddr_in *) &board->req.arp_pa; - sin->sin_family = AF_INET; - sin->sin_addr.s_addr = inet_addr(board_ip); - - board->req.arp_ha.sa_family = AF_LOCAL; - board->req.arp_flags = ATF_PERM | ATF_COM; - ret = fetch_hwaddr( board_ip, board->sockfd, (void*)&board->req.arp_ha.sa_data ); - if(ret < 0) { - LL_PRINT("ERROR: Could not retrieve hardware address (MAC) of %s: %s\n", board_ip, strerror(-ret)); - return ret; - } - - // Pinning the ARP entry needs CAP_NET_ADMIN; rootless without setcap - // fails with EPERM. Best-effort, not fatal: fall back to dynamic ARP - // so the board still loads. Clear ATF_PERM so the SIOCDARP teardown - // in close_board() does not try to remove an entry we never set. - ret = ioctl_siocsarp(board); - if(ret < 0) { - LL_PRINT("WARNING: ioctl SIOCSARP failed: %s; continuing with " - "dynamic ARP. Install file capabilities (sudo make " - "setcap) or run setuid to pin the board's ARP entry and " - "avoid occasional transmit latency.\n", strerror(errno)); - board->req.arp_flags &= ~ATF_PERM; +/// ethernet io functions mapping +static int init_board(hm2_eth_t *board, const char *board_ip, const char *board_rtnet){ + //Default (NULL) is posix + if (board_rtnet == NULL || strcmp(board_rtnet, "posix") == 0) { + board->init_board = &hm2_posix_init_board; + board->init_board_realtime = &hm2_posix_init_board_realtime; + board->close_board = &hm2_posix_close_board; + board->eth_socket_send = &hm2_posix_eth_socket_send; + board->eth_socket_recv = &hm2_posix_eth_socket_recv; + } else if (strcmp(board_rtnet, "evl") == 0) { +#ifdef USPACE_XENOMAI_EVL + if (hal_get_realtime_type() != REALTIME_TYPE_XENOMAI_EVL) { + LL_PRINT("ERROR: board_rtnet = %s not available, LinuxCNC not running with Xenomai4 EVL realtime\n", board_rtnet) + return -1; + } + board->init_board = &hm2_evl_init_board; + board->init_board_realtime = &hm2_evl_init_board_realtime; + board->close_board = &hm2_evl_close_board; + board->eth_socket_send = &hm2_evl_eth_socket_send; + board->eth_socket_recv = &hm2_evl_eth_socket_recv; +#else + LL_PRINT("ERROR: board_rtnet = %s not available, LinuxCNC was built without Xenomai EVL support\n", board_rtnet); + return -1; +#endif + } else { + LL_PRINT("ERROR: board_rtnet = %s undefined\n", board_rtnet) + return -1; } - // install_firewall_board() is a no-op when no firewall backend is - // available (rootless install without CAP_NET_ADMIN, or - // firewall=none), so it is safe to call unconditionally. - ret = install_firewall_board(board->sockfd); - if(ret < 0) return ret; - - board->write_packet_ptr = board->write_packet; - board->read_packet_ptr = board->read_packet; - - return 0; + return board->init_board(board, board_ip); } -static int close_board(hm2_eth_t *board) { - int ret; - board->llio.reset(&board->llio); - - clear_firewall(); +static inline int init_board_realtime(hm2_eth_t *board){ + return board->init_board_realtime(board); +} - if(board->req.arp_flags & ATF_PERM) { - ret = ioctl_siocdarp(board); - if(ret < 0) perror("ioctl SIOCDARP"); - } - ret = shutdown(board->sockfd, SHUT_RDWR); - if (ret == -1) - LL_PRINT("ERROR: can't shutdown socket: %s\n", strerror(errno)); - - ret = close(board->sockfd); - if (ret == -1) - LL_PRINT("ERROR: can't close socket: %s\n", strerror(errno)); - - return ret < 0 ? -errno : 0; +static inline int close_board(hm2_eth_t *board){ + return board->close_board(board); } -static int eth_socket_send(int sockfd, const void *buffer, int len, int flags) { - return send(sockfd, buffer, len, flags); +static inline int eth_socket_send(hm2_eth_t *board, const void *buffer, int len, int flags){ + return board->eth_socket_send(board, buffer, len, flags); } -static int eth_socket_recv(int sockfd, void *buffer, int len, int flags) { - return recv(sockfd, buffer, len, flags); +static inline int eth_socket_recv(hm2_eth_t *board, void *buffer, int len, int flags){ + return board->eth_socket_recv(board, buffer, len, flags); } -static int eth_socket_recv_loop(int sockfd, void *buffer, int len, int flags, long timeout) { +int eth_socket_recv_loop(hm2_eth_t *board, void *buffer, int len, int flags, long timeout) { long long end = rtapi_get_time() + timeout; int result; do { - result = eth_socket_recv(sockfd, buffer, len, flags); + result = eth_socket_recv(board, buffer, len, flags); } while(result < 0 && rtapi_get_time() < end); return result; } @@ -968,7 +898,7 @@ static int hm2_eth_read(hm2_lowlevel_io_t *this, rtapi_u32 addr, void *buffer, i LBP16_INIT_PACKET4(read_packet, CMD_READ_HOSTMOT2_ADDR32_INCR(size/4), addr & 0xFFFF); - send = eth_socket_send(board->sockfd, (void*) &read_packet, sizeof(read_packet), 0); + send = eth_socket_send(board, (void*) &read_packet, sizeof(read_packet), 0); if(send < 0) LL_PRINT("ERROR: sending packet: %s\n", strerror(errno)); LL_PRINT_IF(debug, "read(%d) : PACKET SENT [CMD:%02X%02X | ADDR: %02X%02X | SIZE: %d]\n", board->read_cnt, read_packet.cmd_hi, read_packet.cmd_lo, @@ -976,7 +906,7 @@ static int hm2_eth_read(hm2_lowlevel_io_t *this, rtapi_u32 addr, void *buffer, i t1 = rtapi_get_time(); do { errno = 0; - recv = eth_socket_recv(board->sockfd, (void*) &tmp_buffer, size, 0); + recv = eth_socket_recv(board, (void*) &tmp_buffer, size, 0); if(recv < 0) rtapi_delay(READ_PCK_DELAY_NS); t2 = rtapi_get_time(); i++; @@ -1021,7 +951,7 @@ static int hm2_eth_send_queued_reads(hm2_lowlevel_io_t *this) { board->queue_reads_count++; board->queue_buff_size += 8; - send = eth_socket_send(board->sockfd, (void*) &board->read_packet, board->read_packet_ptr - board->read_packet, 0); + send = eth_socket_send(board, (void*) &board->read_packet, board->read_packet_ptr - board->read_packet, 0); if(send < 0) { LL_PRINT("ERROR: sending packet: %s\n", strerror(errno)); return 0; @@ -1085,7 +1015,7 @@ static int hm2_eth_receive_queued_reads(hm2_lowlevel_io_t *this) { do { do_recv_packet: errno = 0; - recv = eth_socket_recv(board->sockfd, (void*) &tmp_buffer, board->queue_buff_size, MSG_DONTWAIT); + recv = eth_socket_recv(board, (void*) &tmp_buffer, board->queue_buff_size, MSG_DONTWAIT); if(recv < 0) rtapi_delay(READ_PCK_DELAY_NS); t2 = rtapi_get_time(); i++; @@ -1130,7 +1060,7 @@ static int hm2_eth_reset(hm2_lowlevel_io_t *this) { // Make the watchdog timer bite in 1ns from now lbp16_cmd_addr_data32 bite_packet; LBP16_INIT_PACKET8(bite_packet, CMD_WRITE_HOSTMOT2_ADDR32_INCR(1), HM2_ADDR_WATCHDOG, 0x0001); - int ret = eth_socket_send(board->sockfd, (void*) &bite_packet, sizeof(bite_packet), 0); + int ret = eth_socket_send(board, (void*) &bite_packet, sizeof(bite_packet), 0); if(ret < 0) perror("eth_socket_send(bite_packet)"); return ret < 0 ? -errno : 0; } @@ -1170,7 +1100,7 @@ static int hm2_eth_write(hm2_lowlevel_io_t *this, rtapi_u32 addr, const void *bu memcpy(packet.tmp_buffer, buffer, size); LBP16_INIT_PACKET4(packet.wr_packet, CMD_WRITE_HOSTMOT2_ADDR32_INCR(size/4), addr & 0xFFFF); - send = eth_socket_send(board->sockfd, (void*) &packet, sizeof(lbp16_cmd_addr) + size, 0); + send = eth_socket_send(board, (void*) &packet, sizeof(lbp16_cmd_addr) + size, 0); if(send < 0) LL_PRINT("ERROR: sending packet: %s\n", strerror(errno)); LL_PRINT_IF(debug, "write(%d): PACKET SENT [CMD:%02X%02X | ADDR: %02X%02X | SIZE: %d]\n", board->write_cnt, packet.wr_packet.cmd_hi, packet.wr_packet.cmd_lo, @@ -1194,7 +1124,7 @@ static int hm2_eth_send_queued_writes(hm2_lowlevel_io_t *this) { board->write_packet_size += (sizeof(*packet) + 4); t0 = rtapi_get_time(); - send = eth_socket_send(board->sockfd, (void*) &board->write_packet, board->write_packet_size, 0); + send = eth_socket_send(board, (void*) &board->write_packet, board->write_packet_size, 0); if(send < 0) { LL_PRINT("ERROR: sending packet: %s\n", strerror(errno)); return 0; @@ -1246,12 +1176,12 @@ static int hm2_eth_probe(hm2_eth_t *board) { char llio_name[16] = {}; LBP16_INIT_PACKET4(read_packet, CMD_READ_BOARD_INFO_ADDR16_INCR(16/2), 0); - send = eth_socket_send(board->sockfd, (void*) &read_packet, sizeof(read_packet), 0); + send = eth_socket_send(board, (void*) &read_packet, sizeof(read_packet), 0); if(send < 0) { LL_PRINT("ERROR: sending packet: %s\n", strerror(errno)); return -errno; } - recv = eth_socket_recv_loop(board->sockfd, (void*) &board_name, 16, 0, + recv = eth_socket_recv_loop(board, (void*) &board_name, 16, 0, 200 * 1000 * 1000); if(recv < 0) { LL_PRINT("ERROR: receiving packet: %s\n", strerror(errno)); @@ -1728,6 +1658,15 @@ static int hm2_eth_items(hm2_eth_t *board) { return 0; } +void init_board_realtime_all(void *arg, long period){ + (void)arg; + (void)period; + int i; + for(i = 0; i < boards_count; i++) { + init_board_realtime(&boards[i]); + } +} + int rtapi_app_main(void) { RTAPI_INIT_LIST_HEAD(&ifnames); RTAPI_INIT_LIST_HEAD(&board_num); @@ -1744,7 +1683,7 @@ int rtapi_app_main(void) { clear_firewall(); for(i = 0, ret = 0; ret == 0 && i, + * Jeff Epler + * + * EVL Port: + * Copyright 2024 Hannes Diethelm + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "hostmot2-lowlevel.h" +#include "hm2_eth_net_evl.h" + +#define SEND_TIMEOUT_US 10 +#define RECV_TIMEOUT_US 10 + +/// ethernet io functions + +static int oob_enable_port(hm2_eth_t *board); +static int oob_disable_port(hm2_eth_t *board); + +int hm2_evl_init_board(hm2_eth_t *board, const char *board_ip) { + int ret; + LL_PRINT("%s: INFO: init board (Xenomai EVL)\n", board_ip); + board->is_evl_oob_active = false; + board->sockfd = socket(PF_INET, SOCK_DGRAM | SOCK_OOB, IPPROTO_IP); + if (board->sockfd < 0) { + LL_PRINT("ERROR: can't open socket: %s\n", strerror(errno)); + return -errno; + } + + board->server_addr.sin_family = AF_INET; + board->server_addr.sin_port = htons(LBP16_UDP_PORT); + board->server_addr.sin_addr.s_addr = inet_addr(board_ip); + + board->local_addr.sin_family = AF_INET; + board->local_addr.sin_addr.s_addr = INADDR_ANY; + + ret = connect(board->sockfd, (struct sockaddr *) &board->server_addr, sizeof(struct sockaddr_in)); + if (ret < 0) { + LL_PRINT("ERROR: can't connect: %s\n", strerror(errno)); + return -errno; + } + + strncpy(board->ip, board_ip, sizeof(board->ip)-1); + char *ifptr = fetch_ifname(board->sockfd, board->ifname, sizeof(board->ifname)); + if(!ifptr) { + LL_PRINT("failed to retrieve interface name for board\n"); + return 0; + } + + if (setsockopt(board->sockfd, SOL_SOCKET, SO_BINDTODEVICE, board->ifname, strlen(board->ifname))) { + LL_PRINT("ERROR: can't SO_BINDTODEVICE socket: %s\n", strerror(errno)); + } + + if (!use_firewall()) { + LL_PRINT(\ +"WARNING: Unable to restrict other access to the hm2-eth device.\n" +"This means that other software using the same network interface can violate\n" +"realtime guarantees. See hm2_eth(9) for more information.\n"); + } + + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = RECV_TIMEOUT_US; + ret = setsockopt(board->sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); + if (ret < 0) { + LL_PRINT("ERROR: can't set receive timeout socket option: %s\n", strerror(errno)); + return -errno; + } + + timeout.tv_sec = 0; + timeout.tv_usec = SEND_TIMEOUT_US; + ret = setsockopt(board->sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)); + if (ret < 0) { + LL_PRINT("ERROR: can't set send timeout socket option: %s\n", strerror(errno)); + return -errno; + } + + memset(&board->req, 0, sizeof(board->req)); + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *) &board->req.arp_pa; + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = inet_addr(board_ip); + + board->req.arp_ha.sa_family = AF_LOCAL; + board->req.arp_flags = ATF_PERM | ATF_COM; + ret = fetch_hwaddr( board, (void*)&board->req.arp_ha.sa_data ); + if (ret < 0) { + LL_PRINT("ERROR: Could not retrieve hardware address (MAC) of %s: %s\n", board_ip, strerror(-ret)); + return ret; + } + + // install_firewall_board() is a no-op when no firewall backend is + // available (rootless install without CAP_NET_ADMIN, or + // firewall=none), so it is safe to call unconditionally. + ret = install_firewall_board(board->sockfd); + if (ret < 0) return ret; + + board->write_packet_ptr = board->write_packet; + board->read_packet_ptr = board->read_packet; + + return 0; +} + +int hm2_evl_init_board_realtime(hm2_eth_t *board) { + static bool error_shown = false; + if (board->is_evl_oob_active ) { + if (!error_shown) { + LL_PRINT("ERROR: realtime-init called while realtime is allready initialized\n" + " You might be you using \"addf hm2_eth.realtime-init servo-thread\"\n" + " instead of \"initf hm2_eth.realtime-init servo-thread\"\n"); + error_shown = true; + } + return 0; //No failure in this case + } + return oob_enable_port(board); +} + +static int oob_enable_port(hm2_eth_t *board) { + //long long t1, t2; + int devfd, ret; + + LL_PRINT("%s: INFO: enable OOB for board on if %s\n", board->ip, board->ifname); + + //t1 = rtapi_get_time(); + size_t poolsz = 0, bufsz = 0; /* Use defaults. */ + + //Need root fsuid for this + uid_t previous = setfsuid(0); + + devfd = evl_net_open_dev(board->ifname); + if (devfd < 0) { + LL_PRINT("ERROR: cannot open out-of-band port %s\n", board->ifname); + return -1; + } + + ret = evl_net_enable_port(devfd, poolsz, bufsz); + if (ret) { + LL_PRINT("ERROR: cannot enable out-of-band port on %s\n", board->ifname); + return -1; + } + + close(devfd); /* We don't need the fildes of the oob port. */ + + ret = evl_net_solicit(board->sockfd, (struct sockaddr*)&board->server_addr, EVL_NEIGH_PERMANENT); + if (ret) { + LL_PRINT("solicit did not respond\n"); + return -1; + } + + setfsuid(previous); + + /*t2 = rtapi_get_time(); + LL_PRINT("Enable dur = %lli\n", t2-t1);*/ + + board->is_evl_oob_active=true; + + return 0; +} + +static int oob_disable_port(hm2_eth_t *board) { + (void)board; + int ret, devfd; + + LL_PRINT("%s: INFO: disable OOB for board on if %s\n", board->ip, board->ifname); + + //Need root fsuid for this + uid_t previous = setfsuid(0); + + devfd = evl_net_open_dev(board->ifname); + if (devfd < 0) { + LL_PRINT("ERROR: cannot open out-of-band port %s\n", board->ifname); + return -1; + } + + ret = evl_net_disable_port(devfd); + if (ret < 0) { + LL_PRINT("ERROR: cannot disable out-of-band port on %s\n", board->ifname); + return -1; + } + + close(devfd); + + setfsuid(previous); + + board->is_evl_oob_active=false; + + return 0; +} + +int hm2_evl_close_board(hm2_eth_t *board) { + int ret; + oob_disable_port(board); + + board->llio.reset(&board->llio); + + clear_firewall(); + + ret = close(board->sockfd); + if (ret == -1) + LL_PRINT("ERROR: can't close socket: %s\n", strerror(errno)); + + return ret < 0 ? -errno : 0; +} + +/* +static void print_addr(char* desc, struct sockaddr_in *addr){ + char ip_str[INET_ADDRSTRLEN+1]; + inet_ntop(AF_INET, &(addr->sin_addr), ip_str, sizeof(ip_str)); + LL_PRINT("--------%s--------\n", desc); + LL_PRINT("IP-Address: %s\n", ip_str); + LL_PRINT("Port: %d\n", ntohs(addr->sin_port)); + LL_PRINT("Family: %d\n", ntohs(addr->sin_family)); +} +*/ + +static int check_evl(hm2_eth_t *board) { + static bool error_shown = false; + if (!board->is_evl_oob_active && evl_get_self() >= 0) { + if (!error_shown) { + LL_PRINT("ERROR: hm2_eth evl mode: OOB not active in realtime thread\n" + " Please add: \"initf hm2_eth.realtime-init servo-thread\"\n" + " to your hal file\n"); + error_shown = true; + } + return -1; + } else { + return 0; + } +} + +int hm2_evl_eth_socket_send(hm2_eth_t *board, const void *buffer, int len, int flags) { + ssize_t ret = 0; + ret = check_evl(board); + if (ret < 0) { + return ret; + } + if (board->is_evl_oob_active) { + struct iovec iov; + struct oob_msghdr msghdr; + struct timespec ts_timeout; + + evl_read_clock(EVL_CLOCK_MONOTONIC, &ts_timeout); + ts_timeout.tv_nsec += 1000*SEND_TIMEOUT_US; + while (ts_timeout.tv_nsec >= 1000000000) { + ts_timeout.tv_nsec -= 1000000000; + ts_timeout.tv_sec ++; + } + + /* OUTPUT */ + iov.iov_base = (void *)buffer; + iov.iov_len = len; + msghdr.msg_iov = &iov; + msghdr.msg_iovlen = 1; + msghdr.msg_control = NULL; + msghdr.msg_controllen = 0; + msghdr.msg_name = &board->server_addr; + msghdr.msg_namelen = sizeof(board->server_addr); + /*msghdr.msg_name = NULL; + msghdr.msg_namelen = 0;*/ + msghdr.msg_flags = 0; + ret = oob_sendmsg(board->sockfd, &msghdr, &ts_timeout, flags); + if (ret == -1) { + LL_PRINT("ERROR: oob_sendmsg %m %i %li\n", errno, ret); + } + } else { + ret = send(board->sockfd, buffer, len, flags); + } + return ret; +} + +int hm2_evl_eth_socket_recv(hm2_eth_t *board, void *buffer, int len, int flags) { + ssize_t ret = 0; + ret = check_evl(board); + if (ret < 0) { + return ret; + } + if (board->is_evl_oob_active) { + struct oob_msghdr msghdr; + struct iovec iov; + struct timespec ts_timeout; + + evl_read_clock(EVL_CLOCK_MONOTONIC, &ts_timeout); + ts_timeout.tv_nsec += 1000*RECV_TIMEOUT_US; + while (ts_timeout.tv_nsec >= 1000000000) { + ts_timeout.tv_nsec -= 1000000000; + ts_timeout.tv_sec ++; + } + + /* INPUT */ + iov.iov_base = buffer; + iov.iov_len = len; + msghdr.msg_iov = &iov; + msghdr.msg_iovlen = 1; + msghdr.msg_control = NULL; + msghdr.msg_controllen = 0; + msghdr.msg_name = &board->local_addr; + msghdr.msg_namelen = sizeof(board->local_addr); + /*msghdr.msg_name = NULL; + msghdr.msg_namelen = 0;*/ + msghdr.msg_flags = 0; + ret = oob_recvmsg(board->sockfd, &msghdr, &ts_timeout, flags); + } else { + ret = recv(board->sockfd, buffer, len, flags); + } + return ret; +} diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth_net_evl.h b/src/hal/drivers/mesa-hostmot2/hm2_eth_net_evl.h new file mode 100644 index 00000000000..bc7deb83724 --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth_net_evl.h @@ -0,0 +1,34 @@ +/* This is a component of LinuxCNC + * Copyright 2013,2014 Michael Geszkiewicz , + * Jeff Epler + * + * EVL Port: + * Copyright 2024 Hannes Diethelm + * + * 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. + */ + +#ifndef __INCLUDE_HM2_ETH_NET_EVL_H +#define __INCLUDE_HM2_ETH_NET_EVL_H + +#include "hm2_eth.h" + +int hm2_evl_init_board(hm2_eth_t *board, const char *board_ip); +int hm2_evl_init_board_realtime(hm2_eth_t *board); +int hm2_evl_close_board(hm2_eth_t *board); +int hm2_evl_eth_socket_send(hm2_eth_t *board, const void *buffer, int len, int flags); +int hm2_evl_eth_socket_recv(hm2_eth_t *board, void *buffer, int len, int flags); + +#endif diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.c b/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.c new file mode 100644 index 00000000000..986e911e406 --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.c @@ -0,0 +1,165 @@ +/* This is a component of LinuxCNC + * Copyright 2013,2014 Michael Geszkiewicz , + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "hostmot2-lowlevel.h" +#include "hm2_eth_net_posix.h" + +#define SEND_TIMEOUT_US 10 +#define RECV_TIMEOUT_US 10 + +/// ethernet io functions + +int hm2_posix_init_board(hm2_eth_t *board, const char *board_ip) { + int ret; + LL_PRINT("%s: INFO: init board (POSIX)\n", board_ip); + board->sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); + if (board->sockfd < 0) { + LL_PRINT("ERROR: can't open socket: %s\n", strerror(errno)); + return -errno; + } + + board->server_addr.sin_family = AF_INET; + board->server_addr.sin_port = htons(LBP16_UDP_PORT); + board->server_addr.sin_addr.s_addr = inet_addr(board_ip); + + board->local_addr.sin_family = AF_INET; + board->local_addr.sin_addr.s_addr = INADDR_ANY; + + ret = connect(board->sockfd, (struct sockaddr *) &board->server_addr, sizeof(struct sockaddr_in)); + if (ret < 0) { + LL_PRINT("ERROR: can't connect: %s\n", strerror(errno)); + return -errno; + } + + strncpy(board->ip, board_ip, sizeof(board->ip)-1); + char *ifptr = fetch_ifname(board->sockfd, board->ifname, sizeof(board->ifname)); + if(!ifptr) { + LL_PRINT("failed to retrieve interface name for board\n"); + return 0; + } + + if(!use_firewall()) { + LL_PRINT(\ +"WARNING: Unable to restrict other access to the hm2-eth device.\n" +"This means that other software using the same network interface can violate\n" +"realtime guarantees. See hm2_eth(9) for more information.\n"); + } + + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = RECV_TIMEOUT_US; + ret = setsockopt(board->sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout)); + if (ret < 0) { + LL_PRINT("ERROR: can't set receive timeout socket option: %s\n", strerror(errno)); + return -errno; + } + + timeout.tv_sec = 0; + timeout.tv_usec = SEND_TIMEOUT_US; + ret = setsockopt(board->sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout, sizeof(timeout)); + if (ret < 0) { + LL_PRINT("ERROR: can't set send timeout socket option: %s\n", strerror(errno)); + return -errno; + } + + memset(&board->req, 0, sizeof(board->req)); + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *) &board->req.arp_pa; + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = inet_addr(board_ip); + + board->req.arp_ha.sa_family = AF_LOCAL; + board->req.arp_flags = ATF_PERM | ATF_COM; + ret = fetch_hwaddr( board, (void*)&board->req.arp_ha.sa_data ); + if(ret < 0) { + LL_PRINT("ERROR: Could not retrieve hardware address (MAC) of %s: %s\n", board_ip, strerror(-ret)); + return ret; + } + + // Pinning the ARP entry needs CAP_NET_ADMIN; rootless without setcap + // fails with EPERM. Best-effort, not fatal: fall back to dynamic ARP + // so the board still loads. Clear ATF_PERM so the SIOCDARP teardown + // in close_board() does not try to remove an entry we never set. + ret = ioctl(board->sockfd, SIOCSARP, &board->req); + if(ret < 0) { + LL_PRINT("WARNING: ioctl SIOCSARP failed: %s; continuing with " + "dynamic ARP. Install file capabilities (sudo make " + "setcap) or run setuid to pin the board's ARP entry and " + "avoid occasional transmit latency.\n", strerror(errno)); + board->req.arp_flags &= ~ATF_PERM; + } + + // install_firewall_board() is a no-op when no firewall backend is + // available (rootless install without CAP_NET_ADMIN, or + // firewall=none), so it is safe to call unconditionally. + ret = install_firewall_board(board->sockfd); + if(ret < 0) return ret; + + board->write_packet_ptr = board->write_packet; + board->read_packet_ptr = board->read_packet; + + return 0; +} + +int hm2_posix_init_board_realtime(hm2_eth_t *board){ + (void)board; + return 0; //Nothing todo +} + +int hm2_posix_close_board(hm2_eth_t *board) { + int ret; + board->llio.reset(&board->llio); + + clear_firewall(); + + if(board->req.arp_flags & ATF_PERM) { + ret = ioctl(board->sockfd, SIOCDARP, &board->req); + if(ret < 0) perror("ioctl SIOCDARP"); + } + ret = shutdown(board->sockfd, SHUT_RDWR); + if (ret == -1) + LL_PRINT("ERROR: can't shutdown socket: %s\n", strerror(errno)); + + ret = close(board->sockfd); + if (ret == -1) + LL_PRINT("ERROR: can't close socket: %s\n", strerror(errno)); + + return ret < 0 ? -errno : 0; +} + +int hm2_posix_eth_socket_send(hm2_eth_t *board, const void *buffer, int len, int flags) { + return send(board->sockfd, buffer, len, flags); +} + +int hm2_posix_eth_socket_recv(hm2_eth_t *board, void *buffer, int len, int flags) { + return recv(board->sockfd, buffer, len, flags); +} diff --git a/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.h b/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.h new file mode 100644 index 00000000000..9dfe3009fc4 --- /dev/null +++ b/src/hal/drivers/mesa-hostmot2/hm2_eth_net_posix.h @@ -0,0 +1,31 @@ +/* This is a component of LinuxCNC + * Copyright 2013,2014 Michael Geszkiewicz , + * 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. + */ + +#ifndef __INCLUDE_HM2_ETH_NET_POSIX_H +#define __INCLUDE_HM2_ETH_NET_POSIX_H + +#include "hm2_eth.h" + +int hm2_posix_init_board(hm2_eth_t *board, const char *board_ip); +int hm2_posix_init_board_realtime(hm2_eth_t *board); +int hm2_posix_close_board(hm2_eth_t *board); +int hm2_posix_eth_socket_send(hm2_eth_t *board, const void *buffer, int len, int flags); +int hm2_posix_eth_socket_recv(hm2_eth_t *board, void *buffer, int len, int flags); + +#endif