/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/*
 * Copyright 2001 Lutz Mueller <lutz@users.sf.net>
 * Copyright 2000 Philippe Marzouk <pmarzouk@bigfoot.com>
 * Copyright 2000 Edouard Lafargue <Edouard.Lafargue@bigfoot.com>
 * Copyright 1999 Johannes Erdfelt <johannes@erdfelt.com>
 * Copyright 1999 Scott Fritzinger <scottf@unr.edu>
 *
 * Based on work by:
 * Copyright 1999 Beat Christen <spiff@longstreet.ch>
 * 	for the toshiba gPhoto library.
 *
 *
 * 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 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., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

#define _DEFAULT_SOURCE
#define _DARWIN_C_SOURCE
/* Solaris needs this */
#define __EXTENSIONS__

#include "config.h"
#include <gphoto2/gphoto2-port-library.h>


#include <stdlib.h>
#include <stdio.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_ERRNO_H
#include <errno.h>
#endif
#include <string.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif

#ifdef HAVE_TERMIOS_H
#  include <termios.h>
#  ifndef CRTSCTS
#    define CRTSCTS  020000000000
#  endif
#else
#  include <sgtty.h>
#endif

#ifdef HAVE_TTYLOCK
#  include <ttylock.h>
#elif defined(HAVE_LOCKDEV)
#  include <lockdev.h>
#endif

#include <gphoto2/gphoto2-port-result.h>
#include <gphoto2/gphoto2-port-log.h>
#include <gphoto2/gphoto2-port.h>

#include "libgphoto2_port/i18n.h"


#define CHECK(result) {int r=(result); if (r<0) return (r);}

/* Linux */
#ifdef __linux__
/* devfs is accounted for in the implementation */
#define GP_PORT_SERIAL_PREFIX   "/dev/ttyS%i"
#define GP_PORT_SERIAL_RANGE_LOW        0
#define GP_PORT_SERIAL_RANGE_HIGH       32

#ifndef IXANY
#define IXANY  0004000
#endif

#endif

/* FreeBSD */
#ifdef __FreeBSD__
#if __FreeBSD_version < 600000
#define GP_PORT_SERIAL_PREFIX   "/dev/cuaa%x"
#else
#define GP_PORT_SERIAL_PREFIX   "/dev/cuad%x"
#endif
#define GP_PORT_SERIAL_RANGE_LOW        0
#define GP_PORT_SERIAL_RANGE_HIGH       (0xf)
#endif

/* OpenBSD */
/* devices appear to go up to /dev/cuac7, but we just list the first 4 */
#ifdef __OpenBSD__
# define GP_PORT_SERIAL_PREFIX   "/dev/cua%02x"
# define GP_PORT_SERIAL_RANGE_LOW        0
# define GP_PORT_SERIAL_RANGE_HIGH       3
#endif

/* NetBSD */
#ifdef __NetBSD__
#define GP_PORT_SERIAL_PREFIX   "/dev/tty0%i"
#define GP_PORT_SERIAL_RANGE_LOW        0
#define GP_PORT_SERIAL_RANGE_HIGH       32
#endif

/* Tru64 UNIX */
#ifdef __osf__
#define GP_PORT_SERIAL_PREFIX   "/dev/tty%02i"
#define GP_PORT_SERIAL_RANGE_LOW        0
#define GP_PORT_SERIAL_RANGE_HIGH       4
#endif

/* Darwin */
#ifdef __APPLE__
/* This is the Keyspan USB serial adapter device (UNTESTED) */
#define GP_PORT_SERIAL_PREFIX   "/dev/tty.KeyUSA28X%i"
#define GP_PORT_SERIAL_RANGE_LOW        111
#define GP_PORT_SERIAL_RANGE_HIGH       1112
#endif

/* Solaris */
#ifdef sun
#define GP_PORT_SERIAL_PREFIX "/dev/tty%c"
#define GP_PORT_SERIAL_RANGE_LOW        'a'
#define GP_PORT_SERIAL_RANGE_HIGH       'z'
#endif

/* BeOS */
#ifdef beos
/* ????????????? */
#define GP_PORT_SERIAL_PREFIX NULL
#define GP_PORT_SERIAL_RANGE_LOW        0
#define GP_PORT_SERIAL_RANGE_HIGH       0
#endif

