From b9de08cf608669e2198b344b61373a77a1f52b0b Mon Sep 17 00:00:00 2001 From: Patrick Grimm Date: Mon, 21 Oct 2024 14:53:01 +0200 Subject: [PATCH] Feature/router bsd (#821) * fix router compile warnings declaration-after-statement overlength-strings * router disable PRINT(debug_level) * ports rename dlmstp_[linux|bsd] to dlmstp_port * copy ports/linux/dlmstp_port.c ports/bsd/dlmstp_port.c * copy ports/linux/dlmstp_port.c ports/bsd/dlmstp_port.c * fix typedef in bip_get_local_address_ioctl * copy ports/linux/dlmstp_port.c ports/bsd/dlmstp_port.c * add bsd support for router app * fix clang __attribute__ optimize dont work clang O2/O3/Os are also not working * fix pre-commit * fix bsd SO_BINDTODEVICE is not available * add brew install libconfig for app router and bsd * fix BACDL_MSTP test on macOS * remove old comments --- .github/workflows/main.yml | 10 +- CMakeLists.txt | 21 +- apps/router/Makefile | 6 +- apps/router/ipmodule.c | 6 +- apps/router/main.c | 24 +- apps/router/mstpmodule.c | 9 +- apps/router/network_layer.c | 5 +- apps/router/portthread.h | 2 +- ports/bsd/bacport.h | 22 +- ports/bsd/bip-init.c | 109 +- ports/bsd/dlmstp_port.c | 991 ++++++++++++++++++ ports/bsd/{dlmstp_bsd.h => dlmstp_port.h} | 9 +- ports/bsd/rs485.c | 5 +- ports/linux/bacport.h | 2 +- ports/linux/bip-init.c | 2 +- ports/linux/{dlmstp_linux.c => dlmstp_port.c} | 5 +- ports/linux/{dlmstp_linux.h => dlmstp_port.h} | 0 ports/linux/rs485.c | 2 +- 18 files changed, 1127 insertions(+), 103 deletions(-) create mode 100644 ports/bsd/dlmstp_port.c rename ports/bsd/{dlmstp_bsd.h => dlmstp_port.h} (97%) rename ports/linux/{dlmstp_linux.c => dlmstp_port.c} (99%) rename ports/linux/{dlmstp_linux.h => dlmstp_port.h} (100%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e300aad8..c6e7653e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,6 +36,11 @@ jobs: # apps/router needs libconfig-dev. sudo apt-get install -y libconfig-dev + - name: Install Dependencies (macOS) + if: matrix.os == 'macOS-latest' && matrix.project == 'root' + run: | + brew install libconfig + - name: Create Build Environment # Some projects don't allow in-source building, so create a separate build directory # We'll use this as our working directory for all subsequent commands @@ -70,11 +75,6 @@ jobs: cmake_options="$cmake_options -DBACDL_ETHERNET=ON" cmake_options="$cmake_options -DBACNET_PROTOCOL_REVISION=24" - if [[ "$RUNNER_OS" != "macOS" ]]; then - # Apple does not have port yet for this. - cmake_options="$cmake_options -DBACDL_MSTP=ON" - fi - if [[ "$RUNNER_OS" == "Linux" ]]; then # Apple nor Windows does not have port yet for this. cmake_options="$cmake_options -DBACDL_ARCNET=ON" diff --git a/CMakeLists.txt b/CMakeLists.txt index a3e6ed4b..fe323052 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -660,8 +660,6 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") $<$:ports/linux/rs485.c> $<$:ports/linux/rs485.h> $<$:ports/linux/dlmstp.c> - $<$:ports/linux/dlmstp_linux.c> - $<$:ports/linux/dlmstp_linux.h> $<$:ports/linux/ethernet.c> ports/linux/mstimer-init.c) @@ -900,15 +898,16 @@ if(BACNET_STACK_BUILD_APPS) add_executable(reinit apps/reinit/main.c) target_link_libraries(reinit PRIVATE ${PROJECT_NAME}) - if(BACDL_MSTP AND NOT WIN32) + if(BACDL_BIP AND BACDL_MSTP AND EXISTS ${BACNET_PORT_DIRECTORY_PATH}/dlmstp_port.c) + find_path(LIBCONFIG_INCLUDE_DIR libconfig.h) find_library(LIBCONFIG_LIBRARIES NAMES config) if(NOT LIBCONFIG_LIBRARIES) message(WARNING "BACNET: Will not build apps/router as libconfig not found") else() add_executable( router - ports/linux/dlmstp_linux.c - ports/linux/dlmstp_linux.h + ${BACNET_PORT_DIRECTORY_PATH}/dlmstp_port.c + ${BACNET_PORT_DIRECTORY_PATH}/dlmstp_port.h apps/router/ipmodule.c apps/router/ipmodule.h apps/router/main.c @@ -921,18 +920,20 @@ if(BACNET_STACK_BUILD_APPS) apps/router/portthread.c apps/router/portthread.h) + target_include_directories( + router + PRIVATE ${PROJECT_NAME} + ${LIBCONFIG_INCLUDE_DIR}) target_link_libraries( router PRIVATE ${PROJECT_NAME} - # needs libconfig - -lconfig) + ${LIBCONFIG_LIBRARIES}) - target_compile_options(router PRIVATE + target_compile_options(router PRIVATE # These make this example not totally C90 compatible but it is ok. - -Wno-declaration-after-statement - -Wno-overlength-strings -Wno-variadic-macros + -O1 ) endif() endif() diff --git a/apps/router/Makefile b/apps/router/Makefile index bc889dbf..d4a091ba 100644 --- a/apps/router/Makefile +++ b/apps/router/Makefile @@ -15,6 +15,10 @@ ifeq (${BACNET_PORT},linux) TARGET_EXT = LIBS = -lpthread -lconfig -lm LFLAGS = $(LIBS) +else ifeq (${BACNET_PORT},bsd) +TARGET_EXT = +LIBS = -lpthread -lconfig -lm +LFLAGS = $(LIBS) endif SOURCE_DIR = ../../src @@ -24,7 +28,7 @@ SRCS = main.c \ ${BACNET_PORT_DIR}/rs485.c \ ${BACNET_PORT_DIR}/mstimer-init.c \ ${BACNET_PORT_DIR}/bip-init.c \ - ${BACNET_PORT_DIR}/dlmstp_linux.c \ + ${BACNET_PORT_DIR}/dlmstp_port.c \ ${BACNET_SOURCE_DIR}/basic/bbmd/h_bbmd.c \ ${BACNET_SOURCE_DIR}/datalink/bvlc.c \ ${BACNET_SOURCE_DIR}/basic/sys/fifo.c \ diff --git a/apps/router/ipmodule.c b/apps/router/ipmodule.c index 6191fef9..ebd24e16 100644 --- a/apps/router/ipmodule.c +++ b/apps/router/ipmodule.c @@ -24,7 +24,7 @@ uint8_t test_packet[] = { 0x81, 0x0a, 0x00, 0x16, /* BVLC header */ /* BUG with optimize Os */ /* *** bit out of range 0 - FD_SETSIZE on fd_set ***: terminated */ -void __attribute__((optimize("O2"))) * dl_ip_thread(void *pArgs) +void *dl_ip_thread(void *pArgs) { MSGBOX_ID msgboxid; BACMSG msg_storage, *bacmsg = NULL; @@ -260,7 +260,7 @@ int dl_ip_recv( struct timeval select_timeout; struct sockaddr_in sin = { 0 }; socklen_t sin_len = sizeof(sin); - + int ret; /* make sure the socket is open */ if (data->socket < 0) { return 0; @@ -284,7 +284,7 @@ int dl_ip_recv( sin.sin_addr.s_addr = 0x7E1D40A; sin.sin_port = 0xC0BA; #else - int ret = select(data->socket + 1, &read_fds, NULL, NULL, &select_timeout); + ret = select(data->socket + 1, &read_fds, NULL, NULL, &select_timeout); /* see if there is a packet for us */ if (ret > 0) { received_bytes = recvfrom( diff --git a/apps/router/main.c b/apps/router/main.c index 16d49a1b..80268d62 100644 --- a/apps/router/main.c +++ b/apps/router/main.c @@ -61,7 +61,7 @@ uint16_t get_next_free_dnet(void); int kbhit(void); -inline bool is_network_msg(const BACMSG *msg); +bool is_network_msg(const BACMSG *msg); int main(int argc, char *argv[]) { @@ -189,16 +189,16 @@ void print_help(void) "-c, --config \n\tinitialize router with a configuration " "file (.cfg) located at \n" "-D, --device [params]\n\tinitialize a " - "device using an interface specified with\n\t[params]\n" - "\ninit_parameters:\n" - "-n, --network \n\tspecify device network number\n" - "-P, --port \n\tspecify udp port for BIP device\n" - "-m, --mac [max_master] [max_frames]\n\tspecify MSTP " - "port parameters\n" - "-b, --baud \n\tspecify MSTP port baud rate\n" - "-p, --parity \n\tspecify MSTP port parity\n" - "-d, --databits <5|6|7|8>\n\tspecify MSTP port databits\n" - "-s, --stopbits <1|2>\n\tspecify MSTP port stopbits\n"); + "device using an interface specified with\n\t[params]\n"); + printf("\ninit_parameters:\n" + "-n, --network \n\tspecify device network number\n" + "-P, --port \n\tspecify udp port for BIP device\n" + "-m, --mac [max_master] [max_frames]\n\tspecify MSTP " + "port parameters\n" + "-b, --baud \n\tspecify MSTP port baud rate\n" + "-p, --parity \n\tspecify MSTP port parity\n" + "-d, --databits <5|6|7|8>\n\tspecify MSTP port databits\n" + "-s, --stopbits <1|2>\n\tspecify MSTP port stopbits\n"); } bool read_config(const char *filepath) @@ -813,6 +813,7 @@ int kbhit(void) { static const int STDIN = 0; static bool initialized = false; + int bytesWaiting; if (!initialized) { /* use termios to turn off line buffering */ @@ -824,7 +825,6 @@ int kbhit(void) initialized = true; } - int bytesWaiting; ioctl(STDIN, FIONREAD, &bytesWaiting); return bytesWaiting; } diff --git a/apps/router/mstpmodule.c b/apps/router/mstpmodule.c index 0878ec97..a11a62d5 100644 --- a/apps/router/mstpmodule.c +++ b/apps/router/mstpmodule.c @@ -14,16 +14,9 @@ #include #include "mstpmodule.h" #include "bacnet/bacint.h" -#include "dlmstp_linux.h" +#include "dlmstp_port.h" #include -#define MSTP_THREAD_PRINT_ENABLED -#ifdef MSTP_THREAD_PRINT_ENABLED -#define mstp_thread_debug(...) fprintf(stderr, __VA_ARGS__) -#else -#define mstp_thread_debug(...) -#endif - void *dl_mstp_thread(void *pArgs) { ROUTER_PORT *port = (ROUTER_PORT *)pArgs; diff --git a/apps/router/network_layer.c b/apps/router/network_layer.c index 3bdd7c69..a60084dd 100644 --- a/apps/router/network_layer.c +++ b/apps/router/network_layer.c @@ -25,6 +25,8 @@ process_network_message(const BACMSG *msg, MSG_DATA *data, uint8_t **buff) int16_t buff_len = 0; int apdu_offset; int apdu_len; + int net_count; + int i; memmove(data, msg->data, sizeof(MSG_DATA)); @@ -68,8 +70,7 @@ process_network_message(const BACMSG *msg, MSG_DATA *data, uint8_t **buff) case NETWORK_MESSAGE_I_AM_ROUTER_TO_NETWORK: { PRINT(INFO, "Recieved I-Am-Router-To-Network message\n"); - int net_count = apdu_len / 2; - int i; + net_count = apdu_len / 2; for (i = 0; i < net_count; i++) { decode_unsigned16( &data->pdu[apdu_offset + 2 * i], diff --git a/apps/router/portthread.h b/apps/router/portthread.h index 79c9a47f..80382853 100644 --- a/apps/router/portthread.h +++ b/apps/router/portthread.h @@ -25,7 +25,7 @@ #define INFO 2 #define DEBUG 3 -#define DEBUG_LEVEL 3 +/* #define DEBUG_LEVEL 3 */ #ifdef DEBUG_LEVEL #define PRINT(debug_level, ...) \ if (debug_level <= DEBUG_LEVEL) \ diff --git a/ports/bsd/bacport.h b/ports/bsd/bacport.h index cc8e4942..8488483e 100644 --- a/ports/bsd/bacport.h +++ b/ports/bsd/bacport.h @@ -54,7 +54,7 @@ #endif #include -#include +#include #define ENUMS #include @@ -73,18 +73,20 @@ #include #include "bacnet/basic/sys/bacnet_stack_exports.h" -/** @file bsd/net.h Includes BSD network headers. */ +#define BACNET_OBJECT_TABLE( \ + table_name, _type, _init, _count, _index_to_instance, _valid_instance, \ + _object_name, _read_property, _write_property, _RPM_list, _RR_info, \ + _iterator, _value_list, _COV, _COV_clear, _intrinsic_reporting) \ + static_assert(false, "Unsupported BACNET_OBJECT_TABLE for this platform") + +/** @file bsd/bacport.h Includes BSD network headers. */ /* Local helper functions for this port */ BACNET_STACK_EXPORT -extern int bip_get_local_netmask( - struct in_addr *netmask); +extern int bip_get_local_netmask(struct in_addr *netmask); -#define BACNET_OBJECT_TABLE(table_name, _type, _init, _count, \ - _index_to_instance, _valid_instance, _object_name, \ - _read_property, _write_property, _RPM_list, \ - _RR_info, _iterator, _value_list, _COV, \ - _COV_clear, _intrinsic_reporting) \ - static_assert(false, "Unsupported BACNET_OBJECT_TABLE for this platform") +BACNET_STACK_EXPORT +extern int bip_get_local_address_ioctl( + const char *ifname, struct in_addr *addr, uint32_t request); #endif diff --git a/ports/bsd/bip-init.c b/ports/bsd/bip-init.c index 6531c697..dd38682d 100644 --- a/ports/bsd/bip-init.c +++ b/ports/bsd/bip-init.c @@ -44,13 +44,15 @@ static char BIP_Interface_Name[IF_NAMESIZE] = { 0 }; * @param str - debug info string * @param addr - IPv4 address */ -static void debug_print_ipv4(const char *str, +static void debug_print_ipv4( + const char *str, const struct in_addr *addr, const unsigned int port, const unsigned int count) { if (BIP_Debug) { - fprintf(stderr, "BIP: %s %s:%hu (%u bytes)\n", str, inet_ntoa(*addr), + fprintf( + stderr, "BIP: %s %s:%hu (%u bytes)\n", str, inet_ntoa(*addr), ntohs(port), count); fflush(stderr); } @@ -164,8 +166,8 @@ void bip_get_broadcast_address(BACNET_ADDRESS *dest) */ bool bip_set_addr(const BACNET_IP_ADDRESS *addr) { - (void)addr; /* not something we do within this driver */ + (void)addr; return false; } @@ -191,8 +193,8 @@ bool bip_get_addr(BACNET_IP_ADDRESS *addr) */ bool bip_set_broadcast_addr(const BACNET_IP_ADDRESS *addr) { - (void)addr; /* not something we do within this driver */ + (void)addr; return false; } @@ -217,8 +219,8 @@ bool bip_get_broadcast_addr(BACNET_IP_ADDRESS *addr) */ bool bip_set_subnet_prefix(uint8_t prefix) { - (void)prefix; /* not something we do within this driver */ + (void)prefix; return false; } @@ -277,8 +279,9 @@ int bip_send_mpdu( /* Send the packet */ debug_print_ipv4( "Sending MPDU->", &bip_dest.sin_addr, bip_dest.sin_port, mtu_len); - return sendto(BIP_Socket, (const char *)mtu, mtu_len, 0, - (struct sockaddr *)&bip_dest, sizeof(struct sockaddr)); + return sendto( + BIP_Socket, (const char *)mtu, mtu_len, 0, (struct sockaddr *)&bip_dest, + sizeof(struct sockaddr)); } /** @@ -329,10 +332,11 @@ uint16_t bip_receive( /* see if there is a packet for us */ if (select(max + 1, &read_fds, NULL, NULL, &select_timeout) > 0) { - socket = FD_ISSET(BIP_Socket, &read_fds) ? BIP_Socket : - BIP_Broadcast_Socket; - received_bytes = recvfrom(socket, (char *)&npdu[0], max_npdu, 0, - (struct sockaddr *)&sin, &sin_len); + socket = + FD_ISSET(BIP_Socket, &read_fds) ? BIP_Socket : BIP_Broadcast_Socket; + received_bytes = recvfrom( + socket, (char *)&npdu[0], max_npdu, 0, (struct sockaddr *)&sin, + &sin_len); } else { return 0; } @@ -369,9 +373,11 @@ uint16_t bip_receive( debug_print_ipv4( "Received MPDU->", &sin.sin_addr, sin.sin_port, received_bytes); /* pass the packet into the BBMD handler */ - offset = socket == BIP_Socket ? - bvlc_handler(&addr, src, npdu, received_bytes) : - bvlc_broadcast_handler(&addr, src, npdu, received_bytes); + if (socket == BIP_Socket) { + offset = bvlc_handler(&addr, src, npdu, received_bytes); + } else { + offset = bvlc_broadcast_handler(&addr, src, npdu, received_bytes); + } if (offset > 0) { npdu_len = received_bytes - offset; debug_print_ipv4( @@ -405,7 +411,8 @@ uint16_t bip_receive( * @return Upon successful completion, returns the number of bytes sent. * Otherwise, -1 shall be returned and errno set to indicate the error. */ -int bip_send_pdu(BACNET_ADDRESS *dest, +int bip_send_pdu( + BACNET_ADDRESS *dest, BACNET_NPDU_DATA *npdu_data, uint8_t *pdu, unsigned pdu_len) @@ -473,8 +480,15 @@ static char *ifname_default(void) * @param addr [out] The netmask addr, broadcast addr, ip addr. * @param request [in] addr broadaddr netmask */ -static int get_local_address( - const char *ifname, struct in_addr *addr, const char *request) +/** + * @brief Issue a specific request foor an interface via an ioctl() call. + * @param ifname - the interface name + * @param addr [out] the address in host order. + * @param request - the ioctl() request + * @return 0 on success, else the error from the ioctl() call. + */ +int bip_get_local_address_ioctl( + const char *ifname, struct in_addr *addr, uint32_t request) { char rv = '\0'; /* return value */ @@ -492,12 +506,18 @@ static int get_local_address( if (!ifaddrs_ptr->ifa_addr) { return rv; } - if (strcmp(request, "addr") == 0) { - addr_ptr = get_addr_ptr(ifaddrs_ptr->ifa_addr); - } else if (strcmp(request, "broadaddr") == 0) { - addr_ptr = get_addr_ptr(ifaddrs_ptr->ifa_broadaddr); - } else if (strcmp(request, "netmask") == 0) { - addr_ptr = get_addr_ptr(ifaddrs_ptr->ifa_netmask); + switch (request) { + case SIOCGIFADDR: + addr_ptr = get_addr_ptr(ifaddrs_ptr->ifa_addr); + break; + case SIOCGIFBRDADDR: + addr_ptr = get_addr_ptr(ifaddrs_ptr->ifa_broadaddr); + break; + case SIOCGIFNETMASK: + addr_ptr = get_addr_ptr(ifaddrs_ptr->ifa_netmask); + break; + default: + break; } if (addr_ptr) { memcpy(addr, addr_ptr, sizeof(struct in_addr)); @@ -509,18 +529,20 @@ static int get_local_address( return rv; } -/** Get the netmask of the BACnet/IP's interface via an getifaddrs() call. +/** + * @brief Get the netmask of the BACnet/IP's interface via an getifaddrs() call. * @param netmask [out] The netmask, in host order. * @return 0 on success, else the error from the getifaddrs() call. */ int bip_get_local_netmask(struct in_addr *netmask) { int rv; - char *ifname = getenv("BACNET_IFACE"); /* will probably be null */ - if (ifname == NULL) + char *ifname = getenv("BACNET_IFACE"); + + if (ifname == NULL) { ifname = "en0"; - printf("ifname %s", ifname); - rv = get_local_address(ifname, netmask, "netmask"); + } + rv = bip_get_local_address_ioctl(ifname, netmask, SIOCGIFNETMASK); return rv; } @@ -530,8 +552,7 @@ int bip_get_local_netmask(struct in_addr *netmask) * @param baddr The broadcast socket binding address, in host order. * @return 0 on success */ -int bip_set_broadcast_binding( - const char *ip4_broadcast) +int bip_set_broadcast_binding(const char *ip4_broadcast) { BIP_Broadcast_Binding_Address.s_addr = inet_addr(ip4_broadcast); BIP_Broadcast_Binding_Address_Override = true; @@ -552,8 +573,7 @@ void bip_set_interface(const char *ifname) int rv = 0; /* setup local address */ - char *request = "addr"; - rv = get_local_address(ifname, &local_address, request); + rv = bip_get_local_address_ioctl(ifname, &local_address, SIOCGIFADDR); if (rv < 0) { local_address.s_addr = 0; } @@ -587,18 +607,20 @@ void bip_set_interface(const char *ifname) } BIP_Broadcast_Addr.s_addr = htonl(broadcast_address); #else - request = "broadaddr"; - rv = get_local_address(ifname, &broadcast_address, request); + rv = bip_get_local_address_ioctl(ifname, &broadcast_address, SIOCGIFBRDADDR); if (rv < 0) { BIP_Broadcast_Addr.s_addr = ~0; } else { + BIP_Broadcast_Addr = local_address; BIP_Broadcast_Addr.s_addr = broadcast_address.s_addr; } #endif if (BIP_Debug) { - fprintf(stderr, "BIP: Broadcast Address: %s\n", + fprintf( + stderr, "BIP: Broadcast Address: %s\n", inet_ntoa(BIP_Broadcast_Addr)); - fprintf(stderr, "BIP: UDP Port: 0x%04X [%hu]\n", ntohs(BIP_Port), + fprintf( + stderr, "BIP: UDP Port: 0x%04X [%hu]\n", ntohs(BIP_Port), ntohs(BIP_Port)); fflush(stderr); } @@ -631,7 +653,15 @@ static int createSocket(const struct sockaddr_in *sin) close(sock_fd); return status; } - + /* Bind to the proper interface to send without default gateway */ + /* status = setsockopt( + sock_fd, SOL_SOCKET, SO_BINDTODEVICE, BIP_Interface_Name, + strlen(BIP_Interface_Name)); + if (status < 0) { + if (BIP_Debug) { + perror("SO_BINDTODEVICE: "); + } + } */ /* bind the socket to the local port number and IP address */ status = bind(sock_fd, (const struct sockaddr *)sin, sizeof(struct sockaddr)); @@ -666,13 +696,14 @@ bool bip_init(char *ifname) int sock_fd = -1; if (ifname) { - strncpy(BIP_Interface_Name, ifname, sizeof(BIP_Interface_Name)); + snprintf(BIP_Interface_Name, sizeof(BIP_Interface_Name), "%s", ifname); bip_set_interface(ifname); } else { bip_set_interface(ifname_default()); } if (BIP_Address.s_addr == 0) { - fprintf(stderr, "BIP: Failed to get an IP address from %s!\n", + fprintf( + stderr, "BIP: Failed to get an IP address from %s!\n", BIP_Interface_Name); fflush(stderr); return false; diff --git a/ports/bsd/dlmstp_port.c b/ports/bsd/dlmstp_port.c new file mode 100644 index 00000000..026d2c0c --- /dev/null +++ b/ports/bsd/dlmstp_port.c @@ -0,0 +1,991 @@ +/************************************************************************** + * + * Copyright (C) 2008 Steve Karg + * + * SPDX-License-Identifier: MIT + * + *********************************************************************/ +#include +#include +#include +#include +#include +#include +#include +/* BSD includes */ +#include +/* BACnet Stack defines - first */ +#include "bacnet/bacdef.h" +/* BACnet Stack API */ +#include "bacnet/bacdef.h" +#include "bacnet/bacaddr.h" +#include "bacnet/npdu.h" +#include "bacnet/datalink/mstp.h" +#include "bacnet/basic/sys/ringbuf.h" +#include "bacnet/basic/sys/debug.h" +/* port specific */ +#include "dlmstp_port.h" +#include "rs485.h" +/* OS Specific include */ +#include "bacport.h" + +/** @file bsd/dlmstp_port.c Provides BSD-specific DataLink functions for MS/TP. + */ + +#define BACNET_PDU_CONTROL_BYTE_OFFSET 1 +#define BACNET_DATA_EXPECTING_REPLY_BIT 2 +#define BACNET_DATA_EXPECTING_REPLY(control) \ + ((control & (1 << BACNET_DATA_EXPECTING_REPLY_BIT)) > 0) + +#define INCREMENT_AND_LIMIT_UINT16(x) \ + { \ + if (x < 0xFFFF) \ + x++; \ + } + +/** + * Calculate the time difference between two timespec values. + * + * @param l - The minued (time from which we subtract). + * @param r - The subtrahend (time that is being subtracted). + * + * @returns True if the difference is negative, otherwise 0. + */ +static int timespec_subtract( + struct timespec *result, const struct timespec *l, const struct timespec *r) +{ +#define NS_PER_S 1000000000 /* nano-seconds per second */ + struct timespec right = *r; + int secs; + + /* Perform the carry for the later subtraction by updating y. */ + if (l->tv_nsec < right.tv_nsec) { + secs = (right.tv_nsec - l->tv_nsec) / NS_PER_S + 1; + right.tv_nsec -= NS_PER_S * secs; + right.tv_sec += secs; + } + if (l->tv_nsec - right.tv_nsec > NS_PER_S) { + secs = (l->tv_nsec - right.tv_nsec) / NS_PER_S; + right.tv_nsec += NS_PER_S * secs; + right.tv_sec -= secs; + } + + /* Compute the time remaining. tv_nsec is certainly positive. */ + result->tv_sec = l->tv_sec - right.tv_sec; + result->tv_nsec = l->tv_nsec - right.tv_nsec; + + return l->tv_sec < right.tv_sec; +} + +/** + * Add a certain number of nanoseconds to the specified time. + * + * @param ts - The time to which to add to. + * @param ns - The number of nanoseconds to add. Allowed range + * is -NS_PER_S..NS_PER_S (i.e., plus minus one second). + */ +static void timespec_add_ns(struct timespec *ts, long ns) +{ + ts->tv_nsec += ns; + if (ts->tv_nsec > NS_PER_S) { + ts->tv_nsec -= NS_PER_S; + ts->tv_sec += 1; + } else if (ts->tv_nsec < 0) { + ts->tv_nsec += NS_PER_S; + ts->tv_sec -= 1; + } +} + +static uint32_t Timer_Silence(void *poPort) +{ + int32_t res; + struct timespec now, tmp_diff; + SHARED_MSTP_DATA *poSharedData; + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + if (!mstp_port) { + return -1; + } + poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; + if (!poSharedData) { + return -1; + } + + clock_gettime(CLOCK_MONOTONIC, &now); + timespec_subtract(&tmp_diff, &now, &poSharedData->start); + res = ((tmp_diff.tv_sec) * 1000 + (tmp_diff.tv_nsec) / 1000000); + + return (res >= 0 ? res : -res); +} + +static void Timer_Silence_Reset(void *poPort) +{ + SHARED_MSTP_DATA *poSharedData; + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + if (!mstp_port) { + return; + } + poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; + if (!poSharedData) { + return; + } + + clock_gettime(CLOCK_MONOTONIC, &poSharedData->start); +} + +static void get_abstime(struct timespec *abstime, unsigned long milliseconds) +{ + clock_gettime(CLOCK_MONOTONIC, abstime); + if (milliseconds > 1000) { + fprintf( + stderr, "DLMSTP: limited timeout of %lums to 1000ms\n", + milliseconds); + milliseconds = 1000; + } + timespec_add_ns(abstime, 1000000 * milliseconds); +} + +void dlmstp_cleanup(void *poPort) +{ + SHARED_MSTP_DATA *poSharedData; + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + if (!mstp_port) { + return; + } + poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; + if (!poSharedData) { + return; + } + + /* restore the old port settings */ + tcsetattr(poSharedData->RS485_Handle, TCSANOW, &poSharedData->RS485_oldtio); + close(poSharedData->RS485_Handle); + + pthread_cond_destroy(&poSharedData->Received_Frame_Flag); + pthread_cond_destroy(&poSharedData->Master_Done_Flag); + pthread_mutex_destroy(&poSharedData->Received_Frame_Mutex); + pthread_mutex_destroy(&poSharedData->Master_Done_Mutex); +} + +/* returns number of bytes sent on success, zero on failure */ +int dlmstp_send_pdu( + void *poPort, + BACNET_ADDRESS *dest, /* destination address */ + uint8_t *pdu, /* any data to be sent - may be null */ + unsigned pdu_len) +{ /* number of bytes of data */ + int bytes_sent = 0; + struct mstp_pdu_packet *pkt; + unsigned i = 0; + SHARED_MSTP_DATA *poSharedData; + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + if (!mstp_port) { + return 0; + } + poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; + if (!poSharedData) { + return 0; + } + + pkt = (struct mstp_pdu_packet *)Ringbuf_Data_Peek(&poSharedData->PDU_Queue); + if (pkt) { + pkt->data_expecting_reply = + BACNET_DATA_EXPECTING_REPLY(pdu[BACNET_PDU_CONTROL_BYTE_OFFSET]); + for (i = 0; i < pdu_len; i++) { + pkt->buffer[i] = pdu[i]; + } + pkt->length = pdu_len; + pkt->destination_mac = dest->mac[0]; + if (Ringbuf_Data_Put(&poSharedData->PDU_Queue, (uint8_t *)pkt)) { + bytes_sent = pdu_len; + } + } + + return bytes_sent; +} + +uint16_t dlmstp_receive( + void *poPort, + BACNET_ADDRESS *src, /* source address */ + uint8_t *pdu, /* PDU data */ + uint16_t max_pdu, /* amount of space available in the PDU */ + unsigned timeout) +{ /* milliseconds to wait for a packet */ + uint16_t pdu_len = 0; + struct timespec abstime; + int rv = 0; + SHARED_MSTP_DATA *poSharedData; + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + if (!mstp_port) { + return 0; + } + poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; + if (!poSharedData) { + return 0; + } + (void)max_pdu; + /* see if there is a packet available, and a place + to put the reply (if necessary) and process it */ + get_abstime(&abstime, timeout); + rv = dispatch_semaphore_wait(poSharedData->Receive_Packet_Flag, &abstime); + if (rv == 0) { + if (poSharedData->Receive_Packet.ready) { + if (poSharedData->Receive_Packet.pdu_len) { + poSharedData->MSTP_Packets++; + if (src) { + memmove( + src, &poSharedData->Receive_Packet.address, + sizeof(poSharedData->Receive_Packet.address)); + } + if (pdu) { + memmove( + pdu, &poSharedData->Receive_Packet.pdu, + sizeof(poSharedData->Receive_Packet.pdu)); + } + pdu_len = poSharedData->Receive_Packet.pdu_len; + } + poSharedData->Receive_Packet.ready = false; + } + } + + return pdu_len; +} + +static void *dlmstp_receive_fsm_task(void *pArg) +{ + bool received_frame; + SHARED_MSTP_DATA *poSharedData; + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)pArg; + if (!mstp_port) { + return NULL; + } + + poSharedData = + (SHARED_MSTP_DATA *)((struct mstp_port_struct_t *)pArg)->UserData; + if (!poSharedData) { + return NULL; + } + + for (;;) { + /* only do receive state machine while we don't have a frame */ + if ((mstp_port->ReceivedValidFrame == false) && + (mstp_port->ReceivedInvalidFrame == false)) { + do { + RS485_Check_UART_Data(mstp_port); + MSTP_Receive_Frame_FSM((struct mstp_port_struct_t *)pArg); + received_frame = mstp_port->ReceivedValidFrame || + mstp_port->ReceivedInvalidFrame; + if (received_frame) { + pthread_cond_signal(&poSharedData->Received_Frame_Flag); + break; + } + } while (mstp_port->DataAvailable); + } + } + + return NULL; +} + +static void *dlmstp_master_fsm_task(void *pArg) +{ + uint32_t silence = 0; + bool run_master = false; + SHARED_MSTP_DATA *poSharedData; + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)pArg; + if (!mstp_port) { + return NULL; + } + + poSharedData = + (SHARED_MSTP_DATA *)((struct mstp_port_struct_t *)pArg)->UserData; + if (!poSharedData) { + return NULL; + } + + for (;;) { + if (mstp_port->ReceivedValidFrame == false && + mstp_port->ReceivedInvalidFrame == false) { + RS485_Check_UART_Data(mstp_port); + MSTP_Receive_Frame_FSM(mstp_port); + } + if (mstp_port->ReceivedValidFrame || mstp_port->ReceivedInvalidFrame) { + run_master = true; + } else { + silence = mstp_port->SilenceTimer(NULL); + switch (mstp_port->master_state) { + case MSTP_MASTER_STATE_IDLE: + if (silence >= Tno_token) { + run_master = true; + } + break; + case MSTP_MASTER_STATE_WAIT_FOR_REPLY: + if (silence >= mstp_port->Treply_timeout) { + run_master = true; + } + break; + case MSTP_MASTER_STATE_POLL_FOR_MASTER: + if (silence >= mstp_port->Tusage_timeout) { + run_master = true; + } + break; + default: + run_master = true; + break; + } + } + if (run_master) { + if (mstp_port->This_Station <= DEFAULT_MAX_MASTER) { + while (MSTP_Master_Node_FSM(mstp_port)) { + /* do nothing while immediate transitioning */ + } + } else if (mstp_port->This_Station < 255) { + MSTP_Slave_Node_FSM(mstp_port); + } + } + } + + return NULL; +} + +void dlmstp_fill_bacnet_address(BACNET_ADDRESS *src, uint8_t mstp_address) +{ + int i = 0; + + if (mstp_address == MSTP_BROADCAST_ADDRESS) { + /* mac_len = 0 if broadcast address */ + src->mac_len = 0; + src->mac[0] = 0; + } else { + src->mac_len = 1; + src->mac[0] = mstp_address; + } + /* fill with 0's starting with index 1; index 0 filled above */ + for (i = 1; i < MAX_MAC_LEN; i++) { + src->mac[i] = 0; + } + src->net = 0; + src->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + src->adr[i] = 0; + } +} + +/* for the MS/TP state machine to use for putting received data */ +uint16_t MSTP_Put_Receive(struct mstp_port_struct_t *mstp_port) +{ + uint16_t pdu_len = 0; + SHARED_MSTP_DATA *poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; + + if (!poSharedData) { + return 0; + } + + if (!poSharedData->Receive_Packet.ready) { + /* bounds check - maybe this should send an abort? */ + pdu_len = mstp_port->DataLength; + if (pdu_len > sizeof(poSharedData->Receive_Packet.pdu)) { + pdu_len = sizeof(poSharedData->Receive_Packet.pdu); + } + memmove( + (void *)&poSharedData->Receive_Packet.pdu[0], + (void *)&mstp_port->InputBuffer[0], pdu_len); + dlmstp_fill_bacnet_address( + &poSharedData->Receive_Packet.address, mstp_port->SourceAddress); + poSharedData->Receive_Packet.pdu_len = mstp_port->DataLength; + poSharedData->Receive_Packet.ready = true; + dispatch_semaphore_signal(poSharedData->Receive_Packet_Flag); + } + + return pdu_len; +} + +/* for the MS/TP state machine to use for getting data to send */ +/* Return: amount of PDU data */ +uint16_t MSTP_Get_Send(struct mstp_port_struct_t *mstp_port, unsigned timeout) +{ /* milliseconds to wait for a packet */ + uint16_t pdu_len = 0; + uint8_t frame_type = 0; + struct mstp_pdu_packet *pkt; + SHARED_MSTP_DATA *poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; + + if (!poSharedData) { + return 0; + } + + (void)timeout; + if (Ringbuf_Empty(&poSharedData->PDU_Queue)) { + return 0; + } + pkt = (struct mstp_pdu_packet *)Ringbuf_Peek(&poSharedData->PDU_Queue); + if (pkt->data_expecting_reply) { + frame_type = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY; + } else { + frame_type = FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY; + } + /* convert the PDU into the MSTP Frame */ + pdu_len = MSTP_Create_Frame( + &mstp_port->OutputBuffer[0], /* <-- loading this */ + mstp_port->OutputBufferSize, frame_type, pkt->destination_mac, + mstp_port->This_Station, (uint8_t *)&pkt->buffer[0], pkt->length); + (void)Ringbuf_Pop(&poSharedData->PDU_Queue, NULL); + + return pdu_len; +} + +/** + * @brief Send an MSTP frame + * @param mstp_port - port specific data + * @param buffer - data to send + * @param nbytes - number of bytes of data to send + */ +void MSTP_Send_Frame( + struct mstp_port_struct_t *mstp_port, + const uint8_t *buffer, + uint16_t nbytes) +{ + RS485_Send_Frame(mstp_port, buffer, nbytes); +} + +static bool dlmstp_compare_data_expecting_reply( + const uint8_t *request_pdu, + uint16_t request_pdu_len, + uint8_t src_address, + const uint8_t *reply_pdu, + uint16_t reply_pdu_len, + uint8_t dest_address) +{ + uint16_t offset; + /* One way to check the message is to compare NPDU + src, dest, along with the APDU type, invoke id. + Seems a bit overkill */ + struct DER_compare_t { + BACNET_NPDU_DATA npdu_data; + BACNET_ADDRESS address; + uint8_t pdu_type; + uint8_t invoke_id; + uint8_t service_choice; + }; + struct DER_compare_t request; + struct DER_compare_t reply; + + /* unused parameters */ + (void)request_pdu_len; + (void)reply_pdu_len; + + /* decode the request data */ + request.address.mac[0] = src_address; + request.address.mac_len = 1; + offset = bacnet_npdu_decode( + request_pdu, request_pdu_len, NULL, &request.address, + &request.npdu_data); + if (request.npdu_data.network_layer_message) { + debug_printf("DLMSTP: DER Compare failed: " + "Request is Network message.\n"); + return false; + } + request.pdu_type = request_pdu[offset] & 0xF0; + if (request.pdu_type != PDU_TYPE_CONFIRMED_SERVICE_REQUEST) { + debug_printf("DLMSTP: DER Compare failed: " + "Not Confirmed Request.\n"); + return false; + } + request.invoke_id = request_pdu[offset + 2]; + /* segmented message? */ + if (request_pdu[offset] & BIT(3)) { + request.service_choice = request_pdu[offset + 5]; + } else { + request.service_choice = request_pdu[offset + 3]; + } + /* decode the reply data */ + reply.address.mac[0] = dest_address; + reply.address.mac_len = 1; + offset = bacnet_npdu_decode( + reply_pdu, reply_pdu_len, &reply.address, NULL, &reply.npdu_data); + if (reply.npdu_data.network_layer_message) { + debug_printf("DLMSTP: DER Compare failed: " + "Reply is Network message.\n"); + return false; + } + /* reply could be a lot of things: + confirmed, simple ack, abort, reject, error */ + reply.pdu_type = reply_pdu[offset] & 0xF0; + switch (reply.pdu_type) { + case PDU_TYPE_SIMPLE_ACK: + reply.invoke_id = reply_pdu[offset + 1]; + reply.service_choice = reply_pdu[offset + 2]; + break; + case PDU_TYPE_COMPLEX_ACK: + reply.invoke_id = reply_pdu[offset + 1]; + /* segmented message? */ + if (reply_pdu[offset] & BIT(3)) { + reply.service_choice = reply_pdu[offset + 4]; + } else { + reply.service_choice = reply_pdu[offset + 2]; + } + break; + case PDU_TYPE_ERROR: + reply.invoke_id = reply_pdu[offset + 1]; + reply.service_choice = reply_pdu[offset + 2]; + break; + case PDU_TYPE_REJECT: + case PDU_TYPE_ABORT: + reply.invoke_id = reply_pdu[offset + 1]; + break; + default: + return false; + } + /* these don't have service choice included */ + if ((reply.pdu_type == PDU_TYPE_REJECT) || + (reply.pdu_type == PDU_TYPE_ABORT)) { + if (request.invoke_id != reply.invoke_id) { + debug_printf("DLMSTP: DER Compare failed: " + "Invoke ID mismatch.\n"); + return false; + } + } else { + if (request.invoke_id != reply.invoke_id) { + debug_printf("DLMSTP: DER Compare failed: " + "Invoke ID mismatch.\n"); + return false; + } + if (request.service_choice != reply.service_choice) { + debug_printf("DLMSTP: DER Compare failed: " + "Service choice mismatch.\n"); + return false; + } + } + if (request.npdu_data.protocol_version != + reply.npdu_data.protocol_version) { + debug_printf("DLMSTP: DER Compare failed: " + "NPDU Protocol Version mismatch.\n"); + return false; + } +#if 0 + /* the NDPU priority doesn't get passed through the stack, and + all outgoing messages have NORMAL priority */ + if (request.npdu_data.priority != reply.npdu_data.priority) { + debug_printf( + "DLMSTP: DER Compare failed: " "NPDU Priority mismatch.\n"); + return false; + } +#endif + if (!bacnet_address_same(&request.address, &reply.address)) { + debug_printf("DLMSTP: DER Compare failed: " + "BACnet Address mismatch.\n"); + return false; + } + + return true; +} + +/* Get the reply to a DATA_EXPECTING_REPLY frame, or nothing */ +uint16_t MSTP_Get_Reply(struct mstp_port_struct_t *mstp_port, unsigned timeout) +{ /* milliseconds to wait for a packet */ + uint16_t pdu_len = 0; /* return value */ + bool matched = false; + uint8_t frame_type = 0; + struct mstp_pdu_packet *pkt; + SHARED_MSTP_DATA *poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; + + (void)timeout; + if (!poSharedData) { + return 0; + } + if (Ringbuf_Empty(&poSharedData->PDU_Queue)) { + return 0; + } + pkt = (struct mstp_pdu_packet *)Ringbuf_Peek(&poSharedData->PDU_Queue); + /* is this the reply to the DER? */ + matched = dlmstp_compare_data_expecting_reply( + &mstp_port->InputBuffer[0], mstp_port->DataLength, + mstp_port->SourceAddress, (uint8_t *)&pkt->buffer[0], pkt->length, + pkt->destination_mac); + if (!matched) { + /* Walk the rest of the ring buffer to see if we can find a match */ + while (!matched && + (pkt = (struct mstp_pdu_packet *)Ringbuf_Peek_Next( + &poSharedData->PDU_Queue, (uint8_t *)pkt)) != NULL) { + matched = dlmstp_compare_data_expecting_reply( + &mstp_port->InputBuffer[0], mstp_port->DataLength, + mstp_port->SourceAddress, (uint8_t *)&pkt->buffer[0], + pkt->length, pkt->destination_mac); + } + if (!matched) { + /* Still didn't find a match so just bail out */ + return 0; + } + } + if (pkt->data_expecting_reply) { + frame_type = FRAME_TYPE_BACNET_DATA_EXPECTING_REPLY; + } else { + frame_type = FRAME_TYPE_BACNET_DATA_NOT_EXPECTING_REPLY; + } + /* convert the PDU into the MSTP Frame */ + pdu_len = MSTP_Create_Frame( + &mstp_port->OutputBuffer[0], /* <-- loading this */ + mstp_port->OutputBufferSize, frame_type, pkt->destination_mac, + mstp_port->This_Station, (uint8_t *)&pkt->buffer[0], pkt->length); + /* This will pop the element no matter where we found it */ + (void)Ringbuf_Pop_Element(&poSharedData->PDU_Queue, (uint8_t *)pkt, NULL); + + return pdu_len; +} + +void dlmstp_set_mac_address(void *poPort, uint8_t mac_address) +{ + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + if (!mstp_port) { + return; + } + /* Master Nodes can only have address 0-127 */ + if (mac_address <= 127) { + mstp_port->This_Station = mac_address; + if (mac_address > mstp_port->Nmax_master) { + dlmstp_set_max_master(mstp_port, mac_address); + } + } + + return; +} + +uint8_t dlmstp_mac_address(void *poPort) +{ + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + if (!mstp_port) { + return 0; + } + + return mstp_port->This_Station; +} + +/* This parameter represents the value of the Max_Info_Frames property of */ +/* the node's Device object. The value of Max_Info_Frames specifies the */ +/* maximum number of information frames the node may send before it must */ +/* pass the token. Max_Info_Frames may have different values on different */ +/* nodes. This may be used to allocate more or less of the available link */ +/* bandwidth to particular nodes. If Max_Info_Frames is not writable in a */ +/* node, its value shall be 1. */ +void dlmstp_set_max_info_frames(void *poPort, uint8_t max_info_frames) +{ + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + + if (!mstp_port) { + return; + } + if (max_info_frames >= 1) { + mstp_port->Nmax_info_frames = max_info_frames; + } + + return; +} + +uint8_t dlmstp_max_info_frames(void *poPort) +{ + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + if (!mstp_port) { + return 0; + } + + return mstp_port->Nmax_info_frames; +} + +/* This parameter represents the value of the Max_Master property of the */ +/* node's Device object. The value of Max_Master specifies the highest */ +/* allowable address for master nodes. The value of Max_Master shall be */ +/* less than or equal to 127. If Max_Master is not writable in a node, */ +/* its value shall be 127. */ +void dlmstp_set_max_master(void *poPort, uint8_t max_master) +{ + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + if (!mstp_port) { + return; + } + if (max_master <= 127) { + if (mstp_port->This_Station <= max_master) { + mstp_port->Nmax_master = max_master; + } + } + + return; +} + +uint8_t dlmstp_max_master(void *poPort) +{ + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + + if (!mstp_port) { + return 0; + } + + return mstp_port->Nmax_master; +} + +/* RS485 Baud Rate 9600, 19200, 38400, 57600, 115200 */ +void dlmstp_set_baud_rate(void *poPort, uint32_t baud) +{ + SHARED_MSTP_DATA *poSharedData; + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + if (!mstp_port) { + return; + } + poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; + if (!poSharedData) { + return; + } + + switch (baud) { + case 9600: + poSharedData->RS485_Baud = B9600; + break; + case 19200: + poSharedData->RS485_Baud = B19200; + break; + case 38400: + poSharedData->RS485_Baud = B38400; + break; + case 57600: + poSharedData->RS485_Baud = B57600; + break; + case 115200: + poSharedData->RS485_Baud = B115200; + break; + default: + break; + } +} + +uint32_t dlmstp_baud_rate(void *poPort) +{ + SHARED_MSTP_DATA *poSharedData; + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + if (!mstp_port) { + return false; + } + poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; + if (!poSharedData) { + return false; + } + + switch (poSharedData->RS485_Baud) { + case B19200: + return 19200; + case B38400: + return 38400; + case B57600: + return 57600; + case B115200: + return 115200; + default: + case B9600: + return 9600; + } +} + +void dlmstp_get_my_address(void *poPort, BACNET_ADDRESS *my_address) +{ + int i = 0; /* counter */ + SHARED_MSTP_DATA *poSharedData; + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + if (!mstp_port) { + return; + } + poSharedData = (SHARED_MSTP_DATA *)mstp_port->UserData; + if (!poSharedData) { + return; + } + my_address->mac_len = 1; + my_address->mac[0] = mstp_port->This_Station; + my_address->net = 0; /* local only, no routing */ + my_address->len = 0; + for (i = 0; i < MAX_MAC_LEN; i++) { + my_address->adr[i] = 0; + } + + return; +} + +void dlmstp_get_broadcast_address(BACNET_ADDRESS *dest) +{ /* destination address */ + int i = 0; /* counter */ + + if (dest) { + dest->mac_len = 1; + dest->mac[0] = MSTP_BROADCAST_ADDRESS; + dest->net = BACNET_BROADCAST_NETWORK; + dest->len = 0; /* always zero when DNET is broadcast */ + for (i = 0; i < MAX_MAC_LEN; i++) { + dest->adr[i] = 0; + } + } + + return; +} + +bool dlmstp_init(void *poPort, char *ifname) +{ + pthread_t hThread = 0; + int rv = 0; + SHARED_MSTP_DATA *poSharedData; + struct termios newtio; + struct mstp_port_struct_t *mstp_port = (struct mstp_port_struct_t *)poPort; + dispatch_semaphore_t *sem = NULL; + int handshake; + unsigned long mics = 1UL; + if (!mstp_port) { + return false; + } + + poSharedData = + (SHARED_MSTP_DATA *)((struct mstp_port_struct_t *)mstp_port)->UserData; + if (!poSharedData) { + return false; + } + + poSharedData->RS485_Port_Name = ifname; + /* initialize PDU queue */ + Ringbuf_Init( + &poSharedData->PDU_Queue, (uint8_t *)&poSharedData->PDU_Buffer, + sizeof(struct mstp_pdu_packet), MSTP_PDU_PACKET_COUNT); + /* initialize packet queue */ + poSharedData->Receive_Packet.ready = false; + poSharedData->Receive_Packet.pdu_len = 0; + sem = &poSharedData->Receive_Packet_Flag; + *sem = dispatch_semaphore_create(0); + + printf("RS485 Port: Initializing %s\n", poSharedData->RS485_Port_Name); + /* + Open device for reading and writing. + Blocking mode - more CPU effecient + */ + poSharedData->RS485_Handle = open( + poSharedData->RS485_Port_Name, + O_RDWR | O_NOCTTY | O_NONBLOCK /*| O_NDELAY */); + if (poSharedData->RS485_Handle < 0) { + perror(poSharedData->RS485_Port_Name); + exit(-1); + } + if (ioctl(poSharedData->RS485_Handle, TIOCEXCL) == -1) { + printf("Error setting TIOCEXCL on %s - %s(%d).\n", poSharedData->RS485_Port_Name, + strerror(errno), errno); + exit(-1); + } +#if 0 + /* non blocking for the read */ + fcntl(poSharedData->RS485_Handle, F_SETFL, FNDELAY); +#else + /* efficient blocking for the read */ + fcntl(poSharedData->RS485_Handle, F_SETFL, 0); +#endif + /* save current serial port settings */ + tcgetattr(poSharedData->RS485_Handle, &poSharedData->RS485_oldtio); + /* clear struct for new port settings */ + bzero(&newtio, sizeof(newtio)); + /* + BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed. + CRTSCTS : output hardware flow control (only used if the cable has + all necessary lines. See sect. 7 of Serial-HOWTO) + CLOCAL : local connection, no modem contol + CREAD : enable receiving characters + */ + printf( + "Default/current input baud rate is %d\n", (int)cfgetispeed(&poSharedData->RS485_oldtio)); + printf( + "Default/current output baud rate is %d\n", (int)cfgetospeed(&poSharedData->RS485_oldtio)); + newtio.c_cc[VMIN] = 0; + newtio.c_cc[VTIME] = 10; + //newtio.c_cflag = + // poSharedData->RS485_Baud | poSharedData->RS485MOD | CLOCAL | CREAD; + cfsetspeed(&newtio, poSharedData->RS485_Baud); + newtio.c_cflag &= ~PARENB; /* No Parity */ + newtio.c_cflag &= ~CSTOPB; /* 1 Stop Bit */ + newtio.c_cflag &= ~CSIZE; + newtio.c_cflag |= CS8; /* Use 8 bit words */ + /* Raw input */ + newtio.c_iflag = 0; + /* Raw output */ + newtio.c_oflag = 0; + /* no processing */ + newtio.c_lflag = 0; + if (ioctl(poSharedData->RS485_Handle, IOSSIOSPEED, &poSharedData->RS485_Baud) == -1) { + printf("Error calling ioctl(..., IOSSIOSPEED, ...) %s - %s(%d).\n", + poSharedData->RS485_Port_Name, strerror(errno), errno); + } + printf("Input baud rate changed to %d\n", (int)cfgetispeed(&newtio)); + printf("Output baud rate changed to %d\n", (int)cfgetospeed(&newtio)); + + /* activate the settings for the port after flushing I/O */ + tcsetattr(poSharedData->RS485_Handle, TCSANOW, &newtio); + + /* To set the modem handshake lines, use the following ioctls. + See tty(4) and ioctl(2) for + details.*/ + + /* Assert Data Terminal Ready (DTR) */ + if (ioctl(poSharedData->RS485_Handle, TIOCSDTR) == -1) { + printf("Error asserting DTR %s - %s(%d).\n", poSharedData->RS485_Port_Name, strerror(errno), + errno); + } + + /* Clear Data Terminal Ready (DTR) */ + if (ioctl(poSharedData->RS485_Handle, TIOCCDTR) == -1) { + printf("Error clearing DTR %s - %s(%d).\n", poSharedData->RS485_Port_Name, strerror(errno), + errno); + } + + /* Set the modem lines depending on the bits set in handshake */ + handshake = TIOCM_DTR | TIOCM_RTS | TIOCM_CTS | TIOCM_DSR; + if (ioctl(poSharedData->RS485_Handle, TIOCMSET, &handshake) == -1) { + printf("Error setting handshake lines %s - %s(%d).\n", poSharedData->RS485_Port_Name, + strerror(errno), errno); + } + + /* To read the state of the modem lines, use the following ioctl. + See tty(4) and ioctl(2) for + details. */ + + /* Store the state of the modem lines in handshake */ + if (ioctl(poSharedData->RS485_Handle, TIOCMGET, &handshake) == -1) { + printf("Error getting handshake lines %s - %s(%d).\n", poSharedData->RS485_Port_Name, + strerror(errno), errno); + } + + printf("Handshake lines currently set to %d\n", handshake); + + if (ioctl(poSharedData->RS485_Handle, IOSSDATALAT, &mics) == -1) { + /* set latency to 1 microsecond */ + printf("Error setting read latency %s - %s(%d).\n", poSharedData->RS485_Port_Name, + strerror(errno), errno); + exit(-1); + } + + /* flush any data waiting */ + usleep(200000); + tcflush(poSharedData->RS485_Handle, TCIOFLUSH); + /* ringbuffer */ + FIFO_Init( + &poSharedData->Rx_FIFO, poSharedData->Rx_Buffer, + sizeof(poSharedData->Rx_Buffer)); + printf("success!\n"); + mstp_port->InputBuffer = &poSharedData->RxBuffer[0]; + mstp_port->InputBufferSize = sizeof(poSharedData->RxBuffer); + mstp_port->OutputBuffer = &poSharedData->TxBuffer[0]; + mstp_port->OutputBufferSize = sizeof(poSharedData->TxBuffer); + clock_gettime(CLOCK_MONOTONIC, &poSharedData->start); + mstp_port->SilenceTimer = Timer_Silence; + mstp_port->SilenceTimerReset = Timer_Silence_Reset; + MSTP_Init(mstp_port); + debug_fprintf(stderr, "MS/TP MAC: %02X\n", mstp_port->This_Station); + debug_fprintf(stderr, "MS/TP Max_Master: %02X\n", mstp_port->Nmax_master); + debug_fprintf( + stderr, "MS/TP Max_Info_Frames: %u\n", mstp_port->Nmax_info_frames); + rv = pthread_create(&hThread, NULL, dlmstp_master_fsm_task, mstp_port); + if (rv != 0) { + fprintf(stderr, "Failed to start Master Node FSM task\n"); + } + + /* You can try also this for thread. This here so we ignore + * -Wunused-function compiler warning + */ + dlmstp_receive_fsm_task(NULL); + + return true; +} diff --git a/ports/bsd/dlmstp_bsd.h b/ports/bsd/dlmstp_port.h similarity index 97% rename from ports/bsd/dlmstp_bsd.h rename to ports/bsd/dlmstp_port.h index 1b62eb9b..6f84c869 100644 --- a/ports/bsd/dlmstp_bsd.h +++ b/ports/bsd/dlmstp_port.h @@ -1,5 +1,5 @@ /** - * @file port/bsd/dlmstp_bsd.h + * @file port/bsd/dlmstp_port.h * @brief Data structure definitions needed for the MS/TP Datalink Layer. * Function declarations needed for the MS/TP Datalink Layer. * @author Steve Karg @@ -13,7 +13,7 @@ #include "bacnet/datalink/mstp.h" #include -#include +#include #include #include @@ -59,7 +59,7 @@ typedef struct shared_mstp_data { /* RT_SEM Receive_Packet_Flag; */ - sem_t Receive_Packet_Flag; + dispatch_semaphore_t Receive_Packet_Flag; /* mechanism to wait for a frame in state machine */ /* RT_COND Received_Frame_Flag; @@ -91,8 +91,7 @@ typedef struct shared_mstp_data { FIFO_BUFFER Rx_FIFO; /* buffer size needs to be a power of 2 */ uint8_t Rx_Buffer[4096]; - struct timeval start; - + struct timespec start; RING_BUFFER PDU_Queue; struct mstp_pdu_packet PDU_Buffer[MSTP_PDU_PACKET_COUNT]; diff --git a/ports/bsd/rs485.c b/ports/bsd/rs485.c index 839f56a0..9663712e 100644 --- a/ports/bsd/rs485.c +++ b/ports/bsd/rs485.c @@ -40,7 +40,7 @@ #include #include -#include "dlmstp_bsd.h" +#include "dlmstp_port.h" #if defined(__APPLE__) || defined(__darwin__) #include @@ -118,7 +118,8 @@ static void closeSerialPort(int fileDescriptor); void RS485_Set_Interface(char *ifname) { /* note: expects a constant char, or char from the heap */ - if (ifname) { + if (ifname && ifname != NULL) { + printf("### RS485_Set_Interface %s\n", ifname); RS485_Port_Name = ifname; } } diff --git a/ports/linux/bacport.h b/ports/linux/bacport.h index ea71bed0..2c3436e0 100644 --- a/ports/linux/bacport.h +++ b/ports/linux/bacport.h @@ -98,6 +98,6 @@ extern int bip_get_local_netmask(struct in_addr *netmask); BACNET_STACK_EXPORT extern int bip_get_local_address_ioctl( - const char *ifname, struct in_addr *addr, int request); + const char *ifname, struct in_addr *addr, uint32_t request); #endif diff --git a/ports/linux/bip-init.c b/ports/linux/bip-init.c index e55c8a7a..6208bf31 100644 --- a/ports/linux/bip-init.c +++ b/ports/linux/bip-init.c @@ -499,7 +499,7 @@ get_local_ifr_ioctl(const char *ifname, struct ifreq *ifr, int request) * @return 0 on success, else the error from the ioctl() call. */ int bip_get_local_address_ioctl( - const char *ifname, struct in_addr *addr, int request) + const char *ifname, struct in_addr *addr, uint32_t request) { struct ifreq ifr = { 0 }; struct sockaddr_in *tcpip_address; diff --git a/ports/linux/dlmstp_linux.c b/ports/linux/dlmstp_port.c similarity index 99% rename from ports/linux/dlmstp_linux.c rename to ports/linux/dlmstp_port.c index 0ea87ad2..0693895b 100644 --- a/ports/linux/dlmstp_linux.c +++ b/ports/linux/dlmstp_port.c @@ -22,12 +22,13 @@ #include "bacnet/basic/sys/ringbuf.h" #include "bacnet/basic/sys/debug.h" /* port specific */ -#include "dlmstp_linux.h" +#include "dlmstp_port.h" #include "rs485.h" /* OS Specific include */ #include "bacport.h" -/** @file linux/dlmstp.c Provides Linux-specific DataLink functions for MS/TP. +/** @file linux/dlmstp_port.c Provides Linux-specific DataLink functions for + * MS/TP. */ #define BACNET_PDU_CONTROL_BYTE_OFFSET 1 diff --git a/ports/linux/dlmstp_linux.h b/ports/linux/dlmstp_port.h similarity index 100% rename from ports/linux/dlmstp_linux.h rename to ports/linux/dlmstp_port.h diff --git a/ports/linux/rs485.c b/ports/linux/rs485.c index acd04b41..ce16d9f4 100644 --- a/ports/linux/rs485.c +++ b/ports/linux/rs485.c @@ -42,7 +42,7 @@ #include #include -#include "dlmstp_linux.h" +#include "dlmstp_port.h" /* Posix serial programming reference: http://www.easysw.com/~mike/serial/serial.html */