drivers/can_serial/can_serial_hub.c
author fbeaulier
Tue, 16 Aug 2011 14:15:52 +0200
changeset 663 70fc3603e36f
parent 451 236c2e901e2e
permissions -rw-r--r--
timers_unix.c : remove sigint and sigterm catch
sdo : Allow multiple servers
The sdo transfer struct is not anymore referenced by server's node id but by
client or server number in the OD. Node id is not relevant in SDO transfert.
/*
This file is part of CanFestival, a library implementing CanOpen Stack. 

Copyright (C): James Steward

See COPYING file for copyrights details.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/**
	Pseudo CAN hub application.
*/

#define _GNU_SOURCE  //for asprintf()

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <termios.h>
#include <stdlib.h>
#include <dlfcn.h>

#define NEED_PRINT_MESSAGE

#ifdef DLL_CALL
#undef DLL_CALL
#endif

#define DLL_CALL(funcname) (* funcname##_driver)

#include "canfestival.h"

int DLL_CALL(canfd)(CAN_HANDLE)FCT_PTR_INIT;

#ifdef DLL_CALL
#undef DLL_CALL
#endif

#define DLL_CALL(funcname) funcname##_driver

#define DLSYM(name)\
        *(void **) (&name##_driver) = dlsym(handle, #name"_driver");\
        if ((error = dlerror()) != NULL)  {\
                fprintf (stderr, "%s\n", error);\
                UnLoadCanDriver(handle);\
                return NULL;\
        }

#define MAX_HUB_PORTS 16

typedef struct {
	int fd;
	struct termios old_termio, new_termio;
	char *name;
} port;

port hub_ports[MAX_HUB_PORTS + 1];  //An extra for a CAN driver port

s_BOARD bus_if = { "/dev/ttyS0", "125K" };

/* Receive a CAN message from a hub (pseudo) port */
int hub_receive(port *p, Message *m)
{
	int rv, N, n = 0;
	fd_set rfds;
	struct timeval tv;
	
	N = 4; //initially try to read 4 bytes, including the length byte

retry:
	rv = read(p->fd, &((char *)m)[n], N - n);

	if (rv == -1) {
		fprintf(stderr, "read: %d, %s\n", p->fd, strerror(errno));
		return -1;
	}

	n += rv;

	if (n == 4) {
		N = (4 + m->len);
		
		if (m->len > 8) {
			fprintf(stderr, "Warning: invalid message length %d\n", 
					m->len);
			
			//try to resync
			return 0;
		}

	}

	if (n < N) {

		FD_ZERO(&rfds);
		FD_SET(p->fd, &rfds);

		tv.tv_sec = 0;
		tv.tv_usec=100000;
		
		rv = select(p->fd + 1, &rfds, NULL, NULL, &tv);
		if (rv == 0 || rv == -1) {
			fprintf(stderr, "select: %s\n", strerror(errno));
			return 0;
		}

		goto retry;	
	}

	return 1;
}

/* send a CAN message to one of the hub ports */
UNS8 hub_send(port *p, Message *m)
{
	int rv;

	rv = write(p->fd, m, 4 + m->len);

	if (rv != 4 + m->len) {
		return 1;
	}

	return 0;
}

/* Open a hub port */
int hub_open(port *p)
{
	if (p->fd != -1) {
		fprintf(stderr, "Warning, port %s is already open, fd %d!\n", 
					p->name, p->fd);	
	}

	p->fd = open(p->name, O_RDWR);
	
	if (p->fd < 0) {
		fprintf(stderr, "open: %s, %s\n", p->name, strerror(errno));
		goto exit_here;
	}

	if (tcgetattr(p->fd, &p->old_termio) != 0) {
		fprintf(stderr, "tcgetattr: %s, %s\n", 
					p->name, strerror(errno));
		close(p->fd);
		p->fd = -1;
		goto exit_here;
	}

	memcpy(&p->new_termio, &p->old_termio, sizeof(p->old_termio));
	cfmakeraw(&p->new_termio);
	cfsetispeed(&p->new_termio, B115200);
	cfsetospeed(&p->new_termio, B115200);
	tcsetattr(p->fd, TCSANOW, &p->new_termio);

exit_here:
	return p->fd;
}

/* Close a hub port*/
int hub_close(port *p)
{
	if (p->fd >= 0) {
		tcsetattr(p->fd, TCSANOW, &p->old_termio);
		close(p->fd);
		p->fd = -1;
	}

	return 0;
}

/** Read from the port index rd_port, and write to all other ports. */
int read_write(int rd_port, port *p, CAN_HANDLE h, fd_set *wfds, int max_fd)
{
	Message m;
	int rv, i;
	fd_set wfds_copy;
	struct timeval tv = {.tv_sec = 0, .tv_usec = 0}; //wait 0 msec

	if (rd_port == MAX_HUB_PORTS) {
		rv = DLL_CALL(canReceive)(h, &m);

		if (rv == 1) {
			return 0;
		}
	} else {
		rv = hub_receive(&p[rd_port], &m);

		if (rv != 1) {
			return rv;
		}
	}
	
	memcpy(&wfds_copy, wfds, sizeof(fd_set));

	rv = select(max_fd + 1, NULL, &wfds_copy, NULL, &tv);
	
	if (rv <= 0) {
		return 0;
	}

	for (i = 0; i < MAX_HUB_PORTS + 1; i++) {

		if (i == rd_port) {
			fprintf(stderr, "[%d] ", i);
			continue;
		}

		if (p[i].fd < 0 || !FD_ISSET(p[i].fd, &wfds_copy)) {
			fprintf(stderr, "{%d} ", i);
			continue;
		}

		fprintf(stderr, "<%d> ", i);

		if (i == MAX_HUB_PORTS && h) {
			DLL_CALL(canSend)(h, &m);
		} else {
			hub_send(&p[i], &m);
		}
	}

	print_message(&m);

	return 0;
}

void help(void)
{
	printf("\n\n");
	printf("This is a software hub for the CANFestival library, \n");
	printf("based on the *nix pseudo tty.  It supports up to 16\n");
	printf("connections from clients, and a connection to a CANFestival\n");
	printf("driver, for connection to the outside world.\n");
	printf("\n");
	printf("Basic use is simply to run can_hub.  Without arguments, it\n");
	printf("will use /dev/ptya[0..f] ptys.  You should then run your\n");
	printf("linux CANFestival app using libcanfestival_can_serial.so\n");
	printf("with the bus name /dev/ttyaX, where X is 0..f and unused.\n");
	printf("\n");
	printf("You can alter the pty base with -p /dev/ptyx .\n");
	printf("\n");
	printf("If you want to interface with some other CAN driver, supply\n");
	printf("the option -l /path/to/libcanfestival_can_foo.so .\n");
	printf("The default bus name and baud are /dev/ttyS0 and 125k.\n");
	printf("These can be overridden with -b /dev/{bus name} and -s {baud}.\n");
}

/*UnLoads the dll*/
UNS8 UnLoadCanDriver(LIB_HANDLE handle)
{
        if(handle!=NULL)
        {
                dlclose(handle);

                handle=NULL;
                return 0;
        }
        return -1;
}

/*Loads the dll and get funcs ptr*/
LIB_HANDLE LoadCanDriver(char* driver_name)
{
        LIB_HANDLE handle = NULL;
        char *error;

        if(handle==NULL)
        {
                handle = dlopen(driver_name, RTLD_LAZY);
        }

        if (!handle) {
                fprintf (stderr, "%s\n", dlerror());
                return NULL;
        }

        /*Get function ptr*/
        DLSYM(canReceive)
        DLSYM(canSend)
        DLSYM(canOpen)
        DLSYM(canChangeBaudRate)
        DLSYM(canClose)
	DLSYM(canfd)

        return handle;
}

/**
*/
int main(int argc, char **argv)
{
	int i, rv, max_fd = 0, ret = 0;
	fd_set rfds, rfds_copy;
	CAN_HANDLE can_h = NULL;
        LIB_HANDLE lib_h = NULL;

	int c;
	extern char *optarg;

	char *can_drv = NULL;
	char *pty_base = "/dev/ptya";

	while ((c = getopt(argc, argv, "-b:s:l:p:h")) != EOF) {
		switch (c) {
		case 'b':
			if (optarg[0] == 0) {
				help();
				exit(1);
			}
			bus_if.busname = optarg;
			break;
		case 's':
			if (optarg[0] == 0) {
				help();
				exit(1);
			}
			bus_if.baudrate = optarg;
			break;
		case 'l':
			if (optarg[0] == 0) {
				help();
				exit(1);
			}
			can_drv = optarg;
			break;
		case 'p':
			if (optarg[0] == 0) {
				help();
				exit(1);
			}
			pty_base = optarg;
			break;
		case 'h':
			help();
			exit(1);
			break;
		default:
			help();
			exit(1);
		}
	}

	FD_ZERO(&rfds);

	hub_ports[MAX_HUB_PORTS].fd = -1;

	if (can_drv) {
		lib_h = LoadCanDriver(can_drv);
		if (lib_h == NULL) {
			printf("Unable to load library: %s\n", can_drv);
			exit(1);
		}

	        can_h = DLL_CALL(canOpen)(&bus_if);
        	if(!can_h) {
        	        fprintf(stderr,"canOpen : failed\n");
        	        exit(1);
        	}

		hub_ports[MAX_HUB_PORTS].fd = DLL_CALL(canfd)(can_h);

		FD_SET(hub_ports[MAX_HUB_PORTS].fd, &rfds);

		if (hub_ports[MAX_HUB_PORTS].fd > max_fd) {
			max_fd = hub_ports[MAX_HUB_PORTS].fd;
		}

	}

	for (i = 0; i < MAX_HUB_PORTS; i++) {

		hub_ports[i].fd = -1;
		hub_ports[i].name = NULL;

		rv = asprintf(&hub_ports[i].name, "%s%x", pty_base, i);

		if (rv < 0) {
			fprintf(stderr, "asprintf: %s\n", strerror(errno));
			ret = 1;
			break;
		}

		rv = hub_open(&hub_ports[i]);

		if (rv < 0) {
			ret = 1;
			break;
		}

		FD_SET(rv, &rfds);

		if (rv > max_fd) {
			max_fd = rv;
		}
	}

	if (ret) {
		return ret;
	}

	while (!ret) {
		memcpy(&rfds_copy, &rfds, sizeof(rfds));

		rv = select(max_fd + 1, &rfds_copy, NULL, NULL, NULL);

		if (rv < 0) {
			//select error
			fprintf(stderr, "select: %s\n", strerror(errno));
			ret = 1;
			continue;
		}

		//as timeout is NULL, must be a rfds set.
		for (i = 0; i < MAX_HUB_PORTS + 1; i++) {
			if (hub_ports[i].fd >= 0 && 
					FD_ISSET(hub_ports[i].fd, &rfds_copy)) {

				rv = read_write(i, hub_ports, can_h, &rfds, max_fd);

				if (rv < 0 && i < MAX_HUB_PORTS) {

					FD_CLR(hub_ports[i].fd, &rfds);

					hub_close(&hub_ports[i]);
				}						
			}

			if (hub_ports[i].fd < 0 && i < MAX_HUB_PORTS) {
				rv = hub_open(&hub_ports[i]);

				if (rv >= 0) {
					FD_SET(rv, &rfds);

					if (rv > max_fd) {
						max_fd = rv;
					}
				}
			}
		}
	}

	for (i = 0; i < MAX_HUB_PORTS; i++) {
		hub_close(&hub_ports[i]);
	}

	if (hub_ports[MAX_HUB_PORTS].fd >= 0) {
		DLL_CALL(canClose)(&bus_if);
	}

	return ret;
}