/* Windows */
#ifdef WIN32
#define GP_PORT_SERIAL_PREFIX   "COM%i:"
#define GP_PORT_SERIAL_RANGE_LOW        1
#define GP_PORT_SERIAL_RANGE_HIGH       4
#endif

/* IRIX */
#if defined(__sgi)
#define GP_PORT_SERIAL_PREFIX "/dev/ttyd%i"
#define GP_PORT_SERIAL_RANGE_LOW       1
#define GP_PORT_SERIAL_RANGE_HIGH     11
#endif

/* MidnightBSD */
#ifdef __MidnightBSD__
#define GP_PORT_SERIAL_PREFIX   "/dev/cuad%x"
#define GP_PORT_SERIAL_RANGE_LOW        0
#define GP_PORT_SERIAL_RANGE_HIGH       (0xf)
#endif

/* Others? */

/* Default */
#ifndef GP_PORT_SERIAL_PREFIX
#define GP_PORT_SERIAL_PREFIX   "/dev/cua%i"
#define GP_PORT_SERIAL_RANGE_LOW        0
#define GP_PORT_SERIAL_RANGE_HIGH       0
#endif

struct _GPPortPrivateLibrary {
	int fd;       /* Device handle */
	int baudrate; /* Current speed */
	int parity;   /* Current parity */

	/* Internal cache for faster reading */
	char cache[512];
	const char *cachep;
	const char *cachee;
};

static int gp_port_serial_check_speed (GPPort *dev);

GPPortType
gp_port_library_type ()
{
	return (GP_PORT_SERIAL);
}

static int
gp_port_serial_lock (GPPort *dev, const char *path)
{
#if defined(HAVE_LOCKDEV)
	int pid;
#endif

	GP_LOG_D ("Trying to lock '%s'...", path);

#if defined(HAVE_TTYLOCK)
	if (ttylock ((char*) path)) {
		if (dev)
			gp_port_set_error (dev, _("Could not lock device "
						  "'%s'"), path);
		return (GP_ERROR_IO_LOCK);
	}
#define __HAVE_LOCKING
#elif defined(HAVE_LOCKDEV)
	pid = dev_lock (path);
	if (pid) {
		if (dev) {
			if (pid > 0)
				gp_port_set_error (dev, _("Device '%s' is "
					"locked by pid %d"), path, pid);
			else
				gp_port_set_error (dev, _("Device '%s' could "
					"not be locked (dev_lock returned "
					"%d)"), path, pid);
		}
		return (GP_ERROR_IO_LOCK);
	}
#define __HAVE_LOCKING
#endif

#ifndef __HAVE_LOCKING
# ifdef __GCC__
#  warning No locking library found.
#  warning You will run into problems if you use
#  warning gphoto2 with a serial (RS232) camera in
#  warning combination with Konqueror (KDE) or Nautilus (GNOME).
#  warning This will *not* concern USB cameras.
# endif
#endif

	return (GP_OK);
}

static int
gp_port_serial_unlock (GPPort *dev, const char *path)
{

#if defined(HAVE_TTYLOCK)
	if (ttyunlock ((char*) path)) {
		if (dev)
			gp_port_set_error (dev, _("Device '%s' could not be "
					   "unlocked."), path);
		return (GP_ERROR_IO_LOCK);
	}
#elif defined(HAVE_LOCKDEV)

	int pid;

	pid = dev_unlock (path, 0);
	if (pid) {
		if (dev) {
			if (pid > 0)
				gp_port_set_error (dev, _("Device '%s' could "
					"not be unlocked as it is locked by "
					"pid %d."), path, pid);
			else
				gp_port_set_error (dev, _("Device '%s' could "
					"not be unlocked (dev_unlock "
					"returned %d)"), path, pid);
		}
		return (GP_ERROR_IO_LOCK);
	}
#endif /* !HAVE_LOCKDEV */

	return (GP_OK);
}

