623 lines
21 KiB
C
623 lines
21 KiB
C
/*
|
|
* Helper functions for Linux epoll interface to CANopenNode.
|
|
*
|
|
* @file CO_epoll_interface.c
|
|
* @author Janez Paternoster
|
|
* @author Martin Wagner
|
|
* @copyright 2004 - 2015 Janez Paternoster
|
|
* @copyright 2018 - 2020 Neuberger Gebaeudeautomation GmbH
|
|
*
|
|
*
|
|
* This file is part of <https://github.com/CANopenNode/CANopenNode>, a CANopen Stack.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
|
|
* file except in compliance with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software distributed under the License is
|
|
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and limitations under the License.
|
|
*/
|
|
|
|
/* following macro is necessary for accept4() function call (sockets) */
|
|
#define _GNU_SOURCE
|
|
|
|
#include "CO_epoll_interface.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <syslog.h>
|
|
#include <time.h>
|
|
#include <fcntl.h>
|
|
|
|
#if (CO_CONFIG_GTW) & CO_CONFIG_GTW_ASCII
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
#include <netinet/in.h>
|
|
#include <signal.h>
|
|
|
|
#ifndef LISTEN_BACKLOG
|
|
#define LISTEN_BACKLOG 50
|
|
#endif
|
|
#endif /* (CO_CONFIG_GTW) & CO_CONFIG_GTW_ASCII */
|
|
|
|
/* delay for recall CANsend(), if CAN TX buffer is full */
|
|
#ifndef CANSEND_DELAY_US
|
|
#define CANSEND_DELAY_US 100
|
|
#endif
|
|
|
|
/* EPOLL **********************************************************************/
|
|
/* Helper function - get monotonic clock time in microseconds */
|
|
static inline uint64_t
|
|
clock_gettime_us(void) {
|
|
struct timespec ts;
|
|
|
|
(void)clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return ts.tv_sec * 1000000 + ts.tv_nsec / 1000;
|
|
}
|
|
|
|
CO_ReturnError_t
|
|
CO_epoll_create(CO_epoll_t* ep, uint32_t timerInterval_us) {
|
|
int ret;
|
|
struct epoll_event ev = {0};
|
|
|
|
if (ep == NULL) {
|
|
return CO_ERROR_ILLEGAL_ARGUMENT;
|
|
}
|
|
|
|
/* Configure epoll for mainline */
|
|
ep->epoll_new = false;
|
|
ep->epoll_fd = epoll_create(1);
|
|
if (ep->epoll_fd < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "epoll_create()");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
/* Configure eventfd for notifications and add it to epoll */
|
|
ep->event_fd = eventfd(0, EFD_NONBLOCK);
|
|
if (ep->event_fd < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "eventfd()");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
ev.events = EPOLLIN;
|
|
ev.data.fd = ep->event_fd;
|
|
ret = epoll_ctl(ep->epoll_fd, EPOLL_CTL_ADD, ev.data.fd, &ev);
|
|
if (ret < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "epoll_ctl(event_fd)");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
/* Configure timer for timerInterval_us and add it to epoll */
|
|
ep->timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
|
|
if (ep->timer_fd < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "timerfd_create()");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
ep->tm.it_interval.tv_sec = timerInterval_us / 1000000;
|
|
ep->tm.it_interval.tv_nsec = (timerInterval_us % 1000000) * 1000;
|
|
ep->tm.it_value.tv_sec = 0;
|
|
ep->tm.it_value.tv_nsec = 1;
|
|
ret = timerfd_settime(ep->timer_fd, 0, &ep->tm, NULL);
|
|
if (ret < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "timerfd_settime");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
ev.events = EPOLLIN;
|
|
ev.data.fd = ep->timer_fd;
|
|
ret = epoll_ctl(ep->epoll_fd, EPOLL_CTL_ADD, ev.data.fd, &ev);
|
|
if (ret < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "epoll_ctl(timer_fd)");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
ep->timerInterval_us = timerInterval_us;
|
|
ep->previousTime_us = clock_gettime_us();
|
|
ep->timeDifference_us = 0;
|
|
|
|
return CO_ERROR_NO;
|
|
}
|
|
|
|
void
|
|
CO_epoll_close(CO_epoll_t* ep) {
|
|
if (ep == NULL) {
|
|
return;
|
|
}
|
|
|
|
close(ep->epoll_fd);
|
|
ep->epoll_fd = -1;
|
|
|
|
close(ep->event_fd);
|
|
ep->event_fd = -1;
|
|
|
|
close(ep->timer_fd);
|
|
ep->timer_fd = -1;
|
|
}
|
|
|
|
void
|
|
CO_epoll_wait(CO_epoll_t* ep) {
|
|
if (ep == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* wait for an event */
|
|
int ready = epoll_wait(ep->epoll_fd, &ep->ev, 1, -1);
|
|
ep->epoll_new = true;
|
|
ep->timerEvent = false;
|
|
|
|
/* calculate time difference since last call */
|
|
uint64_t now = clock_gettime_us();
|
|
ep->timeDifference_us = (uint32_t)(now - ep->previousTime_us);
|
|
ep->previousTime_us = now;
|
|
/* application may will lower this */
|
|
ep->timerNext_us = ep->timerInterval_us;
|
|
|
|
/* process event */
|
|
if (ready != 1 && errno == EINTR) {
|
|
/* event from interrupt or signal, nothing to process, continue */
|
|
ep->epoll_new = false;
|
|
} else if (ready != 1) {
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "epoll_wait");
|
|
ep->epoll_new = false;
|
|
} else if ((ep->ev.events & EPOLLIN) != 0 && ep->ev.data.fd == ep->event_fd) {
|
|
uint64_t val;
|
|
ssize_t s = read(ep->event_fd, &val, sizeof(uint64_t));
|
|
if (s != sizeof(uint64_t)) {
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "read(event_fd)");
|
|
}
|
|
ep->epoll_new = false;
|
|
} else if ((ep->ev.events & EPOLLIN) != 0 && ep->ev.data.fd == ep->timer_fd) {
|
|
uint64_t val;
|
|
ssize_t s = read(ep->timer_fd, &val, sizeof(uint64_t));
|
|
if (s != sizeof(uint64_t) && errno != EAGAIN) {
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "read(timer_fd)");
|
|
}
|
|
ep->epoll_new = false;
|
|
ep->timerEvent = true;
|
|
}
|
|
}
|
|
|
|
void
|
|
CO_epoll_processLast(CO_epoll_t* ep) {
|
|
if (ep == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (ep->epoll_new) {
|
|
log_printf(LOG_DEBUG, DBG_EPOLL_UNKNOWN, ep->ev.events, ep->ev.data.fd);
|
|
ep->epoll_new = false;
|
|
}
|
|
|
|
/* lower next timer interval if changed by application */
|
|
if (ep->timerNext_us < ep->timerInterval_us) {
|
|
/* add one microsecond extra delay and make sure it is not zero */
|
|
ep->timerNext_us += 1;
|
|
if (ep->timerInterval_us < 1000000) {
|
|
ep->tm.it_value.tv_nsec = ep->timerNext_us * 1000;
|
|
} else {
|
|
ep->tm.it_value.tv_sec = ep->timerNext_us / 1000000;
|
|
ep->tm.it_value.tv_nsec = (ep->timerNext_us % 1000000) * 1000;
|
|
}
|
|
int ret = timerfd_settime(ep->timer_fd, 0, &ep->tm, NULL);
|
|
if (ret < 0) {
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "timerfd_settime");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* MAINLINE *******************************************************************/
|
|
#ifndef CO_SINGLE_THREAD
|
|
/* Send event to wake CO_epoll_processMain() */
|
|
static void
|
|
wakeupCallback(void* object) {
|
|
CO_epoll_t* ep = (CO_epoll_t*)object;
|
|
uint64_t u = 1;
|
|
ssize_t s;
|
|
s = write(ep->event_fd, &u, sizeof(uint64_t));
|
|
if (s != sizeof(uint64_t)) {
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "write()");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void
|
|
CO_epoll_initCANopenMain(CO_epoll_t* ep, CO_t* co) {
|
|
if (ep == NULL || co == NULL) {
|
|
return;
|
|
}
|
|
|
|
#ifndef CO_SINGLE_THREAD
|
|
|
|
/* Configure LSS slave callback function */
|
|
#if (CO_CONFIG_LSS) & CO_CONFIG_FLAG_CALLBACK_PRE
|
|
#if (CO_CONFIG_LSS) & CO_CONFIG_LSS_SLAVE
|
|
CO_LSSslave_initCallbackPre(co->LSSslave, (void*)ep, wakeupCallback);
|
|
#endif
|
|
#endif
|
|
|
|
if (co->nodeIdUnconfigured) {
|
|
return;
|
|
}
|
|
|
|
/* Configure callback functions */
|
|
#if (CO_CONFIG_NMT) & CO_CONFIG_FLAG_CALLBACK_PRE
|
|
CO_NMT_initCallbackPre(co->NMT, (void*)ep, wakeupCallback);
|
|
#endif
|
|
#if (CO_CONFIG_HB_CONS) & CO_CONFIG_FLAG_CALLBACK_PRE
|
|
CO_HBconsumer_initCallbackPre(co->HBcons, (void*)ep, wakeupCallback);
|
|
#endif
|
|
#if (CO_CONFIG_EM) & CO_CONFIG_FLAG_CALLBACK_PRE
|
|
CO_EM_initCallbackPre(co->em, (void*)ep, wakeupCallback);
|
|
#endif
|
|
#if (CO_CONFIG_SDO_SRV) & CO_CONFIG_FLAG_CALLBACK_PRE
|
|
CO_SDOserver_initCallbackPre(&co->SDOserver[0], (void*)ep, wakeupCallback);
|
|
#endif
|
|
#if (CO_CONFIG_SDO_CLI) & CO_CONFIG_FLAG_CALLBACK_PRE
|
|
CO_SDOclient_initCallbackPre(&co->SDOclient[0], (void*)ep, wakeupCallback);
|
|
#endif
|
|
#if (CO_CONFIG_TIME) & CO_CONFIG_FLAG_CALLBACK_PRE
|
|
CO_TIME_initCallbackPre(co->TIME, (void*)ep, wakeupCallback);
|
|
#endif
|
|
#if (CO_CONFIG_LSS) & CO_CONFIG_FLAG_CALLBACK_PRE
|
|
#if (CO_CONFIG_LSS) & CO_CONFIG_LSS_MASTER
|
|
CO_LSSmaster_initCallbackPre(co->LSSmaster, (void*)ep, wakeupCallback);
|
|
#endif
|
|
#endif
|
|
|
|
#endif /* CO_SINGLE_THREAD */
|
|
}
|
|
|
|
void
|
|
CO_epoll_processMain(CO_epoll_t* ep, CO_t* co, bool_t enableGateway, CO_NMT_reset_cmd_t* reset) {
|
|
if (ep == NULL || co == NULL || reset == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* process CANopen objects */
|
|
*reset = CO_process(co, enableGateway, ep->timeDifference_us, &ep->timerNext_us);
|
|
|
|
/* If there are unsent CAN messages, call CO_CANmodule_process() earlier */
|
|
if (co->CANmodule->CANtxCount > 0 && ep->timerNext_us > CANSEND_DELAY_US) {
|
|
ep->timerNext_us = CANSEND_DELAY_US;
|
|
}
|
|
}
|
|
|
|
/* CANrx and REALTIME *********************************************************/
|
|
void
|
|
CO_epoll_processRT(CO_epoll_t* ep, CO_t* co, bool_t realtime) {
|
|
if (co == NULL || ep == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Verify for epoll events */
|
|
if (ep->epoll_new) {
|
|
if (CO_CANrxFromEpoll(co->CANmodule, &ep->ev, NULL, NULL)) {
|
|
ep->epoll_new = false;
|
|
}
|
|
}
|
|
|
|
if (!realtime || ep->timerEvent) {
|
|
uint32_t* pTimerNext_us = realtime ? NULL : &ep->timerNext_us;
|
|
|
|
CO_LOCK_OD(co->CANmodule);
|
|
if (!co->nodeIdUnconfigured && co->CANmodule->CANnormal) {
|
|
bool_t syncWas = false;
|
|
|
|
#if (CO_CONFIG_SYNC) & CO_CONFIG_SYNC_ENABLE
|
|
syncWas = CO_process_SYNC(co, ep->timeDifference_us, pTimerNext_us);
|
|
#endif
|
|
#if (CO_CONFIG_PDO) & CO_CONFIG_RPDO_ENABLE
|
|
CO_process_RPDO(co, syncWas, ep->timeDifference_us, pTimerNext_us);
|
|
#endif
|
|
#if (CO_CONFIG_PDO) & CO_CONFIG_TPDO_ENABLE
|
|
CO_process_TPDO(co, syncWas, ep->timeDifference_us, pTimerNext_us);
|
|
#endif
|
|
(void)syncWas;
|
|
(void)pTimerNext_us;
|
|
}
|
|
CO_UNLOCK_OD(co->CANmodule);
|
|
}
|
|
}
|
|
|
|
/* GATEWAY ********************************************************************/
|
|
#if (CO_CONFIG_GTW) & CO_CONFIG_GTW_ASCII
|
|
/* write response string from gateway-ascii object */
|
|
static size_t
|
|
gtwa_write_response(void* object, const char* buf, size_t count, uint8_t* connectionOK) {
|
|
int* fd = (int*)object;
|
|
/* nWritten = count -> in case of error (non-existing fd) data are purged */
|
|
size_t nWritten = count;
|
|
|
|
if (fd != NULL && *fd >= 0) {
|
|
ssize_t n = write(*fd, (const void*)buf, count);
|
|
if (n >= 0) {
|
|
nWritten = (size_t)n;
|
|
} else {
|
|
/* probably EAGAIN - "Resource temporarily unavailable". Retry. */
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "write(gtwa_response)");
|
|
nWritten = 0;
|
|
}
|
|
} else {
|
|
*connectionOK = 0;
|
|
}
|
|
return nWritten;
|
|
}
|
|
|
|
static inline void
|
|
socketAcceptEnableForEpoll(CO_epoll_gtw_t* epGtw) {
|
|
struct epoll_event ev = {0};
|
|
int ret;
|
|
|
|
ev.events = EPOLLIN | EPOLLONESHOT;
|
|
ev.data.fd = epGtw->gtwa_fdSocket;
|
|
ret = epoll_ctl(epGtw->epoll_fd, EPOLL_CTL_MOD, ev.data.fd, &ev);
|
|
if (ret < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "epoll_ctl(gtwa_fdSocket)");
|
|
}
|
|
}
|
|
|
|
CO_ReturnError_t
|
|
CO_epoll_createGtw(CO_epoll_gtw_t* epGtw, int epoll_fd, int32_t commandInterface, uint32_t socketTimeout_ms,
|
|
char* localSocketPath) {
|
|
int ret;
|
|
struct epoll_event ev = {0};
|
|
|
|
if (epGtw == NULL || epoll_fd < 0) {
|
|
return CO_ERROR_ILLEGAL_ARGUMENT;
|
|
}
|
|
|
|
epGtw->epoll_fd = epoll_fd;
|
|
epGtw->commandInterface = commandInterface;
|
|
|
|
epGtw->socketTimeout_us = (socketTimeout_ms < (UINT_MAX / 1000 - 1000000)) ? socketTimeout_ms * 1000
|
|
: (UINT_MAX - 1000000);
|
|
epGtw->gtwa_fdSocket = -1;
|
|
epGtw->gtwa_fd = -1;
|
|
|
|
if (commandInterface == CO_COMMAND_IF_STDIO) {
|
|
epGtw->gtwa_fd = STDIN_FILENO;
|
|
log_printf(LOG_INFO, DBG_COMMAND_STDIO_INFO);
|
|
} else if (commandInterface == CO_COMMAND_IF_LOCAL_SOCKET) {
|
|
struct sockaddr_un addr;
|
|
epGtw->localSocketPath = localSocketPath;
|
|
|
|
/* Create, bind and listen local socket */
|
|
epGtw->gtwa_fdSocket = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
|
if (epGtw->gtwa_fdSocket < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "socket(local)");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
memset(&addr, 0, sizeof(struct sockaddr_un));
|
|
addr.sun_family = AF_UNIX;
|
|
strncpy(addr.sun_path, localSocketPath, sizeof(addr.sun_path) - 1);
|
|
ret = bind(epGtw->gtwa_fdSocket, (struct sockaddr*)&addr, sizeof(struct sockaddr_un));
|
|
if (ret < 0) {
|
|
log_printf(LOG_CRIT, DBG_COMMAND_LOCAL_BIND, localSocketPath);
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
ret = listen(epGtw->gtwa_fdSocket, LISTEN_BACKLOG);
|
|
if (ret < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "listen(local)");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
/* Ignore the SIGPIPE signal, which may happen, if remote client broke
|
|
* the connection. Signal may be triggered by write call inside
|
|
* gtwa_write_response() function */
|
|
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "signal");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
log_printf(LOG_INFO, DBG_COMMAND_LOCAL_INFO, localSocketPath);
|
|
} else if (commandInterface >= CO_COMMAND_IF_TCP_SOCKET_MIN && commandInterface <= CO_COMMAND_IF_TCP_SOCKET_MAX) {
|
|
struct sockaddr_in addr;
|
|
const int yes = 1;
|
|
|
|
/* Create, bind and listen socket */
|
|
epGtw->gtwa_fdSocket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
|
|
if (epGtw->gtwa_fdSocket < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "socket(tcp)");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
setsockopt(epGtw->gtwa_fdSocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
|
|
|
|
memset(&addr, 0, sizeof(struct sockaddr_in));
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(commandInterface);
|
|
addr.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
ret = bind(epGtw->gtwa_fdSocket, (struct sockaddr*)&addr, sizeof(struct sockaddr_in));
|
|
if (ret < 0) {
|
|
log_printf(LOG_CRIT, DBG_COMMAND_TCP_BIND, commandInterface);
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
ret = listen(epGtw->gtwa_fdSocket, LISTEN_BACKLOG);
|
|
if (ret < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "listen(tcp)");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
/* Ignore the SIGPIPE signal, which may happen, if remote client broke
|
|
* the connection. Signal may be triggered by write call inside
|
|
* gtwa_write_response() function */
|
|
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "signal");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
|
|
log_printf(LOG_INFO, DBG_COMMAND_TCP_INFO, commandInterface);
|
|
} else {
|
|
epGtw->commandInterface = CO_COMMAND_IF_DISABLED;
|
|
}
|
|
|
|
if (epGtw->gtwa_fd >= 0) {
|
|
ev.events = EPOLLIN;
|
|
ev.data.fd = epGtw->gtwa_fd;
|
|
ret = epoll_ctl(epGtw->epoll_fd, EPOLL_CTL_ADD, ev.data.fd, &ev);
|
|
if (ret < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "epoll_ctl(gtwa_fd)");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
}
|
|
if (epGtw->gtwa_fdSocket >= 0) {
|
|
/* prepare epoll for listening for new socket connection. After
|
|
* connection will be accepted, fd for io operation will be defined. */
|
|
ev.events = EPOLLIN | EPOLLONESHOT;
|
|
ev.data.fd = epGtw->gtwa_fdSocket;
|
|
ret = epoll_ctl(epGtw->epoll_fd, EPOLL_CTL_ADD, ev.data.fd, &ev);
|
|
if (ret < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "epoll_ctl(gtwa_fdSocket)");
|
|
return CO_ERROR_SYSCALL;
|
|
}
|
|
}
|
|
|
|
return CO_ERROR_NO;
|
|
}
|
|
|
|
void
|
|
CO_epoll_closeGtw(CO_epoll_gtw_t* epGtw) {
|
|
if (epGtw == NULL) {
|
|
return;
|
|
}
|
|
|
|
if (epGtw->commandInterface == CO_COMMAND_IF_LOCAL_SOCKET) {
|
|
if (epGtw->gtwa_fd > 0) {
|
|
close(epGtw->gtwa_fd);
|
|
}
|
|
close(epGtw->gtwa_fdSocket);
|
|
/* Remove local socket file from filesystem. */
|
|
if (remove(epGtw->localSocketPath) < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "remove(local)");
|
|
}
|
|
} else if (epGtw->commandInterface >= CO_COMMAND_IF_TCP_SOCKET_MIN) {
|
|
if (epGtw->gtwa_fd > 0) {
|
|
close(epGtw->gtwa_fd);
|
|
}
|
|
close(epGtw->gtwa_fdSocket);
|
|
}
|
|
epGtw->gtwa_fd = -1;
|
|
epGtw->gtwa_fdSocket = -1;
|
|
}
|
|
|
|
void
|
|
CO_epoll_initCANopenGtw(CO_epoll_gtw_t* epGtw, CO_t* co) {
|
|
if (epGtw == NULL || co == NULL || co->nodeIdUnconfigured) {
|
|
return;
|
|
}
|
|
|
|
CO_GTWA_initRead(co->gtwa, gtwa_write_response, (void*)&epGtw->gtwa_fd);
|
|
epGtw->freshCommand = true;
|
|
}
|
|
|
|
void
|
|
CO_epoll_processGtw(CO_epoll_gtw_t* epGtw, CO_t* co, CO_epoll_t* ep) {
|
|
if (epGtw == NULL || co == NULL || ep == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* Verify for epoll events */
|
|
if (ep->epoll_new && (ep->ev.data.fd == epGtw->gtwa_fdSocket || ep->ev.data.fd == epGtw->gtwa_fd)) {
|
|
if ((ep->ev.events & EPOLLIN) != 0 && ep->ev.data.fd == epGtw->gtwa_fdSocket) {
|
|
bool_t fail = false;
|
|
|
|
epGtw->gtwa_fd = accept4(epGtw->gtwa_fdSocket, NULL, NULL, SOCK_NONBLOCK);
|
|
if (epGtw->gtwa_fd < 0) {
|
|
fail = true;
|
|
if (errno != EAGAIN && errno != EWOULDBLOCK) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "accept(gtwa_fdSocket)");
|
|
}
|
|
} else {
|
|
/* add fd to epoll */
|
|
struct epoll_event ev2 = {0};
|
|
ev2.events = EPOLLIN;
|
|
ev2.data.fd = epGtw->gtwa_fd;
|
|
int ret = epoll_ctl(ep->epoll_fd, EPOLL_CTL_ADD, ev2.data.fd, &ev2);
|
|
if (ret < 0) {
|
|
fail = true;
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "epoll_ctl(add, gtwa_fd)");
|
|
}
|
|
epGtw->socketTimeoutTmr_us = 0;
|
|
}
|
|
|
|
if (fail) {
|
|
socketAcceptEnableForEpoll(epGtw);
|
|
}
|
|
ep->epoll_new = false;
|
|
} else if ((ep->ev.events & EPOLLIN) != 0 && ep->ev.data.fd == epGtw->gtwa_fd) {
|
|
char buf[CO_CONFIG_GTWA_COMM_BUF_SIZE];
|
|
size_t space = co->nodeIdUnconfigured ? CO_CONFIG_GTWA_COMM_BUF_SIZE : CO_GTWA_write_getSpace(co->gtwa);
|
|
|
|
ssize_t s = read(epGtw->gtwa_fd, buf, space);
|
|
|
|
if (space == 0 || co->nodeIdUnconfigured) {
|
|
/* continue or purge data */
|
|
} else if (s < 0 && errno != EAGAIN) {
|
|
log_printf(LOG_DEBUG, DBG_ERRNO, "read(gtwa_fd)");
|
|
} else if (s >= 0) {
|
|
if (epGtw->commandInterface == CO_COMMAND_IF_STDIO) {
|
|
/* simplify command interface on stdio, make hard to type
|
|
* sequence optional, prepend "[0] " to string, if missing */
|
|
const char sequence[] = "[0] ";
|
|
bool_t closed = (buf[s - 1] == '\n'); /* is command closed? */
|
|
|
|
if (buf[0] != '[' && (space - s) >= strlen(sequence) && isgraph(buf[0]) && buf[0] != '#' && closed
|
|
&& epGtw->freshCommand) {
|
|
CO_GTWA_write(co->gtwa, sequence, strlen(sequence));
|
|
}
|
|
epGtw->freshCommand = closed;
|
|
CO_GTWA_write(co->gtwa, buf, s);
|
|
} else { /* socket, local or tcp */
|
|
if (s == 0) {
|
|
/* EOF received, close connection and enable socket
|
|
* accepting */
|
|
int ret = epoll_ctl(ep->epoll_fd, EPOLL_CTL_DEL, epGtw->gtwa_fd, NULL);
|
|
if (ret < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "epoll_ctl(del, gtwa_fd)");
|
|
}
|
|
if (close(epGtw->gtwa_fd) < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "close(gtwa_fd)");
|
|
}
|
|
epGtw->gtwa_fd = -1;
|
|
socketAcceptEnableForEpoll(epGtw);
|
|
} else {
|
|
CO_GTWA_write(co->gtwa, buf, s);
|
|
}
|
|
}
|
|
}
|
|
epGtw->socketTimeoutTmr_us = 0;
|
|
|
|
ep->epoll_new = false;
|
|
} else if ((ep->ev.events & (EPOLLERR | EPOLLHUP)) != 0) {
|
|
log_printf(LOG_DEBUG, DBG_GENERAL, "socket error or hangup, event=", ep->ev.events);
|
|
if (close(epGtw->gtwa_fd) < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "close(gtwa_fd, hangup)");
|
|
}
|
|
}
|
|
} /* if (ep->epoll_new) */
|
|
|
|
/* if socket connection is established, verify timeout */
|
|
if (epGtw->socketTimeout_us > 0 && epGtw->gtwa_fdSocket > 0 && epGtw->gtwa_fd > 0) {
|
|
if (epGtw->socketTimeoutTmr_us > epGtw->socketTimeout_us) {
|
|
/* timout expired, close current connection and accept next */
|
|
int ret = epoll_ctl(ep->epoll_fd, EPOLL_CTL_DEL, epGtw->gtwa_fd, NULL);
|
|
if (ret < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "epoll_ctl(del, gtwa_fd), tmo");
|
|
}
|
|
if (close(epGtw->gtwa_fd) < 0) {
|
|
log_printf(LOG_CRIT, DBG_ERRNO, "close(gtwa_fd), tmo");
|
|
}
|
|
epGtw->gtwa_fd = -1;
|
|
socketAcceptEnableForEpoll(epGtw);
|
|
} else {
|
|
epGtw->socketTimeoutTmr_us += ep->timeDifference_us;
|
|
}
|
|
}
|
|
}
|
|
#endif /* (CO_CONFIG_GTW) & CO_CONFIG_GTW_ASCII */
|