int
gp_port_library_list (GPPortInfoList *list)
{
	GPPortInfo info;
	char path[1024], prefix[1024];
	int x;
	struct stat s;

	/* Copy in the serial port prefix */
	strcpy (prefix, GP_PORT_SERIAL_PREFIX);

	/* On Linux systems, check for devfs */
#ifdef __linux
	if (!stat ("/dev/tts", &s))
		strcpy (prefix, "/dev/tts/%i");
#endif

	for (x=GP_PORT_SERIAL_RANGE_LOW; x<=GP_PORT_SERIAL_RANGE_HIGH; x++) {
		char *xname;

		sprintf (path, prefix, x);

		/* Very first of all, if the device node is not there,
		 * there is no need to try locking. */
		if ((stat (path, &s) == -1) && ((errno == ENOENT) || (errno == ENODEV)))
			continue;

		gp_port_info_new (&info);
		gp_port_info_set_type (info, GP_PORT_SERIAL);
		xname = aprintf("serial:%s", path);
		gp_port_info_set_path (info, xname);
		free (xname);
		xname = aprintf(_("Serial Port %i"), x);
		gp_port_info_set_name (info, xname);
		free (xname);
		CHECK (gp_port_info_list_append (list, info));
	}

	/*
	 * Generic support. Append it to the list without checking for
	 * return values, because this entry will not be counted.
	 */
	gp_port_info_new (&info);
	gp_port_info_set_type (info, GP_PORT_SERIAL);
	gp_port_info_set_path (info, "serial:");
	gp_port_info_set_name (info, _("Serial Port Device"));
	CHECK (gp_port_info_list_append (list, info));

	gp_port_info_new (&info);
	gp_port_info_set_type (info, GP_PORT_SERIAL);
	gp_port_info_set_path (info, "^serial:");
	gp_port_info_set_name (info, "");
	gp_port_info_list_append (list, info); /* do not check */
	return GP_OK;
}

static int
gp_port_serial_init (GPPort *dev)
{
	C_PARAMS (dev);

	C_MEM (dev->pl = calloc (1, sizeof (GPPortPrivateLibrary)));

	/* There is no default speed or parity. */
	dev->pl->baudrate = -1;
	dev->pl->parity   = -1;

	return GP_OK;
}

static int
gp_port_serial_exit (GPPort *dev)
{
	C_PARAMS (dev);

	free (dev->pl);
	dev->pl = NULL;

	return GP_OK;
}

static int
gp_port_serial_open (GPPort *dev)
{
	int result, max_tries = 5, i;
	char *port;
	GPPortInfo	info;

	result = gp_port_get_info (dev, &info);
	if (result < GP_OK) return result;
	result = gp_port_info_get_path (info, &port);
	if (result < GP_OK) return result;
	GP_LOG_D ("opening %s", port);

	/* Ports are named "serial:/dev/whatever/port" */
	port = strchr (port, ':');
	if (!port)
		return GP_ERROR_UNKNOWN_PORT;
	port++;

	result = gp_port_serial_lock (dev, port);
	if (result != GP_OK) {
		for (i = 0; i < max_tries; i++) {
			result = gp_port_serial_lock (dev, port);
			if (result == GP_OK)
				break;
			GP_LOG_D ("Failed to get a lock, trying again...");
			sleep (1);
		}
		CHECK (result);
	}
	dev->pl->fd = -1;

#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__DragonFly__) || defined(__MidnightBSD__)
	dev->pl->fd = open (port, O_RDWR | O_NOCTTY | O_NONBLOCK);
#else
	if (dev->pl->fd == -1)
		dev->pl->fd = open (port, O_RDWR | O_NOCTTY | O_SYNC | O_NONBLOCK);
#endif
	if (dev->pl->fd == -1) {
		int saved_errno = errno;
		gp_port_set_error (dev, _("Failed to open '%s' (%s)."),
				   port, strerror(saved_errno));
		dev->pl->fd = 0;
		return GP_ERROR_IO;
	}

	return GP_OK;
}

static int
gp_port_serial_close (GPPort *dev)
{
	const char *path;

	if (!dev)
		return (GP_OK);

	if (dev->pl->fd) {
		if (close (dev->pl->fd) == -1) {
			int saved_errno = errno;
			gp_port_set_error (dev, _("Could not close "
						  "'%s' (%s)."),
					   dev->settings.serial.port,
					   strerror(saved_errno));
			return GP_ERROR_IO;
		}
		dev->pl->fd = 0;
	}

	/* Unlock the port */
	path = strchr (dev->settings.serial.port, ':');
	C_PARAMS (path);
	path++;
	CHECK (gp_port_serial_unlock (dev, path));

#if defined(__sgi)
	/*
	 * On IRIX, we need to set the baudrate each time after opening
	 * the port.
	 */
	dev->pl->baudrate = 0;
#endif

	return GP_OK;
}

static int
gp_port_serial_write (GPPort *dev, const char *bytes, int size)
{
	int len, ret;

	C_PARAMS (dev);

	/* The device needs to be opened for that operation */
	if (!dev->pl->fd)
		CHECK (gp_port_serial_open (dev));

	/* Make sure we are operating at the specified speed */
	CHECK (gp_port_serial_check_speed (dev));

	len = 0;
	while (len < size) {

		/*
		 * Make sure we write all data while handling
		 * the harmless errors
		 */
		ret = write (dev->pl->fd, bytes, size - len);
		if (ret == -1) {
			int saved_errno = errno;
			switch (saved_errno) {
			case EAGAIN:
			case EINTR:
				ret = 0;
				break;
			default:
				gp_port_set_error (dev, _("Could not write "
							  "to port (%s)"),
						   strerror(saved_errno));
				return GP_ERROR_IO_WRITE;
			}
		}
		len += ret;
	}

	/* wait till all bytes are really sent */
#ifdef HAVE_TERMIOS_H
	tcdrain (dev->pl->fd);
#else
	ioctl (dev->pl->fd, TCDRAIN, 0);
#endif
	return len;
}


static int
gp_port_serial_read (GPPort *dev, char *bytes, int size)
{
	struct timeval timeout;
	fd_set readfs;          /* file descriptor set */
	int readen = 0, now;

	C_PARAMS (dev);

	/* The device needs to be opened for that operation */
	if (!dev->pl->fd)
		CHECK (gp_port_serial_open (dev));

	/* Make sure we are operating at the specified speed */
	CHECK (gp_port_serial_check_speed (dev));

	FD_ZERO (&readfs);
	FD_SET (dev->pl->fd, &readfs);

	while (size > 0) {

		/* Set timeout value within input loop */
		timeout.tv_usec = (dev->timeout % 1000) * 1000;
		timeout.tv_sec = (dev->timeout / 1000);

		/* Any data available? */
		if (dev->pl->cachep == dev->pl->cachee && !select (dev->pl->fd + 1, &readfs, NULL, NULL, &timeout))
			return GP_ERROR_TIMEOUT;
		if (!FD_ISSET (dev->pl->fd, &readfs))
			return (GP_ERROR_TIMEOUT);

		if (dev->settings.serial.parity != GP_PORT_SERIAL_PARITY_OFF) {
			unsigned char ffchar[1];
			unsigned char nullchar[1];

			ffchar[0] = 0xff;
			nullchar[0] = 0;
			now = read (dev->pl->fd, bytes, 1);
			if (now < 0)
				return GP_ERROR_IO_READ;
			if (!memcmp(bytes, ffchar, 1)) {
				now = read (dev->pl->fd, bytes, 1);
				if (now < 0)
					return GP_ERROR_IO_READ;

				/* Parity errors are signaled by the serial layer
				 * as 0xff 0x00 sequence.
				 *
				 * 0xff sent by the camera are escaped as
				 * 0xff 0xff sequence.
				 *
				 * All other 0xff 0xXX sequences are errors.
				 *
				 * cf. man tcsetattr, description of PARMRK.
				 */
				if (!memcmp(bytes, nullchar, 1)) {
					gp_port_set_error (dev, _("Parity error."));
					return GP_ERROR_IO_READ;
				}
				if (memcmp(bytes, ffchar, 1)) {
					gp_port_set_error (dev, _("Unexpected parity response sequence 0xff 0x%02x."), ((unsigned char*)bytes)[0]);
					return GP_ERROR_IO_READ;
				}
				/* Ok, we read 1 byte and it is 0xff */
				/* FALLTHROUGH */
			}
		} else if (dev->pl->cachep == dev->pl->cachee && size >= sizeof(dev->pl->cache)) {
			/* We're trying to read a chunk larger than the cache and the cache is empty.
			 * In this case, skip the cache entirely and read as much as we can directly into the destination. */
			now = read (dev->pl->fd, bytes, size);
			if (now < 0)
				return GP_ERROR_IO_READ;
		} else {
			if (dev->pl->cachep == dev->pl->cachee) {
				/* We're reading only a few bytes and the cache is empty; fill it up. */
				now = read (dev->pl->fd, dev->pl->cache, sizeof(dev->pl->cache));
				if (now < 0)
					return GP_ERROR_IO_READ;
				/* Reset cache pointers */
				dev->pl->cachep = dev->pl->cache;
				dev->pl->cachee = dev->pl->cache + now;
			}
			/* read up to the required chunk size from cache */
			now = MIN(size, dev->pl->cachee - dev->pl->cachep);
			memcpy(bytes, dev->pl->cachep, now);
			dev->pl->cachep += now;
		}
		bytes += now;
		readen += now;
		size -= readen;
	}

	return readen;
}

#ifdef HAVE_TERMIOS_H
static int
get_termios_bit (GPPort *dev, GPPin pin, int *bit)
{
	switch (pin) {
	case GP_PIN_RTS:
		*bit = TIOCM_RTS;
		break;
	case GP_PIN_DTR:
		*bit = TIOCM_DTR;
		break;
	case GP_PIN_CTS:
		*bit = TIOCM_CTS;
		break;
	case GP_PIN_DSR:
		*bit = TIOCM_DSR;
		break;
	case GP_PIN_CD:
		*bit = TIOCM_CD;
		break;
	case GP_PIN_RING:
		*bit = TIOCM_RNG;
		break;
	default:
		gp_port_set_error (dev, _("Unknown pin %i."), pin);
		return GP_ERROR_IO;
	}
	return (GP_OK);
}
#endif

static int
gp_port_serial_get_pin (GPPort *dev, GPPin pin, GPLevel *level)
{
#ifdef HAVE_TERMIOS_H
	int j, bit;
#endif

	C_PARAMS (dev && level);

	*level = 0;

#ifdef HAVE_TERMIOS_H
	CHECK (get_termios_bit (dev, pin, &bit));
	if (ioctl (dev->pl->fd, TIOCMGET, &j) < 0) {
		int saved_errno = errno;
		gp_port_set_error (dev, _("Could not get level of pin %i "
			"(%s)."),
			pin, strerror(saved_errno));
		return GP_ERROR_IO;
	}
	*level = j & bit;
#else
# ifdef __GCC__
	#  warning ACCESSING PINS IS NOT IMPLEMENTED FOR NON-TERMIOS SYSTEMS!
# endif
#endif

		return (GP_OK);
}

static int
gp_port_serial_set_pin (GPPort *dev, GPPin pin, GPLevel level)
{
#ifdef HAVE_TERMIOS_H
	int bit, request;
#endif

	C_PARAMS (dev);

#ifdef HAVE_TERMIOS_H
	CHECK (get_termios_bit (dev, pin, &bit));
	switch (level) {
	case GP_LEVEL_LOW:
		request = TIOCMBIS;
		break;
	default:
		request = TIOCMBIC;
		break;
	}
	if (ioctl (dev->pl->fd, request, &bit) < 0) {
		int saved_errno = errno;
		gp_port_set_error (dev, _("Could not set level of pin %i to "
					  "%i (%s)."),
				   pin, level, strerror(saved_errno));
		return GP_ERROR_IO;
	}
#else
# ifdef __GCC__
#  warning ACCESSING PINS IS NOT IMPLEMENTED FOR NON-TERMIOS SYSTEMS!
# endif
#endif

	return GP_OK;
}

static int
gp_port_serial_flush (GPPort *dev, int direction)
{
	/* The device needs to be opened for that operation */
	if (!dev->pl->fd)
		CHECK (gp_port_serial_open (dev));

	/* Make sure we are operating at the specified speed */
	CHECK (gp_port_serial_check_speed (dev));

#ifdef HAVE_TERMIOS_H
	if (tcflush (dev->pl->fd, direction ? TCOFLUSH : TCIFLUSH) < 0) {
		int saved_errno = errno;
		gp_port_set_error (dev, _("Could not flush '%s' (%s)."),
				   dev->settings.serial.port,
				   strerror(saved_errno));
		return (GP_ERROR_IO);
	}
#else
# ifdef __GCC__
#  warning SERIAL FLUSH NOT IMPLEMENTED FOR NON TERMIOS SYSTEMS!
# endif
#endif

	return (GP_OK);
}

static speed_t
gp_port_serial_baudconv (int baudrate)
{
#define BAUDCASE(x)     case (x): { ret = B##x; break; }
	speed_t ret;

	switch (baudrate) {

		/* POSIX defined baudrates */
		BAUDCASE(0);
		BAUDCASE(50);
		BAUDCASE(75);
		BAUDCASE(110);
		BAUDCASE(134);
		BAUDCASE(150);
		BAUDCASE(200);
		BAUDCASE(300);
		BAUDCASE(600);
		BAUDCASE(1200);
		BAUDCASE(1800);
		BAUDCASE(2400);
		BAUDCASE(4800);
		BAUDCASE(9600);
		BAUDCASE(19200);
		BAUDCASE(38400);

		/* non POSIX values */
#ifdef B7200
		BAUDCASE(7200);
#endif
#ifdef B14400
		BAUDCASE(14400);
#endif
#ifdef B28800
		BAUDCASE(28800);
#endif
#ifdef B57600
		BAUDCASE(57600);
#endif
#ifdef B115200
		BAUDCASE(115200);
#endif
#ifdef B230400
		BAUDCASE(230400);
#endif
	default:
		ret = (speed_t) baudrate;
		GP_LOG_D ("Baudrate %d unknown - using as is", baudrate);
	}

	return ret;
#undef BAUDCASE
}

static int
gp_port_serial_check_speed (GPPort *dev)
{
	speed_t speed;
#ifdef HAVE_TERMIOS_H
	struct termios tio;
#else
	struct sgttyb ttyb;
#endif

	/*
	 * We need an open device in order to set the speed. If there is no
	 * open device, postpone setting of speed.
	 */
	if (!dev->pl->fd)
		return (GP_OK);

	/* If baudrate and parity are up to date, do nothing */
	if (dev->pl->baudrate == dev->settings.serial.speed
	    && dev->pl->parity == dev->settings.serial.parity)
		return (GP_OK);

	GP_LOG_D ("Setting baudrate to %d...", dev->settings.serial.speed);
	speed = gp_port_serial_baudconv (dev->settings.serial.speed);

#ifdef HAVE_TERMIOS_H
	if (tcgetattr(dev->pl->fd, &tio) < 0) {
		gp_port_set_error (dev, _("Could not set the baudrate to %d"),
				   dev->settings.serial.speed);
		return GP_ERROR_IO_SERIAL_SPEED;
	}
	tio.c_cflag = (tio.c_cflag & ~CSIZE) | CS8;

	/* Set into raw, no echo mode */
	tio.c_iflag &= ~(IGNBRK | IGNCR | INLCR | ICRNL |
			 IXANY | IXON | IXOFF | INPCK | ISTRIP);
#ifdef IUCLC
	tio.c_iflag &= ~IUCLC;
#endif
	tio.c_iflag |= (BRKINT | IGNPAR);
	tio.c_oflag &= ~OPOST;
	tio.c_lflag &= ~(ICANON | ISIG | ECHO | ECHONL | ECHOE |
			 ECHOK | IEXTEN);
	tio.c_cflag &= ~(CRTSCTS | PARENB | PARODD);
	tio.c_cflag |= CLOCAL | CREAD;

	tio.c_cc[VMIN] = 1;
	tio.c_cc[VTIME] = 0;

	if (dev->settings.serial.parity != GP_PORT_SERIAL_PARITY_OFF) {
		GP_LOG_D ("Setting parity to %s...", dev->settings.serial.parity == GP_PORT_SERIAL_PARITY_ODD ? "odd" : "even");
		tio.c_iflag &= ~IGNPAR;
		tio.c_iflag |= INPCK | PARMRK;
		tio.c_cflag |= PARENB;
		if (dev->settings.serial.parity == GP_PORT_SERIAL_PARITY_ODD)
			tio.c_cflag |= PARODD;
	}

	/* Set the new speed. */
	cfsetispeed (&tio, speed);
	cfsetospeed (&tio, speed);
	if (tcsetattr (dev->pl->fd, TCSANOW, &tio) < 0) {
		GP_LOG_E ("Error on 'tcsetattr'.");
		return GP_ERROR_IO_SERIAL_SPEED;
	}

	/* Clear O_NONBLOCK. */
	if (fcntl (dev->pl->fd, F_SETFL, 0) < 0) {
		GP_LOG_E ("Error on 'fcntl'.");
		return GP_ERROR_IO_SERIAL_SPEED;
	}

	/*
	 * Verify if the speed change has been successful.
	 * This is needed because some Solaris systems just ignore unsupported
	 * speeds (like 115200) instead of complaining.
	 *
	 * Only perform the check if we really changed to some speed.
	 */
	if (speed != B0) {
		if (tcgetattr (dev->pl->fd, &tio)) {
			GP_LOG_E ("Error on 'tcgetattr'.");
			return (GP_ERROR_IO_SERIAL_SPEED);
		}
		if ((cfgetispeed (&tio) != speed) ||
		    (cfgetospeed (&tio) != speed)) {
			GP_LOG_E ("Cannot set baudrate to %d...",
				  dev->settings.serial.speed);
			return (GP_ERROR_NOT_SUPPORTED);
		}
	}

#else /* !HAVE_TERMIOS_H */
	if (ioctl (dev->pl->fd, TIOCGETP, &ttyb) < 0) {
		perror("ioctl(TIOCGETP)");
		return GP_ERROR_IO_SERIAL_SPEED;
	}
	ttyb.sg_ispeed = dev->settings.serial.speed;
	ttyb.sg_ospeed = dev->settings.serial.speed;
	ttyb.sg_flags = 0;

	if (ioctl (dev->pl->fd, TIOCSETP, &ttyb) < 0) {
		perror("ioctl(TIOCSETP)");
		return GP_ERROR_IO_SERIAL_SPEED;
	}
#endif

	dev->pl->baudrate = dev->settings.serial.speed;
	dev->pl->parity = dev->settings.serial.parity;
	return GP_OK;
}

static int
gp_port_serial_update (GPPort *dev)
{
	memcpy (&dev->settings, &dev->settings_pending, sizeof (dev->settings));

	CHECK (gp_port_serial_check_speed (dev));

	return GP_OK;
}

static int
gp_port_serial_send_break (GPPort *dev, int duration)
{
	/* The device needs to be opened for that operation */
	if (!dev->pl->fd)
		CHECK (gp_port_serial_open (dev));

	/* Make sure we are operating at the specified speed */
	CHECK (gp_port_serial_check_speed (dev));

	/* Duration is in milliseconds */
#ifdef HAVE_TERMIOS_H
	tcsendbreak (dev->pl->fd, duration / 310);
	tcdrain (dev->pl->fd);
#else
# ifdef __GCC__
#  warning SEND BREAK NOT IMPLEMENTED FOR NON TERMIOS SYSTEMS!
# endif
#endif

	return GP_OK;
}

GPPortOperations *
gp_port_library_operations ()
{
	GPPortOperations *ops;

	ops = malloc (sizeof (GPPortOperations));
	if (!ops)
		return (NULL);
	memset (ops, 0, sizeof (GPPortOperations));

	ops->init   = gp_port_serial_init;
	ops->exit   = gp_port_serial_exit;
	ops->open   = gp_port_serial_open;
	ops->close  = gp_port_serial_close;
	ops->read   = gp_port_serial_read;
	ops->write  = gp_port_serial_write;
	ops->update = gp_port_serial_update;
	ops->get_pin = gp_port_serial_get_pin;
	ops->set_pin = gp_port_serial_set_pin;
	ops->send_break = gp_port_serial_send_break;
	ops->flush  = gp_port_serial_flush;

	return (ops);
}
